| Flutter iOS Embedder |

FlutterEmbedderKeyResponder.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/FlutterEmbedderKeyResponder.h"
6 #include <objc/NSObjCRuntime.h>
7
8 #import <objc/message.h>
9 #include <map>
10 #include "fml/memory/weak_ptr.h"
11
12 #import "KeyCodeMap_Internal.h"
13 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
14 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
15
16 FLUTTER_ASSERT_ARC
17
18 namespace {
19
20 /**
21 * Isolate the least significant 1-bit.
22 *
23 * For example,
24 *
25 * * lowestSetBit(0x1010) returns 0x10.
26 * * lowestSetBit(0) returns 0.
27 */
28 static NSUInteger lowestSetBit(NSUInteger bitmask) {
29 // This utilizes property of two's complement (negation), which propagates a
30 // carry bit from LSB to the lowest set bit.
31 return bitmask & -bitmask;
32 }
33
34 /**
35 * Whether a string represents a control character.
36 */
37 static bool IsControlCharacter(NSUInteger length, NSString* label) {
38 if (length > 1) {
39 return false;
40 }
41 unichar codeUnit = [label characterAtIndex:0];
42 return (codeUnit <= 0x1f && codeUnit >= 0x00) || (codeUnit >= 0x7f && codeUnit <= 0x9f);
43 }
44
45 /**
46 * Whether a string represents an unprintable key.
47 */
48 static bool IsUnprintableKey(NSUInteger length, NSString* label) {
49 if (length > 1) {
50 return false;
51 }
52 unichar codeUnit = [label characterAtIndex:0];
53 return codeUnit >= 0xF700 && codeUnit <= 0xF8FF;
54 }
55
56 /**
57 * Returns a key code composed with a base key and a plane.
58 *
59 * Examples of unprintable keys are "NSUpArrowFunctionKey = 0xF700" or
60 * "NSHomeFunctionKey = 0xF729".
61 *
62 * See
63 * https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc
64 * for more information.
65 */
66 static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) {
67 return plane | (baseKey & kValueMask);
68 }
69
70 /**
71 * Returns the physical key for a key code.
72 */
73 static uint64_t GetPhysicalKeyForKeyCode(UInt32 keyCode) {
74 auto physicalKey = keyCodeToPhysicalKey.find(keyCode);
75 if (physicalKey == keyCodeToPhysicalKey.end()) {
76 return KeyOfPlane(keyCode, kIosPlane);
77 }
78 return physicalKey->second;
79 }
80
81 /**
82 * Returns the logical key for a modifier physical key.
83 */
84 static uint64_t GetLogicalKeyForModifier(UInt32 keyCode, uint64_t hidCode) {
85 auto fromKeyCode = keyCodeToLogicalKey.find(keyCode);
86 if (fromKeyCode != keyCodeToLogicalKey.end()) {
87 return fromKeyCode->second;
88 }
89 return KeyOfPlane(hidCode, kIosPlane);
90 }
91
92 /**
93 * Converts upper letters to lower letters in ASCII and extended ASCII, and
94 * returns as-is otherwise.
95 *
96 * Independent of locale.
97 */
98 static uint64_t toLower(uint64_t n) {
99 constexpr uint64_t lower_a = 0x61;
100 constexpr uint64_t upper_a = 0x41;
101 constexpr uint64_t upper_z = 0x5a;
102
103 constexpr uint64_t lower_a_grave = 0xe0;
104 constexpr uint64_t upper_a_grave = 0xc0;
105 constexpr uint64_t upper_thorn = 0xde;
106 constexpr uint64_t division = 0xf7;
107
108 // ASCII range.
109 if (n >= upper_a && n <= upper_z) {
110 return n - upper_a + lower_a;
111 }
112
113 // EASCII range.
114 if (n >= upper_a_grave && n <= upper_thorn && n != division) {
115 return n - upper_a_grave + lower_a_grave;
116 }
117
118 return n;
119 }
120
121 /**
122 * Filters out some special cases in the characters field on UIKey.
123 */
124 static const char* getEventCharacters(NSString* characters, UIKeyboardHIDUsage keyCode)
125 API_AVAILABLE(ios(13.4)) {
126 if (characters == nil) {
127 return nullptr;
128 }
129 if ([characters length] == 0) {
130 return nullptr;
131 }
132 if (@available(iOS 13.4, *)) {
133 // On iOS, function keys return the UTF8 string "\^P" (with a literal '/',
134 // '^' and a 'P', not escaped ctrl-P) as their "characters" field. This
135 // isn't a valid (single) UTF8 character. Looking at the only UTF16
136 // character for a function key yields a value of "16", which is a Unicode
137 // "SHIFT IN" character, which is just odd. UTF8 conversion of that string
138 // is what generates the three characters "\^P".
139 //
140 // Anyhow, we strip them all out and replace them with empty strings, since
141 // function keys shouldn't be printable.
142 if (functionKeyCodes.find(keyCode) != functionKeyCodes.end()) {
143 return nullptr;
144 }
145 }
146 return [characters UTF8String];
147 }
148
149 /**
150 * Returns the logical key of a KeyUp or KeyDown event.
151 *
152 * The `maybeSpecialKey` is a nullable integer, and if not nil, indicates
153 * that the event key is a special key as defined by `specialKeyMapping`,
154 * and is the corresponding logical key.
155 *
156 * For modifier keys, use GetLogicalKeyForModifier.
157 */
158 static uint64_t GetLogicalKeyForEvent(FlutterUIPressProxy* press, NSNumber* maybeSpecialKey)
159 API_AVAILABLE(ios(13.4)) {
160 if (maybeSpecialKey != nil) {
161 return [maybeSpecialKey unsignedLongLongValue];
162 }
163 // Look to see if the keyCode can be mapped from keycode.
164 auto fromKeyCode = keyCodeToLogicalKey.find(press.key.keyCode);
165 if (fromKeyCode != keyCodeToLogicalKey.end()) {
166 return fromKeyCode->second;
167 }
168 const char* characters =
169 getEventCharacters(press.key.charactersIgnoringModifiers, press.key.keyCode);
170 NSString* keyLabel =
171 characters == nullptr ? nil : [[NSString alloc] initWithUTF8String:characters];
172 NSUInteger keyLabelLength = [keyLabel length];
173 // If this key is printable, generate the logical key from its Unicode
174 // value. Control keys such as ESC, CTRL, and SHIFT are not printable. HOME,
175 // DEL, arrow keys, and function keys are considered modifier function keys,
176 // which generate invalid Unicode scalar values.
177 if (keyLabelLength != 0 && !IsControlCharacter(keyLabelLength, keyLabel) &&
178 !IsUnprintableKey(keyLabelLength, keyLabel)) {
179 // Given that charactersIgnoringModifiers can contain a string of arbitrary
180 // length, limit to a maximum of two Unicode scalar values. It is unlikely
181 // that a keyboard would produce a code point bigger than 32 bits, but it is
182 // still worth defending against this case.
183 NSCAssert((keyLabelLength < 2), @"Unexpected long key label: |%@|.", keyLabel);
184
185 uint64_t codeUnit = (uint64_t)[keyLabel characterAtIndex:0];
186 if (keyLabelLength == 2) {
187 uint64_t secondCode = (uint64_t)[keyLabel characterAtIndex:1];
188 codeUnit = (codeUnit << 16) | secondCode;
189 }
190 return KeyOfPlane(toLower(codeUnit), kUnicodePlane);
191 }
192
193 // This is a non-printable key that is unrecognized, so a new code is minted
194 // with the autogenerated bit set.
195 return KeyOfPlane(press.key.keyCode, kIosPlane);
196 }
197
198 /**
199 * Converts NSEvent.timestamp to the timestamp for Flutter.
200 */
201 static double GetFlutterTimestampFrom(NSTimeInterval timestamp) {
202 // Timestamp in microseconds. The event.timestamp is in seconds with sub-ms precision.
203 return timestamp * 1000000.0;
204 }
205
206 /**
207 * Compute |modifierFlagOfInterestMask| out of |keyCodeToModifierFlag|.
208 *
209 * This is equal to the bitwise-or of all values of |keyCodeToModifierFlag|.
210 */
211 static NSUInteger computeModifierFlagOfInterestMask() {
212 NSUInteger modifierFlagOfInterestMask = kModifierFlagCapsLock | kModifierFlagShiftAny |
213 kModifierFlagControlAny | kModifierFlagAltAny |
214 kModifierFlagMetaAny;
215 for (std::pair<UInt32, ModifierFlag> entry : keyCodeToModifierFlag) {
216 modifierFlagOfInterestMask = modifierFlagOfInterestMask | entry.second;
217 }
218 return modifierFlagOfInterestMask;
219 }
220
221 static bool isKeyDown(FlutterUIPressProxy* press) API_AVAILABLE(ios(13.4)) {
222 switch (press.phase) {
223 case UIPressPhaseStationary:
224 case UIPressPhaseChanged:
225 // Not sure if this is the right thing to do for these two, but true seems
226 // more correct than false.
227 return true;
228 case UIPressPhaseBegan:
229 return true;
230 case UIPressPhaseCancelled:
231 case UIPressPhaseEnded:
232 return false;
233 }
234 return false;
235 }
236
237 /**
238 * The C-function sent to the engine's |sendKeyEvent|, wrapping
239 * |FlutterEmbedderKeyResponder.handleResponse|.
240 *
241 * For the reason of this wrap, see |FlutterKeyPendingResponse|.
242 */
243 void HandleResponse(bool handled, void* user_data);
244 } // namespace
245
246 /**
247 * The invocation context for |HandleResponse|, wrapping
248 * |FlutterEmbedderKeyResponder.handleResponse|.
249 *
250 * The key responder's functions only accept C-functions as callbacks, as well
251 * as arbitrary user_data. In order to send an instance method of
252 * |FlutterEmbedderKeyResponder.handleResponse| to the engine's |SendKeyEvent|,
253 * we wrap the invocation into a C-function |HandleResponse| and invocation
254 * context |FlutterKeyPendingResponse|.
255 */
256 @interface FlutterKeyPendingResponse : NSObject
257
258 @property(readonly, weak) FlutterEmbedderKeyResponder* responder;
259
260 @property(nonatomic) uint64_t responseId;
261
262 - (nonnull instancetype)initWithHandler:(nonnull FlutterEmbedderKeyResponder*)responder
263 responseId:(uint64_t)responseId;
264
265 @end
266
267 @implementation FlutterKeyPendingResponse
268 - (instancetype)initWithHandler:(FlutterEmbedderKeyResponder*)responder
269 responseId:(uint64_t)responseId {
270 self = [super init];
271 if (self != nil) {
272 _responder = responder;
273 _responseId = responseId;
274 }
275 return self;
276 }
277 @end
278
279 /**
280 * Guards a |FlutterAsyncKeyCallback| to make sure it's handled exactly once
281 * throughout the process of handling an event in |FlutterEmbedderKeyResponder|.
282 *
283 * A callback can either be handled with |pendTo:withId:|, or with |resolveTo:|.
284 * Either way, the callback cannot be handled again, or an assertion will be
285 * thrown.
286 */
287 @interface FlutterKeyCallbackGuard : NSObject
288 - (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback;
289
290 /**
291 * Handle the callback by storing it to pending responses.
292 */
293 - (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses
294 withId:(uint64_t)responseId;
295
296 /**
297 * Handle the callback by calling it with a result.
298 */
299 - (void)resolveTo:(BOOL)handled;
300
301 @property(nonatomic) BOOL handled;
302 /**
303 * A string indicating how the callback is handled.
304 *
305 * Only set in debug mode. Nil in release mode, or if the callback has not been
306 * handled.
307 */
308 @property(readonly, copy) NSString* debugHandleSource;
309 @end
310
311 @implementation FlutterKeyCallbackGuard {
312 // The callback is declared in the implementation block to avoid being
313 // accessed directly.
314 FlutterAsyncKeyCallback _callback;
315 }
316 - (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback {
317 self = [super init];
318 if (self != nil) {
319 _callback = [callback copy];
320 _handled = FALSE;
321 }
322 return self;
323 }
324
325 - (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses
326 withId:(uint64_t)responseId {
327 NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource);
328 if (_handled) {
329 return;
330 }
331 pendingResponses[@(responseId)] = _callback;
332 _handled = TRUE;
333 NSAssert(
334 ((_debugHandleSource = [NSString stringWithFormat:@"pending event %llu", responseId]), TRUE),
335 @"");
336 }
337
338 - (void)resolveTo:(BOOL)handled {
339 NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource);
340 if (_handled) {
341 return;
342 }
343 _callback(handled);
344 _handled = TRUE;
345 NSAssert(((_debugHandleSource = [NSString stringWithFormat:@"resolved with %d", _handled]), TRUE),
346 @"");
347 }
348 @end
349
350 @interface FlutterEmbedderKeyResponder ()
351
352 /**
353 * The function to send converted events to.
354 *
355 * Set by the initializer.
356 */
357 @property(nonatomic, copy, readonly) FlutterSendKeyEvent sendEvent;
358
359 /**
360 * A map of pressed keys.
361 *
362 * The keys of the dictionary are physical keys, while the values are the logical keys
363 * of the key down event.
364 */
365 @property(nonatomic, copy, readonly) NSMutableDictionary<NSNumber*, NSNumber*>* pressingRecords;
366
367 /**
368 * A constant mask for NSEvent.modifierFlags that Flutter synchronizes with.
369 *
370 * Flutter keeps track of the last |modifierFlags| and compares it with the
371 * incoming one. Any bit within |modifierFlagOfInterestMask| that is different
372 * (except for the one that corresponds to the event key) indicates that an
373 * event for this modifier was missed, and Flutter synthesizes an event to make
374 * up for the state difference.
375 *
376 * It is computed by computeModifierFlagOfInterestMask.
377 */
378 @property(nonatomic) NSUInteger modifierFlagOfInterestMask;
379
380 /**
381 * The modifier flags of the last received key event, excluding uninterested
382 * bits.
383 *
384 * This should be kept synchronized with the last |NSEvent.modifierFlags|
385 * after masking with |modifierFlagOfInterestMask|. This should also be kept
386 * synchronized with the corresponding keys of |pressingRecords|.
387 *
388 * This is used by |synchronizeModifiers| to quickly find out modifier keys that
389 * are desynchronized.
390 */
391 @property(nonatomic) NSUInteger lastModifierFlagsOfInterest;
392
393 /**
394 * A self-incrementing ID used to label key events sent to the framework.
395 */
396 @property(nonatomic) uint64_t responseId;
397
398 /**
399 * A map of unresponded key events sent to the framework.
400 *
401 * Its values are |responseId|s, and keys are the callback that was received
402 * along with the event.
403 */
404 @property(nonatomic, copy, readonly)
405 NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>* pendingResponses;
406
407 /**
408 * Compare the last modifier flags and the current, and dispatch synthesized
409 * key events for each different modifier flag bit.
410 *
411 * The flags compared are all flags after masking with
412 * |modifierFlagOfInterestMask| and excluding |ignoringFlags|.
413 */
414 - (void)synchronizeModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4));
415
416 /**
417 * Update the pressing state.
418 *
419 * If `logicalKey` is not 0, `physicalKey` is pressed as `logicalKey`.
420 * Otherwise, `physicalKey` is released.
421 */
422 - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey;
423
424 /**
425 * Synthesize a CapsLock down event, then a CapsLock up event.
426 */
427 - (void)synthesizeCapsLockTapWithTimestamp:(NSTimeInterval)timestamp;
428
429 /**
430 * Send an event to the framework, expecting its response.
431 */
432 - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
433 callback:(nonnull FlutterKeyCallbackGuard*)callback;
434
435 /**
436 * Send an empty key event.
437 *
438 * The event is never synthesized, and never expects an event result. An empty
439 * event is sent when no other events should be sent, such as upon back-to-back
440 * keydown events of the same key.
441 */
442 - (void)sendEmptyEvent;
443
444 /**
445 * Send a key event for a modifier key.
446 */
447 - (void)synthesizeModifierEventOfType:(BOOL)isDownEvent
448 timestamp:(NSTimeInterval)timestamp
449 keyCode:(UInt32)keyCode;
450
451 /**
452 * Processes a down event from the system.
453 */
454 - (void)handlePressBegin:(nonnull FlutterUIPressProxy*)press
455 callback:(nonnull FlutterKeyCallbackGuard*)callback API_AVAILABLE(ios(13.4));
456
457 /**
458 * Processes an up event from the system.
459 */
460 - (void)handlePressEnd:(nonnull FlutterUIPressProxy*)press
461 callback:(nonnull FlutterKeyCallbackGuard*)callback API_AVAILABLE(ios(13.4));
462
463 /**
464 * Processes the response from the framework.
465 */
466 - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId;
467
468 /**
469 * Fix up the modifiers for a particular type of modifier key.
470 */
471 - (UInt32)fixSidedFlags:(ModifierFlag)anyFlag
472 withLeftFlag:(ModifierFlag)leftSide
473 withRightFlag:(ModifierFlag)rightSide
474 withLeftKey:(UInt16)leftKeyCode
475 withRightKey:(UInt16)rightKeyCode
476 withKeyCode:(UInt16)keyCode
477 keyDown:(BOOL)isKeyDown
478 forFlags:(UInt32)modifiersPressed API_AVAILABLE(ios(13.4));
479
480 /**
481 * Because iOS differs from other platforms in that the modifier flags still
482 * contain the flag for the key that is being released on the keyup event, we
483 * adjust the modifiers when the released key is a matching modifier key.
484 */
485 - (UInt32)adjustModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4));
486 @end
487
488 @implementation FlutterEmbedderKeyResponder
489
490 - (nonnull instancetype)initWithSendEvent:(FlutterSendKeyEvent)sendEvent {
491 self = [super init];
492 if (self != nil) {
493 _sendEvent = [sendEvent copy];
494 _pressingRecords = [[NSMutableDictionary alloc] init];
495 _pendingResponses = [[NSMutableDictionary alloc] init];
496 _responseId = 1;
497 _lastModifierFlagsOfInterest = 0;
498 _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask();
499 }
500 return self;
501 }
502
503 - (void)handlePress:(nonnull FlutterUIPressProxy*)press
504 callback:(FlutterAsyncKeyCallback)callback API_AVAILABLE(ios(13.4)) {
505 if (@available(iOS 13.4, *)) {
506 } else {
507 return;
508 }
509 // The conversion algorithm relies on a non-nil callback to properly compute
510 // `synthesized`.
511 NSAssert(callback != nil, @"The callback must not be nil.");
512
513 FlutterKeyCallbackGuard* guardedCallback = nil;
514 switch (press.phase) {
515 case UIPressPhaseBegan:
516 guardedCallback = [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
517 [self handlePressBegin:press callback:guardedCallback];
518 break;
519 case UIPressPhaseEnded:
520 guardedCallback = [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
521 [self handlePressEnd:press callback:guardedCallback];
522 break;
523 case UIPressPhaseChanged:
524 case UIPressPhaseCancelled:
525 // TODO(gspencergoog): Handle cancelled events as synthesized up events.
526 case UIPressPhaseStationary:
527 NSAssert(false, @"Unexpected press phase receieved in handlePress");
528 return;
529 }
530 NSAssert(guardedCallback.handled, @"The callback returned without being handled.");
531 NSAssert(
532 (_lastModifierFlagsOfInterest & ~kModifierFlagCapsLock) ==
533 ([self adjustModifiers:press] & (_modifierFlagOfInterestMask & ~kModifierFlagCapsLock)),
534 @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx",
535 static_cast<unsigned long>(_lastModifierFlagsOfInterest & ~kModifierFlagCapsLock),
536 static_cast<unsigned long>([self adjustModifiers:press] &
537 (_modifierFlagOfInterestMask & ~kModifierFlagCapsLock)));
538 }
539
540 #pragma mark - Private
541
542 - (void)synchronizeModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4)) {
543 if (@available(iOS 13.4, *)) {
544 } else {
545 return;
546 }
547
548 const UInt32 lastFlagsOfInterest = _lastModifierFlagsOfInterest & _modifierFlagOfInterestMask;
549 const UInt32 pressedModifiers = [self adjustModifiers:press];
550 const UInt32 currentFlagsOfInterest = pressedModifiers & _modifierFlagOfInterestMask;
551 UInt32 flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest;
552 if (flagDifference & kModifierFlagCapsLock) {
553 // If the caps lock changed, and we didn't expect that, then send a
554 // synthesized down and an up to simulate a toggle of the state.
555 if (press.key.keyCode != UIKeyboardHIDUsageKeyboardCapsLock) {
556 [self synthesizeCapsLockTapWithTimestamp:press.timestamp];
557 }
558 flagDifference &= ~kModifierFlagCapsLock;
559 }
560 while (true) {
561 const UInt32 currentFlag = lowestSetBit(flagDifference);
562 if (currentFlag == 0) {
563 break;
564 }
565 flagDifference &= ~currentFlag;
566 if (currentFlag & kModifierFlagAnyMask) {
567 // Skip synthesizing keys for the "any" flags, since their synthesis will
568 // be handled when we do the sided flags. We still want them in the flags
569 // of interest, though, so we can keep their state.
570 continue;
571 }
572 auto keyCode = modifierFlagToKeyCode.find(static_cast<ModifierFlag>(currentFlag));
573 NSAssert(keyCode != modifierFlagToKeyCode.end(), @"Invalid modifier flag of interest 0x%lx",
574 static_cast<unsigned long>(currentFlag));
575 if (keyCode == modifierFlagToKeyCode.end()) {
576 continue;
577 }
578 // If this press matches the modifier key in question, then don't synthesize
579 // it, because it's already a "real" keypress.
580 if (keyCode->second == static_cast<UInt32>(press.key.keyCode)) {
581 continue;
582 }
583 BOOL isDownEvent = currentFlagsOfInterest & currentFlag;
584 [self synthesizeModifierEventOfType:isDownEvent
585 timestamp:press.timestamp
586 keyCode:keyCode->second];
587 }
588 _lastModifierFlagsOfInterest =
589 (_lastModifierFlagsOfInterest & ~_modifierFlagOfInterestMask) | currentFlagsOfInterest;
590 }
591
592 - (void)synthesizeCapsLockTapWithTimestamp:(NSTimeInterval)timestamp {
593 // The assumption when the app starts is that caps lock is off, but if that
594 // turns out to be untrue (according to the modifier flags), then this is used
595 // to simulate a key down and a key up of the caps lock key, to simulate
596 // toggling of that state in the framework.
597 FlutterKeyEvent flutterEvent = {
598 .struct_size = sizeof(FlutterKeyEvent),
599 .timestamp = GetFlutterTimestampFrom(timestamp),
600 .type = kFlutterKeyEventTypeDown,
601 .physical = kCapsLockPhysicalKey,
602 .logical = kCapsLockLogicalKey,
603 .character = nil,
604 .synthesized = true,
605 .device_type = kFlutterKeyEventDeviceTypeKeyboard,
606 };
607 _sendEvent(flutterEvent, nullptr, nullptr);
608
609 flutterEvent.type = kFlutterKeyEventTypeUp;
610 _sendEvent(flutterEvent, nullptr, nullptr);
611 }
612
613 - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey {
614 if (logicalKey == 0) {
615 [_pressingRecords removeObjectForKey:@(physicalKey)];
616 } else {
617 _pressingRecords[@(physicalKey)] = @(logicalKey);
618 }
619 }
620
621 - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
622 callback:(FlutterKeyCallbackGuard*)callback {
623 _responseId += 1;
624 uint64_t responseId = _responseId;
625 FlutterKeyPendingResponse* pending =
626 [[FlutterKeyPendingResponse alloc] initWithHandler:self responseId:responseId];
627 [callback pendTo:_pendingResponses withId:responseId];
628 _sendEvent(event, HandleResponse, (__bridge_retained void* _Nullable)pending);
629 }
630
631 - (void)sendEmptyEvent {
632 FlutterKeyEvent event = {
633 .struct_size = sizeof(FlutterKeyEvent),
634 .timestamp = 0,
635 .type = kFlutterKeyEventTypeDown,
636 .physical = 0,
637 .logical = 0,
638 .character = nil,
639 .synthesized = false,
640 .device_type = kFlutterKeyEventDeviceTypeKeyboard,
641 };
642 _sendEvent(event, nil, nil);
643 }
644
645 - (void)synthesizeModifierEventOfType:(BOOL)isDownEvent
646 timestamp:(NSTimeInterval)timestamp
647 keyCode:(UInt32)keyCode {
648 uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);
649 uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);
650 if (physicalKey == 0 || logicalKey == 0) {
651 return;
652 }
653 FlutterKeyEvent flutterEvent = {
654 .struct_size = sizeof(FlutterKeyEvent),
655 .timestamp = GetFlutterTimestampFrom(timestamp),
656 .type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp,
657 .physical = physicalKey,
658 .logical = logicalKey,
659 .character = nil,
660 .synthesized = true,
661 .device_type = kFlutterKeyEventDeviceTypeKeyboard,
662 };
663 [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];
664 _sendEvent(flutterEvent, nullptr, nullptr);
665 }
666
667 - (void)handlePressBegin:(nonnull FlutterUIPressProxy*)press
668 callback:(nonnull FlutterKeyCallbackGuard*)callback API_AVAILABLE(ios(13.4)) {
669 if (@available(iOS 13.4, *)) {
670 } else {
671 return;
672 }
673 uint64_t physicalKey = GetPhysicalKeyForKeyCode(press.key.keyCode);
674 // Some unprintable keys on iOS have literal names on their key label, such as
675 // @"UIKeyInputEscape". They are called the "special keys" and have predefined
676 // logical keys and empty characters.
677 NSNumber* specialKey = [specialKeyMapping objectForKey:press.key.charactersIgnoringModifiers];
678 uint64_t logicalKey = GetLogicalKeyForEvent(press, specialKey);
679 [self synchronizeModifiers:press];
680
681 NSNumber* pressedLogicalKey = nil;
682 if ([_pressingRecords count] > 0) {
683 pressedLogicalKey = _pressingRecords[@(physicalKey)];
684 if (pressedLogicalKey != nil) {
685 // Normally the key up events won't be missed since iOS always sends the
686 // key up event to the view where the corresponding key down occurred.
687 // However this might happen in add-to-app scenarios if the focus is changed
688 // from the native view to the Flutter view amid the key tap.
689 [callback resolveTo:TRUE];
690 [self sendEmptyEvent];
691 return;
692 }
693 }
694
695 if (pressedLogicalKey == nil) {
696 [self updateKey:physicalKey asPressed:logicalKey];
697 }
698
699 FlutterKeyEvent flutterEvent = {
700 .struct_size = sizeof(FlutterKeyEvent),
701 .timestamp = GetFlutterTimestampFrom(press.timestamp),
702 .type = kFlutterKeyEventTypeDown,
703 .physical = physicalKey,
704 .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],
705 .character =
706 specialKey != nil ? nil : getEventCharacters(press.key.characters, press.key.keyCode),
707 .synthesized = false,
708 .device_type = kFlutterKeyEventDeviceTypeKeyboard,
709 };
710 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
711 }
712
713 - (void)handlePressEnd:(nonnull FlutterUIPressProxy*)press
714 callback:(nonnull FlutterKeyCallbackGuard*)callback API_AVAILABLE(ios(13.4)) {
715 if (@available(iOS 13.4, *)) {
716 } else {
717 return;
718 }
719 [self synchronizeModifiers:press];
720
721 uint64_t physicalKey = GetPhysicalKeyForKeyCode(press.key.keyCode);
722 NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
723 if (pressedLogicalKey == nil) {
724 // Normally the key up events won't be missed since iOS always sends the
725 // key up event to the view where the corresponding key down occurred.
726 // However this might happen in add-to-app scenarios if the focus is changed
727 // from the native view to the Flutter view amid the key tap.
728 [callback resolveTo:TRUE];
729 [self sendEmptyEvent];
730 return;
731 }
732 [self updateKey:physicalKey asPressed:0];
733
734 FlutterKeyEvent flutterEvent = {
735 .struct_size = sizeof(FlutterKeyEvent),
736 .timestamp = GetFlutterTimestampFrom(press.timestamp),
737 .type = kFlutterKeyEventTypeUp,
738 .physical = physicalKey,
739 .logical = [pressedLogicalKey unsignedLongLongValue],
740 .character = nil,
741 .synthesized = false,
742 .device_type = kFlutterKeyEventDeviceTypeKeyboard,
743 };
744 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
745 }
746
747 - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId {
748 FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)];
749 callback(handled);
750 [_pendingResponses removeObjectForKey:@(responseId)];
751 }
752
753 - (UInt32)fixSidedFlags:(ModifierFlag)anyFlag
754 withLeftFlag:(ModifierFlag)leftSide
755 withRightFlag:(ModifierFlag)rightSide
756 withLeftKey:(UInt16)leftKeyCode
757 withRightKey:(UInt16)rightKeyCode
758 withKeyCode:(UInt16)keyCode
759 keyDown:(BOOL)isKeyDown
760 forFlags:(UInt32)modifiersPressed API_AVAILABLE(ios(13.4)) {
761 UInt32 newModifiers = modifiersPressed;
762 if (isKeyDown) {
763 // Add in the modifier flags that correspond to this key code, if any.
764 if (keyCode == leftKeyCode) {
765 newModifiers |= leftSide | anyFlag;
766 } else if (keyCode == rightKeyCode) {
767 newModifiers |= rightSide | anyFlag;
768 }
769 } else {
770 // If this is a key up, then remove any modifier that matches the keycode in
771 // the event from the flags, and the anyFlag if the other side isn't also
772 // pressed.
773 if (keyCode == leftKeyCode) {
774 newModifiers &= ~leftSide;
775 if (!(newModifiers & rightSide)) {
776 newModifiers &= ~anyFlag;
777 }
778 } else if (keyCode == rightKeyCode) {
779 newModifiers &= ~rightSide;
780 if (!(newModifiers & leftSide)) {
781 newModifiers &= ~anyFlag;
782 }
783 }
784 }
785
786 if (!(newModifiers & anyFlag)) {
787 // Turn off any sided flags, since the "any" flag is gone.
788 newModifiers &= ~(leftSide | rightSide);
789 }
790
791 return newModifiers;
792 }
793
794 // This fixes a few cases where iOS provides modifier flags differently from how
795 // the framework would like to receive them.
796 //
797 // 1) iOS turns off the flag associated with a modifier key AFTER the modifier
798 // key up event, so when the key up event arrives, the flags must be modified
799 // before synchronizing so they do not include the modifier that arrived in
800 // the key up event.
801 // 2) Modifier flags can be set even when that modifier is not being pressed.
802 // One example of this is when a special character is produced with the Alt
803 // (Option) key, and the Alt key is released before the letter key: the
804 // letter key's key up event still contains the Alt key flag.
805 // 3) iOS doesn't provide information about which side modifier was pressed,
806 // except through the keycode of the pressed key, so we look at the pressed
807 // key code to decide which side to indicate in the flags. If we can't know
808 // (in the case of a non-modifier key event having an "any" modifier set, but
809 // we don't know already that the modifier is down), then we just pick the
810 // left one arbitrarily.
811 - (UInt32)adjustModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4)) {
812 if (@available(iOS 13.4, *)) {
813 // no-op
814 } else {
815 return press.key.modifierFlags;
816 }
817
818 bool keyDown = isKeyDown(press);
819
820 // Start with the current modifier flags, along with any sided flags that we
821 // already know are down.
822 UInt32 pressedModifiers =
823 press.key.modifierFlags | (_lastModifierFlagsOfInterest & kModifierFlagSidedMask);
824
825 pressedModifiers = [self fixSidedFlags:kModifierFlagShiftAny
826 withLeftFlag:kModifierFlagShiftLeft
827 withRightFlag:kModifierFlagShiftRight
828 withLeftKey:UIKeyboardHIDUsageKeyboardLeftShift
829 withRightKey:UIKeyboardHIDUsageKeyboardRightShift
830 withKeyCode:press.key.keyCode
831 keyDown:keyDown
832 forFlags:pressedModifiers];
833 pressedModifiers = [self fixSidedFlags:kModifierFlagControlAny
834 withLeftFlag:kModifierFlagControlLeft
835 withRightFlag:kModifierFlagControlRight
836 withLeftKey:UIKeyboardHIDUsageKeyboardLeftControl
837 withRightKey:UIKeyboardHIDUsageKeyboardRightControl
838 withKeyCode:press.key.keyCode
839 keyDown:keyDown
840 forFlags:pressedModifiers];
841 pressedModifiers = [self fixSidedFlags:kModifierFlagAltAny
842 withLeftFlag:kModifierFlagAltLeft
843 withRightFlag:kModifierFlagAltRight
844 withLeftKey:UIKeyboardHIDUsageKeyboardLeftAlt
845 withRightKey:UIKeyboardHIDUsageKeyboardRightAlt
846 withKeyCode:press.key.keyCode
847 keyDown:keyDown
848 forFlags:pressedModifiers];
849 pressedModifiers = [self fixSidedFlags:kModifierFlagMetaAny
850 withLeftFlag:kModifierFlagMetaLeft
851 withRightFlag:kModifierFlagMetaRight
852 withLeftKey:UIKeyboardHIDUsageKeyboardLeftGUI
853 withRightKey:UIKeyboardHIDUsageKeyboardRightGUI
854 withKeyCode:press.key.keyCode
855 keyDown:keyDown
856 forFlags:pressedModifiers];
857
858 if (press.key.keyCode == UIKeyboardHIDUsageKeyboardCapsLock) {
859 // The caps lock modifier needs to be unset only if it was already on
860 // and this is a key up. This is because it indicates the lock state, and
861 // not the key press state. The caps lock state should be on between the
862 // first down, and the second up (i.e. while the lock in effect), and
863 // this code turns it off at the second up event. The OS leaves it on still
864 // because of iOS's weird late processing of modifier states. Synthesis of
865 // the appropriate synthesized key events happens in synchronizeModifiers.
866 if (!keyDown && _lastModifierFlagsOfInterest & kModifierFlagCapsLock) {
867 pressedModifiers &= ~kModifierFlagCapsLock;
868 }
869 }
870 return pressedModifiers;
871 }
872
873 @end
874
875 namespace {
876 void HandleResponse(bool handled, void* user_data) {
877 FlutterKeyPendingResponse* pending = (__bridge_transfer FlutterKeyPendingResponse*)user_data;
878 [pending.responder handleResponse:handled forId:pending.responseId];
879 }
880 } // namespace
FlutterCodecs.h
FlutterEmbedderKeyResponder.h
FlutterSendKeyEvent
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
Definition: FlutterEmbedderKeyResponder.h:13
FlutterAsyncKeyCallback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
Definition: FlutterKeyPrimaryResponder.h:10
FlutterMacros.h
API_AVAILABLE
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
keyCodeToModifierFlag
const std::map< uint32_t, ModifierFlag > keyCodeToModifierFlag
Definition: KeyCodeMap.g.mm:279
functionKeyCodes
const std::set< uint32_t > functionKeyCodes
Definition: KeyCodeMap.g.mm:304
kValueMask
const uint64_t kValueMask
Definition: KeyCodeMap.g.mm:22
keyCodeToPhysicalKey
const std::map< uint32_t, uint64_t > keyCodeToPhysicalKey
Definition: KeyCodeMap.g.mm:37
kUnicodePlane
const uint64_t kUnicodePlane
Definition: KeyCodeMap.g.mm:27
keyCodeToLogicalKey
const std::map< uint32_t, uint64_t > keyCodeToLogicalKey
Definition: KeyCodeMap.g.mm:201
kCapsLockLogicalKey
const uint64_t kCapsLockLogicalKey
Definition: KeyCodeMap.g.mm:357
kCapsLockPhysicalKey
const uint64_t kCapsLockPhysicalKey
Definition: KeyCodeMap.g.mm:356
modifierFlagToKeyCode
const std::map< ModifierFlag, uint32_t > modifierFlagToKeyCode
Definition: KeyCodeMap.g.mm:291
kIosPlane
const uint64_t kIosPlane
Definition: KeyCodeMap.g.mm:32
KeyCodeMap_Internal.h
ModifierFlag
ModifierFlag
Definition: KeyCodeMap_Internal.h:61
kModifierFlagCapsLock
@ kModifierFlagCapsLock
Definition: KeyCodeMap_Internal.h:74
kModifierFlagShiftAny
@ kModifierFlagShiftAny
Definition: KeyCodeMap_Internal.h:75
kModifierFlagMetaAny
@ kModifierFlagMetaAny
Definition: KeyCodeMap_Internal.h:78
kModifierFlagControlAny
@ kModifierFlagControlAny
Definition: KeyCodeMap_Internal.h:76
kModifierFlagAltAny
@ kModifierFlagAltAny
Definition: KeyCodeMap_Internal.h:77
kModifierFlagAnyMask
constexpr uint32_t kModifierFlagAnyMask
Definition: KeyCodeMap_Internal.h:86
kModifierFlagSidedMask
constexpr uint32_t kModifierFlagSidedMask
Definition: KeyCodeMap_Internal.h:93
FlutterEmbedderKeyResponder
Definition: FlutterEmbedderKeyResponder.h:23
FlutterKeyCallbackGuard
Definition: FlutterEmbedderKeyResponder.mm:288
FlutterKeyCallbackGuard::debugHandleSource
NSString * debugHandleSource
Definition: FlutterEmbedderKeyResponder.mm:308
-[FlutterKeyCallbackGuard resolveTo:]
void resolveTo:(BOOL handled)
Definition: FlutterEmbedderKeyResponder.mm:338
-[FlutterKeyCallbackGuard pendTo:withId:]
void pendTo:withId:(nonnull NSMutableDictionary< NSNumber *, FlutterAsyncKeyCallback > *pendingResponses,[withId] uint64_t responseId)
Definition: FlutterEmbedderKeyResponder.mm:325
FlutterKeyCallbackGuard::handled
BOOL handled
Definition: FlutterEmbedderKeyResponder.mm:301
FlutterKeyPendingResponse
Definition: FlutterEmbedderKeyResponder.mm:257
FlutterKeyPendingResponse::responder
FlutterEmbedderKeyResponder * responder
Definition: FlutterEmbedderKeyResponder.mm:258
FlutterKeyPendingResponse::responseId
uint64_t responseId
Definition: FlutterEmbedderKeyResponder.mm:260
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FLUTTER_ASSERT_ARC::KeyOfPlane
static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane)
Definition: FlutterEmbedderKeyResponder.mm:66
FLUTTER_ASSERT_ARC::toLower
static uint64_t toLower(uint64_t n)
Definition: FlutterEmbedderKeyResponder.mm:98
FLUTTER_ASSERT_ARC::GetFlutterTimestampFrom
static double GetFlutterTimestampFrom(NSTimeInterval timestamp)
Definition: FlutterEmbedderKeyResponder.mm:201
FLUTTER_ASSERT_ARC::getEventCharacters
static const char * getEventCharacters(NSString *characters, UIKeyboardHIDUsage keyCode) API_AVAILABLE(ios(13.4))
Definition: FlutterEmbedderKeyResponder.mm:124
FLUTTER_ASSERT_ARC::lowestSetBit
static NSUInteger lowestSetBit(NSUInteger bitmask)
Definition: FlutterEmbedderKeyResponder.mm:28
FLUTTER_ASSERT_ARC::isKeyDown
static bool isKeyDown(FlutterUIPressProxy *press) API_AVAILABLE(ios(13.4))
Definition: FlutterEmbedderKeyResponder.mm:221
FLUTTER_ASSERT_ARC::computeModifierFlagOfInterestMask
static NSUInteger computeModifierFlagOfInterestMask()
Definition: FlutterEmbedderKeyResponder.mm:211
FLUTTER_ASSERT_ARC::GetPhysicalKeyForKeyCode
static uint64_t GetPhysicalKeyForKeyCode(UInt32 keyCode)
Definition: FlutterEmbedderKeyResponder.mm:73
FLUTTER_ASSERT_ARC::IsControlCharacter
static bool IsControlCharacter(NSUInteger length, NSString *label)
Definition: FlutterEmbedderKeyResponder.mm:37
FLUTTER_ASSERT_ARC::HandleResponse
void HandleResponse(bool handled, void *user_data)
FLUTTER_ASSERT_ARC::GetLogicalKeyForEvent
static uint64_t GetLogicalKeyForEvent(FlutterUIPressProxy *press, NSNumber *maybeSpecialKey) API_AVAILABLE(ios(13.4))
Definition: FlutterEmbedderKeyResponder.mm:158
FLUTTER_ASSERT_ARC::GetLogicalKeyForModifier
static uint64_t GetLogicalKeyForModifier(UInt32 keyCode, uint64_t hidCode)
Definition: FlutterEmbedderKeyResponder.mm:84
FLUTTER_ASSERT_ARC::IsUnprintableKey
static bool IsUnprintableKey(NSUInteger length, NSString *label)
Definition: FlutterEmbedderKeyResponder.mm:48
user_data
void * user_data
Definition: texture_registrar_unittests.cc:27