Skip to content

Commit 538bb80

Browse files
authored
[Win32, Keyboard] Store keyboard message session (flutter#31047)
* Impl * Move session to pending event * Format * better type
1 parent bd404ac commit 538bb80

File tree

2 files changed

+137
-113
lines changed

2 files changed

+137
-113
lines changed

shell/platform/windows/keyboard_manager_win32.cc

Lines changed: 103 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ static bool IsKeyDownShiftRight(int virtual_key, bool was_down) {
9393
}
9494

9595
// Returns if a character sent by Win32 is a dead key.
96-
static bool _IsDeadKey(uint32_t ch) {
96+
static bool IsDeadKey(uint32_t ch) {
9797
return (ch & kDeadKeyCharMask) != 0;
9898
}
9999

@@ -181,49 +181,33 @@ bool KeyboardManagerWin32::RemoveRedispatchedEvent(
181181
const PendingEvent& incoming) {
182182
for (auto iter = pending_redispatches_.begin();
183183
iter != pending_redispatches_.end(); ++iter) {
184-
if ((*iter)->hash == incoming.hash) {
184+
if ((*iter)->Hash() == incoming.Hash()) {
185185
pending_redispatches_.erase(iter);
186186
return true;
187187
}
188188
}
189189
return false;
190190
}
191191

192-
bool KeyboardManagerWin32::OnKey(int key,
193-
int scancode,
194-
int action,
195-
char32_t character,
196-
bool extended,
197-
bool was_down,
192+
bool KeyboardManagerWin32::OnKey(std::unique_ptr<PendingEvent> event,
198193
OnKeyCallback callback) {
199-
std::unique_ptr<PendingEvent> incoming =
200-
std::make_unique<PendingEvent>(PendingEvent{
201-
.key = static_cast<uint32_t>(key),
202-
.scancode = static_cast<uint8_t>(scancode),
203-
.action = static_cast<uint32_t>(action),
204-
.character = character,
205-
.extended = extended,
206-
.was_down = was_down,
207-
});
208-
incoming->hash = ComputeEventHash(*incoming);
209-
210-
if (RemoveRedispatchedEvent(*incoming)) {
194+
if (RemoveRedispatchedEvent(*event)) {
211195
return false;
212196
}
213197

214-
if (IsKeyDownAltRight(action, key, extended)) {
198+
if (IsKeyDownAltRight(event->action, event->key, event->extended)) {
215199
if (last_key_is_ctrl_left_down) {
216200
should_synthesize_ctrl_left_up = true;
217201
}
218202
}
219-
if (IsKeyDownCtrlLeft(action, key)) {
203+
if (IsKeyDownCtrlLeft(event->action, event->key)) {
220204
last_key_is_ctrl_left_down = true;
221-
ctrl_left_scancode = scancode;
205+
ctrl_left_scancode = event->scancode;
222206
should_synthesize_ctrl_left_up = false;
223207
} else {
224208
last_key_is_ctrl_left_down = false;
225209
}
226-
if (IsKeyUpAltRight(action, key, extended)) {
210+
if (IsKeyUpAltRight(event->action, event->key, event->extended)) {
227211
if (should_synthesize_ctrl_left_up) {
228212
should_synthesize_ctrl_left_up = false;
229213
PendingEvent ctrl_left_up{
@@ -236,8 +220,10 @@ bool KeyboardManagerWin32::OnKey(int key,
236220
}
237221
}
238222

239-
window_delegate_->OnKey(key, scancode, action, character, extended, was_down,
240-
[this, event = incoming.release(),
223+
const PendingEvent clone = *event;
224+
window_delegate_->OnKey(clone.key, clone.scancode, clone.action,
225+
clone.character, clone.extended, clone.was_down,
226+
[this, event = event.release(),
241227
callback = std::move(callback)](bool handled) {
242228
callback(std::unique_ptr<PendingEvent>(event),
243229
handled);
@@ -261,7 +247,7 @@ void KeyboardManagerWin32::HandleOnKeyResult(
261247
// |SendInput|.
262248
const bool is_syskey =
263249
event->action == WM_SYSKEYDOWN || event->action == WM_SYSKEYUP;
264-
const bool real_handled = handled || _IsDeadKey(event->character) ||
250+
const bool real_handled = handled || IsDeadKey(event->character) ||
265251
is_syskey ||
266252
IsKeyDownShiftRight(event->key, event->was_down);
267253

@@ -284,93 +270,101 @@ void KeyboardManagerWin32::HandleOnKeyResult(
284270
RedispatchEvent(std::move(event));
285271
}
286272

287-
bool KeyboardManagerWin32::HandleMessage(UINT const message,
273+
bool KeyboardManagerWin32::HandleMessage(UINT const action,
288274
WPARAM const wparam,
289275
LPARAM const lparam) {
290-
switch (message) {
276+
switch (action) {
291277
case WM_DEADCHAR:
292278
case WM_SYSDEADCHAR:
293279
case WM_CHAR:
294280
case WM_SYSCHAR: {
295-
static wchar_t s_pending_high_surrogate = 0;
281+
const Win32Message message =
282+
Win32Message{.action = action, .wparam = wparam, .lparam = lparam};
283+
current_session_.push_back(message);
296284

297-
wchar_t character = static_cast<wchar_t>(wparam);
298285
std::u16string text;
299286
char32_t code_point;
300-
if (IS_HIGH_SURROGATE(character)) {
301-
// Save to send later with the trailing surrogate.
302-
s_pending_high_surrogate = character;
287+
if (message.IsHighSurrogate()) {
288+
// A high surrogate is always followed by a low surrogate. Process the
289+
// session later and consider this message as handled.
303290
return true;
304-
} else if (IS_LOW_SURROGATE(character) && s_pending_high_surrogate != 0) {
305-
text.push_back(s_pending_high_surrogate);
306-
text.push_back(character);
307-
// Merge the surrogate pairs for the key event.
291+
} else if (message.IsLowSurrogate()) {
292+
const Win32Message* last_message =
293+
current_session_.size() <= 1
294+
? nullptr
295+
: &current_session_[current_session_.size() - 2];
296+
if (last_message == nullptr || !last_message->IsHighSurrogate()) {
297+
return false;
298+
}
299+
// A low surrogate always follows a high surrogate, marking the end of
300+
// a char session. Process the session after the if clause.
301+
text.push_back(static_cast<wchar_t>(last_message->wparam));
302+
text.push_back(static_cast<wchar_t>(message.wparam));
308303
code_point =
309-
CodePointFromSurrogatePair(s_pending_high_surrogate, character);
310-
s_pending_high_surrogate = 0;
304+
CodePointFromSurrogatePair(last_message->wparam, message.wparam);
311305
} else {
312-
text.push_back(character);
313-
code_point = character;
306+
// A non-surrogate character always appears alone. Process the session
307+
// after the if clause.
308+
text.push_back(static_cast<wchar_t>(message.wparam));
309+
code_point = static_cast<wchar_t>(message.wparam);
314310
}
315311

316-
const unsigned int scancode = (lparam >> 16) & 0xff;
317-
318-
// All key presses that generate a character should be sent from
319-
// WM_CHAR. In order to send the full key press information, the keycode
320-
// is persisted in keycode_for_char_message_ obtained from WM_KEYDOWN.
321-
//
322-
// A high surrogate is always followed by a low surrogate, while a
323-
// non-surrogate character always appears alone. Filter out high
324-
// surrogates so that it's the low surrogate message that triggers
325-
// the onKey, asks if the framework handles it (which can only be done
326-
// once), and calls OnText during the redispatched messages.
327-
if (keycode_for_char_message_ != 0 && !IS_HIGH_SURROGATE(character)) {
312+
// If this char message is preceded by a key down message, then dispatch
313+
// the key down message as a key down event first, and only dispatch the
314+
// OnText if the key down event is not handled.
315+
if (current_session_.front().IsGeneralKeyDown()) {
316+
const Win32Message first_message = current_session_.front();
317+
current_session_.clear();
318+
const uint8_t scancode = (lparam >> 16) & 0xff;
319+
const uint16_t key_code = first_message.wparam;
328320
const bool extended = ((lparam >> 24) & 0x01) == 0x01;
329321
const bool was_down = lparam & 0x40000000;
330322
// Certain key combinations yield control characters as WM_CHAR's
331323
// lParam. For example, 0x01 for Ctrl-A. Filter these characters. See
332324
// https://docs.microsoft.com/en-us/windows/win32/learnwin32/accelerator-tables
333-
char32_t event_character;
334-
if (message == WM_DEADCHAR || message == WM_SYSDEADCHAR) {
325+
char32_t character;
326+
if (action == WM_DEADCHAR || action == WM_SYSDEADCHAR) {
335327
// Mask the resulting char with kDeadKeyCharMask anyway, because in
336328
// rare cases the bit is *not* set (US INTL Shift-6 circumflex, see
337329
// https://github.com/flutter/flutter/issues/92654 .)
338-
event_character =
339-
window_delegate_->Win32MapVkToChar(keycode_for_char_message_) |
340-
kDeadKeyCharMask;
330+
character =
331+
window_delegate_->Win32MapVkToChar(key_code) | kDeadKeyCharMask;
341332
} else {
342-
event_character = IsPrintable(code_point) ? code_point : 0;
343-
}
344-
bool is_new_event =
345-
OnKey(keycode_for_char_message_, scancode,
346-
message == WM_SYSCHAR ? WM_SYSKEYDOWN : WM_KEYDOWN,
347-
event_character, extended, was_down,
348-
[this, message, text](std::unique_ptr<PendingEvent> event,
349-
bool handled) {
350-
HandleOnKeyResult(std::move(event), handled, message, text);
351-
});
352-
if (!is_new_event) {
353-
break;
333+
character = IsPrintable(code_point) ? code_point : 0;
354334
}
355-
keycode_for_char_message_ = 0;
356-
335+
auto event = std::make_unique<PendingEvent>(PendingEvent{
336+
.key = key_code,
337+
.scancode = scancode,
338+
.action = static_cast<UINT>(action == WM_SYSCHAR ? WM_SYSKEYDOWN
339+
: WM_KEYDOWN),
340+
.character = character,
341+
.extended = extended,
342+
.was_down = was_down,
343+
.session = std::move(current_session_),
344+
});
345+
const bool is_unmet_event = OnKey(
346+
std::move(event),
347+
[this, char_action = action, text](
348+
std::unique_ptr<PendingEvent> event, bool handled) {
349+
HandleOnKeyResult(std::move(event), handled, char_action, text);
350+
});
351+
const bool is_syskey = action == WM_SYSCHAR;
357352
// For system characters, always pass them to the default WndProc so
358353
// that system keys like the ALT-TAB are processed correctly.
359-
if (message == WM_SYSCHAR) {
360-
break;
361-
}
362-
return true;
354+
return is_unmet_event && !is_syskey;
363355
}
364356

365-
// Of the messages handled here, only WM_CHAR should be treated as
366-
// characters. WM_SYS*CHAR are not part of text input, and WM_DEADCHAR
367-
// will be incorporated into a later WM_CHAR with the full character.
368-
// Also filter out:
369-
// - Lead surrogates, which like dead keys will be send once combined.
370-
// - ASCII control characters, which are sent as WM_CHAR events for all
371-
// control key shortcuts.
372-
if (message == WM_CHAR && s_pending_high_surrogate == 0 &&
373-
IsPrintable(character)) {
357+
// If the charcter session is not preceded by a key down message, dispatch
358+
// the OnText immediately.
359+
360+
// Only WM_CHAR should be treated as characters. WM_SYS*CHAR are not part
361+
// of text input, and WM_DEADCHAR will be incorporated into a later
362+
// WM_CHAR with the full character.
363+
//
364+
// Also filter out ASCII control characters, which are sent as WM_CHAR
365+
// events for all control key shortcuts.
366+
current_session_.clear();
367+
if (action == WM_CHAR && IsPrintable(wparam)) {
374368
window_delegate_->OnText(text);
375369
}
376370
return true;
@@ -380,8 +374,11 @@ bool KeyboardManagerWin32::HandleMessage(UINT const message,
380374
case WM_SYSKEYDOWN:
381375
case WM_KEYUP:
382376
case WM_SYSKEYUP: {
377+
current_session_.clear();
378+
current_session_.push_back(
379+
Win32Message{.action = action, .wparam = wparam, .lparam = lparam});
383380
const bool is_keydown_message =
384-
(message == WM_KEYDOWN || message == WM_SYSKEYDOWN);
381+
(action == WM_KEYDOWN || action == WM_SYSKEYDOWN);
385382
// Check if this key produces a character. If so, the key press should
386383
// be sent with the character produced at WM_CHAR. Store the produced
387384
// keycode (it's not accessible from WM_CHAR) to be used in WM_CHAR.
@@ -397,30 +394,36 @@ bool KeyboardManagerWin32::HandleMessage(UINT const message,
397394
next_key_message == WM_SYSDEADCHAR || next_key_message == WM_CHAR ||
398395
next_key_message == WM_SYSCHAR);
399396
if (character > 0 && is_keydown_message && has_wm_char) {
400-
keycode_for_char_message_ = wparam;
397+
// This key down message has following char events. Process later,
398+
// because the character for the OnKey should be decided by the char
399+
// events. Consider this event as handled.
401400
return true;
402401
}
403-
unsigned int keyCode(wparam);
402+
403+
// Resolve session: A non-char key event.
404404
const uint8_t scancode = (lparam >> 16) & 0xff;
405405
const bool extended = ((lparam >> 24) & 0x01) == 0x01;
406406
// If the key is a modifier, get its side.
407-
keyCode = ResolveKeyCode(keyCode, extended, scancode);
407+
const uint16_t key_code = ResolveKeyCode(wparam, extended, scancode);
408408
const bool was_down = lparam & 0x40000000;
409-
bool is_syskey = message == WM_SYSKEYDOWN || message == WM_SYSKEYUP;
410-
bool is_new_event = OnKey(
411-
keyCode, scancode, message, 0, extended, was_down,
409+
auto event = std::make_unique<PendingEvent>(PendingEvent{
410+
.key = key_code,
411+
.scancode = scancode,
412+
.action = action,
413+
.character = 0,
414+
.extended = extended,
415+
.was_down = was_down,
416+
.session = std::move(current_session_),
417+
});
418+
const bool is_unmet_event = OnKey(
419+
std::move(event),
412420
[this](std::unique_ptr<PendingEvent> event, bool handled) {
413421
HandleOnKeyResult(std::move(event), handled, 0, std::u16string());
414422
});
415-
if (!is_new_event) {
416-
break;
417-
}
423+
const bool is_syskey = action == WM_SYSKEYDOWN || action == WM_SYSKEYUP;
418424
// For system keys, always pass them to the default WndProc so that keys
419425
// like the ALT-TAB or Kanji switches are processed correctly.
420-
if (is_syskey) {
421-
break;
422-
}
423-
return true;
426+
return is_unmet_event && !is_syskey;
424427
}
425428
default:
426429
assert(false);

shell/platform/windows/keyboard_manager_win32.h

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ namespace flutter {
2626
// implements the window delegate. The |OnKey| and |OnText| results are
2727
// passed to those of |WindowWin32|'s, and consequently, those of
2828
// |FlutterWindowsView|'s.
29+
//
30+
// ## Terminology
31+
//
32+
// The keyboard system follows the following terminology instead of the
33+
// inconsistent/incomplete one used by Win32:
34+
//
35+
// * Message: An invocation of |WndProc|, which consists of an
36+
// action, an lparam, and a wparam.
37+
// * Action: The type of a message.
38+
// * Session: One to three messages that should be processed together, such
39+
// as a key down message followed by char messages.
40+
// * Event: A FlutterKeyEvent/ui.KeyData sent to the framework.
2941
class KeyboardManagerWin32 {
3042
public:
3143
// Define how the keyboard manager accesses Win32 system calls (to allow
@@ -92,30 +104,40 @@ class KeyboardManagerWin32 {
92104
LPARAM const lparam);
93105

94106
private:
107+
struct Win32Message {
108+
UINT const action;
109+
WPARAM const wparam;
110+
LPARAM const lparam;
111+
112+
bool IsHighSurrogate() const { return IS_HIGH_SURROGATE(wparam); }
113+
114+
bool IsLowSurrogate() const { return IS_LOW_SURROGATE(wparam); }
115+
116+
bool IsGeneralKeyDown() const {
117+
return action == WM_KEYDOWN || action == WM_SYSKEYDOWN;
118+
}
119+
};
120+
95121
struct PendingEvent {
96-
uint32_t key;
122+
WPARAM key;
97123
uint8_t scancode;
98-
uint32_t action;
124+
UINT action;
99125
char32_t character;
100126
bool extended;
101127
bool was_down;
102128

129+
std::vector<Win32Message> session;
130+
103131
// A value calculated out of critical event information that can be used
104132
// to identify redispatched events.
105-
uint64_t hash;
133+
uint64_t Hash() const { return ComputeEventHash(*this); }
106134
};
107135

108136
using OnKeyCallback =
109137
std::function<void(std::unique_ptr<PendingEvent>, bool)>;
110138

111139
// Returns true if it's a new event, or false if it's a redispatched event.
112-
bool OnKey(int key,
113-
int scancode,
114-
int action,
115-
char32_t character,
116-
bool extended,
117-
bool was_down,
118-
OnKeyCallback callback);
140+
bool OnKey(std::unique_ptr<PendingEvent> event, OnKeyCallback callback);
119141

120142
void HandleOnKeyResult(std::unique_ptr<PendingEvent> event,
121143
bool handled,
@@ -141,9 +163,8 @@ class KeyboardManagerWin32 {
141163

142164
WindowDelegate* window_delegate_;
143165

144-
// Keeps track of the last key code produced by a WM_KEYDOWN or WM_SYSKEYDOWN
145-
// message.
146-
int keycode_for_char_message_ = 0;
166+
// Keeps track of all messages during the current session.
167+
std::vector<Win32Message> current_session_;
147168

148169
std::map<uint16_t, std::u16string> text_for_scancode_on_redispatch_;
149170

0 commit comments

Comments
 (0)