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" 8*73b4b4d7SBen Roth#import "EXShellManager.h" 90fbf6a33SBen Roth 100fbf6a33SBen Roth#import "RCTDefines.h" 110fbf6a33SBen Roth#import "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{ 172*73b4b4d7SBen 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