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" 80fbf6a33SBen Roth 90fbf6a33SBen Roth#import "RCTDefines.h" 100fbf6a33SBen Roth#import "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]; 131*f148175aSBen 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{ 171*f148175aSBen Roth if (_isLegacyMenuBehaviorEnabled) { 1720fbf6a33SBen Roth [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager showDevMenu]; 1730fbf6a33SBen Roth } else { 1740fbf6a33SBen Roth [[EXKernel sharedInstance] dispatchKernelJSEvent:@"switchTasks" body:@{} onSuccess:nil onFailure:nil]; 1750fbf6a33SBen Roth } 1760fbf6a33SBen Roth} 1770fbf6a33SBen Roth 1780fbf6a33SBen Roth- (void)_handleRefreshCommand 1790fbf6a33SBen Roth{ 1800fbf6a33SBen Roth [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager reloadBridge]; 1810fbf6a33SBen Roth} 1820fbf6a33SBen Roth 1830fbf6a33SBen Roth- (void)_handleDisableDebuggingCommand 1840fbf6a33SBen Roth{ 1850fbf6a33SBen Roth [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager disableRemoteDebugging]; 1860fbf6a33SBen Roth} 1870fbf6a33SBen Roth 1880fbf6a33SBen Roth- (void)_handleToggleInspectorCommand 1890fbf6a33SBen Roth{ 1900fbf6a33SBen Roth [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager toggleElementInspector]; 1910fbf6a33SBen Roth} 1920fbf6a33SBen Roth 1930fbf6a33SBen Roth- (void)_handleKernelMenuCommand 1940fbf6a33SBen Roth{ 1950fbf6a33SBen Roth EXReactAppManager *foregroundAppManager = [EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager; 1960fbf6a33SBen Roth if (foregroundAppManager == [EXKernel sharedInstance].bridgeRegistry.kernelAppManager) { 1970fbf6a33SBen Roth [foregroundAppManager showDevMenu]; 1980fbf6a33SBen Roth } 1990fbf6a33SBen Roth} 2000fbf6a33SBen Roth 2010fbf6a33SBen Roth#pragma mark - managing list of commands 2020fbf6a33SBen Roth 2030fbf6a33SBen Roth- (void)registerKeyCommandWithInput:(NSString *)input 2040fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 2050fbf6a33SBen Roth action:(void (^)(UIKeyCommand *))block 2060fbf6a33SBen Roth{ 2070fbf6a33SBen Roth RCTAssertMainQueue(); 2080fbf6a33SBen Roth 2090fbf6a33SBen Roth UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input 2100fbf6a33SBen Roth modifierFlags:flags 2110fbf6a33SBen Roth action:@selector(EX_handleKeyCommand:)]; 2120fbf6a33SBen Roth 2130fbf6a33SBen Roth EXKeyCommand *keyCommand = [[EXKeyCommand alloc] initWithKeyCommand:command block:block]; 2140fbf6a33SBen Roth [_commands removeObject:keyCommand]; 2150fbf6a33SBen Roth [_commands addObject:keyCommand]; 2160fbf6a33SBen Roth} 2170fbf6a33SBen Roth 2180fbf6a33SBen Roth- (void)unregisterKeyCommandWithInput:(NSString *)input 2190fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 2200fbf6a33SBen Roth{ 2210fbf6a33SBen Roth RCTAssertMainQueue(); 2220fbf6a33SBen Roth 2230fbf6a33SBen Roth for (EXKeyCommand *command in _commands.allObjects) { 2240fbf6a33SBen Roth if ([command matchesInput:input flags:flags]) { 2250fbf6a33SBen Roth [_commands removeObject:command]; 2260fbf6a33SBen Roth break; 2270fbf6a33SBen Roth } 2280fbf6a33SBen Roth } 2290fbf6a33SBen Roth} 2300fbf6a33SBen Roth 2310fbf6a33SBen Roth- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input 2320fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 2330fbf6a33SBen Roth{ 2340fbf6a33SBen Roth RCTAssertMainQueue(); 2350fbf6a33SBen Roth 2360fbf6a33SBen Roth for (EXKeyCommand *command in _commands) { 2370fbf6a33SBen Roth if ([command matchesInput:input flags:flags]) { 2380fbf6a33SBen Roth return YES; 2390fbf6a33SBen Roth } 2400fbf6a33SBen Roth } 2410fbf6a33SBen Roth return NO; 2420fbf6a33SBen Roth} 2430fbf6a33SBen Roth 2440fbf6a33SBen Roth@end 2450fbf6a33SBen Roth 246