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 
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"
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 |
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 
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 
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