From 8111945eac5c6d01614d15101766bdf59dfec12b Mon Sep 17 00:00:00 2001 From: DoLT Date: Fri, 21 Nov 2025 23:32:10 +0900 Subject: [PATCH] Fix Windows Enter key after focus loss (#178523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix issue: https://github.com/flutter/flutter/issues/169447 Problem: On Windows, pressing Enter after refocusing the app could drop the first keydown because the embedder still thought the key was pressed, causing a double-press requirement and HardwareKeyboard assertions. Fix: In KeyboardKeyEmbedderHandler, synthesize a key-up whenever Win32 reports a “fresh” down for a key still tracked as pressed, so Flutter sees a release before the new down. This keeps modifier/lock-key synchronization intact. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- AUTHORS | 1 + .../windows/keyboard_key_embedder_handler.cc | 32 +++++++++-------- ...keyboard_key_embedder_handler_unittests.cc | 23 ++++++++++--- .../platform/windows/keyboard_unittests.cc | 34 +++++++++++++++++++ 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/AUTHORS b/AUTHORS index 57248e0bacb..89e904a7d2a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -136,3 +136,4 @@ Jin Jeongsu Mairon Slusarz Ricardo Dalarme yiiim +letrungdo diff --git a/engine/src/flutter/shell/platform/windows/keyboard_key_embedder_handler.cc b/engine/src/flutter/shell/platform/windows/keyboard_key_embedder_handler.cc index efe76779408..9351f6e04d7 100644 --- a/engine/src/flutter/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/engine/src/flutter/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -225,22 +225,24 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl( uint64_t result_logical_key; if (is_event_down) { + if (had_record && !was_down) { + // Windows delivered a new down event without a matching up event. This + // happens if the window lost focus before it could receive the key up. + // Synthesize an up event so the framework releases the key before + // processing the incoming down. + SendSynthesizeUpEvent(physical_key, last_logical_record); + last_logical_record_iter = pressingRecords_.find(physical_key); + had_record = last_logical_record_iter != pressingRecords_.end(); + last_logical_record = had_record ? last_logical_record_iter->second : 0; + } + if (had_record) { - if (was_down) { - // A normal repeated key. - type = kFlutterKeyEventTypeRepeat; - FML_DCHECK(had_record); - ConvertUtf32ToUtf8_(character_bytes, character); - eventual_logical_record = last_logical_record; - result_logical_key = last_logical_record; - } else { - // A non-repeated key has been pressed that has the exact physical key - // as a currently pressed one, usually indicating multiple keyboards are - // pressing keys with the same physical key, or the up event was lost - // during a loss of focus. The down event is ignored. - callback(true); - return; - } + // A normal repeated key. + type = kFlutterKeyEventTypeRepeat; + FML_DCHECK(had_record); + ConvertUtf32ToUtf8_(character_bytes, character); + eventual_logical_record = last_logical_record; + result_logical_key = last_logical_record; } else { // A normal down event (whether the system event is a repeat or not). type = kFlutterKeyEventTypeDown; diff --git a/engine/src/flutter/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc b/engine/src/flutter/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc index d8532c2b117..146aff79c11 100644 --- a/engine/src/flutter/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc @@ -747,17 +747,30 @@ TEST(KeyboardKeyEmbedderHandlerTest, RepeatedDownIsIgnored) { // KeyA's key up is missed. - // Press A again (should yield an empty event) + // Press A again (should synthesize an up event followed by a new down). last_handled = false; handler->KeyboardHook( kVirtualKeyA, kScanCodeKeyA, WM_KEYDOWN, 'a', false, false, [&last_handled](bool handled) { last_handled = handled; }); - EXPECT_EQ(last_handled, true); - EXPECT_EQ(results.size(), 1); + EXPECT_EQ(last_handled, false); + ASSERT_EQ(results.size(), 2u); + event = &results[0]; - EXPECT_EQ(event->physical, 0); - EXPECT_EQ(event->logical, 0); + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, ""); + EXPECT_EQ(event->synthesized, true); EXPECT_EQ(event->callback, nullptr); + + event = &results[1]; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + event->callback(true, event->user_data); + EXPECT_EQ(last_handled, true); results.clear(); } diff --git a/engine/src/flutter/shell/platform/windows/keyboard_unittests.cc b/engine/src/flutter/shell/platform/windows/keyboard_unittests.cc index b6ae283ea2a..281d436729a 100644 --- a/engine/src/flutter/shell/platform/windows/keyboard_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/keyboard_unittests.cc @@ -1027,6 +1027,40 @@ TEST_F(KeyboardTest, RestartClearsKeyboardState) { EXPECT_EQ(tester.RedispatchedMessageCountAndClear(), 0); } +// Press Enter, lose focus before the up event arrives, and press Enter again +// once focus returns. The second down event should not be dropped. +TEST_F(KeyboardTest, FreshKeyDownAfterMissedUpIsDelivered) { + KeyboardTester tester{GetContext()}; + tester.Responding(true); + + tester.InjectKeyboardChanges(std::vector{ + WmKeyDownInfo{VK_RETURN, kScanCodeEnter, kNotExtended, kWasUp}.Build( + kWmResultZero)}); + + tester.InjectKeyboardChanges(std::vector{ + WmKeyDownInfo{VK_RETURN, kScanCodeEnter, kNotExtended, kWasUp}.Build( + kWmResultZero)}); + + ASSERT_EQ(tester.key_calls.size(), 3u); + EXPECT_CALL_IS_EVENT(tester.key_calls[0], kFlutterKeyEventTypeDown, + kPhysicalEnter, kLogicalEnter, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(tester.key_calls[1], kFlutterKeyEventTypeUp, + kPhysicalEnter, kLogicalEnter, "", kSynthesized); + EXPECT_CALL_IS_EVENT(tester.key_calls[2], kFlutterKeyEventTypeDown, + kPhysicalEnter, kLogicalEnter, "", kNotSynthesized); + tester.clear_key_calls(); + + tester.InjectKeyboardChanges(std::vector{ + WmKeyUpInfo{VK_RETURN, kScanCodeEnter, kNotExtended}.Build( + kWmResultZero)}); + + ASSERT_EQ(tester.key_calls.size(), 1u); + EXPECT_CALL_IS_EVENT(tester.key_calls[0], kFlutterKeyEventTypeUp, + kPhysicalEnter, kLogicalEnter, "", kNotSynthesized); + tester.clear_key_calls(); + EXPECT_EQ(tester.RedispatchedMessageCountAndClear(), 0u); +} + // Press Shift-A. This is special because Win32 gives 'A' as character for the // KeyA press. TEST_F(KeyboardTest, ShiftLeftKeyA) {