Flutter macOS Embedder
FlutterEmbedderKeyResponder.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #import <objc/message.h>
6 #include <memory>
7 
9 #import "KeyCodeMap_Internal.h"
12 #import "flutter/shell/platform/embedder/embedder.h"
13 
14 namespace {
15 
16 /**
17  * Isolate the least significant 1-bit.
18  *
19  * For example,
20  *
21  * * lowestSetBit(0x1010) returns 0x10.
22  * * lowestSetBit(0) returns 0.
23  */
24 static NSUInteger lowestSetBit(NSUInteger bitmask) {
25  // This utilizes property of two's complement (negation), which propagates a
26  // carry bit from LSB to the lowest set bit.
27  return bitmask & -bitmask;
28 }
29 
30 /**
31  * Whether a string represents a control character.
32  */
33 static bool IsControlCharacter(uint64_t character) {
34  return (character <= 0x1f && character >= 0x00) || (character >= 0x7f && character <= 0x9f);
35 }
36 
37 /**
38  * Whether a string represents an unprintable key.
39  */
40 static bool IsUnprintableKey(uint64_t character) {
41  return character >= 0xF700 && character <= 0xF8FF;
42 }
43 
44 /**
45  * Returns a key code composed with a base key and a plane.
46  *
47  * Examples of unprintable keys are "NSUpArrowFunctionKey = 0xF700" or
48  * "NSHomeFunctionKey = 0xF729".
49  *
50  * See
51  * https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc
52  * for more information.
53  */
54 static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) {
55  return plane | (baseKey & flutter::kValueMask);
56 }
57 
58 /**
59  * Returns the physical key for a key code.
60  */
61 static uint64_t GetPhysicalKeyForKeyCode(unsigned short keyCode) {
62  NSNumber* physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:@(keyCode)];
63  if (physicalKey == nil) {
64  return KeyOfPlane(keyCode, flutter::kMacosPlane);
65  }
66  return physicalKey.unsignedLongLongValue;
67 }
68 
69 /**
70  * Returns the logical key for a modifier physical key.
71  */
72 static uint64_t GetLogicalKeyForModifier(unsigned short keyCode, uint64_t hidCode) {
73  NSNumber* fromKeyCode = [flutter::keyCodeToLogicalKey objectForKey:@(keyCode)];
74  if (fromKeyCode != nil) {
75  return fromKeyCode.unsignedLongLongValue;
76  }
77  return KeyOfPlane(hidCode, flutter::kMacosPlane);
78 }
79 
80 /**
81  * Converts upper letters to lower letters in ASCII, and returns as-is
82  * otherwise.
83  *
84  * Independent of locale.
85  */
86 static uint64_t toLower(uint64_t n) {
87  constexpr uint64_t lowerA = 0x61;
88  constexpr uint64_t upperA = 0x41;
89  constexpr uint64_t upperZ = 0x5a;
90 
91  constexpr uint64_t lowerAGrave = 0xe0;
92  constexpr uint64_t upperAGrave = 0xc0;
93  constexpr uint64_t upperThorn = 0xde;
94  constexpr uint64_t division = 0xf7;
95 
96  // ASCII range.
97  if (n >= upperA && n <= upperZ) {
98  return n - upperA + lowerA;
99  }
100 
101  // EASCII range.
102  if (n >= upperAGrave && n <= upperThorn && n != division) {
103  return n - upperAGrave + lowerAGrave;
104  }
105 
106  return n;
107 }
108 
109 // Decode a UTF-16 sequence to an array of char32 (UTF-32).
110 //
111 // See https://en.wikipedia.org/wiki/UTF-16#Description for the algorithm.
112 //
113 // The returned character array must be deallocated with delete[]. The length of
114 // the result is stored in `out_length`.
115 //
116 // Although NSString has a dataUsingEncoding method, we implement our own
117 // because dataUsingEncoding outputs redundant characters for unknown reasons.
118 static uint32_t* DecodeUtf16(NSString* target, size_t* out_length) {
119  // The result always has a length less or equal to target.
120  size_t result_pos = 0;
121  uint32_t* result = new uint32_t[target.length];
122  uint16_t high_surrogate = 0;
123  for (NSUInteger target_pos = 0; target_pos < target.length; target_pos += 1) {
124  uint16_t codeUnit = [target characterAtIndex:target_pos];
125  // BMP
126  if (codeUnit <= 0xD7FF || codeUnit >= 0xE000) {
127  result[result_pos] = codeUnit;
128  result_pos += 1;
129  // High surrogates
130  } else if (codeUnit <= 0xDBFF) {
131  high_surrogate = codeUnit - 0xD800;
132  // Low surrogates
133  } else {
134  uint16_t low_surrogate = codeUnit - 0xDC00;
135  result[result_pos] = (high_surrogate << 10) + low_surrogate + 0x10000;
136  result_pos += 1;
137  }
138  }
139  *out_length = result_pos;
140  return result;
141 }
142 
143 /**
144  * Returns the logical key of a KeyUp or KeyDown event.
145  *
146  * For FlagsChanged event, use GetLogicalKeyForModifier.
147  */
148 static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) {
149  // Look to see if the keyCode can be mapped from keycode.
150  NSNumber* fromKeyCode = [flutter::keyCodeToLogicalKey objectForKey:@(event.keyCode)];
151  if (fromKeyCode != nil) {
152  return fromKeyCode.unsignedLongLongValue;
153  }
154 
155  // Convert `charactersIgnoringModifiers` to UTF32.
156  NSString* keyLabelUtf16 = event.charactersIgnoringModifiers;
157 
158  // Check if this key is a single character, which will be used to generate the
159  // logical key from its Unicode value.
160  //
161  // Multi-char keys will be minted onto the macOS plane because there are no
162  // meaningful values for them. Control keys and unprintable keys have been
163  // converted by `keyCodeToLogicalKey` earlier.
164  uint32_t character = 0;
165  if (keyLabelUtf16.length != 0) {
166  size_t keyLabelLength;
167  uint32_t* keyLabel = DecodeUtf16(keyLabelUtf16, &keyLabelLength);
168  if (keyLabelLength == 1) {
169  uint32_t keyLabelChar = *keyLabel;
170  FML_DCHECK(!IsControlCharacter(keyLabelChar) && !IsUnprintableKey(keyLabelChar))
171  << "Unexpected control or unprintable keylabel 0x" << std::hex << keyLabelChar;
172  FML_DCHECK(keyLabelChar <= 0x10FFFF)
173  << "Out of range keylabel 0x" << std::hex << keyLabelChar;
174  character = keyLabelChar;
175  }
176  delete[] keyLabel;
177  }
178  if (character != 0) {
179  return KeyOfPlane(toLower(character), flutter::kUnicodePlane);
180  }
181 
182  // We can't represent this key with a single printable unicode, so a new code
183  // is minted to the macOS plane.
184  return KeyOfPlane(event.keyCode, flutter::kMacosPlane);
185 }
186 
187 /**
188  * Converts NSEvent.timestamp to the timestamp for Flutter.
189  */
190 static double GetFlutterTimestampFrom(NSTimeInterval timestamp) {
191  // Timestamp in microseconds. The event.timestamp is in seconds with sub-ms precision.
192  return timestamp * 1000000.0;
193 }
194 
195 /**
196  * Compute |modifierFlagOfInterestMask| out of |keyCodeToModifierFlag|.
197  *
198  * This is equal to the bitwise-or of all values of |keyCodeToModifierFlag| as
199  * well as NSEventModifierFlagCapsLock.
200  */
201 static NSUInteger computeModifierFlagOfInterestMask() {
202  __block NSUInteger modifierFlagOfInterestMask = NSEventModifierFlagCapsLock;
204  enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL* stop) {
205  modifierFlagOfInterestMask = modifierFlagOfInterestMask | [flag unsignedLongValue];
206  }];
207  return modifierFlagOfInterestMask;
208 }
209 
210 /**
211  * The C-function sent to the embedder's |SendKeyEvent|, wrapping
212  * |FlutterEmbedderKeyResponder.handleResponse|.
213  *
214  * For the reason of this wrap, see |FlutterKeyPendingResponse|.
215  */
216 void HandleResponse(bool handled, void* user_data);
217 
218 /**
219  * Converts NSEvent.characters to a C-string for FlutterKeyEvent.
220  */
221 const char* getEventString(NSString* characters) {
222  if ([characters length] == 0) {
223  return nullptr;
224  }
225  unichar utf16Code = [characters characterAtIndex:0];
226  if (utf16Code >= 0xf700 && utf16Code <= 0xf7ff) {
227  // Some function keys are assigned characters with codepoints from the
228  // private use area. These characters are filtered out since they're
229  // unprintable.
230  //
231  // The official documentation reserves 0xF700-0xF8FF as private use area
232  // (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc).
233  // But macOS seems to only use a reduced range of it. The official doc
234  // defines a few constants, all of which are within 0xF700-0xF747.
235  // (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc).
236  // This mostly aligns with the experimentation result, except for 0xF8FF,
237  // which is used for the "Apple logo" character (Option-Shift-K on a US
238  // keyboard.)
239  //
240  // Assume that non-printable function keys are defined from
241  // 0xF700 upwards, and printable private keys are defined from 0xF8FF
242  // downwards. This function filters out 0xF700-0xF7FF in order to keep
243  // the printable private keys.
244  return nullptr;
245  }
246  return [characters UTF8String];
247 }
248 } // namespace
249 
250 /**
251  * The invocation context for |HandleResponse|, wrapping
252  * |FlutterEmbedderKeyResponder.handleResponse|.
253  */
256  uint64_t responseId;
257 };
258 
259 /**
260  * Guards a |FlutterAsyncKeyCallback| to make sure it's handled exactly once
261  * throughout |FlutterEmbedderKeyResponder.handleEvent|.
262  *
263  * A callback can either be handled with |pendTo:withId:|, or with |resolveTo:|.
264  * Either way, the callback cannot be handled again, or an assertion will be
265  * thrown.
266  */
267 @interface FlutterKeyCallbackGuard : NSObject
268 - (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback;
269 
270 /**
271  * Handle the callback by storing it to pending responses.
272  */
273 - (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses
274  withId:(uint64_t)responseId;
275 
276 /**
277  * Handle the callback by calling it with a result.
278  */
279 - (void)resolveTo:(BOOL)handled;
280 
281 @property(nonatomic) BOOL handled;
282 @property(nonatomic) BOOL sentAnyEvents;
283 /**
284  * A string indicating how the callback is handled.
285  *
286  * Only set in debug mode. Nil in release mode, or if the callback has not been
287  * handled.
288  */
289 @property(nonatomic, copy) NSString* debugHandleSource;
290 @end
291 
292 @implementation FlutterKeyCallbackGuard {
293  // The callback is declared in the implemnetation block to avoid being
294  // accessed directly.
295  FlutterAsyncKeyCallback _callback;
296 }
297 - (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback {
298  self = [super init];
299  if (self != nil) {
300  _callback = callback;
301  _handled = FALSE;
302  _sentAnyEvents = FALSE;
303  }
304  return self;
305 }
306 
307 - (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses
308  withId:(uint64_t)responseId {
309  NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource);
310  if (_handled) {
311  return;
312  }
313  pendingResponses[@(responseId)] = _callback;
314  _handled = TRUE;
315  NSAssert(
316  ((_debugHandleSource = [NSString stringWithFormat:@"pending event %llu", responseId]), TRUE),
317  @"");
318 }
319 
320 - (void)resolveTo:(BOOL)handled {
321  NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource);
322  if (_handled) {
323  return;
324  }
325  _callback(handled);
326  _handled = TRUE;
327  NSAssert(((_debugHandleSource = [NSString stringWithFormat:@"resolved with %d", _handled]), TRUE),
328  @"");
329 }
330 @end
331 
333 
334 /**
335  * The function to send converted events to.
336  *
337  * Set by the initializer.
338  */
339 @property(nonatomic, copy) FlutterSendEmbedderKeyEvent sendEvent;
340 
341 /**
342  * A map of presessd keys.
343  *
344  * The keys of the dictionary are physical keys, while the values are the logical keys
345  * of the key down event.
346  */
347 @property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>* pressingRecords;
348 
349 /**
350  * A constant mask for NSEvent.modifierFlags that Flutter synchronizes with.
351  *
352  * Flutter keeps track of the last |modifierFlags| and compares it with the
353  * incoming one. Any bit within |modifierFlagOfInterestMask| that is different
354  * (except for the one that corresponds to the event key) indicates that an
355  * event for this modifier was missed, and Flutter synthesizes an event to make
356  * up for the state difference.
357  *
358  * It is computed by computeModifierFlagOfInterestMask.
359  */
360 @property(nonatomic) NSUInteger modifierFlagOfInterestMask;
361 
362 /**
363  * The modifier flags of the last received key event, excluding uninterested
364  * bits.
365  *
366  * This should be kept synchronized with the last |NSEvent.modifierFlags|
367  * after masking with |modifierFlagOfInterestMask|. This should also be kept
368  * synchronized with the corresponding keys of |pressingRecords|.
369  *
370  * This is used by |synchronizeModifiers| to quickly find
371  * out modifier keys that are desynchronized.
372  */
373 @property(nonatomic) NSUInteger lastModifierFlagsOfInterest;
374 
375 /**
376  * A self-incrementing ID used to label key events sent to the framework.
377  */
378 @property(nonatomic) uint64_t responseId;
379 
380 /**
381  * A map of unresponded key events sent to the framework.
382  *
383  * Its values are |responseId|s, and keys are the callback that was received
384  * along with the event.
385  */
386 @property(nonatomic) NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>* pendingResponses;
387 
388 /**
389  * Compare the last modifier flags and the current, and dispatch synthesized
390  * key events for each different modifier flag bit.
391  *
392  * The flags compared are all flags after masking with
393  * |modifierFlagOfInterestMask| and excluding |ignoringFlags|.
394  *
395  * The |guard| is basically a regular guarded callback, but instead of being
396  * called, it is only used to record whether an event is sent.
397  */
398 - (void)synchronizeModifiers:(NSUInteger)currentFlags
399  ignoringFlags:(NSUInteger)ignoringFlags
400  timestamp:(NSTimeInterval)timestamp
401  guard:(nonnull FlutterKeyCallbackGuard*)guard;
402 
403 /**
404  * Update the pressing state.
405  *
406  * If `logicalKey` is not 0, `physicalKey` is pressed as `logicalKey`.
407  * Otherwise, `physicalKey` is released.
408  */
409 - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey;
410 
411 /**
412  * Send an event to the framework, expecting its response.
413  */
414 - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
415  callback:(nonnull FlutterKeyCallbackGuard*)callback;
416 
417 /**
418  * Send a synthesized key event, never expecting its event result.
419  *
420  * The |guard| is basically a regular guarded callback, but instead of being
421  * called, it is only used to record whether an event is sent.
422  */
423 - (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event
424  guard:(FlutterKeyCallbackGuard*)guard;
425 
426 /**
427  * Send a CapsLock down event, then a CapsLock up event.
428  *
429  * If synthesizeDown is TRUE, then both events will be synthesized. Otherwise,
430  * the callback will be used as the callback for the down event, which is not
431  * synthesized, while the up event will always be synthesized.
432  */
433 - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp
434  synthesizeDown:(bool)synthesizeDown
435  callback:(nonnull FlutterKeyCallbackGuard*)callback;
436 
437 /**
438  * Send a key event for a modifier key.
439  */
440 - (void)sendModifierEventOfType:(BOOL)isDownEvent
441  timestamp:(NSTimeInterval)timestamp
442  keyCode:(unsigned short)keyCode
443  synthesized:(bool)synthesized
444  callback:(nonnull FlutterKeyCallbackGuard*)callback;
445 
446 /**
447  * Processes a down event from the system.
448  */
449 - (void)handleDownEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
450 
451 /**
452  * Processes an up event from the system.
453  */
454 - (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
455 
456 /**
457  * Processes an event from the system for the CapsLock key.
458  */
459 - (void)handleCapsLockEvent:(nonnull NSEvent*)event
460  callback:(nonnull FlutterKeyCallbackGuard*)callback;
461 
462 /**
463  * Processes a flags changed event from the system, where modifier keys are pressed or released.
464  */
465 - (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
466 
467 /**
468  * Processes the response from the framework.
469  */
470 - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId;
471 
472 @end
473 
474 @implementation FlutterEmbedderKeyResponder
475 
476 // Synthesize properties declared in FlutterKeyPrimaryResponder protocol.
477 @synthesize layoutMap;
478 
479 - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent {
480  self = [super init];
481  if (self != nil) {
482  _sendEvent = sendEvent;
483  _pressingRecords = [NSMutableDictionary dictionary];
484  _pendingResponses = [NSMutableDictionary dictionary];
485  _responseId = 1;
486  _lastModifierFlagsOfInterest = 0;
487  _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask();
488  }
489  return self;
490 }
491 
492 - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
493  // The conversion algorithm relies on a non-nil callback to properly compute
494  // `synthesized`.
495  NSAssert(callback != nil, @"The callback must not be nil.");
496  FlutterKeyCallbackGuard* guardedCallback =
497  [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
498  switch (event.type) {
499  case NSEventTypeKeyDown:
500  [self handleDownEvent:event callback:guardedCallback];
501  break;
502  case NSEventTypeKeyUp:
503  [self handleUpEvent:event callback:guardedCallback];
504  break;
505  case NSEventTypeFlagsChanged:
506  [self handleFlagEvent:event callback:guardedCallback];
507  break;
508  default:
509  NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type));
510  }
511  NSAssert(guardedCallback.handled, @"The callback is returned without being handled.");
512  if (!guardedCallback.sentAnyEvents) {
513  FlutterKeyEvent flutterEvent = {
514  .struct_size = sizeof(FlutterKeyEvent),
515  .timestamp = 0,
516  .type = kFlutterKeyEventTypeDown,
517  .physical = 0,
518  .logical = 0,
519  .character = nil,
520  .synthesized = false,
521  };
522  _sendEvent(flutterEvent, nullptr, nullptr);
523  }
524  NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask),
525  @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx",
526  _lastModifierFlagsOfInterest, event.modifierFlags & _modifierFlagOfInterestMask);
527 }
528 
529 #pragma mark - Private
530 
531 - (void)synchronizeModifiers:(NSUInteger)currentFlags
532  ignoringFlags:(NSUInteger)ignoringFlags
533  timestamp:(NSTimeInterval)timestamp
534  guard:(FlutterKeyCallbackGuard*)guard {
535  const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags;
536  const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask;
537  const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask;
538  NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest;
539  if (flagDifference & NSEventModifierFlagCapsLock) {
540  [self sendCapsLockTapWithTimestamp:timestamp synthesizeDown:true callback:guard];
541  flagDifference = flagDifference & ~NSEventModifierFlagCapsLock;
542  }
543  while (true) {
544  const NSUInteger currentFlag = lowestSetBit(flagDifference);
545  if (currentFlag == 0) {
546  break;
547  }
548  flagDifference = flagDifference & ~currentFlag;
549  NSNumber* keyCode = [flutter::modifierFlagToKeyCode objectForKey:@(currentFlag)];
550  NSAssert(keyCode != nil, @"Invalid modifier flag 0x%lx", currentFlag);
551  if (keyCode == nil) {
552  continue;
553  }
554  BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0;
555  [self sendModifierEventOfType:isDownEvent
556  timestamp:timestamp
557  keyCode:[keyCode unsignedShortValue]
558  synthesized:true
559  callback:guard];
560  }
561  _lastModifierFlagsOfInterest =
562  (_lastModifierFlagsOfInterest & ~updatingMask) | currentFlagsOfInterest;
563 }
564 
565 - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey {
566  if (logicalKey == 0) {
567  [_pressingRecords removeObjectForKey:@(physicalKey)];
568  } else {
569  _pressingRecords[@(physicalKey)] = @(logicalKey);
570  }
571 }
572 
573 - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
574  callback:(FlutterKeyCallbackGuard*)callback {
575  _responseId += 1;
576  uint64_t responseId = _responseId;
577  // The `pending` is released in `HandleResponse`.
578  FlutterKeyPendingResponse* pending = new FlutterKeyPendingResponse{self, responseId};
579  [callback pendTo:_pendingResponses withId:responseId];
580  _sendEvent(event, HandleResponse, pending);
581  callback.sentAnyEvents = TRUE;
582 }
583 
584 - (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event
585  guard:(FlutterKeyCallbackGuard*)guard {
586  _sendEvent(event, nullptr, nullptr);
587  guard.sentAnyEvents = TRUE;
588 }
589 
590 - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp
591  synthesizeDown:(bool)synthesizeDown
592  callback:(FlutterKeyCallbackGuard*)callback {
593  // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on
594  // even taps and odd taps. A CapsLock down or CapsLock up should always be
595  // converted to a down *and* an up, and the up should always be a synthesized
596  // event, since the FlutterEmbedderKeyResponder will never know when the
597  // button is released.
598  FlutterKeyEvent flutterEvent = {
599  .struct_size = sizeof(FlutterKeyEvent),
600  .timestamp = GetFlutterTimestampFrom(timestamp),
601  .type = kFlutterKeyEventTypeDown,
602  .physical = flutter::kCapsLockPhysicalKey,
603  .logical = flutter::kCapsLockLogicalKey,
604  .character = nil,
605  .synthesized = synthesizeDown,
606  };
607  if (!synthesizeDown) {
608  [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
609  } else {
610  [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
611  }
612 
613  flutterEvent.type = kFlutterKeyEventTypeUp;
614  flutterEvent.synthesized = true;
615  [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
616 }
617 
618 - (void)sendModifierEventOfType:(BOOL)isDownEvent
619  timestamp:(NSTimeInterval)timestamp
620  keyCode:(unsigned short)keyCode
621  synthesized:(bool)synthesized
622  callback:(FlutterKeyCallbackGuard*)callback {
623  uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);
624  uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);
625  if (physicalKey == 0 || logicalKey == 0) {
626  NSLog(@"Unrecognized modifier key: keyCode 0x%hx, physical key 0x%llx", keyCode, physicalKey);
627  [callback resolveTo:TRUE];
628  return;
629  }
630  FlutterKeyEvent flutterEvent = {
631  .struct_size = sizeof(FlutterKeyEvent),
632  .timestamp = GetFlutterTimestampFrom(timestamp),
633  .type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp,
634  .physical = physicalKey,
635  .logical = logicalKey,
636  .character = nil,
637  .synthesized = synthesized,
638  };
639  [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];
640  if (!synthesized) {
641  [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
642  } else {
643  [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
644  }
645 }
646 
647 - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
648  uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
649  NSNumber* logicalKeyFromMap = self.layoutMap[@(event.keyCode)];
650  uint64_t logicalKey = logicalKeyFromMap != nil ? [logicalKeyFromMap unsignedLongLongValue]
651  : GetLogicalKeyForEvent(event, physicalKey);
652  [self synchronizeModifiers:event.modifierFlags
653  ignoringFlags:0
654  timestamp:event.timestamp
655  guard:callback];
656 
657  bool isARepeat = event.isARepeat;
658  NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
659  if (pressedLogicalKey != nil && !isARepeat) {
660  // This might happen in add-to-app scenarios if the focus is changed
661  // from the native view to the Flutter view amid the key tap.
662  //
663  // This might also happen when a key event is forged (such as by an
664  // IME) using the same keyCode as an unreleased key. See
665  // https://github.com/flutter/flutter/issues/82673#issuecomment-988661079
666  FlutterKeyEvent flutterEvent = {
667  .struct_size = sizeof(FlutterKeyEvent),
668  .timestamp = GetFlutterTimestampFrom(event.timestamp),
669  .type = kFlutterKeyEventTypeUp,
670  .physical = physicalKey,
671  .logical = [pressedLogicalKey unsignedLongLongValue],
672  .character = nil,
673  .synthesized = true,
674  };
675  [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
676  pressedLogicalKey = nil;
677  }
678 
679  if (pressedLogicalKey == nil) {
680  [self updateKey:physicalKey asPressed:logicalKey];
681  }
682 
683  FlutterKeyEvent flutterEvent = {
684  .struct_size = sizeof(FlutterKeyEvent),
685  .timestamp = GetFlutterTimestampFrom(event.timestamp),
686  .type = pressedLogicalKey == nil ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeRepeat,
687  .physical = physicalKey,
688  .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],
689  .character = getEventString(event.characters),
690  .synthesized = false,
691  };
692  [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
693 }
694 
695 - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
696  NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@",
697  event.keyCode, event.characters, event.charactersIgnoringModifiers);
698  [self synchronizeModifiers:event.modifierFlags
699  ignoringFlags:0
700  timestamp:event.timestamp
701  guard:callback];
702 
703  uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
704  NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
705  if (pressedLogicalKey == nil) {
706  // Normally the key up events won't be missed since macOS always sends the
707  // key up event to the window where the corresponding key down occurred.
708  // However this might happen in add-to-app scenarios if the focus is changed
709  // from the native view to the Flutter view amid the key tap.
710  [callback resolveTo:TRUE];
711  return;
712  }
713  [self updateKey:physicalKey asPressed:0];
714 
715  FlutterKeyEvent flutterEvent = {
716  .struct_size = sizeof(FlutterKeyEvent),
717  .timestamp = GetFlutterTimestampFrom(event.timestamp),
718  .type = kFlutterKeyEventTypeUp,
719  .physical = physicalKey,
720  .logical = [pressedLogicalKey unsignedLongLongValue],
721  .character = nil,
722  .synthesized = false,
723  };
724  [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
725 }
726 
727 - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
728  [self synchronizeModifiers:event.modifierFlags
729  ignoringFlags:NSEventModifierFlagCapsLock
730  timestamp:event.timestamp
731  guard:callback];
732  if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) !=
733  (event.modifierFlags & NSEventModifierFlagCapsLock)) {
734  [self sendCapsLockTapWithTimestamp:event.timestamp synthesizeDown:false callback:callback];
735  _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock;
736  } else {
737  [callback resolveTo:TRUE];
738  }
739 }
740 
741 - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
742  NSNumber* targetModifierFlagObj = flutter::keyCodeToModifierFlag[@(event.keyCode)];
743  NSUInteger targetModifierFlag =
744  targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue];
745  uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode);
746  if (targetKey == flutter::kCapsLockPhysicalKey) {
747  return [self handleCapsLockEvent:event callback:callback];
748  }
749 
750  [self synchronizeModifiers:event.modifierFlags
751  ignoringFlags:targetModifierFlag
752  timestamp:event.timestamp
753  guard:callback];
754 
755  NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)];
756  BOOL lastTargetPressed = pressedLogicalKey != nil;
757  NSAssert(targetModifierFlagObj == nil ||
758  (_lastModifierFlagsOfInterest & targetModifierFlag) != 0 == lastTargetPressed,
759  @"Desynchronized state between lastModifierFlagsOfInterest (0x%lx) on bit 0x%lx "
760  @"for keyCode 0x%hx, whose pressing state is %@.",
761  _lastModifierFlagsOfInterest, targetModifierFlag, event.keyCode,
762  lastTargetPressed
763  ? [NSString stringWithFormat:@"0x%llx", [pressedLogicalKey unsignedLongLongValue]]
764  : @"empty");
765 
766  BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0;
767  if (lastTargetPressed == shouldBePressed) {
768  [callback resolveTo:TRUE];
769  return;
770  }
771  _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ targetModifierFlag;
772  [self sendModifierEventOfType:shouldBePressed
773  timestamp:event.timestamp
774  keyCode:event.keyCode
775  synthesized:false
776  callback:callback];
777 }
778 
779 - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId {
780  FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)];
781  callback(handled);
782  [_pendingResponses removeObjectForKey:@(responseId)];
783 }
784 
785 - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
786  timestamp:(NSTimeInterval)timestamp {
787  FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
788  // Do nothing.
789  };
790  FlutterKeyCallbackGuard* guardedCallback =
791  [[FlutterKeyCallbackGuard alloc] initWithCallback:replyCallback];
792  [self synchronizeModifiers:modifierFlags
793  ignoringFlags:0
794  timestamp:timestamp
795  guard:guardedCallback];
796 }
797 
798 - (nonnull NSDictionary*)getPressedState {
799  return [NSDictionary dictionaryWithDictionary:_pressingRecords];
800 }
801 @end
802 
803 namespace {
804 void HandleResponse(bool handled, void* user_data) {
805  // Use unique_ptr to release on leaving.
806  auto pending = std::unique_ptr<FlutterKeyPendingResponse>(
807  reinterpret_cast<FlutterKeyPendingResponse*>(user_data));
808  [pending->responder handleResponse:handled forId:pending->responseId];
809 }
810 } // namespace
flutter::kCapsLockPhysicalKey
const uint64_t kCapsLockPhysicalKey
Definition: KeyCodeMap.g.mm:245
flutter::keyCodeToLogicalKey
const NSDictionary * keyCodeToLogicalKey
Definition: KeyCodeMap.g.mm:149
flutter::keyCodeToPhysicalKey
const NSDictionary * keyCodeToPhysicalKey
Definition: KeyCodeMap.g.mm:26
user_data
void * user_data
Definition: texture_registrar_unittests.cc:27
-[FlutterEmbedderKeyResponder getPressedState]
nonnull NSDictionary * getPressedState()
Definition: FlutterEmbedderKeyResponder.mm:798
-[FlutterKeyCallbackGuard pendTo:withId:]
void pendTo:withId:(nonnull NSMutableDictionary< NSNumber *, FlutterAsyncKeyCallback > *pendingResponses,[withId] uint64_t responseId)
Definition: FlutterEmbedderKeyResponder.mm:307
FlutterKeyCallbackGuard::handled
BOOL handled
Definition: FlutterEmbedderKeyResponder.mm:281
FlutterSendEmbedderKeyEvent
void(^ FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
Definition: FlutterEmbedderKeyResponder.h:13
FlutterEmbedderKeyResponder.h
flutter::kValueMask
const uint64_t kValueMask
Definition: KeyCodeMap.g.mm:22
FlutterAsyncKeyCallback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
Definition: FlutterKeyPrimaryResponder.h:10
-[FlutterKeyCallbackGuard resolveTo:]
void resolveTo:(BOOL handled)
Definition: FlutterEmbedderKeyResponder.mm:320
flutter::keyCodeToModifierFlag
const NSDictionary * keyCodeToModifierFlag
Definition: KeyCodeMap.g.mm:223
FlutterCodecs.h
FlutterKeyPrimaryResponder-p::layoutMap
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap
Definition: FlutterKeyPrimaryResponder.h:46
flutter::kCapsLockLogicalKey
const uint64_t kCapsLockLogicalKey
Definition: KeyCodeMap.g.mm:246
FlutterKeyPendingResponse::responder
FlutterEmbedderKeyResponder * responder
Definition: FlutterEmbedderKeyResponder.mm:255
FlutterViewController_Internal.h
FlutterKeyCallbackGuard::debugHandleSource
NSString * debugHandleSource
Definition: FlutterEmbedderKeyResponder.mm:289
KeyCodeMap_Internal.h
FlutterKeyCallbackGuard::sentAnyEvents
BOOL sentAnyEvents
Definition: FlutterEmbedderKeyResponder.mm:282
FlutterKeyCallbackGuard
Definition: FlutterEmbedderKeyResponder.mm:267
flutter::kMacosPlane
const uint64_t kMacosPlane
Definition: KeyCodeMap.g.mm:24
FlutterEmbedderKeyResponder
Definition: FlutterEmbedderKeyResponder.h:23
flutter::kUnicodePlane
const uint64_t kUnicodePlane
Definition: KeyCodeMap.g.mm:23
FlutterKeyPendingResponse::responseId
uint64_t responseId
Definition: FlutterEmbedderKeyResponder.mm:256
FlutterKeyPendingResponse
Definition: FlutterEmbedderKeyResponder.mm:254