Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 0a6098b

Browse files
authored
[Win32, keyboard] Fix dead key events that don't have the dead key mask (#30004)
This PR fixes flutter/flutter#92654, a rare case where dead key events are not properly handled.
1 parent b420c16 commit 0a6098b

File tree

8 files changed

+155
-33
lines changed

8 files changed

+155
-33
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,7 @@ FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler.cc
17591759
FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler.h
17601760
FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler_unittests.cc
17611761
FILE: ../../../flutter/shell/platform/windows/keyboard_unittests.cc
1762+
FILE: ../../../flutter/shell/platform/windows/keyboard_win32_common.h
17621763
FILE: ../../../flutter/shell/platform/windows/platform_handler.cc
17631764
FILE: ../../../flutter/shell/platform/windows/platform_handler.h
17641765
FILE: ../../../flutter/shell/platform/windows/platform_handler_unittests.cc

shell/platform/windows/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ source_set("flutter_windows_source") {
7272
"keyboard_key_embedder_handler.h",
7373
"keyboard_key_handler.cc",
7474
"keyboard_key_handler.h",
75+
"keyboard_win32_common.h",
7576
"platform_handler.cc",
7677
"platform_handler.h",
7778
"sequential_id_generator.cc",

shell/platform/windows/keyboard_key_channel_handler.cc

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <iostream>
1515

1616
#include "flutter/shell/platform/common/json_message_codec.h"
17+
#include "flutter/shell/platform/windows/keyboard_win32_common.h"
1718

1819
namespace flutter {
1920

@@ -146,17 +147,6 @@ int GetModsForKeyState() {
146147
#endif
147148
}
148149

149-
// Revert the "character" for a dead key to its normal value, or the argument
150-
// unchanged otherwise.
151-
//
152-
// When a dead key is pressed, the WM_KEYDOWN's lParam is mapped to a special
153-
// value: the "normal character" | 0x80000000. For example, when pressing
154-
// "dead key caret" (one that makes the following e into ê), its mapped
155-
// character is 0x8000005E. "Reverting" it gives 0x5E, which is character '^'.
156-
uint32_t _UndeadChar(uint32_t ch) {
157-
return ch & ~0x80000000;
158-
}
159-
160150
} // namespace
161151

162152
KeyboardKeyChannelHandler::KeyboardKeyChannelHandler(
@@ -184,7 +174,7 @@ void KeyboardKeyChannelHandler::KeyboardHook(
184174
event.AddMember(kKeyCodeKey, key, allocator);
185175
event.AddMember(kScanCodeKey, scancode | (extended ? kScancodeExtended : 0),
186176
allocator);
187-
event.AddMember(kCharacterCodePointKey, _UndeadChar(character), allocator);
177+
event.AddMember(kCharacterCodePointKey, UndeadChar(character), allocator);
188178
event.AddMember(kKeyMapKey, kWindowsKeyMap, allocator);
189179
event.AddMember(kModifiersKey, GetModsForKeyState(), allocator);
190180

shell/platform/windows/keyboard_key_embedder_handler.cc

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <iostream>
1313
#include <string>
1414

15+
#include "flutter/shell/platform/windows/keyboard_win32_common.h"
1516
#include "flutter/shell/platform/windows/string_conversion.h"
1617

1718
namespace flutter {
@@ -28,17 +29,6 @@ constexpr SHORT kStateMaskPressed = 0x80;
2829

2930
const char* empty_character = "";
3031

31-
// Revert the "character" for a dead key to its normal value, or the argument
32-
// unchanged otherwise.
33-
//
34-
// When a dead key is pressed, the WM_KEYDOWN's lParam is mapped to a special
35-
// value: the "normal character" | 0x80000000. For example, when pressing
36-
// "dead key caret" (one that makes the following e into ê), its mapped
37-
// character is 0x8000005E. "Reverting" it gives 0x5E, which is character '^'.
38-
uint32_t _UndeadChar(uint32_t ch) {
39-
return ch & ~0x80000000;
40-
}
41-
4232
// Get some bits of the char, from the start'th bit from the right (excluded)
4333
// to the end'th bit from the right (included).
4434
//
@@ -185,7 +175,7 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl(
185175
uint64_t eventual_logical_record;
186176
char character_bytes[kCharacterCacheSize];
187177

188-
character = _UndeadChar(character);
178+
character = UndeadChar(character);
189179

190180
if (is_physical_down) {
191181
if (had_record) {

shell/platform/windows/keyboard_key_handler.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <iostream>
1010

1111
#include "flutter/shell/platform/common/json_message_codec.h"
12+
#include "flutter/shell/platform/windows/keyboard_win32_common.h"
1213

1314
namespace flutter {
1415

@@ -20,7 +21,7 @@ static constexpr int kMaxPendingEvents = 1000;
2021

2122
// Returns if a character sent by Win32 is a dead key.
2223
bool _IsDeadKey(uint32_t ch) {
23-
return (ch & 0x80000000) != 0;
24+
return (ch & kDeadKeyCharMask) != 0;
2425
}
2526

2627
// Returns true if this key is a key down event of ShiftRight.

shell/platform/windows/keyboard_unittests.cc

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ constexpr uint64_t kScanCodeKeyE = 0x12;
349349
constexpr uint64_t kScanCodeKeyQ = 0x10;
350350
constexpr uint64_t kScanCodeKeyW = 0x11;
351351
constexpr uint64_t kScanCodeDigit1 = 0x02;
352+
constexpr uint64_t kScanCodeDigit6 = 0x07;
352353
// constexpr uint64_t kScanCodeNumpad1 = 0x4f;
353354
// constexpr uint64_t kScanCodeNumLock = 0x45;
354355
constexpr uint64_t kScanCodeControl = 0x1d;
@@ -860,8 +861,112 @@ TEST(KeyboardTest, DeadKeyThatCombines) {
860861
EXPECT_EQ(key_calls.size(), 0);
861862
}
862863

864+
// This tests dead key ^ then E on a US INTL keyboard, which should be combined
865+
// into ê.
866+
//
867+
// It is different from French AZERTY because the character that the ^ key is
868+
// mapped to does not contain the dead key character somehow.
869+
TEST(KeyboardTest, DeadKeyWithoutDeadMaskThatCombines) {
870+
KeyboardTester tester;
871+
tester.Responding(false);
872+
873+
// Press ShiftLeft
874+
tester.SetKeyState(VK_LSHIFT, true, true);
875+
tester.InjectMessages(
876+
1,
877+
WmKeyDownInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended, kWasUp}.Build(
878+
kWmResultZero));
879+
880+
EXPECT_EQ(key_calls.size(), 1);
881+
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown,
882+
kPhysicalShiftLeft, kLogicalShiftLeft, "",
883+
kNotSynthesized);
884+
clear_key_calls();
885+
886+
tester.InjectPendingEvents();
887+
EXPECT_EQ(key_calls.size(), 0);
888+
clear_key_calls();
889+
890+
// Press 6^
891+
tester.InjectMessages(
892+
2,
893+
WmKeyDownInfo{'6', kScanCodeDigit6, kNotExtended, kWasUp}.Build(
894+
kWmResultZero),
895+
WmDeadCharInfo{'^', kScanCodeDigit6, kNotExtended, kWasUp}.Build(
896+
kWmResultZero));
897+
898+
EXPECT_EQ(key_calls.size(), 1);
899+
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit6,
900+
kLogicalDigit6, "6", kNotSynthesized);
901+
clear_key_calls();
902+
903+
EXPECT_EQ(tester.InjectPendingEvents(), 0);
904+
EXPECT_EQ(key_calls.size(), 0);
905+
clear_key_calls();
906+
907+
// Release 6^
908+
tester.InjectMessages(
909+
1, WmKeyUpInfo{'6', kScanCodeDigit6, kNotExtended}.Build(kWmResultZero));
910+
911+
EXPECT_EQ(key_calls.size(), 1);
912+
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit6,
913+
kLogicalDigit6, "", kNotSynthesized);
914+
clear_key_calls();
915+
916+
tester.InjectPendingEvents();
917+
EXPECT_EQ(key_calls.size(), 0);
918+
clear_key_calls();
919+
920+
// Release ShiftLeft
921+
tester.SetKeyState(VK_LSHIFT, false, true);
922+
tester.InjectMessages(
923+
1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build(
924+
kWmResultZero));
925+
926+
EXPECT_EQ(key_calls.size(), 1);
927+
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalShiftLeft,
928+
kLogicalShiftLeft, "", kNotSynthesized);
929+
clear_key_calls();
930+
931+
tester.InjectPendingEvents();
932+
EXPECT_EQ(key_calls.size(), 0);
933+
clear_key_calls();
934+
935+
// Press E
936+
tester.InjectMessages(
937+
2,
938+
WmKeyDownInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended, kWasUp}.Build(
939+
kWmResultZero),
940+
WmCharInfo{0xEA, kScanCodeKeyE, kNotExtended, kWasUp}.Build(
941+
kWmResultZero));
942+
943+
EXPECT_EQ(key_calls.size(), 1);
944+
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyE,
945+
kLogicalKeyE, "ê", kNotSynthesized);
946+
clear_key_calls();
947+
948+
tester.InjectPendingEvents(
949+
0xEA); // The redispatched event uses unmodified 'e'
950+
EXPECT_EQ(key_calls.size(), 1);
951+
EXPECT_CALL_IS_TEXT(key_calls[0], u"ê");
952+
clear_key_calls();
953+
954+
// Release E
955+
tester.InjectMessages(
956+
1, WmKeyUpInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended}.Build(
957+
kWmResultZero));
958+
959+
EXPECT_EQ(key_calls.size(), 1);
960+
EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyE,
961+
kLogicalKeyE, "", kNotSynthesized);
962+
clear_key_calls();
963+
964+
tester.InjectPendingEvents();
965+
EXPECT_EQ(key_calls.size(), 0);
966+
}
967+
863968
// This tests dead key ^ then & (US: 1) on a French keyboard, which do not
864-
// combine and should output "^$".
969+
// combine and should output "^&".
865970
TEST(KeyboardTest, DeadKeyThatDoesNotCombine) {
866971
KeyboardTester tester;
867972
tester.Responding(false);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_WIN32_COMMON_H_
6+
#define FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_WIN32_COMMON_H_
7+
8+
#include <stdint.h>
9+
10+
namespace flutter {
11+
12+
// The bit of a mapped character in a WM_KEYDOWN message that indicates the
13+
// character is a dead key.
14+
//
15+
// When a dead key is pressed, the WM_KEYDOWN's lParam is mapped to a special
16+
// value: the "normal character" | 0x80000000. For example, when pressing
17+
// "dead key caret" (one that makes the following e into ê), its mapped
18+
// character is 0x8000005E. "Reverting" it gives 0x5E, which is character '^'.
19+
constexpr int kDeadKeyCharMask = 0x80000000;
20+
21+
// Revert the "character" for a dead key to its normal value, or the argument
22+
// unchanged otherwise.
23+
inline uint32_t UndeadChar(uint32_t ch) {
24+
return ch & ~kDeadKeyCharMask;
25+
}
26+
27+
} // namespace flutter
28+
29+
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_WIN32_COMMON_H_

shell/platform/windows/window_win32.cc

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <cstring>
1616

1717
#include "dpi_utils_win32.h"
18+
#include "keyboard_win32_common.h"
1819

1920
namespace flutter {
2021

@@ -514,14 +515,18 @@ WindowWin32::HandleMessage(UINT const message,
514515
const bool extended = ((lparam >> 24) & 0x01) == 0x01;
515516
const bool was_down = lparam & 0x40000000;
516517
// Certain key combinations yield control characters as WM_CHAR's
517-
// lParam. For example, 0x01 for Ctrl-A. Filter these characters.
518-
// See
518+
// lParam. For example, 0x01 for Ctrl-A. Filter these characters. See
519519
// https://docs.microsoft.com/en-us/windows/win32/learnwin32/accelerator-tables
520-
const char32_t event_character =
521-
(message == WM_DEADCHAR || message == WM_SYSDEADCHAR)
522-
? Win32MapVkToChar(keycode_for_char_message_)
523-
: IsPrintable(code_point) ? code_point
524-
: 0;
520+
char32_t event_character;
521+
if (message == WM_DEADCHAR || message == WM_SYSDEADCHAR) {
522+
// Mask the resulting char with kDeadKeyCharMask anyway, because in
523+
// rare cases the bit is *not* set (US INTL Shift-6 circumflex, see
524+
// https://github.com/flutter/flutter/issues/92654 .)
525+
event_character =
526+
Win32MapVkToChar(keycode_for_char_message_) | kDeadKeyCharMask;
527+
} else {
528+
event_character = IsPrintable(code_point) ? code_point : 0;
529+
}
525530
bool handled = OnKey(keycode_for_char_message_, scancode,
526531
message == WM_SYSCHAR ? WM_SYSKEYDOWN : WM_KEYDOWN,
527532
event_character, extended, was_down);

0 commit comments

Comments
 (0)