10fbf6a33SBen Roth// Copyright 2015-present 650 Industries. All rights reserved.
20fbf6a33SBen Roth
300fcd3c6SBen Roth#import "EXEnvironment.h"
40fbf6a33SBen Roth#import "EXKernelDevKeyCommands.h"
50fbf6a33SBen Roth#import "EXKernel.h"
689c6c39cSEric Samelson#import "EXKernelAppRegistry.h"
77f6367d0SBen Roth#import "EXReactAppManager.h"
80fbf6a33SBen Roth
9bd9e856bSJames Ide#import <React/RCTDefines.h>
10bd9e856bSJames Ide#import <React/RCTUtils.h>
110fbf6a33SBen Roth
120fbf6a33SBen Roth#import <UIKit/UIKit.h>
130fbf6a33SBen Roth
140fbf6a33SBen Roth@interface EXKeyCommand : NSObject <NSCopying>
150fbf6a33SBen Roth
160fbf6a33SBen Roth@property (nonatomic, strong) UIKeyCommand *keyCommand;
170fbf6a33SBen Roth@property (nonatomic, copy) void (^block)(UIKeyCommand *);
180fbf6a33SBen Roth
190fbf6a33SBen Roth@end
200fbf6a33SBen Roth
210fbf6a33SBen Roth@implementation EXKeyCommand
220fbf6a33SBen Roth
230fbf6a33SBen Roth- (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand
240fbf6a33SBen Roth                             block:(void (^)(UIKeyCommand *))block
250fbf6a33SBen Roth{
260fbf6a33SBen Roth  if ((self = [super init])) {
270fbf6a33SBen Roth    _keyCommand = keyCommand;
280fbf6a33SBen Roth    _block = block;
290fbf6a33SBen Roth  }
300fbf6a33SBen Roth  return self;
310fbf6a33SBen Roth}
320fbf6a33SBen Roth
330fbf6a33SBen RothRCT_NOT_IMPLEMENTED(- (instancetype)init)
340fbf6a33SBen Roth
350fbf6a33SBen Roth- (id)copyWithZone:(__unused NSZone *)zone
360fbf6a33SBen Roth{
370fbf6a33SBen Roth  return self;
380fbf6a33SBen Roth}
390fbf6a33SBen Roth
400fbf6a33SBen Roth- (NSUInteger)hash
410fbf6a33SBen Roth{
420fbf6a33SBen Roth  return _keyCommand.input.hash ^ _keyCommand.modifierFlags;
430fbf6a33SBen Roth}
440fbf6a33SBen Roth
450fbf6a33SBen Roth- (BOOL)isEqual:(EXKeyCommand *)object
460fbf6a33SBen Roth{
470fbf6a33SBen Roth  if (![object isKindOfClass:[EXKeyCommand class]]) {
480fbf6a33SBen Roth    return NO;
490fbf6a33SBen Roth  }
500fbf6a33SBen Roth  return [self matchesInput:object.keyCommand.input
510fbf6a33SBen Roth                      flags:object.keyCommand.modifierFlags];
520fbf6a33SBen Roth}
530fbf6a33SBen Roth
540fbf6a33SBen Roth- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
550fbf6a33SBen Roth{
560fbf6a33SBen Roth  return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags;
570fbf6a33SBen Roth}
580fbf6a33SBen Roth
590fbf6a33SBen Roth- (NSString *)description
600fbf6a33SBen Roth{
610fbf6a33SBen Roth  return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd hasBlock=%@>",
620fbf6a33SBen Roth          [self class], self, _keyCommand.input, _keyCommand.modifierFlags,
630fbf6a33SBen Roth          _block ? @"YES" : @"NO"];
640fbf6a33SBen Roth}
650fbf6a33SBen Roth
660fbf6a33SBen Roth@end
670fbf6a33SBen Roth
680fbf6a33SBen Roth@interface EXKernelDevKeyCommands ()
690fbf6a33SBen Roth
700fbf6a33SBen Roth@property (nonatomic, strong) NSMutableSet<EXKeyCommand *> *commands;
710fbf6a33SBen Roth
720fbf6a33SBen Roth@end
730fbf6a33SBen Roth
740fbf6a33SBen Roth@implementation UIResponder (EXKeyCommands)
750fbf6a33SBen Roth
760fbf6a33SBen Roth- (NSArray<UIKeyCommand *> *)EX_keyCommands
770fbf6a33SBen Roth{
780fbf6a33SBen Roth  NSSet<EXKeyCommand *> *commands = [EXKernelDevKeyCommands sharedInstance].commands;
790fbf6a33SBen Roth  return [[commands valueForKeyPath:@"keyCommand"] allObjects];
800fbf6a33SBen Roth}
810fbf6a33SBen Roth
820fbf6a33SBen Roth- (void)EX_handleKeyCommand:(UIKeyCommand *)key
830fbf6a33SBen Roth{
840fbf6a33SBen Roth  // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
850fbf6a33SBen Roth  // method gets called repeatedly if the command key is held down.
860fbf6a33SBen Roth  static NSTimeInterval lastCommand = 0;
870fbf6a33SBen Roth  if (CACurrentMediaTime() - lastCommand > 0.5) {
880fbf6a33SBen Roth    for (EXKeyCommand *command in [EXKernelDevKeyCommands sharedInstance].commands) {
890fbf6a33SBen Roth      if ([command.keyCommand.input isEqualToString:key.input] &&
900fbf6a33SBen Roth          command.keyCommand.modifierFlags == key.modifierFlags) {
910fbf6a33SBen Roth        if (command.block) {
920fbf6a33SBen Roth          command.block(key);
930fbf6a33SBen Roth          lastCommand = CACurrentMediaTime();
940fbf6a33SBen Roth        }
950fbf6a33SBen Roth      }
960fbf6a33SBen Roth    }
970fbf6a33SBen Roth  }
980fbf6a33SBen Roth}
990fbf6a33SBen Roth
1000fbf6a33SBen Roth@end
1010fbf6a33SBen Roth
1020fbf6a33SBen Roth@implementation EXKernelDevKeyCommands
1030fbf6a33SBen Roth
1040fbf6a33SBen Roth+ (instancetype)sharedInstance
1050fbf6a33SBen Roth{
1060fbf6a33SBen Roth  static EXKernelDevKeyCommands *instance;
1070fbf6a33SBen Roth  static dispatch_once_t once;
1080fbf6a33SBen Roth  dispatch_once(&once, ^{
1090fbf6a33SBen Roth    if (!instance) {
1100fbf6a33SBen Roth      instance = [[EXKernelDevKeyCommands alloc] init];
1110fbf6a33SBen Roth    }
1120fbf6a33SBen Roth  });
1130fbf6a33SBen Roth  return instance;
1140fbf6a33SBen Roth}
1150fbf6a33SBen Roth
1160fbf6a33SBen Roth+ (void)initialize
1170fbf6a33SBen Roth{
1180fbf6a33SBen Roth  // capture keycommands across all bridges.
1190fbf6a33SBen Roth  // this is the same approach taken by RCTKeyCommands,
1200fbf6a33SBen Roth  // but that class is disabled in the expo react native fork
1210fbf6a33SBen Roth  // since there may be many instances of it.
1220fbf6a33SBen Roth  RCTSwapInstanceMethods([UIResponder class],
1230fbf6a33SBen Roth                         @selector(keyCommands),
1240fbf6a33SBen Roth                         @selector(EX_keyCommands));
1250fbf6a33SBen Roth}
1260fbf6a33SBen Roth
1270fbf6a33SBen Roth- (instancetype)init
1280fbf6a33SBen Roth{
1290fbf6a33SBen Roth  if ((self = [super init])) {
1300fbf6a33SBen Roth    _commands = [NSMutableSet set];
1310fbf6a33SBen Roth  }
1320fbf6a33SBen Roth  return self;
1330fbf6a33SBen Roth}
1340fbf6a33SBen Roth
1350fbf6a33SBen Roth#pragma mark - expo dev commands
1360fbf6a33SBen Roth
137*24a0cefbSTomasz Sapeta- (void)registerDevCommands
1380fbf6a33SBen Roth{
1390fbf6a33SBen Roth  __weak typeof(self) weakSelf = self;
1400fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"d"
1410fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1420fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1430fbf6a33SBen Roth                               [weakSelf _handleMenuCommand];
1440fbf6a33SBen Roth                             }];
1450fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"r"
1460fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1470fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1480fbf6a33SBen Roth                               [weakSelf _handleRefreshCommand];
1490fbf6a33SBen Roth                             }];
1500fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"n"
1510fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1520fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1530fbf6a33SBen Roth                               [weakSelf _handleDisableDebuggingCommand];
1540fbf6a33SBen Roth                             }];
1550fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"i"
1560fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1570fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1580fbf6a33SBen Roth                               [weakSelf _handleToggleInspectorCommand];
1590fbf6a33SBen Roth                             }];
1600fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"k"
1610fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand | UIKeyModifierControl
1620fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1630fbf6a33SBen Roth                               [weakSelf _handleKernelMenuCommand];
1640fbf6a33SBen Roth                             }];
1650fbf6a33SBen Roth}
1660fbf6a33SBen Roth
1670fbf6a33SBen Roth- (void)_handleMenuCommand
1680fbf6a33SBen Roth{
169*24a0cefbSTomasz Sapeta  if ([EXEnvironment sharedEnvironment].isDetached) {
1707f6367d0SBen Roth    [[EXKernel sharedInstance].visibleApp.appManager showDevMenu];
1710fbf6a33SBen Roth  } else {
1727f6367d0SBen Roth    [[EXKernel sharedInstance] switchTasks];
1730fbf6a33SBen Roth  }
1740fbf6a33SBen Roth}
1750fbf6a33SBen Roth
1760fbf6a33SBen Roth- (void)_handleRefreshCommand
1770fbf6a33SBen Roth{
178b333b418SBrent Vatne  // This reloads only JS
179b333b418SBrent Vatne  //  [[EXKernel sharedInstance].visibleApp.appManager reloadBridge];
180b333b418SBrent Vatne
181b333b418SBrent Vatne  // This reloads manifest and JS
182b333b418SBrent Vatne  [[EXKernel sharedInstance] reloadVisibleApp];
1830fbf6a33SBen Roth}
1840fbf6a33SBen Roth
1850fbf6a33SBen Roth- (void)_handleDisableDebuggingCommand
1860fbf6a33SBen Roth{
1877f6367d0SBen Roth  [[EXKernel sharedInstance].visibleApp.appManager disableRemoteDebugging];
1880fbf6a33SBen Roth}
1890fbf6a33SBen Roth
1900fbf6a33SBen Roth- (void)_handleToggleInspectorCommand
1910fbf6a33SBen Roth{
1927f6367d0SBen Roth  [[EXKernel sharedInstance].visibleApp.appManager toggleElementInspector];
1930fbf6a33SBen Roth}
1940fbf6a33SBen Roth
1950fbf6a33SBen Roth- (void)_handleKernelMenuCommand
1960fbf6a33SBen Roth{
1977f6367d0SBen Roth  if ([EXKernel sharedInstance].visibleApp == [EXKernel sharedInstance].appRegistry.homeAppRecord) {
1987f6367d0SBen Roth    [[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager showDevMenu];
1990fbf6a33SBen Roth  }
2000fbf6a33SBen Roth}
2010fbf6a33SBen Roth
2020fbf6a33SBen Roth#pragma mark - managing list of commands
2030fbf6a33SBen Roth
2040fbf6a33SBen Roth- (void)registerKeyCommandWithInput:(NSString *)input
2050fbf6a33SBen Roth                      modifierFlags:(UIKeyModifierFlags)flags
2060fbf6a33SBen Roth                             action:(void (^)(UIKeyCommand *))block
2070fbf6a33SBen Roth{
2080fbf6a33SBen Roth  RCTAssertMainQueue();
2090fbf6a33SBen Roth
2100fbf6a33SBen Roth  UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
2110fbf6a33SBen Roth                                              modifierFlags:flags
2120fbf6a33SBen Roth                                                     action:@selector(EX_handleKeyCommand:)];
2130fbf6a33SBen Roth
2140fbf6a33SBen Roth  EXKeyCommand *keyCommand = [[EXKeyCommand alloc] initWithKeyCommand:command block:block];
2150fbf6a33SBen Roth  [_commands removeObject:keyCommand];
2160fbf6a33SBen Roth  [_commands addObject:keyCommand];
2170fbf6a33SBen Roth}
2180fbf6a33SBen Roth
2190fbf6a33SBen Roth- (void)unregisterKeyCommandWithInput:(NSString *)input
2200fbf6a33SBen Roth                        modifierFlags:(UIKeyModifierFlags)flags
2210fbf6a33SBen Roth{
2220fbf6a33SBen Roth  RCTAssertMainQueue();
2230fbf6a33SBen Roth
2240fbf6a33SBen Roth  for (EXKeyCommand *command in _commands.allObjects) {
2250fbf6a33SBen Roth    if ([command matchesInput:input flags:flags]) {
2260fbf6a33SBen Roth      [_commands removeObject:command];
2270fbf6a33SBen Roth      break;
2280fbf6a33SBen Roth    }
2290fbf6a33SBen Roth  }
2300fbf6a33SBen Roth}
2310fbf6a33SBen Roth
2320fbf6a33SBen Roth- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
2330fbf6a33SBen Roth                         modifierFlags:(UIKeyModifierFlags)flags
2340fbf6a33SBen Roth{
2350fbf6a33SBen Roth  RCTAssertMainQueue();
2360fbf6a33SBen Roth
2370fbf6a33SBen Roth  for (EXKeyCommand *command in _commands) {
2380fbf6a33SBen Roth    if ([command matchesInput:input flags:flags]) {
2390fbf6a33SBen Roth      return YES;
2400fbf6a33SBen Roth    }
2410fbf6a33SBen Roth  }
2420fbf6a33SBen Roth  return NO;
2430fbf6a33SBen Roth}
2440fbf6a33SBen Roth
2450fbf6a33SBen Roth@end
2460fbf6a33SBen Roth
247