10fbf6a33SBen Roth// Copyright 2015-present 650 Industries. All rights reserved.
20fbf6a33SBen Roth
30fbf6a33SBen Roth#import "EXKernelDevKeyCommands.h"
40fbf6a33SBen Roth#import "EXKernel.h"
589c6c39cSEric Samelson#import "EXKernelAppRegistry.h"
6*7f6367d0SBen Roth#import "EXReactAppManager.h"
773b4b4d7SBen Roth#import "EXShellManager.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];
131f148175aSBen Roth    _isLegacyMenuBehaviorEnabled = NO;
1320fbf6a33SBen Roth    [self _addDevCommands];
1330fbf6a33SBen Roth  }
1340fbf6a33SBen Roth  return self;
1350fbf6a33SBen Roth}
1360fbf6a33SBen Roth
1370fbf6a33SBen Roth#pragma mark - expo dev commands
1380fbf6a33SBen Roth
1390fbf6a33SBen Roth- (void)_addDevCommands
1400fbf6a33SBen Roth{
1410fbf6a33SBen Roth  __weak typeof(self) weakSelf = self;
1420fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"d"
1430fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1440fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1450fbf6a33SBen Roth                               [weakSelf _handleMenuCommand];
1460fbf6a33SBen Roth                             }];
1470fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"r"
1480fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1490fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1500fbf6a33SBen Roth                               [weakSelf _handleRefreshCommand];
1510fbf6a33SBen Roth                             }];
1520fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"n"
1530fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1540fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1550fbf6a33SBen Roth                               [weakSelf _handleDisableDebuggingCommand];
1560fbf6a33SBen Roth                             }];
1570fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"i"
1580fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1590fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1600fbf6a33SBen Roth                               [weakSelf _handleToggleInspectorCommand];
1610fbf6a33SBen Roth                             }];
1620fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"k"
1630fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand | UIKeyModifierControl
1640fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1650fbf6a33SBen Roth                               [weakSelf _handleKernelMenuCommand];
1660fbf6a33SBen Roth                             }];
1670fbf6a33SBen Roth}
1680fbf6a33SBen Roth
1690fbf6a33SBen Roth- (void)_handleMenuCommand
1700fbf6a33SBen Roth{
17173b4b4d7SBen Roth  if ([EXShellManager sharedInstance].isDetached || _isLegacyMenuBehaviorEnabled) {
172*7f6367d0SBen Roth    [[EXKernel sharedInstance].visibleApp.appManager showDevMenu];
1730fbf6a33SBen Roth  } else {
174*7f6367d0SBen Roth    [[EXKernel sharedInstance] switchTasks];
1750fbf6a33SBen Roth  }
1760fbf6a33SBen Roth}
1770fbf6a33SBen Roth
1780fbf6a33SBen Roth- (void)_handleRefreshCommand
1790fbf6a33SBen Roth{
180*7f6367d0SBen Roth  [[EXKernel sharedInstance].visibleApp.appManager reloadBridge];
1810fbf6a33SBen Roth}
1820fbf6a33SBen Roth
1830fbf6a33SBen Roth- (void)_handleDisableDebuggingCommand
1840fbf6a33SBen Roth{
185*7f6367d0SBen Roth  [[EXKernel sharedInstance].visibleApp.appManager disableRemoteDebugging];
1860fbf6a33SBen Roth}
1870fbf6a33SBen Roth
1880fbf6a33SBen Roth- (void)_handleToggleInspectorCommand
1890fbf6a33SBen Roth{
190*7f6367d0SBen Roth  [[EXKernel sharedInstance].visibleApp.appManager toggleElementInspector];
1910fbf6a33SBen Roth}
1920fbf6a33SBen Roth
1930fbf6a33SBen Roth- (void)_handleKernelMenuCommand
1940fbf6a33SBen Roth{
195*7f6367d0SBen Roth  if ([EXKernel sharedInstance].visibleApp == [EXKernel sharedInstance].appRegistry.homeAppRecord) {
196*7f6367d0SBen Roth    [[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager showDevMenu];
1970fbf6a33SBen Roth  }
1980fbf6a33SBen Roth}
1990fbf6a33SBen Roth
2000fbf6a33SBen Roth#pragma mark - managing list of commands
2010fbf6a33SBen Roth
2020fbf6a33SBen Roth- (void)registerKeyCommandWithInput:(NSString *)input
2030fbf6a33SBen Roth                      modifierFlags:(UIKeyModifierFlags)flags
2040fbf6a33SBen Roth                             action:(void (^)(UIKeyCommand *))block
2050fbf6a33SBen Roth{
2060fbf6a33SBen Roth  RCTAssertMainQueue();
2070fbf6a33SBen Roth
2080fbf6a33SBen Roth  UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
2090fbf6a33SBen Roth                                              modifierFlags:flags
2100fbf6a33SBen Roth                                                     action:@selector(EX_handleKeyCommand:)];
2110fbf6a33SBen Roth
2120fbf6a33SBen Roth  EXKeyCommand *keyCommand = [[EXKeyCommand alloc] initWithKeyCommand:command block:block];
2130fbf6a33SBen Roth  [_commands removeObject:keyCommand];
2140fbf6a33SBen Roth  [_commands addObject:keyCommand];
2150fbf6a33SBen Roth}
2160fbf6a33SBen Roth
2170fbf6a33SBen Roth- (void)unregisterKeyCommandWithInput:(NSString *)input
2180fbf6a33SBen Roth                        modifierFlags:(UIKeyModifierFlags)flags
2190fbf6a33SBen Roth{
2200fbf6a33SBen Roth  RCTAssertMainQueue();
2210fbf6a33SBen Roth
2220fbf6a33SBen Roth  for (EXKeyCommand *command in _commands.allObjects) {
2230fbf6a33SBen Roth    if ([command matchesInput:input flags:flags]) {
2240fbf6a33SBen Roth      [_commands removeObject:command];
2250fbf6a33SBen Roth      break;
2260fbf6a33SBen Roth    }
2270fbf6a33SBen Roth  }
2280fbf6a33SBen Roth}
2290fbf6a33SBen Roth
2300fbf6a33SBen Roth- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
2310fbf6a33SBen Roth                         modifierFlags:(UIKeyModifierFlags)flags
2320fbf6a33SBen Roth{
2330fbf6a33SBen Roth  RCTAssertMainQueue();
2340fbf6a33SBen Roth
2350fbf6a33SBen Roth  for (EXKeyCommand *command in _commands) {
2360fbf6a33SBen Roth    if ([command matchesInput:input flags:flags]) {
2370fbf6a33SBen Roth      return YES;
2380fbf6a33SBen Roth    }
2390fbf6a33SBen Roth  }
2400fbf6a33SBen Roth  return NO;
2410fbf6a33SBen Roth}
2420fbf6a33SBen Roth
2430fbf6a33SBen Roth@end
2440fbf6a33SBen Roth
245