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 _isLegacyMenuBehaviorEnabled = NO; 132 [self _addDevCommands]; 133 } 134 return self; 135} 136 137#pragma mark - expo dev commands 138 139- (void)_addDevCommands 140{ 141 __weak typeof(self) weakSelf = self; 142 [self registerKeyCommandWithInput:@"d" 143 modifierFlags:UIKeyModifierCommand 144 action:^(__unused UIKeyCommand *_) { 145 [weakSelf _handleMenuCommand]; 146 }]; 147 [self registerKeyCommandWithInput:@"r" 148 modifierFlags:UIKeyModifierCommand 149 action:^(__unused UIKeyCommand *_) { 150 [weakSelf _handleRefreshCommand]; 151 }]; 152 [self registerKeyCommandWithInput:@"n" 153 modifierFlags:UIKeyModifierCommand 154 action:^(__unused UIKeyCommand *_) { 155 [weakSelf _handleDisableDebuggingCommand]; 156 }]; 157 [self registerKeyCommandWithInput:@"i" 158 modifierFlags:UIKeyModifierCommand 159 action:^(__unused UIKeyCommand *_) { 160 [weakSelf _handleToggleInspectorCommand]; 161 }]; 162 [self registerKeyCommandWithInput:@"k" 163 modifierFlags:UIKeyModifierCommand | UIKeyModifierControl 164 action:^(__unused UIKeyCommand *_) { 165 [weakSelf _handleKernelMenuCommand]; 166 }]; 167} 168 169- (void)_handleMenuCommand 170{ 171 BOOL detached = NO; 172#ifdef EX_DETACHED 173 detached = YES; 174#endif 175 if (detached || _isLegacyMenuBehaviorEnabled) { 176 [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager showDevMenu]; 177 } else { 178 [[EXKernel sharedInstance] dispatchKernelJSEvent:@"switchTasks" body:@{} onSuccess:nil onFailure:nil]; 179 } 180} 181 182- (void)_handleRefreshCommand 183{ 184 [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager reloadBridge]; 185} 186 187- (void)_handleDisableDebuggingCommand 188{ 189 [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager disableRemoteDebugging]; 190} 191 192- (void)_handleToggleInspectorCommand 193{ 194 [[EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager toggleElementInspector]; 195} 196 197- (void)_handleKernelMenuCommand 198{ 199 EXReactAppManager *foregroundAppManager = [EXKernel sharedInstance].bridgeRegistry.lastKnownForegroundAppManager; 200 if (foregroundAppManager == [EXKernel sharedInstance].bridgeRegistry.kernelAppManager) { 201 [foregroundAppManager showDevMenu]; 202 } 203} 204 205#pragma mark - managing list of commands 206 207- (void)registerKeyCommandWithInput:(NSString *)input 208 modifierFlags:(UIKeyModifierFlags)flags 209 action:(void (^)(UIKeyCommand *))block 210{ 211 RCTAssertMainQueue(); 212 213 UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input 214 modifierFlags:flags 215 action:@selector(EX_handleKeyCommand:)]; 216 217 EXKeyCommand *keyCommand = [[EXKeyCommand alloc] initWithKeyCommand:command block:block]; 218 [_commands removeObject:keyCommand]; 219 [_commands addObject:keyCommand]; 220} 221 222- (void)unregisterKeyCommandWithInput:(NSString *)input 223 modifierFlags:(UIKeyModifierFlags)flags 224{ 225 RCTAssertMainQueue(); 226 227 for (EXKeyCommand *command in _commands.allObjects) { 228 if ([command matchesInput:input flags:flags]) { 229 [_commands removeObject:command]; 230 break; 231 } 232 } 233} 234 235- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input 236 modifierFlags:(UIKeyModifierFlags)flags 237{ 238 RCTAssertMainQueue(); 239 240 for (EXKeyCommand *command in _commands) { 241 if ([command matchesInput:input flags:flags]) { 242 return YES; 243 } 244 } 245 return NO; 246} 247 248@end 249 250