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