blob: 101dfe2f7b93d31510df84e5ddbed2e044ff138c [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <objc/message.h>
#import "FlutterEmbedderKeyResponder.h"
#import "KeyCodeMap_Internal.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
#import "flutter/shell/platform/embedder/embedder.h"
namespace {
/**
* Isolate the least significant 1-bit.
*
* For example,
*
* * lowestSetBit(0x1010) returns 0x10.
* * lowestSetBit(0) returns 0.
*/
static NSUInteger lowestSetBit(NSUInteger bitmask) {
// This utilizes property of two's complement (negation), which propagates a
// carry bit from LSB to the lowest set bit.
return bitmask & -bitmask;
}
/**
* Whether a string represents a control character.
*/
static bool IsControlCharacter(uint64_t character) {
return (character <= 0x1f && character >= 0x00) || (character >= 0x7f && character <= 0x9f);
}
/**
* Whether a string represents an unprintable key.
*/
static bool IsUnprintableKey(uint64_t character) {
return character >= 0xF700 && character <= 0xF8FF;
}
/**
* Returns a key code composed with a base key and a plane.
*
* Examples of unprintable keys are "NSUpArrowFunctionKey = 0xF700" or
* "NSHomeFunctionKey = 0xF729".
*
* See
* https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc
* for more information.
*/
static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) {
return plane | (baseKey & kValueMask);
}
/**
* Returns the physical key for a key code.
*/
static uint64_t GetPhysicalKeyForKeyCode(unsigned short keyCode) {
NSNumber* physicalKey = [keyCodeToPhysicalKey objectForKey:@(keyCode)];
if (physicalKey == nil) {
return KeyOfPlane(keyCode, kMacosPlane);
}
return physicalKey.unsignedLongLongValue;
}
/**
* Returns the logical key for a modifier physical key.
*/
static uint64_t GetLogicalKeyForModifier(unsigned short keyCode, uint64_t hidCode) {
NSNumber* fromKeyCode = [keyCodeToLogicalKey objectForKey:@(keyCode)];
if (fromKeyCode != nil) {
return fromKeyCode.unsignedLongLongValue;
}
return KeyOfPlane(hidCode, kMacosPlane);
}
/**
* Converts upper letters to lower letters in ASCII, and returns as-is
* otherwise.
*
* Independent of locale.
*/
static uint64_t toLower(uint64_t n) {
constexpr uint64_t lowerA = 0x61;
constexpr uint64_t upperA = 0x41;
constexpr uint64_t upperZ = 0x5a;
constexpr uint64_t lowerAGrave = 0xe0;
constexpr uint64_t upperAGrave = 0xc0;
constexpr uint64_t upperThorn = 0xde;
constexpr uint64_t division = 0xf7;
// ASCII range.
if (n >= upperA && n <= upperZ) {
return n - upperA + lowerA;
}
// EASCII range.
if (n >= upperAGrave && n <= upperThorn && n != division) {
return n - upperAGrave + lowerAGrave;
}
return n;
}
// Decode a UTF-16 sequence to an array of char32 (UTF-32).
//
// See https://en.wikipedia.org/wiki/UTF-16#Description for the algorithm.
//
// The returned character array must be deallocated with delete[]. The length of
// the result is stored in `out_length`.
//
// Although NSString has a dataUsingEncoding method, we implement our own
// because dataUsingEncoding outputs redundant characters for unknown reasons.
static uint32_t* DecodeUtf16(NSString* target, size_t* out_length) {
// The result always has a length less or equal to target.
size_t result_pos = 0;
uint32_t* result = new uint32_t[target.length];
uint16_t high_surrogate = 0;
for (NSUInteger target_pos = 0; target_pos < target.length; target_pos += 1) {
uint16_t codeUnit = [target characterAtIndex:target_pos];
// BMP
if (codeUnit <= 0xD7FF || codeUnit >= 0xE000) {
result[result_pos] = codeUnit;
result_pos += 1;
// High surrogates
} else if (codeUnit <= 0xDBFF) {
high_surrogate = codeUnit - 0xD800;
// Low surrogates
} else {
uint16_t low_surrogate = codeUnit - 0xDC00;
result[result_pos] = (high_surrogate << 10) + low_surrogate + 0x10000;
result_pos += 1;
}
}
*out_length = result_pos;
return result;
}
/**
* Returns the logical key of a KeyUp or KeyDown event.
*
* For FlagsChanged event, use GetLogicalKeyForModifier.
*/
static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) {
// Look to see if the keyCode can be mapped from keycode.
NSNumber* fromKeyCode = [keyCodeToLogicalKey objectForKey:@(event.keyCode)];
if (fromKeyCode != nil) {
return fromKeyCode.unsignedLongLongValue;
}
// Convert `charactersIgnoringModifiers` to UTF32.
NSString* keyLabelUtf16 = event.charactersIgnoringModifiers;
// Check if this key is a single character, which will be used to generate the
// logical key from its Unicode value.
//
// Multi-char keys will be minted onto the macOS plane because there are no
// meaningful values for them. Control keys and unprintable keys have been
// converted by `keyCodeToLogicalKey` earlier.
uint32_t character = 0;
if (keyLabelUtf16.length != 0) {
size_t keyLabelLength;
uint32_t* keyLabel = DecodeUtf16(keyLabelUtf16, &keyLabelLength);
if (keyLabelLength == 1) {
uint32_t keyLabelChar = *keyLabel;
NSCAssert(!IsControlCharacter(keyLabelChar) && !IsUnprintableKey(keyLabelChar),
@"Unexpected control or unprintable keylabel 0x%x", keyLabelChar);
NSCAssert(keyLabelChar <= 0x10FFFF, @"Out of range keylabel 0x%x", keyLabelChar);
character = keyLabelChar;
}
delete[] keyLabel;
}
if (character != 0) {
return KeyOfPlane(toLower(character), kUnicodePlane);
}
// We can't represent this key with a single printable unicode, so a new code
// is minted to the macOS plane.
return KeyOfPlane(event.keyCode, kMacosPlane);
}
/**
* Converts NSEvent.timestamp to the timestamp for Flutter.
*/
static double GetFlutterTimestampFrom(NSTimeInterval timestamp) {
// Timestamp in microseconds. The event.timestamp is in seconds with sub-ms precision.
return timestamp * 1000000.0;
}
/**
* Compute |modifierFlagOfInterestMask| out of |keyCodeToModifierFlag|.
*
* This is equal to the bitwise-or of all values of |keyCodeToModifierFlag| as
* well as NSEventModifierFlagCapsLock.
*/
static NSUInteger computeModifierFlagOfInterestMask() {
__block NSUInteger modifierFlagOfInterestMask = NSEventModifierFlagCapsLock;
[keyCodeToModifierFlag
enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL* stop) {
modifierFlagOfInterestMask = modifierFlagOfInterestMask | [flag unsignedLongValue];
}];
return modifierFlagOfInterestMask;
}
/**
* The C-function sent to the embedder's |SendKeyEvent|, wrapping
* |FlutterEmbedderKeyResponder.handleResponse|.
*
* For the reason of this wrap, see |FlutterKeyPendingResponse|.
*/
void HandleResponse(bool handled, void* user_data);
/**
* Converts NSEvent.characters to a C-string for FlutterKeyEvent.
*/
const char* getEventString(NSString* characters) {
if ([characters length] == 0) {
return nullptr;
}
unichar utf16Code = [characters characterAtIndex:0];
if (utf16Code >= 0xf700 && utf16Code <= 0xf7ff) {
// Some function keys are assigned characters with codepoints from the
// private use area. These characters are filtered out since they're
// unprintable.
//
// The official documentation reserves 0xF700-0xF8FF as private use area
// (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc).
// But macOS seems to only use a reduced range of it. The official doc
// defines a few constants, all of which are within 0xF700-0xF747.
// (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc).
// This mostly aligns with the experimentation result, except for 0xF8FF,
// which is used for the "Apple logo" character (Option-Shift-K on a US
// keyboard.)
//
// Assume that non-printable function keys are defined from
// 0xF700 upwards, and printable private keys are defined from 0xF8FF
// downwards. This function filters out 0xF700-0xF7FF in order to keep
// the printable private keys.
return nullptr;
}
return [characters UTF8String];
}
} // namespace
/**
* The invocation context for |HandleResponse|, wrapping
* |FlutterEmbedderKeyResponder.handleResponse|.
*
* The embedder functions only accept C-functions as callbacks, as well as an
* arbitrary user_data. In order to send an instance method of
* |FlutterEmbedderKeyResponder.handleResponse| to the engine's |SendKeyEvent|,
* the embedder wraps the invocation into a C-function |HandleResponse| and
* invocation context |FlutterKeyPendingResponse|.
*
* When this object is sent to the engine's |SendKeyEvent| as |user_data|, it
* must be attached with |__bridge_retained|. When this object is parsed
* in |HandleResponse| from |user_data|, it will be attached with
* |__bridge_transfer|.
*/
@interface FlutterKeyPendingResponse : NSObject
@property(nonatomic) FlutterEmbedderKeyResponder* responder;
@property(nonatomic) uint64_t responseId;
- (nonnull instancetype)initWithHandler:(nonnull FlutterEmbedderKeyResponder*)responder
responseId:(uint64_t)responseId;
@end
@implementation FlutterKeyPendingResponse
- (instancetype)initWithHandler:(FlutterEmbedderKeyResponder*)responder
responseId:(uint64_t)responseId {
self = [super init];
if (self != nil) {
_responder = responder;
_responseId = responseId;
}
return self;
}
@end
/**
* Guards a |FlutterAsyncKeyCallback| to make sure it's handled exactly once
* throughout |FlutterEmbedderKeyResponder.handleEvent|.
*
* A callback can either be handled with |pendTo:withId:|, or with |resolveTo:|.
* Either way, the callback cannot be handled again, or an assertion will be
* thrown.
*/
@interface FlutterKeyCallbackGuard : NSObject
- (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback;
/**
* Handle the callback by storing it to pending responses.
*/
- (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses
withId:(uint64_t)responseId;
/**
* Handle the callback by calling it with a result.
*/
- (void)resolveTo:(BOOL)handled;
@property(nonatomic) BOOL handled;
@property(nonatomic) BOOL sentAnyEvents;
/**
* A string indicating how the callback is handled.
*
* Only set in debug mode. Nil in release mode, or if the callback has not been
* handled.
*/
@property(nonatomic) NSString* debugHandleSource;
@end
@implementation FlutterKeyCallbackGuard {
// The callback is declared in the implemnetation block to avoid being
// accessed directly.
FlutterAsyncKeyCallback _callback;
}
- (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback {
self = [super init];
if (self != nil) {
_callback = callback;
_handled = FALSE;
_sentAnyEvents = FALSE;
}
return self;
}
- (void)pendTo:(nonnull NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*)pendingResponses
withId:(uint64_t)responseId {
NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource);
if (_handled) {
return;
}
pendingResponses[@(responseId)] = _callback;
_handled = TRUE;
NSAssert(
((_debugHandleSource = [NSString stringWithFormat:@"pending event %llu", responseId]), TRUE),
@"");
}
- (void)resolveTo:(BOOL)handled {
NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource);
if (_handled) {
return;
}
_callback(handled);
_handled = TRUE;
NSAssert(((_debugHandleSource = [NSString stringWithFormat:@"resolved with %d", _handled]), TRUE),
@"");
}
@end
@interface FlutterEmbedderKeyResponder ()
/**
* The function to send converted events to.
*
* Set by the initializer.
*/
@property(nonatomic, copy) FlutterSendEmbedderKeyEvent sendEvent;
/**
* A map of presessd keys.
*
* The keys of the dictionary are physical keys, while the values are the logical keys
* of the key down event.
*/
@property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>* pressingRecords;
/**
* A constant mask for NSEvent.modifierFlags that Flutter synchronizes with.
*
* Flutter keeps track of the last |modifierFlags| and compares it with the
* incoming one. Any bit within |modifierFlagOfInterestMask| that is different
* (except for the one that corresponds to the event key) indicates that an
* event for this modifier was missed, and Flutter synthesizes an event to make
* up for the state difference.
*
* It is computed by computeModifierFlagOfInterestMask.
*/
@property(nonatomic) NSUInteger modifierFlagOfInterestMask;
/**
* The modifier flags of the last received key event, excluding uninterested
* bits.
*
* This should be kept synchronized with the last |NSEvent.modifierFlags|
* after masking with |modifierFlagOfInterestMask|. This should also be kept
* synchronized with the corresponding keys of |pressingRecords|.
*
* This is used by |synchronizeModifiers| to quickly find
* out modifier keys that are desynchronized.
*/
@property(nonatomic) NSUInteger lastModifierFlagsOfInterest;
/**
* A self-incrementing ID used to label key events sent to the framework.
*/
@property(nonatomic) uint64_t responseId;
/**
* A map of unresponded key events sent to the framework.
*
* Its values are |responseId|s, and keys are the callback that was received
* along with the event.
*/
@property(nonatomic) NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>* pendingResponses;
/**
* Compare the last modifier flags and the current, and dispatch synthesized
* key events for each different modifier flag bit.
*
* The flags compared are all flags after masking with
* |modifierFlagOfInterestMask| and excluding |ignoringFlags|.
*
* The |guard| is basically a regular guarded callback, but instead of being
* called, it is only used to record whether an event is sent.
*/
- (void)synchronizeModifiers:(NSUInteger)currentFlags
ignoringFlags:(NSUInteger)ignoringFlags
timestamp:(NSTimeInterval)timestamp
guard:(nonnull FlutterKeyCallbackGuard*)guard;
/**
* Update the pressing state.
*
* If `logicalKey` is not 0, `physicalKey` is pressed as `logicalKey`.
* Otherwise, `physicalKey` is released.
*/
- (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey;
/**
* Send an event to the framework, expecting its response.
*/
- (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
callback:(nonnull FlutterKeyCallbackGuard*)callback;
/**
* Send a synthesized key event, never expecting its event result.
*
* The |guard| is basically a regular guarded callback, but instead of being
* called, it is only used to record whether an event is sent.
*/
- (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event
guard:(FlutterKeyCallbackGuard*)guard;
/**
* Send a CapsLock down event, then a CapsLock up event.
*
* If synthesizeDown is TRUE, then both events will be synthesized. Otherwise,
* the callback will be used as the callback for the down event, which is not
* synthesized, while the up event will always be synthesized.
*/
- (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp
synthesizeDown:(bool)synthesizeDown
callback:(nonnull FlutterKeyCallbackGuard*)callback;
/**
* Send a key event for a modifier key.
*/
- (void)sendModifierEventOfType:(BOOL)isDownEvent
timestamp:(NSTimeInterval)timestamp
keyCode:(unsigned short)keyCode
synthesized:(bool)synthesized
callback:(nonnull FlutterKeyCallbackGuard*)callback;
/**
* Processes a down event from the system.
*/
- (void)handleDownEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
/**
* Processes an up event from the system.
*/
- (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
/**
* Processes an event from the system for the CapsLock key.
*/
- (void)handleCapsLockEvent:(nonnull NSEvent*)event
callback:(nonnull FlutterKeyCallbackGuard*)callback;
/**
* Processes a flags changed event from the system, where modifier keys are pressed or released.
*/
- (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback;
/**
* Processes the response from the framework.
*/
- (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId;
@end
@implementation FlutterEmbedderKeyResponder
- (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent {
self = [super init];
if (self != nil) {
_sendEvent = sendEvent;
_pressingRecords = [NSMutableDictionary dictionary];
_pendingResponses = [NSMutableDictionary dictionary];
_responseId = 1;
_lastModifierFlagsOfInterest = 0;
_modifierFlagOfInterestMask = computeModifierFlagOfInterestMask();
}
return self;
}
- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
// The conversion algorithm relies on a non-nil callback to properly compute
// `synthesized`.
NSAssert(callback != nil, @"The callback must not be nil.");
FlutterKeyCallbackGuard* guardedCallback =
[[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
switch (event.type) {
case NSEventTypeKeyDown:
[self handleDownEvent:event callback:guardedCallback];
break;
case NSEventTypeKeyUp:
[self handleUpEvent:event callback:guardedCallback];
break;
case NSEventTypeFlagsChanged:
[self handleFlagEvent:event callback:guardedCallback];
break;
default:
NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type));
}
NSAssert(guardedCallback.handled, @"The callback is returned without being handled.");
if (!guardedCallback.sentAnyEvents) {
FlutterKeyEvent flutterEvent = {
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = 0,
.type = kFlutterKeyEventTypeDown,
.physical = 0,
.logical = 0,
.character = nil,
.synthesized = false,
};
_sendEvent(flutterEvent, nullptr, nullptr);
}
NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask),
@"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx",
_lastModifierFlagsOfInterest, event.modifierFlags & _modifierFlagOfInterestMask);
}
#pragma mark - Private
- (void)synchronizeModifiers:(NSUInteger)currentFlags
ignoringFlags:(NSUInteger)ignoringFlags
timestamp:(NSTimeInterval)timestamp
guard:(FlutterKeyCallbackGuard*)guard {
const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags;
const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask;
const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask;
NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest;
if (flagDifference & NSEventModifierFlagCapsLock) {
[self sendCapsLockTapWithTimestamp:timestamp synthesizeDown:true callback:guard];
flagDifference = flagDifference & ~NSEventModifierFlagCapsLock;
}
while (true) {
const NSUInteger currentFlag = lowestSetBit(flagDifference);
if (currentFlag == 0) {
break;
}
flagDifference = flagDifference & ~currentFlag;
NSNumber* keyCode = [modifierFlagToKeyCode objectForKey:@(currentFlag)];
NSAssert(keyCode != nil, @"Invalid modifier flag 0x%lx", currentFlag);
if (keyCode == nil) {
continue;
}
BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0;
[self sendModifierEventOfType:isDownEvent
timestamp:timestamp
keyCode:[keyCode unsignedShortValue]
synthesized:true
callback:guard];
}
_lastModifierFlagsOfInterest =
(_lastModifierFlagsOfInterest & ~updatingMask) | currentFlagsOfInterest;
}
- (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey {
if (logicalKey == 0) {
[_pressingRecords removeObjectForKey:@(physicalKey)];
} else {
_pressingRecords[@(physicalKey)] = @(logicalKey);
}
}
- (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event
callback:(FlutterKeyCallbackGuard*)callback {
_responseId += 1;
uint64_t responseId = _responseId;
FlutterKeyPendingResponse* pending =
[[FlutterKeyPendingResponse alloc] initWithHandler:self responseId:responseId];
[callback pendTo:_pendingResponses withId:responseId];
// The `__bridge_retained` here is matched by `__bridge_transfer` in HandleResponse.
_sendEvent(event, HandleResponse, (__bridge_retained void*)pending);
callback.sentAnyEvents = TRUE;
}
- (void)sendSynthesizedFlutterEvent:(const FlutterKeyEvent&)event
guard:(FlutterKeyCallbackGuard*)guard {
_sendEvent(event, nullptr, nullptr);
guard.sentAnyEvents = TRUE;
}
- (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp
synthesizeDown:(bool)synthesizeDown
callback:(FlutterKeyCallbackGuard*)callback {
// MacOS sends a down *or* an up when CapsLock is tapped, alternatively on
// even taps and odd taps. A CapsLock down or CapsLock up should always be
// converted to a down *and* an up, and the up should always be a synthesized
// event, since the FlutterEmbedderKeyResponder will never know when the
// button is released.
FlutterKeyEvent flutterEvent = {
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = GetFlutterTimestampFrom(timestamp),
.type = kFlutterKeyEventTypeDown,
.physical = kCapsLockPhysicalKey,
.logical = kCapsLockLogicalKey,
.character = nil,
.synthesized = synthesizeDown,
};
if (!synthesizeDown) {
[self sendPrimaryFlutterEvent:flutterEvent callback:callback];
} else {
[self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
}
flutterEvent.type = kFlutterKeyEventTypeUp;
flutterEvent.synthesized = true;
[self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
}
- (void)sendModifierEventOfType:(BOOL)isDownEvent
timestamp:(NSTimeInterval)timestamp
keyCode:(unsigned short)keyCode
synthesized:(bool)synthesized
callback:(FlutterKeyCallbackGuard*)callback {
uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);
uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);
if (physicalKey == 0 || logicalKey == 0) {
NSLog(@"Unrecognized modifier key: keyCode 0x%hx, physical key 0x%llx", keyCode, physicalKey);
[callback resolveTo:TRUE];
return;
}
FlutterKeyEvent flutterEvent = {
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = GetFlutterTimestampFrom(timestamp),
.type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp,
.physical = physicalKey,
.logical = logicalKey,
.character = nil,
.synthesized = synthesized,
};
[self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];
if (!synthesized) {
[self sendPrimaryFlutterEvent:flutterEvent callback:callback];
} else {
[self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
}
}
- (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey);
[self synchronizeModifiers:event.modifierFlags
ignoringFlags:0
timestamp:event.timestamp
guard:callback];
bool isARepeat = event.isARepeat;
NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
if (pressedLogicalKey != nil && !isARepeat) {
// This might happen in add-to-app scenarios if the focus is changed
// from the native view to the Flutter view amid the key tap.
//
// This might also happen when a key event is forged (such as by an
// IME) using the same keyCode as an unreleased key. See
// https://github.com/flutter/flutter/issues/82673#issuecomment-988661079
FlutterKeyEvent flutterEvent = {
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = GetFlutterTimestampFrom(event.timestamp),
.type = kFlutterKeyEventTypeUp,
.physical = physicalKey,
.logical = [pressedLogicalKey unsignedLongLongValue],
.character = nil,
.synthesized = true,
};
[self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
pressedLogicalKey = nil;
}
if (pressedLogicalKey == nil) {
[self updateKey:physicalKey asPressed:logicalKey];
}
FlutterKeyEvent flutterEvent = {
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = GetFlutterTimestampFrom(event.timestamp),
.type = pressedLogicalKey == nil ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeRepeat,
.physical = physicalKey,
.logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],
.character = getEventString(event.characters),
.synthesized = false,
};
[self sendPrimaryFlutterEvent:flutterEvent callback:callback];
}
- (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@",
event.keyCode, event.characters, event.charactersIgnoringModifiers);
[self synchronizeModifiers:event.modifierFlags
ignoringFlags:0
timestamp:event.timestamp
guard:callback];
uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
if (pressedLogicalKey == nil) {
// Normally the key up events won't be missed since macOS always sends the
// key up event to the window where the corresponding key down occurred.
// However this might happen in add-to-app scenarios if the focus is changed
// from the native view to the Flutter view amid the key tap.
[callback resolveTo:TRUE];
return;
}
[self updateKey:physicalKey asPressed:0];
FlutterKeyEvent flutterEvent = {
.struct_size = sizeof(FlutterKeyEvent),
.timestamp = GetFlutterTimestampFrom(event.timestamp),
.type = kFlutterKeyEventTypeUp,
.physical = physicalKey,
.logical = [pressedLogicalKey unsignedLongLongValue],
.character = nil,
.synthesized = false,
};
[self sendPrimaryFlutterEvent:flutterEvent callback:callback];
}
- (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
[self synchronizeModifiers:event.modifierFlags
ignoringFlags:NSEventModifierFlagCapsLock
timestamp:event.timestamp
guard:callback];
if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) !=
(event.modifierFlags & NSEventModifierFlagCapsLock)) {
[self sendCapsLockTapWithTimestamp:event.timestamp synthesizeDown:false callback:callback];
_lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock;
} else {
[callback resolveTo:TRUE];
}
}
- (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback {
NSNumber* targetModifierFlagObj = keyCodeToModifierFlag[@(event.keyCode)];
NSUInteger targetModifierFlag =
targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue];
uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode);
if (targetKey == kCapsLockPhysicalKey) {
return [self handleCapsLockEvent:event callback:callback];
}
[self synchronizeModifiers:event.modifierFlags
ignoringFlags:targetModifierFlag
timestamp:event.timestamp
guard:callback];
NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)];
BOOL lastTargetPressed = pressedLogicalKey != nil;
NSAssert(targetModifierFlagObj == nil ||
(_lastModifierFlagsOfInterest & targetModifierFlag) != 0 == lastTargetPressed,
@"Desynchronized state between lastModifierFlagsOfInterest (0x%lx) on bit 0x%lx "
@"for keyCode 0x%hx, whose pressing state is %@.",
_lastModifierFlagsOfInterest, targetModifierFlag, event.keyCode,
lastTargetPressed
? [NSString stringWithFormat:@"0x%llx", [pressedLogicalKey unsignedLongLongValue]]
: @"empty");
BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0;
if (lastTargetPressed == shouldBePressed) {
[callback resolveTo:TRUE];
return;
}
_lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ targetModifierFlag;
[self sendModifierEventOfType:shouldBePressed
timestamp:event.timestamp
keyCode:event.keyCode
synthesized:false
callback:callback];
}
- (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId {
FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)];
callback(handled);
[_pendingResponses removeObjectForKey:@(responseId)];
}
@end
namespace {
void HandleResponse(bool handled, void* user_data) {
// The `__bridge_transfer` here is matched by `__bridge_retained` in sendPrimaryFlutterEvent.
FlutterKeyPendingResponse* pending = (__bridge_transfer FlutterKeyPendingResponse*)user_data;
[pending.responder handleResponse:handled forId:pending.responseId];
}
} // namespace