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