Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -680,12 +680,23 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb
bool isARepeat = event.isARepeat;
NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
if (pressedLogicalKey != nil && !isARepeat) {
// 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
// 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;
//
// 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ - (void)dealloc {
[events removeAllObjects];
}

TEST(FlutterEmbedderKeyResponderUnittests, IgnoreDuplicateDownEvent) {
TEST(FlutterEmbedderKeyResponderUnittests, SynthesizeForDuplicateDownEvent) {
__block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
__block BOOL last_handled = TRUE;
FlutterKeyEvent* event;
Expand All @@ -319,7 +319,7 @@ - (void)dealloc {
userData:user_data]];
}];

last_handled = FALSE;
last_handled = TRUE;
[responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA)
callback:^(BOOL handled) {
last_handled = handled;
Expand All @@ -332,44 +332,35 @@ - (void)dealloc {
EXPECT_EQ(event->logical, kLogicalKeyA);
EXPECT_STREQ(event->character, "a");
EXPECT_EQ(event->synthesized, false);
EXPECT_EQ(last_handled, FALSE);
[[events lastObject] respond:TRUE];
EXPECT_EQ(last_handled, TRUE);
[[events lastObject] respond:FALSE];
EXPECT_EQ(last_handled, FALSE);

[events removeAllObjects];

last_handled = FALSE;
[responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA)
last_handled = TRUE;
[responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"à", @"à", FALSE, kKeyCodeKeyA)
callback:^(BOOL handled) {
last_handled = handled;
}];

EXPECT_EQ([events count], 1u);
EXPECT_EQ(last_handled, TRUE);
event = [events lastObject].data;
EXPECT_EQ(event->physical, 0ull);
EXPECT_EQ(event->logical, 0ull);
EXPECT_FALSE([[events lastObject] hasCallback]);
EXPECT_EQ(last_handled, TRUE);

[events removeAllObjects];

last_handled = FALSE;
[responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA)
callback:^(BOOL handled) {
last_handled = handled;
}];
EXPECT_EQ([events count], 2u);

EXPECT_EQ([events count], 1u);
event = [events lastObject].data;
event = [events firstObject].data;
EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
EXPECT_EQ(event->physical, kPhysicalKeyA);
EXPECT_EQ(event->logical, kLogicalKeyA);
EXPECT_STREQ(event->character, nullptr);
EXPECT_STREQ(event->character, NULL);
EXPECT_EQ(event->synthesized, true);

event = [events lastObject].data;
EXPECT_EQ(event->type, kFlutterKeyEventTypeDown);
EXPECT_EQ(event->physical, kPhysicalKeyA);
EXPECT_EQ(event->logical, 0xE0ull /* à */);
EXPECT_STREQ(event->character, "à");
EXPECT_EQ(event->synthesized, false);
[[events lastObject] respond:FALSE];
EXPECT_EQ(last_handled, FALSE);
[[events lastObject] respond:TRUE];
EXPECT_EQ(last_handled, TRUE);

[events removeAllObjects];
}
Expand Down