1*0fbf6a33SBen Roth// Copyright 2015-present 650 Industries. All rights reserved. 2*0fbf6a33SBen Roth 3*0fbf6a33SBen Roth#import "EXFrameReactAppManager.h" 4*0fbf6a33SBen Roth#import "EXKernelDevKeyCommands.h" 5*0fbf6a33SBen Roth#import "EXKernel.h" 6*0fbf6a33SBen Roth#import "EXKernelBridgeRegistry.h" 7*0fbf6a33SBen Roth#import "EXKernelReactAppManager.h" 8*0fbf6a33SBen Roth 9*0fbf6a33SBen Roth#import "RCTDefines.h" 10*0fbf6a33SBen Roth#import "RCTUtils.h" 11*0fbf6a33SBen Roth 12*0fbf6a33SBen Roth#import <UIKit/UIKit.h> 13*0fbf6a33SBen Roth 14*0fbf6a33SBen Roth@interface EXKeyCommand : NSObject <NSCopying> 15*0fbf6a33SBen Roth 16*0fbf6a33SBen Roth@property (nonatomic, strong) UIKeyCommand *keyCommand; 17*0fbf6a33SBen Roth@property (nonatomic, copy) void (^block)(UIKeyCommand *); 18*0fbf6a33SBen Roth 19*0fbf6a33SBen Roth@end 20*0fbf6a33SBen Roth 21*0fbf6a33SBen Roth@implementation EXKeyCommand 22*0fbf6a33SBen Roth 23*0fbf6a33SBen Roth- (instancetype)initWithKeyCommand:(UIKeyCommand *)keyCommand 24*0fbf6a33SBen Roth block:(void (^)(UIKeyCommand *))block 25*0fbf6a33SBen Roth{ 26*0fbf6a33SBen Roth if ((self = [super init])) { 27*0fbf6a33SBen Roth _keyCommand = keyCommand; 28*0fbf6a33SBen Roth _block = block; 29*0fbf6a33SBen Roth } 30*0fbf6a33SBen Roth return self; 31*0fbf6a33SBen Roth} 32*0fbf6a33SBen Roth 33*0fbf6a33SBen RothRCT_NOT_IMPLEMENTED(- (instancetype)init) 34*0fbf6a33SBen Roth 35*0fbf6a33SBen Roth- (id)copyWithZone:(__unused NSZone *)zone 36*0fbf6a33SBen Roth{ 37*0fbf6a33SBen Roth return self; 38*0fbf6a33SBen Roth} 39*0fbf6a33SBen Roth 40*0fbf6a33SBen Roth- (NSUInteger)hash 41*0fbf6a33SBen Roth{ 42*0fbf6a33SBen Roth return _keyCommand.input.hash ^ _keyCommand.modifierFlags; 43*0fbf6a33SBen Roth} 44*0fbf6a33SBen Roth 45*0fbf6a33SBen Roth- (BOOL)isEqual:(EXKeyCommand *)object 46*0fbf6a33SBen Roth{ 47*0fbf6a33SBen Roth if (![object isKindOfClass:[EXKeyCommand class]]) { 48*0fbf6a33SBen Roth return NO; 49*0fbf6a33SBen Roth } 50*0fbf6a33SBen Roth return [self matchesInput:object.keyCommand.input 51*0fbf6a33SBen Roth flags:object.keyCommand.modifierFlags]; 52*0fbf6a33SBen Roth} 53*0fbf6a33SBen Roth 54*0fbf6a33SBen Roth- (BOOL)matchesInput:(NSString *)input flags:(UIKeyModifierFlags)flags 55*0fbf6a33SBen Roth{ 56*0fbf6a33SBen Roth return [_keyCommand.input isEqual:input] && _keyCommand.modifierFlags == flags; 57*0fbf6a33SBen Roth} 58*0fbf6a33SBen Roth 59*0fbf6a33SBen Roth- (NSString *)description 60*0fbf6a33SBen Roth{ 61*0fbf6a33SBen Roth return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd hasBlock=%@>", 62*0fbf6a33SBen Roth [self class], self, _keyCommand.input, _keyCommand.modifierFlags, 63*0fbf6a33SBen Roth _block ? @"YES" : @"NO"]; 64*0fbf6a33SBen Roth} 65*0fbf6a33SBen Roth 66*0fbf6a33SBen Roth@end 67*0fbf6a33SBen Roth 68*0fbf6a33SBen Roth@interface EXKernelDevKeyCommands () 69*0fbf6a33SBen Roth 70*0fbf6a33SBen Roth@property (nonatomic, strong) NSMutableSet<EXKeyCommand *> *commands; 71*0fbf6a33SBen Roth 72*0fbf6a33SBen Roth@end 73*0fbf6a33SBen Roth 74*0fbf6a33SBen Roth@implementation UIResponder (EXKeyCommands) 75*0fbf6a33SBen Roth 76*0fbf6a33SBen Roth- (NSArray<UIKeyCommand *> *)EX_keyCommands 77*0fbf6a33SBen Roth{ 78*0fbf6a33SBen Roth NSSet<EXKeyCommand *> *commands = [EXKernelDevKeyCommands sharedInstance].commands; 79*0fbf6a33SBen Roth return [[commands valueForKeyPath:@"keyCommand"] allObjects]; 80*0fbf6a33SBen Roth} 81*0fbf6a33SBen Roth 82*0fbf6a33SBen Roth- (void)EX_handleKeyCommand:(UIKeyCommand *)key 83*0fbf6a33SBen Roth{ 84*0fbf6a33SBen Roth // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand: 85*0fbf6a33SBen Roth // method gets called repeatedly if the command key is held down. 86*0fbf6a33SBen Roth static NSTimeInterval lastCommand = 0; 87*0fbf6a33SBen Roth if (CACurrentMediaTime() - lastCommand > 0.5) { 88*0fbf6a33SBen Roth for (EXKeyCommand *command in [EXKernelDevKeyCommands sharedInstance].commands) { 89*0fbf6a33SBen Roth if ([command.keyCommand.input isEqualToString:key.input] && 90*0fbf6a33SBen Roth command.keyCommand.modifierFlags == key.modifierFlags) { 91*0fbf6a33SBen Roth if (command.block) { 92*0fbf6a33SBen Roth command.block(key); 93*0fbf6a33SBen Roth lastCommand = CACurrentMediaTime(); 94*0fbf6a33SBen Roth } 95*0fbf6a33SBen Roth } 96*0fbf6a33SBen Roth } 97*0fbf6a33SBen Roth } 98*0fbf6a33SBen Roth} 99*0fbf6a33SBen Roth 100*0fbf6a33SBen Roth@end 101*0fbf6a33SBen Roth 102*0fbf6a33SBen Roth@implementation EXKernelDevKeyCommands 103*0fbf6a33SBen Roth 104*0fbf6a33SBen Roth+ (instancetype)sharedInstance 105*0fbf6a33SBen Roth{ 106*0fbf6a33SBen Roth static EXKernelDevKeyCommands *instance; 107*0fbf6a33SBen Roth static dispatch_once_t once; 108*0fbf6a33SBen Roth dispatch_once(&once, ^{ 109*0fbf6a33SBen Roth if (!instance) { 110*0fbf6a33SBen Roth instance = [[EXKernelDevKeyCommands alloc] init]; 111*0fbf6a33SBen Roth } 112*0fbf6a33SBen Roth }); 113*0fbf6a33SBen Roth return instance; 114*0fbf6a33SBen Roth} 115*0fbf6a33SBen Roth 116*0fbf6a33SBen Roth+ (void)initialize 117*0fbf6a33SBen Roth{ 118*0fbf6a33SBen Roth // capture keycommands across all bridges. 119*0fbf6a33SBen Roth // this is the same approach taken by RCTKeyCommands, 120*0fbf6a33SBen Roth // but that class is disabled in the expo react native fork 121*0fbf6a33SBen Roth // since there may be many instances of it. 122*0fbf6a33SBen Roth RCTSwapInstanceMethods([UIResponder class], 123*0fbf6a33SBen Roth @selector(keyCommands), 124*0fbf6a33SBen Roth @selector(EX_keyCommands)); 125*0fbf6a33SBen Roth} 126*0fbf6a33SBen Roth 127*0fbf6a33SBen Roth- (instancetype)init 128*0fbf6a33SBen Roth{ 129*0fbf6a33SBen Roth if ((self = [super init])) { 130*0fbf6a33SBen Roth _commands = [NSMutableSet set]; 131*0fbf6a33SBen Roth [self _addDevCommands]; 132*0fbf6a33SBen Roth } 133*0fbf6a33SBen Roth return self; 134*0fbf6a33SBen Roth} 135*0fbf6a33SBen Roth 136*0fbf6a33SBen Roth#pragma mark - expo dev commands 137*0fbf6a33SBen Roth 138*0fbf6a33SBen Roth- (void)_addDevCommands 139*0fbf6a33SBen Roth{ 140*0fbf6a33SBen Roth __weak typeof(self) weakSelf = self; 141*0fbf6a33SBen Roth [self registerKeyCommandWithInput:@"d" 142*0fbf6a33SBen Roth modifierFlags:UIKeyModifierCommand 143*0fbf6a33SBen Roth action:^(__unused UIKeyCommand *_) { 144*0fbf6a33SBen Roth [weakSelf _handleMenuCommand]; 145*0fbf6a33SBen Roth }]; 146*0fbf6a33SBen Roth [self registerKeyCommandWithInput:@"r" 147*0fbf6a33SBen Roth modifierFlags:UIKeyModifierCommand 148*0fbf6a33SBen Roth action:^(__unused UIKeyCommand *_) { 149*0fbf6a33SBen Roth [weakSelf _handleRefreshCommand]; 150*0fbf6a33SBen Roth }]; 151*0fbf6a33SBen Roth [self registerKeyCommandWithInput:@"n" 152*0fbf6a33SBen Roth modifierFlags:UIKeyModifierCommand 153*0fbf6a33SBen Roth action:^(__unused UIKeyCommand *_) { 154*0fbf6a33SBen Roth [weakSelf _handleDisableDebuggingCommand]; 155*0fbf6a33SBen Roth }]; 156*0fbf6a33SBen Roth [self registerKeyCommandWithInput:@"i" 157*0fbf6a33SBen Roth modifierFlags:UIKeyModifierCommand 158*0fbf6a33SBen Roth action:^(__unused UIKeyCommand *_) { 159*0fbf6a33SBen Roth [weakSelf _handleToggleInspectorCommand]; 160*0fbf6a33SBen Roth }]; 161*0fbf6a33SBen Roth [self registerKeyCommandWithInput:@"k" 162*0fbf6a33SBen Roth modifierFlags:UIKeyModifierCommand | UIKeyModifierControl 163*0fbf6a33SBen Roth action:^(__unused UIKeyCommand *_) { 164*0fbf6a33SBen Roth [weakSelf _handleKernelMenuCommand]; 165*0fbf6a33SBen Roth }]; 166*0fbf6a33SBen Roth} 167*0fbf6a33SBen Roth 168*0fbf6a33SBen Roth- (void)_handleMenuCommand 169*0fbf6a33SBen Roth{ 170*0fbf6a33SBen Roth if (EX_ENABLE_LEGACY_MENU_BEHAVIOR) { 171*0fbf6a33SBen Roth [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager showDevMenu]; 172*0fbf6a33SBen Roth } else { 173*0fbf6a33SBen Roth [[EXKernel sharedInstance] dispatchKernelJSEvent:@"switchTasks" body:@{} onSuccess:nil onFailure:nil]; 174*0fbf6a33SBen Roth } 175*0fbf6a33SBen Roth} 176*0fbf6a33SBen Roth 177*0fbf6a33SBen Roth- (void)_handleRefreshCommand 178*0fbf6a33SBen Roth{ 179*0fbf6a33SBen Roth [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager reloadBridge]; 180*0fbf6a33SBen Roth} 181*0fbf6a33SBen Roth 182*0fbf6a33SBen Roth- (void)_handleDisableDebuggingCommand 183*0fbf6a33SBen Roth{ 184*0fbf6a33SBen Roth [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager disableRemoteDebugging]; 185*0fbf6a33SBen Roth} 186*0fbf6a33SBen Roth 187*0fbf6a33SBen Roth- (void)_handleToggleInspectorCommand 188*0fbf6a33SBen Roth{ 189*0fbf6a33SBen Roth [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager toggleElementInspector]; 190*0fbf6a33SBen Roth} 191*0fbf6a33SBen Roth 192*0fbf6a33SBen Roth- (void)_handleKernelMenuCommand 193*0fbf6a33SBen Roth{ 194*0fbf6a33SBen Roth EXReactAppManager *foregroundAppManager = [EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager; 195*0fbf6a33SBen Roth if (foregroundAppManager == [EXKernel sharedInstance].bridgeRegistry.kernelAppManager) { 196*0fbf6a33SBen Roth [foregroundAppManager showDevMenu]; 197*0fbf6a33SBen Roth } 198*0fbf6a33SBen Roth} 199*0fbf6a33SBen Roth 200*0fbf6a33SBen Roth#pragma mark - managing list of commands 201*0fbf6a33SBen Roth 202*0fbf6a33SBen Roth- (void)registerKeyCommandWithInput:(NSString *)input 203*0fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 204*0fbf6a33SBen Roth action:(void (^)(UIKeyCommand *))block 205*0fbf6a33SBen Roth{ 206*0fbf6a33SBen Roth RCTAssertMainQueue(); 207*0fbf6a33SBen Roth 208*0fbf6a33SBen Roth UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input 209*0fbf6a33SBen Roth modifierFlags:flags 210*0fbf6a33SBen Roth action:@selector(EX_handleKeyCommand:)]; 211*0fbf6a33SBen Roth 212*0fbf6a33SBen Roth EXKeyCommand *keyCommand = [[EXKeyCommand alloc] initWithKeyCommand:command block:block]; 213*0fbf6a33SBen Roth [_commands removeObject:keyCommand]; 214*0fbf6a33SBen Roth [_commands addObject:keyCommand]; 215*0fbf6a33SBen Roth} 216*0fbf6a33SBen Roth 217*0fbf6a33SBen Roth- (void)unregisterKeyCommandWithInput:(NSString *)input 218*0fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 219*0fbf6a33SBen Roth{ 220*0fbf6a33SBen Roth RCTAssertMainQueue(); 221*0fbf6a33SBen Roth 222*0fbf6a33SBen Roth for (EXKeyCommand *command in _commands.allObjects) { 223*0fbf6a33SBen Roth if ([command matchesInput:input flags:flags]) { 224*0fbf6a33SBen Roth [_commands removeObject:command]; 225*0fbf6a33SBen Roth break; 226*0fbf6a33SBen Roth } 227*0fbf6a33SBen Roth } 228*0fbf6a33SBen Roth} 229*0fbf6a33SBen Roth 230*0fbf6a33SBen Roth- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input 231*0fbf6a33SBen Roth modifierFlags:(UIKeyModifierFlags)flags 232*0fbf6a33SBen Roth{ 233*0fbf6a33SBen Roth RCTAssertMainQueue(); 234*0fbf6a33SBen Roth 235*0fbf6a33SBen Roth for (EXKeyCommand *command in _commands) { 236*0fbf6a33SBen Roth if ([command matchesInput:input flags:flags]) { 237*0fbf6a33SBen Roth return YES; 238*0fbf6a33SBen Roth } 239*0fbf6a33SBen Roth } 240*0fbf6a33SBen Roth return NO; 241*0fbf6a33SBen Roth} 242*0fbf6a33SBen Roth 243*0fbf6a33SBen Roth@end 244*0fbf6a33SBen Roth 245