| 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
8 #import "FlutterEmbedderKeyResponder.h"
9 #import "KeyCodeMap_Internal.h"
10 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
11 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_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 NSCAssert(!IsControlCharacter(keyLabelChar) && !IsUnprintableKey(keyLabelChar),
171 @"Unexpected control or unprintable keylabel 0x%x", keyLabelChar);
172 NSCAssert(keyLabelChar <= 0x10FFFF, @"Out of range keylabel 0x%x", keyLabelChar);
173 character = keyLabelChar;
174 }
175 delete[] keyLabel;
176 }
177 if (character != 0) {
178 return KeyOfPlane(toLower(character), flutter::kUnicodePlane);
179 }
180
181 // We can't represent this key with a single printable unicode, so a new code
182 // is minted to the macOS plane.
183 return KeyOfPlane(event.keyCode, flutter::kMacosPlane);
184 }
185
186 /**
187 * Converts NSEvent.timestamp to the timestamp for Flutter.
188 */
189 static double GetFlutterTimestampFrom(NSTimeInterval timestamp) {
190 // Timestamp in microseconds. The event.timestamp is in seconds with sub-ms precision.
191 return timestamp * 1000000.0;
192 }
193
194 /**
195 * Compute |modifierFlagOfInterestMask| out of |keyCodeToModifierFlag|.
196 *
197 * This is equal to the bitwise-or of all values of |keyCodeToModifierFlag| as
198 * well as NSEventModifierFlagCapsLock.
199 */
200 static NSUInteger computeModifierFlagOfInterestMask() {
201 __block NSUInteger modifierFlagOfInterestMask = NSEventModifierFlagCapsLock;
202 [flutter::keyCodeToModifierFlag
203 enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL* stop) {
204 modifierFlagOfInterestMask = modifierFlagOfInterestMask | [flag unsignedLongValue];
205 }];
206 return modifierFlagOfInterestMask;
207 }
208
209 /**
210 * The C-function sent to the embedder's |SendKeyEvent|, wrapping
211 * |FlutterEmbedderKeyResponder.handleResponse|.
212 *
213 * For the reason of this wrap, see |FlutterKeyPendingResponse|.
214 */
215 void HandleResponse(bool handled, void* user_data);
216
217 /**
218 * Converts NSEvent.characters to a C-string for FlutterKeyEvent.
219 */
220 const char* getEventString(NSString* characters) {
221 if ([characters length] == 0) {
222 return nullptr;
223 }
224 unichar utf16Code = [characters characterAtIndex:0];
225 if (utf16Code >= 0xf700 && utf16Code <= 0xf7ff) {
226 // Some function keys are assigned characters with codepoints from the
227 // private use area. These characters are filtered out since they're
228 // unprintable.
229 //
230 // The official documentation reserves 0xF700-0xF8FF as private use area
231 // (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc).
232 // But macOS seems to only use a reduced range of it. The official doc
233 // defines a few constants, all of which are within 0xF700-0xF747.
234 // (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc).
235 // This mostly aligns with the experimentation result, except for 0xF8FF,
236 // which is used for the "Apple logo" character (Option-Shift-K on a US
237 // keyboard.)
238 //
239 // Assume that non-printable function keys are defined from
240 // 0xF700 upwards, and printable private keys are defined from 0xF8FF
241 // downwards. This function filters out 0xF700-0xF7FF in order to keep
242 // the printable private keys.
243 return nullptr;
244 }
245 return [characters UTF8String];
246 }
247 } // namespace
248
249 /**
250 * The invocation context for |HandleResponse|, wrapping
251 * |FlutterEmbedderKeyResponder.handleResponse|.
252 */
253 struct FlutterKeyPendingResponse {
254 FlutterEmbedderKeyResponder* responder;
255 uint64_t responseId;
256 };
257
258 /**
259 * Guards a |FlutterAsyncKeyCallback| to make sure it's handled exactly once
260 * throughout |FlutterEmbedderKeyResponder.handleEvent|.
261 *
262 * A callback can either be handled with |pendTo:withId:|, or with |resolveTo:|.
263 * Either way, the callback cannot be handled again, or an assertion will be
264 * thrown.
265 */
266 @interface FlutterKeyCallbackGuard : NSObject
267 - (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback;
268
269 /**
270 * Handle the callback by storing it to pending responses.
271 */
272 - (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses
273 withId:(uint64_t)responseId;
274
275 /**
276 * Handle the callback by calling it with a result.
277 */
278 - (void)resolveTo:(BOOL)handled;
279
280 @property(nonatomic) BOOL handled;
281 @property(nonatomic) BOOL sentAnyEvents;
282 /**
283 * A string indicating how the callback is handled.
284 *
285 * Only set in debug mode. Nil in release mode, or if the callback has not been
286 * handled.
287 */
288 @property(nonatomic, copy) NSString* debugHandleSource;
289 @end
290
291 @implementation FlutterKeyCallbackGuard {
292 // The callback is declared in the implemnetation block to avoid being
293 // accessed directly.
294 FlutterAsyncKeyCallback _callback;
295 }
296 - (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback {
297 self = [super init];
298 if (self != nil) {
299 _callback = callback;
300 _handled = FALSE;
301 _sentAnyEvents = FALSE;
302 }
303 return self;
304 }
305
306 - (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses
307 withId:(uint64_t)responseId {
308 NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource);
309 if (_handled) {
310 return;
311 }
312 pendingResponses[@(responseId)] = _callback;
313 _handled = TRUE;
314 NSAssert(
315 ((_debugHandleSource = [NSString stringWithFormat:@"pending event %llu", responseId]), TRUE),
316 @"");
317 }
318
319 - (void)resolveTo:(BOOL)handled {
320 NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource);
321 if (_handled) {
322 return;
323 }
324 _callback(handled);
325 _handled = TRUE;
326 NSAssert(((_debugHandleSource = [NSString stringWithFormat:@"resolved with %d", _handled]), TRUE),
327 @"");
328 }
329 @end
330
331 @interface FlutterEmbedderKeyResponder ()
332
333 /**
334 * The function to send converted events to.
335 *
336 * Set by the initializer.
337 */
338 @property(nonatomic, copy) FlutterSendEmbedderKeyEvent sendEvent;
339
340 /**
341 * A map of presessd keys.
342 *
343 * The keys of the dictionary are physical keys, while the values are the logical keys
344 * of the key down event.
345 */
346 @property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>* pressingRecords;
347
348 /**
349 * A constant mask for NSEvent.modifierFlags that Flutter synchronizes with.
350 *
351 * Flutter keeps track of the last |modifierFlags| and compares it with the
352 * incoming one. Any bit within |modifierFlagOfInterestMask| that is different
353 * (except for the one that corresponds to the event key) indicates that an
354 * event for this modifier was missed, and Flutter synthesizes an event to make
355 * up for the state difference.
356 *
357 * It is computed by computeModifierFlagOfInterestMask.
358 */
359 @property(nonatomic) NSUInteger modifierFlagOfInterestMask;
360
361 /**
362 * The modifier flags of the last received key event, excluding uninterested
363 * bits.
364 *
365 * This should be kept synchronized with the last |NSEvent.modifierFlags|
366 * after masking with |modifierFlagOfInterestMask|. This should also be kept
367 * synchronized with the corresponding keys of |pressingRecords|.
368 *
369 * This is used by |synchronizeModifiers| to quickly find
370 * out modifier keys that are desynchronized.
371 */
372 @property(nonatomic) NSUInteger lastModifierFlagsOfInterest;
373
374 /**
375 * A self-incrementing ID used to label key events sent to the framework.
376 */
377 @property(nonatomic) uint64_t responseId;
378
379 /**
380 * A map of unresponded key events sent to the framework.
381 *
382 * Its values are |responseId|s, and keys are the callback that was received
383 * along with the event.
384 */
385 @property(nonatomic) NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>* pendingResponses;
386
387 /**
388 * Compare the last modifier flags and the current, and dispatch synthesized
389 * key events for each different modifier flag bit.
390 *
391 * The flags compared are all flags after masking with
392 * |modifierFlagOfInterestMask| and excluding |ignoringFlags|.
393 *
394 * The |guard| is basically a regular guarded callback, but instead of being
395 * called, it is only used to record whether an event is sent.
396 */
397 - (void)synchronizeModifiers:(NSUInteger)currentFlags
398 ignoringFlags:(NSUInteger)ignoringFlags
399 timestamp:(NSTimeInterval)timestamp
400 guard:(nonnull FlutterKeyCallbackGuard*)guard;
401
402 /**
403 * Update the pressing state.
404 *
405 * If `logicalKey` is not 0, `physicalKey` is pressed as `logicalKey`.
406 * Otherwise, `physicalKey` is released.
407 */
408 - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey;
409
410 /**
411 * Send an event to the framework, expecting its response.
412 */
413 - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
414 callback:(nonnull FlutterKeyCallbackGuard*)callback;
415
416 /**
417 * Send a synthesized key event, never expecting its event result.
418 *
419 * The |guard| is basically a regular guarded callback, but instead of being
420 * called, it is only used to record whether an event is sent.
421 */
422 - (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event
423 guard:(FlutterKeyCallbackGuard*)guard;
424
425 /**
426 * Send a CapsLock down event, then a CapsLock up event.
427 *
428 * If synthesizeDown is TRUE, then both events will be synthesized. Otherwise,
429 * the callback will be used as the callback for the down event, which is not
430 * synthesized, while the up event will always be synthesized.
431 */
432 - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp
433 synthesizeDown:(bool)synthesizeDown
434 callback:(nonnull FlutterKeyCallbackGuard*)callback;
435
436 /**
437 * Send a key event for a modifier key.
438 */
439 - (void)sendModifierEventOfType:(BOOL)isDownEvent
440 timestamp:(NSTimeInterval)timestamp
441 keyCode:(unsigned short)keyCode
442 synthesized:(bool)synthesized
443 callback:(nonnull FlutterKeyCallbackGuard*)callback;
444
445 /**
446 * Processes a down event from the system.
447 */
448 - (void)handleDownEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
449
450 /**
451 * Processes an up event from the system.
452 */
453 - (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
454
455 /**
456 * Processes an event from the system for the CapsLock key.
457 */
458 - (void)handleCapsLockEvent:(nonnull NSEvent*)event
459 callback:(nonnull FlutterKeyCallbackGuard*)callback;
460
461 /**
462 * Processes a flags changed event from the system, where modifier keys are pressed or released.
463 */
464 - (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
465
466 /**
467 * Processes the response from the framework.
468 */
469 - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId;
470
471 @end
472
473 @implementation FlutterEmbedderKeyResponder
474
475 @synthesize layoutMap;
476
477 - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent {
478 self = [super init];
479 if (self != nil) {
480 _sendEvent = sendEvent;
481 _pressingRecords = [NSMutableDictionary dictionary];
482 _pendingResponses = [NSMutableDictionary dictionary];
483 _responseId = 1;
484 _lastModifierFlagsOfInterest = 0;
485 _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask();
486 }
487 return self;
488 }
489
490 - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
491 // The conversion algorithm relies on a non-nil callback to properly compute
492 // `synthesized`.
493 NSAssert(callback != nil, @"The callback must not be nil.");
494 FlutterKeyCallbackGuard* guardedCallback =
495 [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
496 switch (event.type) {
497 case NSEventTypeKeyDown:
498 [self handleDownEvent:event callback:guardedCallback];
499 break;
500 case NSEventTypeKeyUp:
501 [self handleUpEvent:event callback:guardedCallback];
502 break;
503 case NSEventTypeFlagsChanged:
504 [self handleFlagEvent:event callback:guardedCallback];
505 break;
506 default:
507 NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type));
508 }
509 NSAssert(guardedCallback.handled, @"The callback is returned without being handled.");
510 if (!guardedCallback.sentAnyEvents) {
511 FlutterKeyEvent flutterEvent = {
512 .struct_size = sizeof(FlutterKeyEvent),
513 .timestamp = 0,
514 .type = kFlutterKeyEventTypeDown,
515 .physical = 0,
516 .logical = 0,
517 .character = nil,
518 .synthesized = false,
519 };
520 _sendEvent(flutterEvent, nullptr, nullptr);
521 }
522 NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask),
523 @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx",
524 _lastModifierFlagsOfInterest, event.modifierFlags & _modifierFlagOfInterestMask);
525 }
526
527 #pragma mark - Private
528
529 - (void)synchronizeModifiers:(NSUInteger)currentFlags
530 ignoringFlags:(NSUInteger)ignoringFlags
531 timestamp:(NSTimeInterval)timestamp
532 guard:(FlutterKeyCallbackGuard*)guard {
533 const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags;
534 const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask;
535 const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask;
536 NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest;
537 if (flagDifference & NSEventModifierFlagCapsLock) {
538 [self sendCapsLockTapWithTimestamp:timestamp synthesizeDown:true callback:guard];
539 flagDifference = flagDifference & ~NSEventModifierFlagCapsLock;
540 }
541 while (true) {
542 const NSUInteger currentFlag = lowestSetBit(flagDifference);
543 if (currentFlag == 0) {
544 break;
545 }
546 flagDifference = flagDifference & ~currentFlag;
547 NSNumber* keyCode = [flutter::modifierFlagToKeyCode objectForKey:@(currentFlag)];
548 NSAssert(keyCode != nil, @"Invalid modifier flag 0x%lx", currentFlag);
549 if (keyCode == nil) {
550 continue;
551 }
552 BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0;
553 [self sendModifierEventOfType:isDownEvent
554 timestamp:timestamp
555 keyCode:[keyCode unsignedShortValue]
556 synthesized:true
557 callback:guard];
558 }
559 _lastModifierFlagsOfInterest =
560 (_lastModifierFlagsOfInterest & ~updatingMask) | currentFlagsOfInterest;
561 }
562
563 - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey {
564 if (logicalKey == 0) {
565 [_pressingRecords removeObjectForKey:@(physicalKey)];
566 } else {
567 _pressingRecords[@(physicalKey)] = @(logicalKey);
568 }
569 }
570
571 - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
572 callback:(FlutterKeyCallbackGuard*)callback {
573 _responseId += 1;
574 uint64_t responseId = _responseId;
575 // The `pending` is released in `HandleResponse`.
576 FlutterKeyPendingResponse* pending = new FlutterKeyPendingResponse{self, responseId};
577 [callback pendTo:_pendingResponses withId:responseId];
578 _sendEvent(event, HandleResponse, pending);
579 callback.sentAnyEvents = TRUE;
580 }
581
582 - (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event
583 guard:(FlutterKeyCallbackGuard*)guard {
584 _sendEvent(event, nullptr, nullptr);
585 guard.sentAnyEvents = TRUE;
586 }
587
588 - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp
589 synthesizeDown:(bool)synthesizeDown
590 callback:(FlutterKeyCallbackGuard*)callback {
591 // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on
592 // even taps and odd taps. A CapsLock down or CapsLock up should always be
593 // converted to a down *and* an up, and the up should always be a synthesized
594 // event, since the FlutterEmbedderKeyResponder will never know when the
595 // button is released.
596 FlutterKeyEvent flutterEvent = {
597 .struct_size = sizeof(FlutterKeyEvent),
598 .timestamp = GetFlutterTimestampFrom(timestamp),
599 .type = kFlutterKeyEventTypeDown,
600 .physical = flutter::kCapsLockPhysicalKey,
601 .logical = flutter::kCapsLockLogicalKey,
602 .character = nil,
603 .synthesized = synthesizeDown,
604 };
605 if (!synthesizeDown) {
606 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
607 } else {
608 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
609 }
610
611 flutterEvent.type = kFlutterKeyEventTypeUp;
612 flutterEvent.synthesized = true;
613 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
614 }
615
616 - (void)sendModifierEventOfType:(BOOL)isDownEvent
617 timestamp:(NSTimeInterval)timestamp
618 keyCode:(unsigned short)keyCode
619 synthesized:(bool)synthesized
620 callback:(FlutterKeyCallbackGuard*)callback {
621 uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);
622 uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);
623 if (physicalKey == 0 || logicalKey == 0) {
624 NSLog(@"Unrecognized modifier key: keyCode 0x%hx, physical key 0x%llx", keyCode, physicalKey);
625 [callback resolveTo:TRUE];
626 return;
627 }
628 FlutterKeyEvent flutterEvent = {
629 .struct_size = sizeof(FlutterKeyEvent),
630 .timestamp = GetFlutterTimestampFrom(timestamp),
631 .type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp,
632 .physical = physicalKey,
633 .logical = logicalKey,
634 .character = nil,
635 .synthesized = synthesized,
636 };
637 [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];
638 if (!synthesized) {
639 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
640 } else {
641 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
642 }
643 }
644
645 - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
646 uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
647 NSNumber* logicalKeyFromMap = self.layoutMap[@(event.keyCode)];
648 uint64_t logicalKey = logicalKeyFromMap != nil ? [logicalKeyFromMap unsignedLongLongValue]
649 : GetLogicalKeyForEvent(event, physicalKey);
650 [self synchronizeModifiers:event.modifierFlags
651 ignoringFlags:0
652 timestamp:event.timestamp
653 guard:callback];
654
655 bool isARepeat = event.isARepeat;
656 NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
657 if (pressedLogicalKey != nil && !isARepeat) {
658 // This might happen in add-to-app scenarios if the focus is changed
659 // from the native view to the Flutter view amid the key tap.
660 //
661 // This might also happen when a key event is forged (such as by an
662 // IME) using the same keyCode as an unreleased key. See
663 // https://github.com/flutter/flutter/issues/82673#issuecomment-988661079
664 FlutterKeyEvent flutterEvent = {
665 .struct_size = sizeof(FlutterKeyEvent),
666 .timestamp = GetFlutterTimestampFrom(event.timestamp),
667 .type = kFlutterKeyEventTypeUp,
668 .physical = physicalKey,
669 .logical = [pressedLogicalKey unsignedLongLongValue],
670 .character = nil,
671 .synthesized = true,
672 };
673 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
674 pressedLogicalKey = nil;
675 }
676
677 if (pressedLogicalKey == nil) {
678 [self updateKey:physicalKey asPressed:logicalKey];
679 }
680
681 FlutterKeyEvent flutterEvent = {
682 .struct_size = sizeof(FlutterKeyEvent),
683 .timestamp = GetFlutterTimestampFrom(event.timestamp),
684 .type = pressedLogicalKey == nil ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeRepeat,
685 .physical = physicalKey,
686 .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],
687 .character = getEventString(event.characters),
688 .synthesized = false,
689 };
690 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
691 }
692
693 - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
694 NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@",
695 event.keyCode, event.characters, event.charactersIgnoringModifiers);
696 [self synchronizeModifiers:event.modifierFlags
697 ignoringFlags:0
698 timestamp:event.timestamp
699 guard:callback];
700
701 uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
702 NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
703 if (pressedLogicalKey == nil) {
704 // Normally the key up events won't be missed since macOS always sends the
705 // key up event to the window where the corresponding key down occurred.
706 // However this might happen in add-to-app scenarios if the focus is changed
707 // from the native view to the Flutter view amid the key tap.
708 [callback resolveTo:TRUE];
709 return;
710 }
711 [self updateKey:physicalKey asPressed:0];
712
713 FlutterKeyEvent flutterEvent = {
714 .struct_size = sizeof(FlutterKeyEvent),
715 .timestamp = GetFlutterTimestampFrom(event.timestamp),
716 .type = kFlutterKeyEventTypeUp,
717 .physical = physicalKey,
718 .logical = [pressedLogicalKey unsignedLongLongValue],
719 .character = nil,
720 .synthesized = false,
721 };
722 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
723 }
724
725 - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
726 [self synchronizeModifiers:event.modifierFlags
727 ignoringFlags:NSEventModifierFlagCapsLock
728 timestamp:event.timestamp
729 guard:callback];
730 if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) !=
731 (event.modifierFlags & NSEventModifierFlagCapsLock)) {
732 [self sendCapsLockTapWithTimestamp:event.timestamp synthesizeDown:false callback:callback];
733 _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock;
734 } else {
735 [callback resolveTo:TRUE];
736 }
737 }
738
739 - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
740 NSNumber* targetModifierFlagObj = flutter::keyCodeToModifierFlag[@(event.keyCode)];
741 NSUInteger targetModifierFlag =
742 targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue];
743 uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode);
744 if (targetKey == flutter::kCapsLockPhysicalKey) {
745 return [self handleCapsLockEvent:event callback:callback];
746 }
747
748 [self synchronizeModifiers:event.modifierFlags
749 ignoringFlags:targetModifierFlag
750 timestamp:event.timestamp
751 guard:callback];
752
753 NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)];
754 BOOL lastTargetPressed = pressedLogicalKey != nil;
755 NSAssert(targetModifierFlagObj == nil ||
756 (_lastModifierFlagsOfInterest & targetModifierFlag) != 0 == lastTargetPressed,
757 @"Desynchronized state between lastModifierFlagsOfInterest (0x%lx) on bit 0x%lx "
758 @"for keyCode 0x%hx, whose pressing state is %@.",
759 _lastModifierFlagsOfInterest, targetModifierFlag, event.keyCode,
760 lastTargetPressed
761 ? [NSString stringWithFormat:@"0x%llx", [pressedLogicalKey unsignedLongLongValue]]
762 : @"empty");
763
764 BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0;
765 if (lastTargetPressed == shouldBePressed) {
766 [callback resolveTo:TRUE];
767 return;
768 }
769 _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ targetModifierFlag;
770 [self sendModifierEventOfType:shouldBePressed
771 timestamp:event.timestamp
772 keyCode:event.keyCode
773 synthesized:false
774 callback:callback];
775 }
776
777 - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId {
778 FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)];
779 callback(handled);
780 [_pendingResponses removeObjectForKey:@(responseId)];
781 }
782
783 - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
784 timestamp:(NSTimeInterval)timestamp {
785 FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
786 // Do nothing.
787 };
788 FlutterKeyCallbackGuard* guardedCallback =
789 [[FlutterKeyCallbackGuard alloc] initWithCallback:replyCallback];
790 [self synchronizeModifiers:modifierFlags
791 ignoringFlags:0
792 timestamp:timestamp
793 guard:guardedCallback];
794 }
795
796 - (nonnull NSDictionary*)getPressedState {
797 return [NSDictionary dictionaryWithDictionary:_pressingRecords];
798 }
799 @end
800
801 namespace {
802 void HandleResponse(bool handled, void* user_data) {
803 // Use unique_ptr to release on leaving.
804 auto pending = std::unique_ptr<FlutterKeyPendingResponse>(
805 reinterpret_cast<FlutterKeyPendingResponse*>(user_data));
806 [pending->responder handleResponse:handled forId:pending->responseId];
807 }
808 } // 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:796
-[FlutterKeyCallbackGuard pendTo:withId:]
void pendTo:withId:(nonnull NSMutableDictionary< NSNumber *, FlutterAsyncKeyCallback > *pendingResponses,[withId] uint64_t responseId)
Definition: FlutterEmbedderKeyResponder.mm:306
FlutterKeyCallbackGuard::handled
BOOL handled
Definition: FlutterEmbedderKeyResponder.mm:280
FlutterEmbedderKeyResponder.h
flutter::kValueMask
const uint64_t kValueMask
Definition: KeyCodeMap.g.mm:22
FlutterAsyncKeyCallback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
Definition: FlutterKeyPrimaryResponder.h:7
-[FlutterKeyCallbackGuard resolveTo:]
void resolveTo:(BOOL handled)
Definition: FlutterEmbedderKeyResponder.mm:319
flutter::keyCodeToModifierFlag
const NSDictionary * keyCodeToModifierFlag
Definition: KeyCodeMap.g.mm:223
FlutterCodecs.h
FlutterKeyPrimaryResponder-p::layoutMap
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap
Definition: FlutterKeyPrimaryResponder.h:43
flutter::kCapsLockLogicalKey
const uint64_t kCapsLockLogicalKey
Definition: KeyCodeMap.g.mm:246
FlutterKeyPendingResponse::responder
FlutterEmbedderKeyResponder * responder
Definition: FlutterEmbedderKeyResponder.mm:254
FlutterViewController_Internal.h
FlutterKeyCallbackGuard::debugHandleSource
NSString * debugHandleSource
Definition: FlutterEmbedderKeyResponder.mm:288
KeyCodeMap_Internal.h
FlutterKeyCallbackGuard::sentAnyEvents
BOOL sentAnyEvents
Definition: FlutterEmbedderKeyResponder.mm:281
FlutterKeyCallbackGuard
Definition: FlutterEmbedderKeyResponder.mm:266
flutter::kMacosPlane
const uint64_t kMacosPlane
Definition: KeyCodeMap.g.mm:24
FlutterSendEmbedderKeyEvent
void(^ FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, _Nullable _VoidPtr)
Definition: FlutterEmbedderKeyResponder.h:14
FlutterEmbedderKeyResponder
Definition: FlutterEmbedderKeyResponder.h:24
flutter::kUnicodePlane
const uint64_t kUnicodePlane
Definition: KeyCodeMap.g.mm:23
FlutterKeyPendingResponse::responseId
uint64_t responseId
Definition: FlutterEmbedderKeyResponder.mm:255
FlutterKeyPendingResponse
Definition: FlutterEmbedderKeyResponder.mm:253