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