From 756344ab2c4f32496db7eedcd577e1ca684331e2 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 3 Mar 2022 21:31:12 -0800 Subject: [PATCH] [macOS, Keyboard] Duplicate down events are no longer ignored, but kept and preceded by up events (#31800) --- .../Source/FlutterEmbedderKeyResponder.mm | 21 ++++++--- .../FlutterEmbedderKeyResponderUnittests.mm | 43 ++++++++----------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index cf3e397a84612..191c0aa8dbbc9 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -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) { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm index 359bdc37e8012..ef5a7a34ce128 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm @@ -306,7 +306,7 @@ - (void)dealloc { [events removeAllObjects]; } -TEST(FlutterEmbedderKeyResponderUnittests, IgnoreDuplicateDownEvent) { +TEST(FlutterEmbedderKeyResponderUnittests, SynthesizeForDuplicateDownEvent) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; FlutterKeyEvent* event; @@ -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; @@ -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]; }