10fbf6a33SBen Roth// Copyright 2015-present 650 Industries. All rights reserved.
20fbf6a33SBen Roth
30fbf6a33SBen Roth#import "EXFrameReactAppManager.h"
40fbf6a33SBen Roth#import "EXKernelDevKeyCommands.h"
50fbf6a33SBen Roth#import "EXKernel.h"
60fbf6a33SBen Roth#import "EXKernelBridgeRegistry.h"
70fbf6a33SBen Roth#import "EXKernelReactAppManager.h"
873b4b4d7SBen Roth#import "EXShellManager.h"
90fbf6a33SBen Roth
10*bd9e856bSJames Ide#import <React/RCTDefines.h>
11*bd9e856bSJames Ide#import <React/RCTUtils.h>
120fbf6a33SBen Roth
130fbf6a33SBen Roth#import <UIKit/UIKit.h>
140fbf6a33SBen Roth
150fbf6a33SBen Roth@interface EXKeyCommand : NSObject <NSCopying>
160fbf6a33SBen Roth
170fbf6a33SBen Roth@property (nonatomic, strong) UIKeyCommand *keyCommand;
180fbf6a33SBen Roth@property (nonatomic, copy) void (^block)(UIKeyCommand *);
190fbf6a33SBen Roth
200fbf6a33SBen Roth@end
210fbf6a33SBen Roth
220fbf6a33SBen Roth@implementation EXKeyCommand
230fbf6a33SBen Roth
240fbf6a33SBen Roth- (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand
250fbf6a33SBen Roth                             block:(void (^)(UIKeyCommand *))block
260fbf6a33SBen Roth{
270fbf6a33SBen Roth  if ((self = [super init])) {
280fbf6a33SBen Roth    _keyCommand = keyCommand;
290fbf6a33SBen Roth    _block = block;
300fbf6a33SBen Roth  }
310fbf6a33SBen Roth  return self;
320fbf6a33SBen Roth}
330fbf6a33SBen Roth
340fbf6a33SBen RothRCT_NOT_IMPLEMENTED(- (instancetype)init)
350fbf6a33SBen Roth
360fbf6a33SBen Roth- (id)copyWithZone:(__unused NSZone *)zone
370fbf6a33SBen Roth{
380fbf6a33SBen Roth  return self;
390fbf6a33SBen Roth}
400fbf6a33SBen Roth
410fbf6a33SBen Roth- (NSUInteger)hash
420fbf6a33SBen Roth{
430fbf6a33SBen Roth  return _keyCommand.input.hash ^ _keyCommand.modifierFlags;
440fbf6a33SBen Roth}
450fbf6a33SBen Roth
460fbf6a33SBen Roth- (BOOL)isEqual:(EXKeyCommand *)object
470fbf6a33SBen Roth{
480fbf6a33SBen Roth  if (![object isKindOfClass:[EXKeyCommand class]]) {
490fbf6a33SBen Roth    return NO;
500fbf6a33SBen Roth  }
510fbf6a33SBen Roth  return [self matchesInput:object.keyCommand.input
520fbf6a33SBen Roth                      flags:object.keyCommand.modifierFlags];
530fbf6a33SBen Roth}
540fbf6a33SBen Roth
550fbf6a33SBen Roth- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
560fbf6a33SBen Roth{
570fbf6a33SBen Roth  return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags;
580fbf6a33SBen Roth}
590fbf6a33SBen Roth
600fbf6a33SBen Roth- (NSString *)description
610fbf6a33SBen Roth{
620fbf6a33SBen Roth  return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd hasBlock=%@>",
630fbf6a33SBen Roth          [self class], self, _keyCommand.input, _keyCommand.modifierFlags,
640fbf6a33SBen Roth          _block ? @"YES" : @"NO"];
650fbf6a33SBen Roth}
660fbf6a33SBen Roth
670fbf6a33SBen Roth@end
680fbf6a33SBen Roth
690fbf6a33SBen Roth@interface EXKernelDevKeyCommands ()
700fbf6a33SBen Roth
710fbf6a33SBen Roth@property (nonatomic, strong) NSMutableSet<EXKeyCommand *> *commands;
720fbf6a33SBen Roth
730fbf6a33SBen Roth@end
740fbf6a33SBen Roth
750fbf6a33SBen Roth@implementation UIResponder (EXKeyCommands)
760fbf6a33SBen Roth
770fbf6a33SBen Roth- (NSArray<UIKeyCommand *> *)EX_keyCommands
780fbf6a33SBen Roth{
790fbf6a33SBen Roth  NSSet<EXKeyCommand *> *commands = [EXKernelDevKeyCommands sharedInstance].commands;
800fbf6a33SBen Roth  return [[commands valueForKeyPath:@"keyCommand"] allObjects];
810fbf6a33SBen Roth}
820fbf6a33SBen Roth
830fbf6a33SBen Roth- (void)EX_handleKeyCommand:(UIKeyCommand *)key
840fbf6a33SBen Roth{
850fbf6a33SBen Roth  // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
860fbf6a33SBen Roth  // method gets called repeatedly if the command key is held down.
870fbf6a33SBen Roth  static NSTimeInterval lastCommand = 0;
880fbf6a33SBen Roth  if (CACurrentMediaTime() - lastCommand > 0.5) {
890fbf6a33SBen Roth    for (EXKeyCommand *command in [EXKernelDevKeyCommands sharedInstance].commands) {
900fbf6a33SBen Roth      if ([command.keyCommand.input isEqualToString:key.input] &&
910fbf6a33SBen Roth          command.keyCommand.modifierFlags == key.modifierFlags) {
920fbf6a33SBen Roth        if (command.block) {
930fbf6a33SBen Roth          command.block(key);
940fbf6a33SBen Roth          lastCommand = CACurrentMediaTime();
950fbf6a33SBen Roth        }
960fbf6a33SBen Roth      }
970fbf6a33SBen Roth    }
980fbf6a33SBen Roth  }
990fbf6a33SBen Roth}
1000fbf6a33SBen Roth
1010fbf6a33SBen Roth@end
1020fbf6a33SBen Roth
1030fbf6a33SBen Roth@implementation EXKernelDevKeyCommands
1040fbf6a33SBen Roth
1050fbf6a33SBen Roth+ (instancetype)sharedInstance
1060fbf6a33SBen Roth{
1070fbf6a33SBen Roth  static EXKernelDevKeyCommands *instance;
1080fbf6a33SBen Roth  static dispatch_once_t once;
1090fbf6a33SBen Roth  dispatch_once(&once, ^{
1100fbf6a33SBen Roth    if (!instance) {
1110fbf6a33SBen Roth      instance = [[EXKernelDevKeyCommands alloc] init];
1120fbf6a33SBen Roth    }
1130fbf6a33SBen Roth  });
1140fbf6a33SBen Roth  return instance;
1150fbf6a33SBen Roth}
1160fbf6a33SBen Roth
1170fbf6a33SBen Roth+ (void)initialize
1180fbf6a33SBen Roth{
1190fbf6a33SBen Roth  // capture keycommands across all bridges.
1200fbf6a33SBen Roth  // this is the same approach taken by RCTKeyCommands,
1210fbf6a33SBen Roth  // but that class is disabled in the expo react native fork
1220fbf6a33SBen Roth  // since there may be many instances of it.
1230fbf6a33SBen Roth  RCTSwapInstanceMethods([UIResponder class],
1240fbf6a33SBen Roth                         @selector(keyCommands),
1250fbf6a33SBen Roth                         @selector(EX_keyCommands));
1260fbf6a33SBen Roth}
1270fbf6a33SBen Roth
1280fbf6a33SBen Roth- (instancetype)init
1290fbf6a33SBen Roth{
1300fbf6a33SBen Roth  if ((self = [super init])) {
1310fbf6a33SBen Roth    _commands = [NSMutableSet set];
132f148175aSBen Roth    _isLegacyMenuBehaviorEnabled = NO;
1330fbf6a33SBen Roth    [self _addDevCommands];
1340fbf6a33SBen Roth  }
1350fbf6a33SBen Roth  return self;
1360fbf6a33SBen Roth}
1370fbf6a33SBen Roth
1380fbf6a33SBen Roth#pragma mark - expo dev commands
1390fbf6a33SBen Roth
1400fbf6a33SBen Roth- (void)_addDevCommands
1410fbf6a33SBen Roth{
1420fbf6a33SBen Roth  __weak typeof(self) weakSelf = self;
1430fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"d"
1440fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1450fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1460fbf6a33SBen Roth                               [weakSelf _handleMenuCommand];
1470fbf6a33SBen Roth                             }];
1480fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"r"
1490fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1500fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1510fbf6a33SBen Roth                               [weakSelf _handleRefreshCommand];
1520fbf6a33SBen Roth                             }];
1530fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"n"
1540fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1550fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1560fbf6a33SBen Roth                               [weakSelf _handleDisableDebuggingCommand];
1570fbf6a33SBen Roth                             }];
1580fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"i"
1590fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1600fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1610fbf6a33SBen Roth                               [weakSelf _handleToggleInspectorCommand];
1620fbf6a33SBen Roth                             }];
1630fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"k"
1640fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand | UIKeyModifierControl
1650fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1660fbf6a33SBen Roth                               [weakSelf _handleKernelMenuCommand];
1670fbf6a33SBen Roth                             }];
1680fbf6a33SBen Roth}
1690fbf6a33SBen Roth
1700fbf6a33SBen Roth- (void)_handleMenuCommand
1710fbf6a33SBen Roth{
17273b4b4d7SBen Roth  if ([EXShellManager sharedInstance].isDetached || _isLegacyMenuBehaviorEnabled) {
1730fbf6a33SBen Roth    [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager showDevMenu];
1740fbf6a33SBen Roth  } else {
1750fbf6a33SBen Roth    [[EXKernel sharedInstance] dispatchKernelJSEvent:@"switchTasks" body:@{} onSuccess:nil onFailure:nil];
1760fbf6a33SBen Roth  }
1770fbf6a33SBen Roth}
1780fbf6a33SBen Roth
1790fbf6a33SBen Roth- (void)_handleRefreshCommand
1800fbf6a33SBen Roth{
1810fbf6a33SBen Roth  [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager reloadBridge];
1820fbf6a33SBen Roth}
1830fbf6a33SBen Roth
1840fbf6a33SBen Roth- (void)_handleDisableDebuggingCommand
1850fbf6a33SBen Roth{
1860fbf6a33SBen Roth  [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager disableRemoteDebugging];
1870fbf6a33SBen Roth}
1880fbf6a33SBen Roth
1890fbf6a33SBen Roth- (void)_handleToggleInspectorCommand
1900fbf6a33SBen Roth{
1910fbf6a33SBen Roth  [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager toggleElementInspector];
1920fbf6a33SBen Roth}
1930fbf6a33SBen Roth
1940fbf6a33SBen Roth- (void)_handleKernelMenuCommand
1950fbf6a33SBen Roth{
1960fbf6a33SBen Roth  EXReactAppManager *foregroundAppManager = [EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager;
1970fbf6a33SBen Roth  if (foregroundAppManager == [EXKernel sharedInstance].bridgeRegistry.kernelAppManager) {
1980fbf6a33SBen Roth    [foregroundAppManager 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