9
9
10
10
#import < UIKit/UIKit.h>
11
11
12
+ #import < objc/message.h>
13
+ #import < objc/runtime.h>
12
14
#import " RCTDefines.h"
13
15
#import " RCTUtils.h"
14
16
15
17
#if RCT_DEV
16
18
19
+ @interface UIEvent (UIPhysicalKeyboardEvent)
20
+
21
+ @property (nonatomic ) NSString *_modifiedInput;
22
+ @property (nonatomic ) NSString *_unmodifiedInput;
23
+ @property (nonatomic ) UIKeyModifierFlags _modifierFlags;
24
+ @property (nonatomic ) BOOL _isKeyDown;
25
+ @property (nonatomic ) long _keyCode;
26
+
27
+ @end
28
+
17
29
@interface RCTKeyCommand : NSObject <NSCopying >
18
30
19
- @property (nonatomic , strong ) UIKeyCommand *keyCommand;
31
+ @property (nonatomic , copy , readonly ) NSString *key;
32
+ @property (nonatomic , readonly ) UIKeyModifierFlags flags;
20
33
@property (nonatomic , copy ) void (^block)(UIKeyCommand *);
21
34
22
35
@end
23
36
24
37
@implementation RCTKeyCommand
25
38
26
- - (instancetype )initWithKeyCommand : (UIKeyCommand *)keyCommand block : (void (^)(UIKeyCommand *))block
39
+ - (instancetype )init : ( NSString *)key flags : (UIKeyModifierFlags) flags block : (void (^)(UIKeyCommand *))block
27
40
{
28
41
if ((self = [super init ])) {
29
- _keyCommand = keyCommand;
42
+ _key = key;
43
+ _flags = flags;
30
44
_block = block;
31
45
}
32
46
return self;
@@ -41,29 +55,32 @@ - (id)copyWithZone:(__unused NSZone *)zone
41
55
42
56
- (NSUInteger )hash
43
57
{
44
- return _keyCommand. input . hash ^ _keyCommand. modifierFlags ;
58
+ return _key. hash ^ _flags ;
45
59
}
46
60
47
61
- (BOOL )isEqual : (RCTKeyCommand *)object
48
62
{
49
63
if (![object isKindOfClass: [RCTKeyCommand class ]]) {
50
64
return NO ;
51
65
}
52
- return [self matchesInput: object.keyCommand.input flags: object.keyCommand.modifierFlags ];
66
+ return [self matchesInput: object.key flags: object.flags ];
53
67
}
54
68
55
69
- (BOOL )matchesInput : (NSString *)input flags : (UIKeyModifierFlags)flags
56
70
{
57
- return [_keyCommand.input isEqual: input] && _keyCommand.modifierFlags == flags;
71
+ // We consider the key command a match if the modifier flags match
72
+ // exactly or is there are no modifier flags. This means that for
73
+ // `cmd + r`, we will match both `cmd + r` and `r` but not `opt + r`.
74
+ return [_key isEqual: input] && (_flags == flags || flags == 0 );
58
75
}
59
76
60
77
- (NSString *)description
61
78
{
62
79
return [NSString stringWithFormat: @" <%@ :%p input=\" %@ \" flags=%lld hasBlock=%@ >" ,
63
80
[self class ],
64
81
self ,
65
- _keyCommand.input ,
66
- (long long )_keyCommand.modifierFlags ,
82
+ _key ,
83
+ (long long )_flags ,
67
84
_block ? @" YES" : @" NO" ];
68
85
}
69
86
@@ -75,67 +92,94 @@ @interface RCTKeyCommands ()
75
92
76
93
@end
77
94
78
- @implementation UIResponder ( RCTKeyCommands)
95
+ @implementation RCTKeyCommands
79
96
80
- + (UIResponder *) RCT_getFirstResponder : (UIResponder *) view
97
+ + (void ) initialize
81
98
{
82
- UIResponder *firstResponder = nil ;
99
+ SEL originalKeyEventSelector = NSSelectorFromString (@" handleKeyUIEvent:" );
100
+ SEL swizzledKeyEventSelector = NSSelectorFromString (
101
+ [NSString stringWithFormat: @" _rct_swizzle_%x _%@ " , arc4random (), NSStringFromSelector (originalKeyEventSelector)]);
83
102
84
- if (view.isFirstResponder ) {
85
- return view;
86
- } else if ([view isKindOfClass: [UIViewController class ]]) {
87
- if ([(UIViewController *)view parentViewController ]) {
88
- firstResponder = [UIResponder RCT_getFirstResponder: [(UIViewController *)view parentViewController ]];
89
- }
90
- return firstResponder ? firstResponder : [UIResponder RCT_getFirstResponder: [(UIViewController *)view view ]];
91
- } else if ([view isKindOfClass: [UIView class ]]) {
92
- for (UIView *subview in [(UIView *)view subviews ]) {
93
- firstResponder = [UIResponder RCT_getFirstResponder: subview];
94
- if (firstResponder) {
95
- return firstResponder;
96
- }
97
- }
98
- }
103
+ void (^handleKeyUIEventSwizzleBlock)(UIApplication *, UIEvent *) = ^(UIApplication *slf, UIEvent *event) {
104
+ [[[self class ] sharedInstance ] handleKeyUIEventSwizzle: event];
99
105
100
- return firstResponder;
106
+ ((void (*)(id , SEL , id ))objc_msgSend)(slf, swizzledKeyEventSelector, event);
107
+ };
108
+
109
+ RCTSwapInstanceMethodWithBlock (
110
+ [UIApplication class ], originalKeyEventSelector, handleKeyUIEventSwizzleBlock, swizzledKeyEventSelector);
101
111
}
102
112
103
- - (NSArray <UIKeyCommand *> *) RCT_keyCommands
113
+ - (void ) handleKeyUIEventSwizzle : (UIEvent *) event
104
114
{
105
- NSSet <RCTKeyCommand *> *commands = [RCTKeyCommands sharedInstance ]. commands ;
106
- return [[commands valueForKeyPath: @" keyCommand " ] allObjects ] ;
107
- }
115
+ NSString *modifiedInput = nil ;
116
+ UIKeyModifierFlags *modifierFlags = nil ;
117
+ BOOL isKeyDown = NO ;
108
118
109
- /* *
110
- * Single Press Key Command Response
111
- * Command + KeyEvent (Command + R/D, etc.)
112
- */
113
- - (void )RCT_handleKeyCommand : (UIKeyCommand *)key
114
- {
115
- // NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
116
- // method gets called repeatedly if the command key is held down.
117
- static NSTimeInterval lastCommand = 0 ;
118
- if (CACurrentMediaTime () - lastCommand > 0.5 ) {
119
- for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance ].commands ) {
120
- if ([command.keyCommand.input isEqualToString: key.input] &&
121
- command.keyCommand .modifierFlags == key.modifierFlags ) {
122
- if (command.block ) {
123
- command.block (key);
124
- lastCommand = CACurrentMediaTime ();
125
- }
119
+ if ([event respondsToSelector: @selector (_modifiedInput )]) {
120
+ modifiedInput = [event _modifiedInput ];
121
+ }
122
+
123
+ if ([event respondsToSelector: @selector (_modifierFlags )]) {
124
+ modifierFlags = [event _modifierFlags ];
125
+ }
126
+
127
+ if ([event respondsToSelector: @selector (_isKeyDown )]) {
128
+ isKeyDown = [event _isKeyDown ];
129
+ }
130
+
131
+ BOOL interactionEnabled = !UIApplication.sharedApplication .isIgnoringInteractionEvents ;
132
+ BOOL hasFirstResponder = NO ;
133
+ if (isKeyDown && modifiedInput.length > 0 && interactionEnabled) {
134
+ UIResponder *firstResponder = nil ;
135
+ for (UIWindow *window in [self allWindows ]) {
136
+ firstResponder = [window valueForKey: @" firstResponder" ];
137
+ if (firstResponder) {
138
+ hasFirstResponder = YES ;
139
+ break ;
126
140
}
127
141
}
128
- }
129
- }
130
142
131
- @end
143
+ // Ignore key commands (except escape) when there's an active responder
144
+ if (!firstResponder) {
145
+ [self RCT_handleKeyCommand: modifiedInput flags: modifierFlags];
146
+ }
147
+ }
148
+ };
132
149
133
- @implementation RCTKeyCommands
150
+ - (NSArray <UIWindow *> *)allWindows
151
+ {
152
+ BOOL includeInternalWindows = YES ;
153
+ BOOL onlyVisibleWindows = NO ;
154
+
155
+ // Obfuscating selector allWindowsIncludingInternalWindows:onlyVisibleWindows:
156
+ NSArray <NSString *> *allWindowsComponents =
157
+ @[ @" al" , @" lWindo" , @" wsIncl" , @" udingInt" , @" ernalWin" , @" dows:o" , @" nlyVisi" , @" bleWin" , @" dows:" ];
158
+ SEL allWindowsSelector = NSSelectorFromString ([allWindowsComponents componentsJoinedByString: @" " ]);
159
+
160
+ NSMethodSignature *methodSignature = [[UIWindow class ] methodSignatureForSelector: allWindowsSelector];
161
+ NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: methodSignature];
162
+
163
+ invocation.target = [UIWindow class ];
164
+ invocation.selector = allWindowsSelector;
165
+ [invocation setArgument: &includeInternalWindows atIndex: 2 ];
166
+ [invocation setArgument: &onlyVisibleWindows atIndex: 3 ];
167
+ [invocation invoke ];
168
+
169
+ __unsafe_unretained NSArray <UIWindow *> *windows = nil ;
170
+ [invocation getReturnValue: &windows];
171
+ return windows;
172
+ }
134
173
135
- + (void )initialize
174
+ - (void )RCT_handleKeyCommand : ( NSString *) input flags : (UIKeyModifierFlags) modifierFlags
136
175
{
137
- // swizzle UIResponder
138
- RCTSwapInstanceMethods ([UIResponder class ], @selector (keyCommands ), @selector (RCT_keyCommands ));
176
+ for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance ].commands ) {
177
+ if ([command matchesInput: input flags: modifierFlags]) {
178
+ if (command.block ) {
179
+ command.block (nil );
180
+ }
181
+ }
182
+ }
139
183
}
140
184
141
185
+ (instancetype )sharedInstance
@@ -163,11 +207,7 @@ - (void)registerKeyCommandWithInput:(NSString *)input
163
207
{
164
208
RCTAssertMainQueue ();
165
209
166
- UIKeyCommand *command = [UIKeyCommand keyCommandWithInput: input
167
- modifierFlags: flags
168
- action: @selector (RCT_handleKeyCommand: )];
169
-
170
- RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc ] initWithKeyCommand: command block: block];
210
+ RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc ] init: input flags: flags block: block];
171
211
[_commands removeObject: keyCommand];
172
212
[_commands addObject: keyCommand];
173
213
}
0 commit comments