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"
15 
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  */
212  NSUInteger modifierFlagOfInterestMask = kModifierFlagCapsLock | kModifierFlagShiftAny |
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 
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
FLUTTER_ASSERT_ARC::GetLogicalKeyForModifier
static uint64_t GetLogicalKeyForModifier(UInt32 keyCode, uint64_t hidCode)
Definition: FlutterEmbedderKeyResponder.mm:84
FLUTTER_ASSERT_ARC::getEventCharacters
static const char * getEventCharacters(NSString *characters, UIKeyboardHIDUsage keyCode) API_AVAILABLE(ios(13.4))
Definition: FlutterEmbedderKeyResponder.mm:124
keyCodeToLogicalKey
const std::map< uint32_t, uint64_t > keyCodeToLogicalKey
Definition: KeyCodeMap.g.mm:201
kModifierFlagCapsLock
@ kModifierFlagCapsLock
Definition: KeyCodeMap_Internal.h:74
kValueMask
const uint64_t kValueMask
Definition: KeyCodeMap.g.mm:22
FlutterKeyPendingResponse::responseId
uint64_t responseId
Definition: FlutterEmbedderKeyResponder.mm:260
FLUTTER_ASSERT_ARC::lowestSetBit
static NSUInteger lowestSetBit(NSUInteger bitmask)
Definition: FlutterEmbedderKeyResponder.mm:28
user_data
void * user_data
Definition: texture_registrar_unittests.cc:27
API_AVAILABLE
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
FLUTTER_ASSERT_ARC::KeyOfPlane
static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane)
Definition: FlutterEmbedderKeyResponder.mm:66
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:325
FLUTTER_ASSERT_ARC::isKeyDown
static bool isKeyDown(FlutterUIPressProxy *press) API_AVAILABLE(ios(13.4))
Definition: FlutterEmbedderKeyResponder.mm:221
FlutterKeyCallbackGuard::handled
BOOL handled
Definition: FlutterEmbedderKeyResponder.mm:301
FLUTTER_ASSERT_ARC::IsUnprintableKey
static bool IsUnprintableKey(NSUInteger length, NSString *label)
Definition: FlutterEmbedderKeyResponder.mm:48
FLUTTER_ASSERT_ARC::computeModifierFlagOfInterestMask
static NSUInteger computeModifierFlagOfInterestMask()
Definition: FlutterEmbedderKeyResponder.mm:211
FlutterMacros.h
kModifierFlagControlAny
@ kModifierFlagControlAny
Definition: KeyCodeMap_Internal.h:76
FlutterEmbedderKeyResponder.h
FLUTTER_ASSERT_ARC::GetPhysicalKeyForKeyCode
static uint64_t GetPhysicalKeyForKeyCode(UInt32 keyCode)
Definition: FlutterEmbedderKeyResponder.mm:73
FlutterSendKeyEvent
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
Definition: FlutterEmbedderKeyResponder.h:13
kModifierFlagAnyMask
constexpr uint32_t kModifierFlagAnyMask
Definition: KeyCodeMap_Internal.h:86
functionKeyCodes
const std::set< uint32_t > functionKeyCodes
Definition: KeyCodeMap.g.mm:304
FLUTTER_ASSERT_ARC::HandleResponse
void HandleResponse(bool handled, void *user_data)
FlutterEmbedderKeyResponder()::sendEvent
FlutterSendKeyEvent sendEvent
Definition: FlutterEmbedderKeyResponder.mm:357
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:338
FlutterKeyPendingResponse
Definition: FlutterEmbedderKeyResponder.mm:256
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
FLUTTER_ASSERT_ARC::toLower
static uint64_t toLower(uint64_t n)
Definition: FlutterEmbedderKeyResponder.mm:98
keyCodeToPhysicalKey
const std::map< uint32_t, uint64_t > keyCodeToPhysicalKey
Definition: KeyCodeMap.g.mm:37
kUnicodePlane
const uint64_t kUnicodePlane
Definition: KeyCodeMap.g.mm:27
FLUTTER_ASSERT_ARC::GetLogicalKeyForEvent
static uint64_t GetLogicalKeyForEvent(FlutterUIPressProxy *press, NSNumber *maybeSpecialKey) API_AVAILABLE(ios(13.4))
Definition: FlutterEmbedderKeyResponder.mm:158
FlutterUIPressProxy
Definition: FlutterUIPressProxy.h:17
FLUTTER_ASSERT_ARC::GetFlutterTimestampFrom
static double GetFlutterTimestampFrom(NSTimeInterval timestamp)
Definition: FlutterEmbedderKeyResponder.mm:201
FLUTTER_ASSERT_ARC::IsControlCharacter
static bool IsControlCharacter(NSUInteger length, NSString *label)
Definition: FlutterEmbedderKeyResponder.mm:37
FlutterKeyCallbackGuard::debugHandleSource
NSString * debugHandleSource
Definition: FlutterEmbedderKeyResponder.mm:308
KeyCodeMap_Internal.h
kCapsLockLogicalKey
const uint64_t kCapsLockLogicalKey
Definition: KeyCodeMap.g.mm:357
kCapsLockPhysicalKey
const uint64_t kCapsLockPhysicalKey
Definition: KeyCodeMap.g.mm:356
FlutterKeyCallbackGuard
Definition: FlutterEmbedderKeyResponder.mm:287
FlutterEmbedderKeyResponder
Definition: FlutterEmbedderKeyResponder.h:23
kModifierFlagMetaAny
@ kModifierFlagMetaAny
Definition: KeyCodeMap_Internal.h:78
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterKeyPendingResponse::responder
FlutterEmbedderKeyResponder * responder
Definition: FlutterEmbedderKeyResponder.mm:258
kIosPlane
const uint64_t kIosPlane
Definition: KeyCodeMap.g.mm:32