mirror of
https://github.com/flutter/flutter.git
synced 2026-01-09 07:51:35 +08:00
Fix Windows Enter key after focus loss (#178523)
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.
This commit is contained in:
parent
4c0d007210
commit
8111945eac
1
AUTHORS
1
AUTHORS
@ -136,3 +136,4 @@ Jin Jeongsu <jinjs.dev@gmail.com>
|
||||
Mairon Slusarz <maironlucaslusarz@gmail.com>
|
||||
Ricardo Dalarme <ricardodalarme@outlook.com>
|
||||
yiiim <ybz975218925@live.com>
|
||||
letrungdo <letrdo@gmail.com>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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<KeyboardChange>{
|
||||
WmKeyDownInfo{VK_RETURN, kScanCodeEnter, kNotExtended, kWasUp}.Build(
|
||||
kWmResultZero)});
|
||||
|
||||
tester.InjectKeyboardChanges(std::vector<KeyboardChange>{
|
||||
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<KeyboardChange>{
|
||||
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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user