| Flutter iOS Embedder |

FlutterChannelKeyResponder.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterChannelKeyResponder.h"
6
7 #import <objc/message.h>
8 #include <sys/types.h>
9 #include "fml/memory/weak_ptr.h"
10
11 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
13 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.h"
14 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
15 #import "flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap_Internal.h"
16
17 namespace {
18 // An enumeration of the modifier values that the framework expects. These are
19 // largely the same values as the OS (UIKeyModifierShift, etc.), but because the
20 // framework code expects certain values, and has additional values (like the
21 // sided modifier values below), we translate the iOS values to the framework
22 // values, and add a mask for all the possible values.
23 typedef NS_OPTIONS(NSInteger, kKeyboardModifier) {
24 kKeyboardModifierAlphaShift = 0x10000,
25 kKeyboardModifierShift = 0x20000,
26 kKeyboardModifierLeftShift = 0x02,
27 kKeyboardModifierRightShift = 0x04,
28 kKeyboardModifierControl = 0x40000,
29 kKeyboardModifierLeftControl = 0x01,
30 kKeyboardModifierRightControl = 0x2000,
31 kKeyboardModifierOption = 0x80000,
32 kKeyboardModifierLeftOption = 0x20,
33 kKeyboardModifierRightOption = 0x40,
34 kKeyboardModifierCommand = 0x100000,
35 kKeyboardModifierLeftCommand = 0x08,
36 kKeyboardModifierRightCommand = 0x10,
37 kKeyboardModifierNumericPad = 0x200000,
38 kKeyboardModifierMask = kKeyboardModifierAlphaShift | //
39 kKeyboardModifierShift | //
40 kKeyboardModifierLeftShift | //
41 kKeyboardModifierRightShift | //
42 kKeyboardModifierControl | //
43 kKeyboardModifierLeftControl | //
44 kKeyboardModifierRightControl | //
45 kKeyboardModifierOption | //
46 kKeyboardModifierLeftOption | //
47 kKeyboardModifierRightOption | //
48 kKeyboardModifierCommand | //
49 kKeyboardModifierLeftCommand | //
50 kKeyboardModifierRightCommand | //
51 kKeyboardModifierNumericPad,
52 };
53
54 /**
55 * Filters out some special cases in the characters field on UIKey.
56 */
57 static NSString* getEventCharacters(NSString* characters, UIKeyboardHIDUsage keyCode)
58 API_AVAILABLE(ios(13.4)) {
59 if (characters == nil) {
60 return nil;
61 }
62 if ([characters length] == 0) {
63 return nil;
64 }
65 if (@available(iOS 13.4, *)) {
66 // On iOS, function keys return the UTF8 string "\^P" (with a literal '/',
67 // '^' and a 'P', not escaped ctrl-P) as their "characters" field. This
68 // isn't a valid (single) UTF8 character. Looking at the only UTF16
69 // character for a function key yields a value of "16", which is a Unicode
70 // "SHIFT IN" character, which is just odd. UTF8 conversion of that string
71 // is what generates the three characters "\^P".
72 //
73 // Anyhow, we strip them all out and replace them with empty strings, since
74 // function keys shouldn't be printable.
75 if (functionKeyCodes.find(keyCode) != functionKeyCodes.end()) {
76 return nil;
77 }
78 }
79 return characters;
80 }
81
82 } // namespace
83 @interface FlutterChannelKeyResponder ()
84
85 /**
86 * The channel used to communicate with Flutter.
87 */
88 @property(nonatomic) FlutterBasicMessageChannel* channel;
89
90 - (NSInteger)adjustModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4));
91 - (void)updatePressedModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4));
92
93 @property(nonatomic) kKeyboardModifier pressedModifiers;
94 @end
95
96 @implementation FlutterChannelKeyResponder
97
98 - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel {
99 self = [super init];
100 if (self != nil) {
101 _channel = channel;
102 _pressedModifiers = 0;
103 }
104 return self;
105 }
106
107 - (void)handlePress:(nonnull FlutterUIPressProxy*)press
108 callback:(nonnull FlutterAsyncKeyCallback)callback API_AVAILABLE(ios(13.4)) {
109 if (@available(iOS 13.4, *)) {
110 // no-op
111 } else {
112 return;
113 }
114 NSString* type;
115 switch (press.phase) {
116 case UIPressPhaseBegan:
117 type = @"keydown";
118 break;
119 case UIPressPhaseCancelled:
120 // This event doesn't appear to happen on iOS, at least when switching
121 // apps. Maybe on tvOS? In any case, it's probably best to send a keyup if
122 // we do receive one, since if the event was canceled, it's likely that a
123 // keyup will never be received otherwise.
124 case UIPressPhaseEnded:
125 type = @"keyup";
126 break;
127 case UIPressPhaseChanged:
128 // This only happens for analog devices like joysticks.
129 return;
130 case UIPressPhaseStationary:
131 // The entire volume of documentation of this phase on the Apple site, and
132 // indeed the Internet, is:
133 // "A button was pressed but hasn’t moved since the previous event."
134 // It's unclear what this is actually used for, and we've yet to see it in
135 // the wild.
136 return;
137 }
138
139 NSString* characters = getEventCharacters(press.key.characters, press.key.keyCode);
140 NSString* charactersIgnoringModifiers =
141 getEventCharacters(press.key.charactersIgnoringModifiers, press.key.keyCode);
142 NSMutableDictionary* keyMessage = [[@{
143 @"keymap" : @"ios",
144 @"type" : type,
145 @"keyCode" : @(press.key.keyCode),
146 @"modifiers" : @([self adjustModifiers:press]),
147 @"characters" : characters == nil ? @"" : characters,
148 @"charactersIgnoringModifiers" : charactersIgnoringModifiers == nil
149 ? @""
150 : charactersIgnoringModifiers,
151 } mutableCopy] autorelease];
152 [self.channel sendMessage:keyMessage
153 reply:^(id reply) {
154 bool handled = reply ? [[reply valueForKey:@"handled"] boolValue] : true;
155 callback(handled);
156 }];
157 }
158
159 #pragma mark - Private
160
161 - (void)updatePressedModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4)) {
162 if (@available(iOS 13.4, *)) {
163 // no-op
164 } else {
165 return;
166 }
167
168 bool isKeyDown = false;
169 switch (press.phase) {
170 case UIPressPhaseStationary:
171 case UIPressPhaseChanged:
172 // These kinds of events shouldn't get this far.
173 NSAssert(false, @"Unexpected key event type received in updatePressedModifiers.");
174 return;
175 case UIPressPhaseBegan:
176 isKeyDown = true;
177 break;
178 case UIPressPhaseCancelled:
179 case UIPressPhaseEnded:
180 isKeyDown = false;
181 break;
182 }
183
184 void (^update)(kKeyboardModifier, bool) = ^(kKeyboardModifier mod, bool isOn) {
185 if (isOn) {
186 _pressedModifiers |= mod;
187 } else {
188 _pressedModifiers &= ~mod;
189 }
190 };
191 switch (press.key.keyCode) {
192 case UIKeyboardHIDUsageKeyboardCapsLock:
193 update(kKeyboardModifierAlphaShift, isKeyDown);
194 break;
195 case UIKeyboardHIDUsageKeypadNumLock:
196 update(kKeyboardModifierNumericPad, isKeyDown);
197 break;
198 case UIKeyboardHIDUsageKeyboardLeftShift:
199 update(kKeyboardModifierLeftShift, isKeyDown);
200 break;
201 case UIKeyboardHIDUsageKeyboardRightShift:
202 update(kKeyboardModifierRightShift, isKeyDown);
203 break;
204 case UIKeyboardHIDUsageKeyboardLeftControl:
205 update(kKeyboardModifierLeftControl, isKeyDown);
206 break;
207 case UIKeyboardHIDUsageKeyboardRightControl:
208 update(kKeyboardModifierRightControl, isKeyDown);
209 break;
210 case UIKeyboardHIDUsageKeyboardLeftAlt:
211 update(kKeyboardModifierLeftOption, isKeyDown);
212 break;
213 case UIKeyboardHIDUsageKeyboardRightAlt:
214 update(kKeyboardModifierRightOption, isKeyDown);
215 break;
216 case UIKeyboardHIDUsageKeyboardLeftGUI:
217 update(kKeyboardModifierLeftCommand, isKeyDown);
218 break;
219 case UIKeyboardHIDUsageKeyboardRightGUI:
220 update(kKeyboardModifierRightCommand, isKeyDown);
221 break;
222 default:
223 // If we didn't update any of the modifiers above, we're done.
224 return;
225 }
226 // Update the non-sided modifier flags to match the content of the sided ones.
227 update(kKeyboardModifierShift,
228 _pressedModifiers & (kKeyboardModifierRightShift | kKeyboardModifierLeftShift));
229 update(kKeyboardModifierControl,
230 _pressedModifiers & (kKeyboardModifierRightControl | kKeyboardModifierLeftControl));
231 update(kKeyboardModifierOption,
232 _pressedModifiers & (kKeyboardModifierRightOption | kKeyboardModifierLeftOption));
233 update(kKeyboardModifierCommand,
234 _pressedModifiers & (kKeyboardModifierRightCommand | kKeyboardModifierLeftCommand));
235 }
236
237 // Because iOS differs from macOS in that the modifier flags still contain the
238 // flag for the key that is being released on the keyup event, we adjust the
239 // modifiers when the key being released is the matching modifier key itself.
240 - (NSInteger)adjustModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4)) {
241 if (@available(iOS 13.4, *)) {
242 // no-op
243 } else {
244 return press.key.modifierFlags;
245 }
246
247 [self updatePressedModifiers:press];
248 // Replace the supplied modifier flags with our computed ones.
249 return _pressedModifiers | (press.key.modifierFlags & ~kKeyboardModifierMask);
250 }
251
252 @end
FlutterBasicMessageChannel
Definition: FlutterChannels.h:37
FlutterEngine.h
API_AVAILABLE
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
FlutterChannelKeyResponder.h
functionKeyCodes
const std::set< uint32_t > functionKeyCodes
Definition: KeyCodeMap.g.mm:304
FlutterAsyncKeyCallback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
Definition: FlutterKeyPrimaryResponder.h:10
FlutterCodecs.h
FlutterChannelKeyResponder
Definition: FlutterChannelKeyResponder.h:20
FlutterViewController_Internal.h
FlutterUIPressProxy
Definition: FlutterUIPressProxy.h:17
FlutterUIPressProxy.h
KeyCodeMap_Internal.h