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" 67f6367d0SBen 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 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 145*4e9980b6SBen Roth- (BOOL)isLegacyMenuButtonAvailable 146*4e9980b6SBen Roth{ 147*4e9980b6SBen Roth BOOL isSimulator = NO; 148*4e9980b6SBen Roth#if TARGET_OS_SIMULATOR 149*4e9980b6SBen Roth isSimulator = YES; 150*4e9980b6SBen Roth#endif 151*4e9980b6SBen Roth return ( 152*4e9980b6SBen Roth isSimulator 153*4e9980b6SBen Roth && _isLegacyMenuBehaviorEnabled 154*4e9980b6SBen Roth && [EXKernel sharedInstance].appRegistry.appEnumerator.allObjects.count > 0 155*4e9980b6SBen Roth ); 156*4e9980b6SBen Roth} 157*4e9980b6SBen 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{ 19273b4b4d7SBen Roth if ([EXShellManager sharedInstance].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{ 2017f6367d0SBen Roth [[EXKernel sharedInstance].visibleApp.appManager reloadBridge]; 2020fbf6a33SBen Roth} 2030fbf6a33SBen Roth 2040fbf6a33SBen Roth- (void)_handleDisableDebuggingCommand 2050fbf6a33SBen Roth{ 2067f6367d0SBen Roth [[EXKernel sharedInstance].visibleApp.appManager disableRemoteDebugging]; 2070fbf6a33SBen Roth} 2080fbf6a33SBen Roth 2090fbf6a33SBen Roth- (void)_handleToggleInspectorCommand 2100fbf6a33SBen Roth{ 2117f6367d0SBen Roth [[EXKernel sharedInstance].visibleApp.appManager toggleElementInspector]; 2120fbf6a33SBen Roth} 2130fbf6a33SBen Roth 2140fbf6a33SBen Roth- (void)_handleKernelMenuCommand 2150fbf6a33SBen Roth{ 2167f6367d0SBen Roth if ([EXKernel sharedInstance].visibleApp == [EXKernel sharedInstance].appRegistry.homeAppRecord) { 2177f6367d0SBen Roth [[EXKernel sharedInstance].appRegistry.homeAppRecord.appManager showDevMenu]; 2180fbf6a33SBen Roth } 2190fbf6a33SBen Roth} 2200fbf6a33SBen Roth 2210fbf6a33SBen Roth#pragma mark - managing list of commands 2220fbf6a33SBen Roth 2230fbf6a33SBen Roth- (void)registerKeyCommandWithInput:(NSString *)input 2240fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 2250fbf6a33SBen Roth action:(void (^)(UIKeyCommand *))block 2260fbf6a33SBen Roth{ 2270fbf6a33SBen Roth RCTAssertMainQueue(); 2280fbf6a33SBen Roth 2290fbf6a33SBen Roth UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input 2300fbf6a33SBen Roth modifierFlags:flags 2310fbf6a33SBen Roth action:@selector(EX_handleKeyCommand:)]; 2320fbf6a33SBen Roth 2330fbf6a33SBen Roth EXKeyCommand *keyCommand = [[EXKeyCommand alloc] initWithKeyCommand:command block:block]; 2340fbf6a33SBen Roth [_commands removeObject:keyCommand]; 2350fbf6a33SBen Roth [_commands addObject:keyCommand]; 2360fbf6a33SBen Roth} 2370fbf6a33SBen Roth 2380fbf6a33SBen Roth- (void)unregisterKeyCommandWithInput:(NSString *)input 2390fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 2400fbf6a33SBen Roth{ 2410fbf6a33SBen Roth RCTAssertMainQueue(); 2420fbf6a33SBen Roth 2430fbf6a33SBen Roth for (EXKeyCommand *command in _commands.allObjects) { 2440fbf6a33SBen Roth if ([command matchesInput:input flags:flags]) { 2450fbf6a33SBen Roth [_commands removeObject:command]; 2460fbf6a33SBen Roth break; 2470fbf6a33SBen Roth } 2480fbf6a33SBen Roth } 2490fbf6a33SBen Roth} 2500fbf6a33SBen Roth 2510fbf6a33SBen Roth- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input 2520fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 2530fbf6a33SBen Roth{ 2540fbf6a33SBen Roth RCTAssertMainQueue(); 2550fbf6a33SBen Roth 2560fbf6a33SBen Roth for (EXKeyCommand *command in _commands) { 2570fbf6a33SBen Roth if ([command matchesInput:input flags:flags]) { 2580fbf6a33SBen Roth return YES; 2590fbf6a33SBen Roth } 2600fbf6a33SBen Roth } 2610fbf6a33SBen Roth return NO; 2620fbf6a33SBen Roth} 2630fbf6a33SBen Roth 2640fbf6a33SBen Roth@end 2650fbf6a33SBen Roth 266