Flutter Engine
The Flutter Engine
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
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
14namespace {
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 */
24static 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 */
33static 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 */
40static 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 */
54static 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 */
61static 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 */
72static 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 */
86static 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.
118static 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 */
148static 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) {
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 */
190static 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 */
201static 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 */
216void HandleResponse(bool handled, void* user_data);
217
218/**
219 * Converts NSEvent.characters to a C-string for FlutterKeyEvent.
220 */
221const 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
332@interface FlutterEmbedderKeyResponder ()
333
334/**
335 * The function to send converted events to.
336 *
337 * Set by the initializer.
338 */
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 layoutMap;
477
478- (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent {
479 self = [super init];
480 if (self != nil) {
481 _sendEvent = sendEvent;
482 _pressingRecords = [NSMutableDictionary dictionary];
483 _pendingResponses = [NSMutableDictionary dictionary];
484 _responseId = 1;
485 _lastModifierFlagsOfInterest = 0;
486 _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask();
487 }
488 return self;
489}
490
491- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
492 // The conversion algorithm relies on a non-nil callback to properly compute
493 // `synthesized`.
494 NSAssert(callback != nil, @"The callback must not be nil.");
495 FlutterKeyCallbackGuard* guardedCallback =
496 [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
497 switch (event.type) {
498 case NSEventTypeKeyDown:
499 [self handleDownEvent:event callback:guardedCallback];
500 break;
501 case NSEventTypeKeyUp:
502 [self handleUpEvent:event callback:guardedCallback];
503 break;
504 case NSEventTypeFlagsChanged:
505 [self handleFlagEvent:event callback:guardedCallback];
506 break;
507 default:
508 NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type));
509 }
510 NSAssert(guardedCallback.handled, @"The callback is returned without being handled.");
511 if (!guardedCallback.sentAnyEvents) {
512 FlutterKeyEvent flutterEvent = {
513 .struct_size = sizeof(FlutterKeyEvent),
514 .timestamp = 0,
516 .physical = 0,
517 .logical = 0,
518 .character = nil,
519 .synthesized = false,
520 };
521 _sendEvent(flutterEvent, nullptr, nullptr);
522 }
523 NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask),
524 @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx",
525 _lastModifierFlagsOfInterest, event.modifierFlags & _modifierFlagOfInterestMask);
526}
527
528#pragma mark - Private
529
530- (void)synchronizeModifiers:(NSUInteger)currentFlags
531 ignoringFlags:(NSUInteger)ignoringFlags
532 timestamp:(NSTimeInterval)timestamp
533 guard:(FlutterKeyCallbackGuard*)guard {
534 const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags;
535 const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask;
536 const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask;
537 NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest;
538 if (flagDifference & NSEventModifierFlagCapsLock) {
539 [self sendCapsLockTapWithTimestamp:timestamp synthesizeDown:true callback:guard];
540 flagDifference = flagDifference & ~NSEventModifierFlagCapsLock;
541 }
542 while (true) {
543 const NSUInteger currentFlag = lowestSetBit(flagDifference);
544 if (currentFlag == 0) {
545 break;
546 }
547 flagDifference = flagDifference & ~currentFlag;
548 NSNumber* keyCode = [flutter::modifierFlagToKeyCode objectForKey:@(currentFlag)];
549 NSAssert(keyCode != nil, @"Invalid modifier flag 0x%lx", currentFlag);
550 if (keyCode == nil) {
551 continue;
552 }
553 BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0;
554 [self sendModifierEventOfType:isDownEvent
555 timestamp:timestamp
556 keyCode:[keyCode unsignedShortValue]
557 synthesized:true
558 callback:guard];
559 }
560 _lastModifierFlagsOfInterest =
561 (_lastModifierFlagsOfInterest & ~updatingMask) | currentFlagsOfInterest;
562}
563
564- (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey {
565 if (logicalKey == 0) {
566 [_pressingRecords removeObjectForKey:@(physicalKey)];
567 } else {
568 _pressingRecords[@(physicalKey)] = @(logicalKey);
569 }
570}
571
572- (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
573 callback:(FlutterKeyCallbackGuard*)callback {
574 _responseId += 1;
575 uint64_t responseId = _responseId;
576 // The `pending` is released in `HandleResponse`.
578 [callback pendTo:_pendingResponses withId:responseId];
579 _sendEvent(event, HandleResponse, pending);
580 callback.sentAnyEvents = TRUE;
581}
582
583- (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event
584 guard:(FlutterKeyCallbackGuard*)guard {
585 _sendEvent(event, nullptr, nullptr);
586 guard.sentAnyEvents = TRUE;
587}
588
589- (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp
590 synthesizeDown:(bool)synthesizeDown
591 callback:(FlutterKeyCallbackGuard*)callback {
592 // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on
593 // even taps and odd taps. A CapsLock down or CapsLock up should always be
594 // converted to a down *and* an up, and the up should always be a synthesized
595 // event, since the FlutterEmbedderKeyResponder will never know when the
596 // button is released.
597 FlutterKeyEvent flutterEvent = {
598 .struct_size = sizeof(FlutterKeyEvent),
599 .timestamp = GetFlutterTimestampFrom(timestamp),
603 .character = nil,
604 .synthesized = synthesizeDown,
605 };
606 if (!synthesizeDown) {
607 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
608 } else {
609 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
610 }
611
612 flutterEvent.type = kFlutterKeyEventTypeUp;
613 flutterEvent.synthesized = true;
614 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
615}
616
617- (void)sendModifierEventOfType:(BOOL)isDownEvent
618 timestamp:(NSTimeInterval)timestamp
619 keyCode:(unsigned short)keyCode
620 synthesized:(bool)synthesized
621 callback:(FlutterKeyCallbackGuard*)callback {
622 uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);
623 uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);
624 if (physicalKey == 0 || logicalKey == 0) {
625 NSLog(@"Unrecognized modifier key: keyCode 0x%hx, physical key 0x%llx", keyCode, physicalKey);
626 [callback resolveTo:TRUE];
627 return;
628 }
629 FlutterKeyEvent flutterEvent = {
630 .struct_size = sizeof(FlutterKeyEvent),
631 .timestamp = GetFlutterTimestampFrom(timestamp),
632 .type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp,
633 .physical = physicalKey,
634 .logical = logicalKey,
635 .character = nil,
636 .synthesized = synthesized,
637 };
638 [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];
639 if (!synthesized) {
640 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
641 } else {
642 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
643 }
644}
645
646- (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
647 uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
648 NSNumber* logicalKeyFromMap = self.layoutMap[@(event.keyCode)];
649 uint64_t logicalKey = logicalKeyFromMap != nil ? [logicalKeyFromMap unsignedLongLongValue]
650 : GetLogicalKeyForEvent(event, physicalKey);
651 [self synchronizeModifiers:event.modifierFlags
652 ignoringFlags:0
653 timestamp:event.timestamp
654 guard:callback];
655
656 bool isARepeat = event.isARepeat;
657 NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
658 if (pressedLogicalKey != nil && !isARepeat) {
659 // This might happen in add-to-app scenarios if the focus is changed
660 // from the native view to the Flutter view amid the key tap.
661 //
662 // This might also happen when a key event is forged (such as by an
663 // IME) using the same keyCode as an unreleased key. See
664 // https://github.com/flutter/flutter/issues/82673#issuecomment-988661079
665 FlutterKeyEvent flutterEvent = {
666 .struct_size = sizeof(FlutterKeyEvent),
667 .timestamp = GetFlutterTimestampFrom(event.timestamp),
669 .physical = physicalKey,
670 .logical = [pressedLogicalKey unsignedLongLongValue],
671 .character = nil,
672 .synthesized = true,
673 };
674 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
675 pressedLogicalKey = nil;
676 }
677
678 if (pressedLogicalKey == nil) {
679 [self updateKey:physicalKey asPressed:logicalKey];
680 }
681
682 FlutterKeyEvent flutterEvent = {
683 .struct_size = sizeof(FlutterKeyEvent),
684 .timestamp = GetFlutterTimestampFrom(event.timestamp),
685 .type = pressedLogicalKey == nil ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeRepeat,
686 .physical = physicalKey,
687 .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],
688 .character = getEventString(event.characters),
689 .synthesized = false,
690 };
691 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
692}
693
694- (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
695 NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@",
696 event.keyCode, event.characters, event.charactersIgnoringModifiers);
697 [self synchronizeModifiers:event.modifierFlags
698 ignoringFlags:0
699 timestamp:event.timestamp
700 guard:callback];
701
702 uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
703 NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
704 if (pressedLogicalKey == nil) {
705 // Normally the key up events won't be missed since macOS always sends the
706 // key up event to the window where the corresponding key down occurred.
707 // However this might happen in add-to-app scenarios if the focus is changed
708 // from the native view to the Flutter view amid the key tap.
709 [callback resolveTo:TRUE];
710 return;
711 }
712 [self updateKey:physicalKey asPressed:0];
713
714 FlutterKeyEvent flutterEvent = {
715 .struct_size = sizeof(FlutterKeyEvent),
716 .timestamp = GetFlutterTimestampFrom(event.timestamp),
718 .physical = physicalKey,
719 .logical = [pressedLogicalKey unsignedLongLongValue],
720 .character = nil,
721 .synthesized = false,
722 };
723 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
724}
725
726- (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
727 [self synchronizeModifiers:event.modifierFlags
728 ignoringFlags:NSEventModifierFlagCapsLock
729 timestamp:event.timestamp
730 guard:callback];
731 if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) !=
732 (event.modifierFlags & NSEventModifierFlagCapsLock)) {
733 [self sendCapsLockTapWithTimestamp:event.timestamp synthesizeDown:false callback:callback];
734 _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock;
735 } else {
736 [callback resolveTo:TRUE];
737 }
738}
739
740- (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
741 NSNumber* targetModifierFlagObj = flutter::keyCodeToModifierFlag[@(event.keyCode)];
742 NSUInteger targetModifierFlag =
743 targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue];
744 uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode);
745 if (targetKey == flutter::kCapsLockPhysicalKey) {
746 return [self handleCapsLockEvent:event callback:callback];
747 }
748
749 [self synchronizeModifiers:event.modifierFlags
750 ignoringFlags:targetModifierFlag
751 timestamp:event.timestamp
752 guard:callback];
753
754 NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)];
755 BOOL lastTargetPressed = pressedLogicalKey != nil;
756 NSAssert(targetModifierFlagObj == nil ||
757 (_lastModifierFlagsOfInterest & targetModifierFlag) != 0 == lastTargetPressed,
758 @"Desynchronized state between lastModifierFlagsOfInterest (0x%lx) on bit 0x%lx "
759 @"for keyCode 0x%hx, whose pressing state is %@.",
760 _lastModifierFlagsOfInterest, targetModifierFlag, event.keyCode,
761 lastTargetPressed
762 ? [NSString stringWithFormat:@"0x%llx", [pressedLogicalKey unsignedLongLongValue]]
763 : @"empty");
764
765 BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0;
766 if (lastTargetPressed == shouldBePressed) {
767 [callback resolveTo:TRUE];
768 return;
769 }
770 _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ targetModifierFlag;
771 [self sendModifierEventOfType:shouldBePressed
772 timestamp:event.timestamp
773 keyCode:event.keyCode
774 synthesized:false
775 callback:callback];
776}
777
778- (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId {
779 FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)];
780 callback(handled);
781 [_pendingResponses removeObjectForKey:@(responseId)];
782}
783
784- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
785 timestamp:(NSTimeInterval)timestamp {
786 FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
787 // Do nothing.
788 };
789 FlutterKeyCallbackGuard* guardedCallback =
790 [[FlutterKeyCallbackGuard alloc] initWithCallback:replyCallback];
791 [self synchronizeModifiers:modifierFlags
792 ignoringFlags:0
793 timestamp:timestamp
794 guard:guardedCallback];
795}
796
797- (nonnull NSDictionary*)getPressedState {
798 return [NSDictionary dictionaryWithDictionary:_pressingRecords];
799}
800@end
801
802namespace {
803void HandleResponse(bool handled, void* user_data) {
804 // Use unique_ptr to release on leaving.
805 auto pending = std::unique_ptr<FlutterKeyPendingResponse>(
806 reinterpret_cast<FlutterKeyPendingResponse*>(user_data));
807 [pending->responder handleResponse:handled forId:pending->responseId];
808}
809} // namespace
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
Definition: SkSwizzler.cpp:31
GLenum type
@ kFlutterKeyEventTypeDown
Definition: embedder.h:1076
@ kFlutterKeyEventTypeUp
Definition: embedder.h:1075
@ kFlutterKeyEventTypeRepeat
Definition: embedder.h:1077
FlutterSemanticsFlag flag
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event
GAsyncResult * result
uint32_t * target
#define FML_DCHECK(condition)
Definition: logging.h:103
NSMutableDictionary< NSNumber *, NSNumber * > * pressingRecords
NSMutableDictionary< NSNumber *, FlutterAsyncKeyCallback > * pendingResponses
void pendTo:withId:(nonnull NSMutableDictionary< NSNumber *, FlutterAsyncKeyCallback > *pendingResponses,[withId] uint64_t responseId)
FlutterEmbedderKeyResponder * responder
void(^ FlutterAsyncKeyCallback)(BOOL handled)
size_t length
void(^ FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
return FALSE
static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane)
static uint64_t toLower(uint64_t n)
static double GetFlutterTimestampFrom(NSTimeInterval timestamp)
static NSUInteger lowestSetBit(NSUInteger bitmask)
static NSUInteger computeModifierFlagOfInterestMask()
static uint64_t GetPhysicalKeyForKeyCode(UInt32 keyCode)
static bool IsControlCharacter(NSUInteger length, NSString *label)
void HandleResponse(bool handled, void *user_data)
static uint64_t GetLogicalKeyForEvent(FlutterUIPressProxy *press, NSNumber *maybeSpecialKey) API_AVAILABLE(ios(13.4))
static uint64_t GetLogicalKeyForModifier(UInt32 keyCode, uint64_t hidCode)
static bool IsUnprintableKey(NSUInteger length, NSString *label)
const NSDictionary * keyCodeToLogicalKey
const uint64_t kUnicodePlane
Definition: KeyCodeMap.g.mm:23
const NSDictionary * keyCodeToPhysicalKey
Definition: KeyCodeMap.g.mm:26
const NSDictionary * keyCodeToModifierFlag
const uint64_t kValueMask
Definition: KeyCodeMap.g.mm:22
const uint64_t kCapsLockLogicalKey
const uint64_t kMacosPlane
Definition: KeyCodeMap.g.mm:24
const uint64_t kCapsLockPhysicalKey
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap
size_t struct_size
The size of this struct. Must be sizeof(FlutterKeyEvent).
Definition: embedder.h:1112
FlutterKeyEventType type
The event kind.
Definition: embedder.h:1118
void * user_data
int BOOL
Definition: windows_types.h:37