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  };
615  _sendEvent(flutterEvent, nullptr, nullptr);
616 
617  flutterEvent.type = kFlutterKeyEventTypeUp;
618  _sendEvent(flutterEvent, nullptr, nullptr);
619 }
620 
621 - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey {
622  if (logicalKey == 0) {
623  [_pressingRecords removeObjectForKey:@(physicalKey)];
624  } else {
625  _pressingRecords[@(physicalKey)] = @(logicalKey);
626  }
627 }
628 
629 - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
630  callback:(FlutterKeyCallbackGuard*)callback {
631  _responseId += 1;
632  uint64_t responseId = _responseId;
633  FlutterKeyPendingResponse* pending =
634  [[[FlutterKeyPendingResponse alloc] initWithHandler:self responseId:responseId] autorelease];
635  [callback pendTo:_pendingResponses withId:responseId];
636  _sendEvent(event, HandleResponse, pending);
637 }
638 
639 - (void)sendEmptyEvent {
640  FlutterKeyEvent event = {
641  .struct_size = sizeof(FlutterKeyEvent),
642  .timestamp = 0,
643  .type = kFlutterKeyEventTypeDown,
644  .physical = 0,
645  .logical = 0,
646  .character = nil,
647  .synthesized = false,
648  };
649  _sendEvent(event, nil, nil);
650 }
651 
652 - (void)synthesizeModifierEventOfType:(BOOL)isDownEvent
653  timestamp:(NSTimeInterval)timestamp
654  keyCode:(UInt32)keyCode {
655  uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);
656  uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);
657  if (physicalKey == 0 || logicalKey == 0) {
658  return;
659  }
660  FlutterKeyEvent flutterEvent = {
661  .struct_size = sizeof(FlutterKeyEvent),
662  .timestamp = GetFlutterTimestampFrom(timestamp),
663  .type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp,
664  .physical = physicalKey,
665  .logical = logicalKey,
666  .character = nil,
667  .synthesized = true,
668  };
669  [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];
670  _sendEvent(flutterEvent, nullptr, nullptr);
671 }
672 
673 - (void)handlePressBegin:(nonnull FlutterUIPressProxy*)press
674  callback:(nonnull FlutterKeyCallbackGuard*)callback API_AVAILABLE(ios(13.4)) {
675  if (@available(iOS 13.4, *)) {
676  } else {
677  return;
678  }
679  uint64_t physicalKey = GetPhysicalKeyForKeyCode(press.key.keyCode);
680  // Some unprintable keys on iOS have literal names on their key label, such as
681  // @"UIKeyInputEscape". They are called the "special keys" and have predefined
682  // logical keys and empty characters.
683  NSNumber* specialKey = [specialKeyMapping objectForKey:press.key.charactersIgnoringModifiers];
684  uint64_t logicalKey = GetLogicalKeyForEvent(press, specialKey);
685  [self synchronizeModifiers:press];
686 
687  NSNumber* pressedLogicalKey = nil;
688  if ([_pressingRecords count] > 0) {
689  pressedLogicalKey = _pressingRecords[@(physicalKey)];
690  if (pressedLogicalKey != nil) {
691  // Normally the key up events won't be missed since iOS always sends the
692  // key up event to the view where the corresponding key down occurred.
693  // However this might happen in add-to-app scenarios if the focus is changed
694  // from the native view to the Flutter view amid the key tap.
695  [callback resolveTo:TRUE];
696  [self sendEmptyEvent];
697  return;
698  }
699  }
700 
701  if (pressedLogicalKey == nil) {
702  [self updateKey:physicalKey asPressed:logicalKey];
703  }
704 
705  FlutterKeyEvent flutterEvent = {
706  .struct_size = sizeof(FlutterKeyEvent),
707  .timestamp = GetFlutterTimestampFrom(press.timestamp),
708  .type = kFlutterKeyEventTypeDown,
709  .physical = physicalKey,
710  .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],
711  .character =
712  specialKey != nil ? nil : getEventCharacters(press.key.characters, press.key.keyCode),
713  .synthesized = false,
714  };
715  [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
716 }
717 
718 - (void)handlePressEnd:(nonnull FlutterUIPressProxy*)press
719  callback:(nonnull FlutterKeyCallbackGuard*)callback API_AVAILABLE(ios(13.4)) {
720  if (@available(iOS 13.4, *)) {
721  } else {
722  return;
723  }
724  [self synchronizeModifiers:press];
725 
726  uint64_t physicalKey = GetPhysicalKeyForKeyCode(press.key.keyCode);
727  NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
728  if (pressedLogicalKey == nil) {
729  // Normally the key up events won't be missed since iOS always sends the
730  // key up event to the view where the corresponding key down occurred.
731  // However this might happen in add-to-app scenarios if the focus is changed
732  // from the native view to the Flutter view amid the key tap.
733  [callback resolveTo:TRUE];
734  [self sendEmptyEvent];
735  return;
736  }
737  [self updateKey:physicalKey asPressed:0];
738 
739  FlutterKeyEvent flutterEvent = {
740  .struct_size = sizeof(FlutterKeyEvent),
741  .timestamp = GetFlutterTimestampFrom(press.timestamp),
742  .type = kFlutterKeyEventTypeUp,
743  .physical = physicalKey,
744  .logical = [pressedLogicalKey unsignedLongLongValue],
745  .character = nil,
746  .synthesized = false,
747  };
748  [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
749 }
750 
751 - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId {
752  FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)];
753  callback(handled);
754  [_pendingResponses removeObjectForKey:@(responseId)];
755 }
756 
757 - (UInt32)fixSidedFlags:(ModifierFlag)anyFlag
758  withLeftFlag:(ModifierFlag)leftSide
759  withRightFlag:(ModifierFlag)rightSide
760  withLeftKey:(UInt16)leftKeyCode
761  withRightKey:(UInt16)rightKeyCode
762  withKeyCode:(UInt16)keyCode
763  keyDown:(BOOL)isKeyDown
764  forFlags:(UInt32)modifiersPressed API_AVAILABLE(ios(13.4)) {
765  UInt32 newModifiers = modifiersPressed;
766  if (isKeyDown) {
767  // Add in the modifier flags that correspond to this key code, if any.
768  if (keyCode == leftKeyCode) {
769  newModifiers |= leftSide | anyFlag;
770  } else if (keyCode == rightKeyCode) {
771  newModifiers |= rightSide | anyFlag;
772  }
773  } else {
774  // If this is a key up, then remove any modifier that matches the keycode in
775  // the event from the flags, and the anyFlag if the other side isn't also
776  // pressed.
777  if (keyCode == leftKeyCode) {
778  newModifiers &= ~leftSide;
779  if (!(newModifiers & rightSide)) {
780  newModifiers &= ~anyFlag;
781  }
782  } else if (keyCode == rightKeyCode) {
783  newModifiers &= ~rightSide;
784  if (!(newModifiers & leftSide)) {
785  newModifiers &= ~anyFlag;
786  }
787  }
788  }
789 
790  if (!(newModifiers & anyFlag)) {
791  // Turn off any sided flags, since the "any" flag is gone.
792  newModifiers &= ~(leftSide | rightSide);
793  }
794 
795  return newModifiers;
796 }
797 
798 // This fixes a few cases where iOS provides modifier flags differently from how
799 // the framework would like to receive them.
800 //
801 // 1) iOS turns off the flag associated with a modifier key AFTER the modifier
802 // key up event, so when the key up event arrives, the flags must be modified
803 // before synchronizing so they do not include the modifier that arrived in
804 // the key up event.
805 // 2) Modifier flags can be set even when that modifier is not being pressed.
806 // One example of this is when a special character is produced with the Alt
807 // (Option) key, and the Alt key is released before the letter key: the
808 // letter key's key up event still contains the Alt key flag.
809 // 3) iOS doesn't provide information about which side modifier was pressed,
810 // except through the keycode of the pressed key, so we look at the pressed
811 // key code to decide which side to indicate in the flags. If we can't know
812 // (in the case of a non-modifier key event having an "any" modifier set, but
813 // we don't know already that the modifier is down), then we just pick the
814 // left one arbitrarily.
815 - (UInt32)adjustModifiers:(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4)) {
816  if (@available(iOS 13.4, *)) {
817  // no-op
818  } else {
819  return press.key.modifierFlags;
820  }
821 
822  bool keyDown = isKeyDown(press);
823 
824  // Start with the current modifier flags, along with any sided flags that we
825  // already know are down.
826  UInt32 pressedModifiers =
827  press.key.modifierFlags | (_lastModifierFlagsOfInterest & kModifierFlagSidedMask);
828 
829  pressedModifiers = [self fixSidedFlags:kModifierFlagShiftAny
830  withLeftFlag:kModifierFlagShiftLeft
831  withRightFlag:kModifierFlagShiftRight
832  withLeftKey:UIKeyboardHIDUsageKeyboardLeftShift
833  withRightKey:UIKeyboardHIDUsageKeyboardRightShift
834  withKeyCode:press.key.keyCode
835  keyDown:keyDown
836  forFlags:pressedModifiers];
837  pressedModifiers = [self fixSidedFlags:kModifierFlagControlAny
838  withLeftFlag:kModifierFlagControlLeft
839  withRightFlag:kModifierFlagControlRight
840  withLeftKey:UIKeyboardHIDUsageKeyboardLeftControl
841  withRightKey:UIKeyboardHIDUsageKeyboardRightControl
842  withKeyCode:press.key.keyCode
843  keyDown:keyDown
844  forFlags:pressedModifiers];
845  pressedModifiers = [self fixSidedFlags:kModifierFlagAltAny
846  withLeftFlag:kModifierFlagAltLeft
847  withRightFlag:kModifierFlagAltRight
848  withLeftKey:UIKeyboardHIDUsageKeyboardLeftAlt
849  withRightKey:UIKeyboardHIDUsageKeyboardRightAlt
850  withKeyCode:press.key.keyCode
851  keyDown:keyDown
852  forFlags:pressedModifiers];
853  pressedModifiers = [self fixSidedFlags:kModifierFlagMetaAny
854  withLeftFlag:kModifierFlagMetaLeft
855  withRightFlag:kModifierFlagMetaRight
856  withLeftKey:UIKeyboardHIDUsageKeyboardLeftGUI
857  withRightKey:UIKeyboardHIDUsageKeyboardRightGUI
858  withKeyCode:press.key.keyCode
859  keyDown:keyDown
860  forFlags:pressedModifiers];
861 
862  if (press.key.keyCode == UIKeyboardHIDUsageKeyboardCapsLock) {
863  // The caps lock modifier needs to be unset only if it was already on
864  // and this is a key up. This is because it indicates the lock state, and
865  // not the key press state. The caps lock state should be on between the
866  // first down, and the second up (i.e. while the lock in effect), and
867  // this code turns it off at the second up event. The OS leaves it on still
868  // because of iOS's weird late processing of modifier states. Synthesis of
869  // the appropriate synthesized key events happens in synchronizeModifiers.
870  if (!keyDown && _lastModifierFlagsOfInterest & kModifierFlagCapsLock) {
871  pressedModifiers &= ~kModifierFlagCapsLock;
872  }
873  }
874  return pressedModifiers;
875 }
876 
877 @end
878 
879 namespace {
880 void HandleResponse(bool handled, void* user_data) {
881  FlutterKeyPendingResponse* pending = reinterpret_cast<FlutterKeyPendingResponse*>(user_data);
882  [pending.responder handleResponse:handled forId:pending.responseId];
883 }
884 } // namespace
keyCodeToLogicalKey
const std::map< uint32_t, uint64_t > keyCodeToLogicalKey
Definition: KeyCodeMap.g.mm:201
kModifierFlagCapsLock
@ kModifierFlagCapsLock
Definition: KeyCodeMap_Internal.h:72
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:75
-[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:74
FlutterEmbedderKeyResponder.h
kModifierFlagAnyMask
constexpr uint32_t kModifierFlagAnyMask
Definition: KeyCodeMap_Internal.h:84
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:91
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:59
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:73
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
FlutterSendKeyEvent
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, _Nullable _VoidPtr)
Definition: FlutterEmbedderKeyResponder.h:20
FlutterEmbedderKeyResponder
Definition: FlutterEmbedderKeyResponder.h:30
kModifierFlagMetaAny
@ kModifierFlagMetaAny
Definition: KeyCodeMap_Internal.h:76
FlutterKeyPendingResponse::responder
FlutterEmbedderKeyResponder * responder
Definition: FlutterEmbedderKeyResponder.mm:255
kIosPlane
const uint64_t kIosPlane
Definition: KeyCodeMap.g.mm:32