Skip to content

Commit 2eb5e42

Browse files
authored
Fix dead key handling on Win32 (again) (flutter#28125)
- Fix dead character crashes the channel handler. - Fix dead key messages are redispatched, making the message a regular character message.
1 parent f459515 commit 2eb5e42

5 files changed

+72
-17
lines changed

shell/platform/windows/keyboard_key_channel_handler.cc

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ int GetModsForKeyState() {
103103
#endif
104104
}
105105

106+
// Revert the "character" for a dead key to its normal value, or the argument
107+
// unchanged otherwise.
108+
//
109+
// When a dead key is pressed, the WM_KEYDOWN's lParam is mapped to a special
110+
// value: the "normal character" | 0x80000000. For example, when pressing
111+
// "dead key caret" (one that makes the following e into ê), its mapped
112+
// character is 0x8000005E. "Reverting" it gives 0x5E, which is character '^'.
113+
uint32_t _UndeadChar(uint32_t ch) {
114+
return ch & ~0x80000000;
115+
}
116+
106117
} // namespace
107118

108119
KeyboardKeyChannelHandler::KeyboardKeyChannelHandler(
@@ -130,7 +141,7 @@ void KeyboardKeyChannelHandler::KeyboardHook(
130141
event.AddMember(kKeyCodeKey, key, allocator);
131142
event.AddMember(kScanCodeKey, scancode | (extended ? kScancodeExtended : 0),
132143
allocator);
133-
event.AddMember(kCharacterCodePointKey, character, allocator);
144+
event.AddMember(kCharacterCodePointKey, _UndeadChar(character), allocator);
134145
event.AddMember(kKeyMapKey, kWindowsKeyMap, allocator);
135146
event.AddMember(kModifiersKey, GetModsForKeyState(), allocator);
136147

shell/platform/windows/keyboard_key_channel_handler_unittests.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace testing {
1616

1717
namespace {
1818
static constexpr char kScanCodeKey[] = "scanCode";
19+
static constexpr char kCharacterCodePointKey[] = "characterCodePoint";
1920
static constexpr int kHandledScanCode = 0x14;
2021
static constexpr int kUnhandledScanCode = 0x15;
2122
static constexpr int kUnhandledScanCodeExtended = 0xe015;
@@ -108,5 +109,31 @@ TEST(KeyboardKeyChannelHandlerTest, ExtendedKeysAreSentToRedispatch) {
108109
EXPECT_EQ(received_scancode, kUnhandledScanCode);
109110
}
110111

112+
TEST(KeyboardKeyChannelHandlerTest, DeadKeysDoNotCrash) {
113+
auto handled_message = CreateResponse(true);
114+
auto unhandled_message = CreateResponse(false);
115+
int received_scancode = 0;
116+
117+
TestBinaryMessenger messenger(
118+
[&received_scancode, &handled_message, &unhandled_message](
119+
const std::string& channel, const uint8_t* message,
120+
size_t message_size, BinaryReply reply) {
121+
if (channel == "flutter/keyevent") {
122+
auto message_doc = JsonMessageCodec::GetInstance().DecodeMessage(
123+
message, message_size);
124+
uint32_t character = (*message_doc)[kCharacterCodePointKey].GetUint();
125+
EXPECT_EQ(character, (uint32_t)'^');
126+
}
127+
return true;
128+
});
129+
130+
KeyboardKeyChannelHandler handler(&messenger);
131+
// Extended key flag is passed to redispatched events if set.
132+
handler.KeyboardHook(0xDD, 0x1a, WM_KEYDOWN, 0x8000005E, false, false,
133+
[](bool handled) {});
134+
135+
// EXPECT is done during the callback above.
136+
}
137+
111138
} // namespace testing
112139
} // namespace flutter

shell/platform/windows/keyboard_key_embedder_handler.cc

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,9 @@ constexpr SHORT kStateMaskToggled = 0x01;
2727
constexpr SHORT kStateMaskPressed = 0x80;
2828

2929
const char* empty_character = "";
30-
} // namespace
31-
32-
// Get some bits of the char, from the start'th bit from the right (excluded)
33-
// to the end'th bit from the right (included).
34-
//
35-
// For example, _GetBit(0x1234, 8, 4) => 0x3.
36-
char _GetBit(char32_t ch, size_t start, size_t end) {
37-
return (ch >> end) & ((1 << (start - end)) - 1);
38-
}
3930

40-
// Revert the "character" for a dead key to its normal value.
31+
// Revert the "character" for a dead key to its normal value, or the argument
32+
// unchanged otherwise.
4133
//
4234
// When a dead key is pressed, the WM_KEYDOWN's lParam is mapped to a special
4335
// value: the "normal character" | 0x80000000. For example, when pressing
@@ -47,6 +39,15 @@ uint32_t _UndeadChar(uint32_t ch) {
4739
return ch & ~0x80000000;
4840
}
4941

42+
// Get some bits of the char, from the start'th bit from the right (excluded)
43+
// to the end'th bit from the right (included).
44+
//
45+
// For example, _GetBit(0x1234, 8, 4) => 0x3.
46+
char _GetBit(char32_t ch, size_t start, size_t end) {
47+
return (ch >> end) & ((1 << (start - end)) - 1);
48+
}
49+
} // namespace
50+
5051
std::string ConvertChar32ToUtf8(char32_t ch) {
5152
std::string result;
5253
assert(0 <= ch && ch <= 0x10FFFF);

shell/platform/windows/keyboard_key_handler.cc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ namespace {
1818
// emitting a warning on the console about unhandled events.
1919
static constexpr int kMaxPendingEvents = 1000;
2020

21+
// Returns if a character sent by Win32 is a dead key.
22+
bool _IsDeadKey(uint32_t ch) {
23+
return (ch & 0x80000000) != 0;
24+
}
25+
2126
// Returns true if this key is a key down event of ShiftRight.
2227
//
2328
// This is a temporary solution to
@@ -261,7 +266,14 @@ void KeyboardKeyHandler::ResolvePendingEvent(uint64_t sequence_id,
261266
if (event.unreplied == 0) {
262267
std::unique_ptr<PendingEvent> event_ptr = std::move(*iter);
263268
pending_responds_.erase(iter);
264-
if (!event_ptr->any_handled) {
269+
// Don't dispatch handled events or dead key events.
270+
//
271+
// Redispatching dead keys events makes Win32 ignore the dead key state
272+
// and redispatches a normal character without combining it with the
273+
// next letter key.
274+
const bool should_redispatch =
275+
!event_ptr->any_handled && !_IsDeadKey(event_ptr->character);
276+
if (should_redispatch) {
265277
RedispatchEvent(std::move(event_ptr));
266278
}
267279
}

shell/platform/windows/keyboard_unittests.cc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,10 @@ class TestFlutterWindowsView : public FlutterWindowsView {
175175

176176
uint32_t redispatch_char;
177177

178-
void InjectPendingEvents(MockFlutterWindowWin32* win32window,
179-
uint32_t redispatch_char) {
178+
int InjectPendingEvents(MockFlutterWindowWin32* win32window,
179+
uint32_t redispatch_char) {
180180
std::vector<Win32Message> messages;
181+
int num_pending_responds = pending_responds_.size();
181182
for (const SendInputInfo& input : pending_responds_) {
182183
const KEYBDINPUT kbdinput = input.kbdinput;
183184
const UINT message =
@@ -200,6 +201,7 @@ class TestFlutterWindowsView : public FlutterWindowsView {
200201

201202
win32window->InjectMessageList(messages.size(), messages.data());
202203
pending_responds_.clear();
204+
return num_pending_responds;
203205
}
204206

205207
void SetKeyState(uint32_t key, bool pressed, bool toggled_on) {
@@ -294,10 +296,12 @@ class KeyboardTester {
294296
// Inject all events called with |SendInput| to the event queue,
295297
// then process the event queue.
296298
//
299+
// Returns the number of events injected.
300+
//
297301
// If |redispatch_char| is not 0, then WM_KEYDOWN events will
298302
// also redispatch a WM_CHAR event with that value as lparam.
299-
void InjectPendingEvents(uint32_t redispatch_char = 0) {
300-
view_->InjectPendingEvents(window_.get(), redispatch_char);
303+
int InjectPendingEvents(uint32_t redispatch_char = 0) {
304+
return view_->InjectPendingEvents(window_.get(), redispatch_char);
301305
}
302306

303307
static bool test_response;
@@ -802,7 +806,7 @@ TEST(KeyboardTest, DeadKeyThatCombines) {
802806
kNotSynthesized);
803807
clear_key_calls();
804808

805-
tester.InjectPendingEvents(0); // No WM_DEADCHAR messages sent here.
809+
EXPECT_EQ(tester.InjectPendingEvents(), 0);
806810
EXPECT_EQ(key_calls.size(), 0);
807811
clear_key_calls();
808812

0 commit comments

Comments
 (0)