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
14b0932293SBen RothNSNotificationName kEXKernelDidChangeMenuBehaviorNotification = @"EXKernelDidChangeMenuBehaviorNotification";
15b0932293SBen Roth
160fbf6a33SBen Roth@interface EXKeyCommand : NSObject <NSCopying>
170fbf6a33SBen Roth
180fbf6a33SBen Roth@property (nonatomic, strong) UIKeyCommand *keyCommand;
190fbf6a33SBen Roth@property (nonatomic, copy) void (^block)(UIKeyCommand *);
200fbf6a33SBen Roth
210fbf6a33SBen Roth@end
220fbf6a33SBen Roth
230fbf6a33SBen Roth@implementation EXKeyCommand
240fbf6a33SBen Roth
250fbf6a33SBen Roth- (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand
260fbf6a33SBen Roth                             block:(void (^)(UIKeyCommand *))block
270fbf6a33SBen Roth{
280fbf6a33SBen Roth  if ((self = [super init])) {
290fbf6a33SBen Roth    _keyCommand = keyCommand;
300fbf6a33SBen Roth    _block = block;
310fbf6a33SBen Roth  }
320fbf6a33SBen Roth  return self;
330fbf6a33SBen Roth}
340fbf6a33SBen Roth
350fbf6a33SBen RothRCT_NOT_IMPLEMENTED(- (instancetype)init)
360fbf6a33SBen Roth
370fbf6a33SBen Roth- (id)copyWithZone:(__unused NSZone *)zone
380fbf6a33SBen Roth{
390fbf6a33SBen Roth  return self;
400fbf6a33SBen Roth}
410fbf6a33SBen Roth
420fbf6a33SBen Roth- (NSUInteger)hash
430fbf6a33SBen Roth{
440fbf6a33SBen Roth  return _keyCommand.input.hash ^ _keyCommand.modifierFlags;
450fbf6a33SBen Roth}
460fbf6a33SBen Roth
470fbf6a33SBen Roth- (BOOL)isEqual:(EXKeyCommand *)object
480fbf6a33SBen Roth{
490fbf6a33SBen Roth  if (![object isKindOfClass:[EXKeyCommand class]]) {
500fbf6a33SBen Roth    return NO;
510fbf6a33SBen Roth  }
520fbf6a33SBen Roth  return [self matchesInput:object.keyCommand.input
530fbf6a33SBen Roth                      flags:object.keyCommand.modifierFlags];
540fbf6a33SBen Roth}
550fbf6a33SBen Roth
560fbf6a33SBen Roth- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags
570fbf6a33SBen Roth{
580fbf6a33SBen Roth  return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags;
590fbf6a33SBen Roth}
600fbf6a33SBen Roth
610fbf6a33SBen Roth- (NSString *)description
620fbf6a33SBen Roth{
630fbf6a33SBen Roth  return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd hasBlock=%@>",
640fbf6a33SBen Roth          [self class], self, _keyCommand.input, _keyCommand.modifierFlags,
650fbf6a33SBen Roth          _block ? @"YES" : @"NO"];
660fbf6a33SBen Roth}
670fbf6a33SBen Roth
680fbf6a33SBen Roth@end
690fbf6a33SBen Roth
700fbf6a33SBen Roth@interface EXKernelDevKeyCommands ()
710fbf6a33SBen Roth
720fbf6a33SBen Roth@property (nonatomic, strong) NSMutableSet<EXKeyCommand *> *commands;
730fbf6a33SBen Roth
740fbf6a33SBen Roth@end
750fbf6a33SBen Roth
760fbf6a33SBen Roth@implementation UIResponder (EXKeyCommands)
770fbf6a33SBen Roth
780fbf6a33SBen Roth- (NSArray<UIKeyCommand *> *)EX_keyCommands
790fbf6a33SBen Roth{
800fbf6a33SBen Roth  NSSet<EXKeyCommand *> *commands = [EXKernelDevKeyCommands sharedInstance].commands;
810fbf6a33SBen Roth  return [[commands valueForKeyPath:@"keyCommand"] allObjects];
820fbf6a33SBen Roth}
830fbf6a33SBen Roth
840fbf6a33SBen Roth- (void)EX_handleKeyCommand:(UIKeyCommand *)key
850fbf6a33SBen Roth{
860fbf6a33SBen Roth  // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
870fbf6a33SBen Roth  // method gets called repeatedly if the command key is held down.
880fbf6a33SBen Roth  static NSTimeInterval lastCommand = 0;
890fbf6a33SBen Roth  if (CACurrentMediaTime() - lastCommand > 0.5) {
900fbf6a33SBen Roth    for (EXKeyCommand *command in [EXKernelDevKeyCommands sharedInstance].commands) {
910fbf6a33SBen Roth      if ([command.keyCommand.input isEqualToString:key.input] &&
920fbf6a33SBen Roth          command.keyCommand.modifierFlags == key.modifierFlags) {
930fbf6a33SBen Roth        if (command.block) {
940fbf6a33SBen Roth          command.block(key);
950fbf6a33SBen Roth          lastCommand = CACurrentMediaTime();
960fbf6a33SBen Roth        }
970fbf6a33SBen Roth      }
980fbf6a33SBen Roth    }
990fbf6a33SBen Roth  }
1000fbf6a33SBen Roth}
1010fbf6a33SBen Roth
1020fbf6a33SBen Roth@end
1030fbf6a33SBen Roth
1040fbf6a33SBen Roth@implementation EXKernelDevKeyCommands
1050fbf6a33SBen Roth
1060fbf6a33SBen Roth+ (instancetype)sharedInstance
1070fbf6a33SBen Roth{
1080fbf6a33SBen Roth  static EXKernelDevKeyCommands *instance;
1090fbf6a33SBen Roth  static dispatch_once_t once;
1100fbf6a33SBen Roth  dispatch_once(&once, ^{
1110fbf6a33SBen Roth    if (!instance) {
1120fbf6a33SBen Roth      instance = [[EXKernelDevKeyCommands alloc] init];
1130fbf6a33SBen Roth    }
1140fbf6a33SBen Roth  });
1150fbf6a33SBen Roth  return instance;
1160fbf6a33SBen Roth}
1170fbf6a33SBen Roth
1180fbf6a33SBen Roth+ (void)initialize
1190fbf6a33SBen Roth{
1200fbf6a33SBen Roth  // capture keycommands across all bridges.
1210fbf6a33SBen Roth  // this is the same approach taken by RCTKeyCommands,
1220fbf6a33SBen Roth  // but that class is disabled in the expo react native fork
1230fbf6a33SBen Roth  // since there may be many instances of it.
1240fbf6a33SBen Roth  RCTSwapInstanceMethods([UIResponder class],
1250fbf6a33SBen Roth                         @selector(keyCommands),
1260fbf6a33SBen Roth                         @selector(EX_keyCommands));
1270fbf6a33SBen Roth}
1280fbf6a33SBen Roth
1290fbf6a33SBen Roth- (instancetype)init
1300fbf6a33SBen Roth{
1310fbf6a33SBen Roth  if ((self = [super init])) {
1320fbf6a33SBen Roth    _commands = [NSMutableSet set];
133f148175aSBen Roth    _isLegacyMenuBehaviorEnabled = NO;
1340fbf6a33SBen Roth    [self _addDevCommands];
1350fbf6a33SBen Roth  }
1360fbf6a33SBen Roth  return self;
1370fbf6a33SBen Roth}
1380fbf6a33SBen Roth
139b0932293SBen Roth- (void)setIsLegacyMenuBehaviorEnabled:(BOOL)isLegacyMenuBehaviorEnabled
140b0932293SBen Roth{
141b0932293SBen Roth  _isLegacyMenuBehaviorEnabled = isLegacyMenuBehaviorEnabled;
142b0932293SBen Roth  [[NSNotificationCenter defaultCenter] postNotificationName:kEXKernelDidChangeMenuBehaviorNotification object:nil];
143b0932293SBen Roth}
144b0932293SBen Roth
1454e9980b6SBen Roth- (BOOL)isLegacyMenuButtonAvailable
1464e9980b6SBen Roth{
1474e9980b6SBen Roth    BOOL isSimulator = NO;
1484e9980b6SBen Roth#if TARGET_OS_SIMULATOR
1494e9980b6SBen Roth    isSimulator = YES;
1504e9980b6SBen Roth#endif
1514e9980b6SBen Roth  return (
1524e9980b6SBen Roth    isSimulator
1534e9980b6SBen Roth    && _isLegacyMenuBehaviorEnabled
1544e9980b6SBen Roth    && [EXKernel sharedInstance].appRegistry.appEnumerator.allObjects.count > 0
1554e9980b6SBen Roth  );
1564e9980b6SBen Roth}
1574e9980b6SBen Roth
1580fbf6a33SBen Roth#pragma mark - expo dev commands
1590fbf6a33SBen Roth
1600fbf6a33SBen Roth- (void)_addDevCommands
1610fbf6a33SBen Roth{
1620fbf6a33SBen Roth  __weak typeof(self) weakSelf = self;
1630fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"d"
1640fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1650fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1660fbf6a33SBen Roth                               [weakSelf _handleMenuCommand];
1670fbf6a33SBen Roth                             }];
1680fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"r"
1690fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1700fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1710fbf6a33SBen Roth                               [weakSelf _handleRefreshCommand];
1720fbf6a33SBen Roth                             }];
1730fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"n"
1740fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1750fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1760fbf6a33SBen Roth                               [weakSelf _handleDisableDebuggingCommand];
1770fbf6a33SBen Roth                             }];
1780fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"i"
1790fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand
1800fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1810fbf6a33SBen Roth                               [weakSelf _handleToggleInspectorCommand];
1820fbf6a33SBen Roth                             }];
1830fbf6a33SBen Roth  [self registerKeyCommandWithInput:@"k"
1840fbf6a33SBen Roth                      modifierFlags:UIKeyModifierCommand | UIKeyModifierControl
1850fbf6a33SBen Roth                             action:^(__unused UIKeyCommand *_) {
1860fbf6a33SBen Roth                               [weakSelf _handleKernelMenuCommand];
1870fbf6a33SBen Roth                             }];
1880fbf6a33SBen Roth}
1890fbf6a33SBen Roth
1900fbf6a33SBen Roth- (void)_handleMenuCommand
1910fbf6a33SBen Roth{
19200fcd3c6SBen Roth  if ([EXEnvironment sharedEnvironment].isDetached || _isLegacyMenuBehaviorEnabled) {
1937f6367d0SBen Roth    [[EXKernel sharedInstance].visibleApp.appManager showDevMenu];
1940fbf6a33SBen Roth  } else {
1957f6367d0SBen Roth    [[EXKernel sharedInstance] switchTasks];
1960fbf6a33SBen Roth  }
1970fbf6a33SBen Roth}
1980fbf6a33SBen Roth
1990fbf6a33SBen Roth- (void)_handleRefreshCommand
2000fbf6a33SBen Roth{
201*b333b418SBrent Vatne  // This reloads only JS
202*b333b418SBrent Vatne  //  [[EXKernel sharedInstance].visibleApp.appManager reloadBridge];
203*b333b418SBrent Vatne
204*b333b418SBrent Vatne  // This reloads manifest and JS
205*b333b418SBrent Vatne  [[EXKernel sharedInstance] reloadVisibleApp];
2060fbf6a33SBen Roth}
2070fbf6a33SBen Roth
2080fbf6a33SBen Roth- (void)_handleDisableDebuggingCommand
2090fbf6a33SBen Roth{
2107f6367d0SBen Roth  [[EXKernel sharedInstance].visibleApp.appManager disableRemoteDebugging];
2110fbf6a33SBen Roth}
2120fbf6a33SBen Roth
2130fbf6a33SBen Roth- (void)_handleToggleInspectorCommand
2140fbf6a33SBen Roth{
2157f6367d0SBen Roth  [[EXKernel sharedInstance].visibleApp.appManager toggleElementInspector];
2160fbf6a33SBen Roth}
2170fbf6a33SBen Roth
2180fbf6a33SBen Roth- (void)_handleKernelMenuCommand
2190fbf6a33SBen Roth{
2207f6367d0SBen Roth  if ([EXKernel sharedInstance].visibleApp == [EXKernel sharedInstance].appRegistry.homeAppRecord) {
2217f6367d0SBen Roth    [[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager showDevMenu];
2220fbf6a33SBen Roth  }
2230fbf6a33SBen Roth}
2240fbf6a33SBen Roth
2250fbf6a33SBen Roth#pragma mark - managing list of commands
2260fbf6a33SBen Roth
2270fbf6a33SBen Roth- (void)registerKeyCommandWithInput:(NSString *)input
2280fbf6a33SBen Roth                      modifierFlags:(UIKeyModifierFlags)flags
2290fbf6a33SBen Roth                             action:(void (^)(UIKeyCommand *))block
2300fbf6a33SBen Roth{
2310fbf6a33SBen Roth  RCTAssertMainQueue();
2320fbf6a33SBen Roth
2330fbf6a33SBen Roth  UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
2340fbf6a33SBen Roth                                              modifierFlags:flags
2350fbf6a33SBen Roth                                                     action:@selector(EX_handleKeyCommand:)];
2360fbf6a33SBen Roth
2370fbf6a33SBen Roth  EXKeyCommand *keyCommand = [[EXKeyCommand alloc] initWithKeyCommand:command block:block];
2380fbf6a33SBen Roth  [_commands removeObject:keyCommand];
2390fbf6a33SBen Roth  [_commands addObject:keyCommand];
2400fbf6a33SBen Roth}
2410fbf6a33SBen Roth
2420fbf6a33SBen Roth- (void)unregisterKeyCommandWithInput:(NSString *)input
2430fbf6a33SBen Roth                        modifierFlags:(UIKeyModifierFlags)flags
2440fbf6a33SBen Roth{
2450fbf6a33SBen Roth  RCTAssertMainQueue();
2460fbf6a33SBen Roth
2470fbf6a33SBen Roth  for (EXKeyCommand *command in _commands.allObjects) {
2480fbf6a33SBen Roth    if ([command matchesInput:input flags:flags]) {
2490fbf6a33SBen Roth      [_commands removeObject:command];
2500fbf6a33SBen Roth      break;
2510fbf6a33SBen Roth    }
2520fbf6a33SBen Roth  }
2530fbf6a33SBen Roth}
2540fbf6a33SBen Roth
2550fbf6a33SBen Roth- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
2560fbf6a33SBen Roth                         modifierFlags:(UIKeyModifierFlags)flags
2570fbf6a33SBen Roth{
2580fbf6a33SBen Roth  RCTAssertMainQueue();
2590fbf6a33SBen Roth
2600fbf6a33SBen Roth  for (EXKeyCommand *command in _commands) {
2610fbf6a33SBen Roth    if ([command matchesInput:input flags:flags]) {
2620fbf6a33SBen Roth      return YES;
2630fbf6a33SBen Roth    }
2640fbf6a33SBen Roth  }
2650fbf6a33SBen Roth  return NO;
2660fbf6a33SBen Roth}
2670fbf6a33SBen Roth
2680fbf6a33SBen Roth@end
2690fbf6a33SBen Roth
270