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