Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
keyboard_manager.cc
Go to the documentation of this file.
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#include <memory>
6#include <string>
7
8#include "flutter/fml/logging.h"
9#include "flutter/shell/platform/windows/keyboard_manager.h"
10#include "flutter/shell/platform/windows/keyboard_utils.h"
11
12namespace flutter {
13
14namespace {
15
16// The maximum number of pending events to keep before
17// emitting a warning on the console about unhandled events.
18constexpr int kMaxPendingEvents = 1000;
19
20// Returns true if this key is an AltRight key down event.
21//
22// This is used to resolve an issue where an AltGr press causes CtrlLeft to hang
23// when pressed, as reported in https://github.com/flutter/flutter/issues/78005.
24//
25// When AltGr is pressed (in a supporting layout such as Spanish), Win32 first
26// fires a fake CtrlLeft down event, then an AltRight down event.
27// This is significant because this fake CtrlLeft down event will not be paired
28// with a up event, which is fine until Flutter redispatches the CtrlDown
29// event, which Win32 then interprets as a real event, leaving both Win32 and
30// the Flutter framework thinking that CtrlLeft is still pressed.
31//
32// To resolve this, Flutter recognizes this fake CtrlLeft down event using the
33// following AltRight down event. Flutter then forges a CtrlLeft key up event
34// immediately after the corresponding AltRight key up event.
35//
36// One catch is that it is impossible to distinguish the fake CtrlLeft down
37// from a normal CtrlLeft down (followed by a AltRight down), since they
38// contain the exactly same information, including the GetKeyState result.
39// Fortunately, this will require the two events to occur *really* close, which
40// would be rare, and a misrecognition would only cause a minor consequence
41// where the CtrlLeft is released early; the later, real, CtrlLeft up event will
42// be ignored.
43bool IsKeyDownAltRight(int action, int virtual_key, bool extended) {
44 return virtual_key == VK_RMENU && extended &&
45 (action == WM_KEYDOWN || action == WM_SYSKEYDOWN);
46}
47
48// Returns true if this key is a key up event of AltRight.
49//
50// This is used to assist a corner case described in |IsKeyDownAltRight|.
51bool IsKeyUpAltRight(int action, int virtual_key, bool extended) {
52 return virtual_key == VK_RMENU && extended &&
53 (action == WM_KEYUP || action == WM_SYSKEYUP);
54}
55
56// Returns true if this key is a key down event of CtrlLeft.
57//
58// This is used to assist a corner case described in |IsKeyDownAltRight|.
59bool IsKeyDownCtrlLeft(int action, int virtual_key) {
60 return virtual_key == VK_LCONTROL &&
61 (action == WM_KEYDOWN || action == WM_SYSKEYDOWN);
62}
63
64// Returns if a character sent by Win32 is a dead key.
65bool IsDeadKey(uint32_t ch) {
66 return (ch & kDeadKeyCharMask) != 0;
67}
68
69char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) {
70 return 0x10000 + ((static_cast<char32_t>(high) & 0x000003FF) << 10) +
71 (low & 0x3FF);
72}
73
74uint16_t ResolveKeyCode(uint16_t original, bool extended, uint8_t scancode) {
75 switch (original) {
76 case VK_SHIFT:
77 case VK_LSHIFT:
78 return MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX);
79 case VK_MENU:
80 case VK_LMENU:
81 return extended ? VK_RMENU : VK_LMENU;
82 case VK_CONTROL:
83 case VK_LCONTROL:
84 return extended ? VK_RCONTROL : VK_LCONTROL;
85 default:
86 return original;
87 }
88}
89
90bool IsPrintable(uint32_t c) {
91 constexpr char32_t kMinPrintable = ' ';
92 constexpr char32_t kDelete = 0x7F;
93 return c >= kMinPrintable && c != kDelete;
94}
95
96bool IsSysAction(UINT action) {
97 return action == WM_SYSKEYDOWN || action == WM_SYSKEYUP ||
98 action == WM_SYSCHAR || action == WM_SYSDEADCHAR;
99}
100
101} // namespace
102
104 : window_delegate_(delegate),
105 last_key_is_ctrl_left_down(false),
106 should_synthesize_ctrl_left_up(false),
107 processing_event_(false) {}
108
109void KeyboardManager::RedispatchEvent(std::unique_ptr<PendingEvent> event) {
110 for (const Win32Message& message : event->session) {
111 // Never redispatch sys keys, because their original messages have been
112 // passed to the system default processor.
113 if (IsSysAction(message.action)) {
114 continue;
115 }
116 pending_redispatches_.push_back(message);
117 UINT result = window_delegate_->Win32DispatchMessage(
119 if (result != 0) {
120 FML_LOG(ERROR) << "Unable to synthesize event for keyboard event.";
121 }
122 }
123 if (pending_redispatches_.size() > kMaxPendingEvents) {
125 << "There are " << pending_redispatches_.size()
126 << " keyboard events that have not yet received a response from the "
127 << "framework. Are responses being sent?";
128 }
129}
130
131bool KeyboardManager::RemoveRedispatchedMessage(UINT const action,
132 WPARAM const wparam,
133 LPARAM const lparam) {
134 for (auto iter = pending_redispatches_.begin();
135 iter != pending_redispatches_.end(); ++iter) {
136 if (action == iter->action && wparam == iter->wparam) {
137 pending_redispatches_.erase(iter);
138 return true;
139 }
140 }
141 return false;
142}
143
145 WPARAM const wparam,
146 LPARAM const lparam) {
147 if (RemoveRedispatchedMessage(action, wparam, lparam)) {
148 return false;
149 }
150 switch (action) {
151 case WM_DEADCHAR:
152 case WM_SYSDEADCHAR:
153 case WM_CHAR:
154 case WM_SYSCHAR: {
155 const Win32Message message =
156 Win32Message{.action = action, .wparam = wparam, .lparam = lparam};
157 current_session_.push_back(message);
158
159 char32_t code_point;
160 if (message.IsHighSurrogate()) {
161 // A high surrogate is always followed by a low surrogate. Process the
162 // session later and consider this message as handled.
163 return true;
164 } else if (message.IsLowSurrogate()) {
165 const Win32Message* last_message =
166 current_session_.size() <= 1
167 ? nullptr
168 : &current_session_[current_session_.size() - 2];
169 if (last_message == nullptr || !last_message->IsHighSurrogate()) {
170 return false;
171 }
172 // A low surrogate always follows a high surrogate, marking the end of
173 // a char session. Process the session after the if clause.
174 code_point =
175 CodePointFromSurrogatePair(last_message->wparam, message.wparam);
176 } else {
177 // A non-surrogate character always appears alone. Process the session
178 // after the if clause.
179 code_point = static_cast<wchar_t>(message.wparam);
180 }
181
182 // If this char message is preceded by a key down message, then dispatch
183 // the key down message as a key down event first, and only dispatch the
184 // OnText if the key down event is not handled.
185 if (current_session_.front().IsGeneralKeyDown()) {
186 const Win32Message first_message = current_session_.front();
187 const uint8_t scancode = (lparam >> 16) & 0xff;
188 const uint16_t key_code = first_message.wparam;
189 const bool extended = ((lparam >> 24) & 0x01) == 0x01;
190 const bool was_down = lparam & 0x40000000;
191 // Certain key combinations yield control characters as WM_CHAR's
192 // lParam. For example, 0x01 for Ctrl-A. Filter these characters. See
193 // https://docs.microsoft.com/en-us/windows/win32/learnwin32/accelerator-tables
194 char32_t character;
195 if (action == WM_DEADCHAR || action == WM_SYSDEADCHAR) {
196 // Mask the resulting char with kDeadKeyCharMask anyway, because in
197 // rare cases the bit is *not* set (US INTL Shift-6 circumflex, see
198 // https://github.com/flutter/flutter/issues/92654 .)
199 character =
200 window_delegate_->Win32MapVkToChar(key_code) | kDeadKeyCharMask;
201 } else {
202 character = IsPrintable(code_point) ? code_point : 0;
203 }
204 auto event = std::make_unique<PendingEvent>(PendingEvent{
205 .key = key_code,
206 .scancode = scancode,
207 .action = static_cast<UINT>(action == WM_SYSCHAR ? WM_SYSKEYDOWN
208 : WM_KEYDOWN),
212 .session = std::move(current_session_),
213 });
214
215 pending_events_.push_back(std::move(event));
216 ProcessNextEvent();
217
218 // SYS messages must not be consumed by `HandleMessage` so that they are
219 // forwarded to the system.
220 return !IsSysAction(action);
221 }
222
223 // If the charcter session is not preceded by a key down message,
224 // mark PendingEvent::action as WM_CHAR, informing |PerformProcessEvent|
225 // to dispatch the text content immediately.
226 //
227 // Only WM_CHAR should be treated as characters. WM_SYS*CHAR are not part
228 // of text input, and WM_DEADCHAR will be incorporated into a later
229 // WM_CHAR with the full character.
230 if (action == WM_CHAR) {
231 auto event = std::make_unique<PendingEvent>(PendingEvent{
232 .action = WM_CHAR,
233 .character = code_point,
234 .session = std::move(current_session_),
235 });
236 pending_events_.push_back(std::move(event));
237 ProcessNextEvent();
238 }
239 return true;
240 }
241
242 case WM_KEYDOWN:
243 case WM_SYSKEYDOWN:
244 case WM_KEYUP:
245 case WM_SYSKEYUP: {
246 if (wparam == VK_PACKET) {
247 return false;
248 }
249
250 const uint8_t scancode = (lparam >> 16) & 0xff;
251 const bool extended = ((lparam >> 24) & 0x01) == 0x01;
252 // If the key is a modifier, get its side.
253 const uint16_t key_code = ResolveKeyCode(wparam, extended, scancode);
254 const bool was_down = lparam & 0x40000000;
255
256 // Detect a pattern of key events in order to forge a CtrlLeft up event.
257 // See |IsKeyDownAltRight| for explanation.
258 if (IsKeyDownAltRight(action, key_code, extended)) {
259 if (last_key_is_ctrl_left_down) {
260 should_synthesize_ctrl_left_up = true;
261 }
262 }
263 if (IsKeyDownCtrlLeft(action, key_code)) {
264 last_key_is_ctrl_left_down = true;
265 ctrl_left_scancode = scancode;
266 should_synthesize_ctrl_left_up = false;
267 } else {
268 last_key_is_ctrl_left_down = false;
269 }
270 if (IsKeyUpAltRight(action, key_code, extended)) {
271 if (should_synthesize_ctrl_left_up) {
272 should_synthesize_ctrl_left_up = false;
273 const LPARAM lParam =
274 (1 /* repeat_count */ << 0) | (ctrl_left_scancode << 16) |
275 (0 /* extended */ << 24) | (1 /* prev_state */ << 30) |
276 (1 /* transition */ << 31);
277 window_delegate_->Win32DispatchMessage(WM_KEYUP, VK_CONTROL, lParam);
278 }
279 }
280
281 current_session_.clear();
282 current_session_.push_back(
283 Win32Message{.action = action, .wparam = wparam, .lparam = lparam});
284 const bool is_keydown_message =
285 (action == WM_KEYDOWN || action == WM_SYSKEYDOWN);
286 // Check if this key produces a character by peeking if this key down
287 // message has a following char message. Certain key messages are not
288 // followed by char messages even though `MapVirtualKey` returns a valid
289 // character (such as Ctrl + Digit, see
290 // https://github.com/flutter/flutter/issues/85587 ).
291 unsigned int character = window_delegate_->Win32MapVkToChar(wparam);
292 UINT next_key_action = PeekNextMessageType(WM_KEYFIRST, WM_KEYLAST);
293 bool has_char_action =
294 (next_key_action == WM_DEADCHAR ||
295 next_key_action == WM_SYSDEADCHAR || next_key_action == WM_CHAR ||
296 next_key_action == WM_SYSCHAR);
297 if (character > 0 && is_keydown_message && has_char_action) {
298 // This key down message has a following char message. Process this
299 // session in the char message, because the character for the key call
300 // should be decided by the char events. Consider this message as
301 // handled.
302 return true;
303 }
304
305 // This key down message is not followed by a char message. Conclude this
306 // session.
307 auto event = std::make_unique<PendingEvent>(PendingEvent{
308 .key = key_code,
309 .scancode = scancode,
310 .action = action,
311 .character = 0,
312 .extended = extended,
313 .was_down = was_down,
314 .session = std::move(current_session_),
315 });
316 pending_events_.push_back(std::move(event));
317 ProcessNextEvent();
318 // SYS messages must not be consumed by `HandleMessage` so that they are
319 // forwarded to the system.
320 return !IsSysAction(action);
321 }
322 default:
323 FML_LOG(FATAL) << "No event handler for keyboard event with action "
324 << action;
325 }
326 return false;
327}
328
329void KeyboardManager::ProcessNextEvent() {
330 if (processing_event_ || pending_events_.empty()) {
331 return;
332 }
333 processing_event_ = true;
334 auto pending_event = std::move(pending_events_.front());
335 pending_events_.pop_front();
336 PerformProcessEvent(std::move(pending_event), [this] {
337 FML_DCHECK(processing_event_);
338 processing_event_ = false;
339 ProcessNextEvent();
340 });
341}
342
343void KeyboardManager::PerformProcessEvent(std::unique_ptr<PendingEvent> event,
344 std::function<void()> callback) {
345 // PendingEvent::action being WM_CHAR means this is a char message without
346 // a preceding key message, and should be dispatched immediately.
347 if (event->action == WM_CHAR) {
348 DispatchText(*event);
349 callback();
350 return;
351 }
352
353 // A unique_ptr can't be sent into a lambda without C++23's
354 // move_only_function. Until then, `event` is sent as a raw pointer, hoping
355 // WindowDelegate::OnKey to correctly call it once and only once.
356 PendingEvent* event_p = event.release();
357 window_delegate_->OnKey(
358 event_p->key, event_p->scancode, event_p->action, event_p->character,
359 event_p->extended, event_p->was_down,
360 [this, event_p, callback = std::move(callback)](bool handled) {
361 HandleOnKeyResult(std::unique_ptr<PendingEvent>(event_p), handled);
362 callback();
363 });
364}
365
366void KeyboardManager::HandleOnKeyResult(std::unique_ptr<PendingEvent> event,
367 bool framework_handled) {
368 const UINT last_action = event->session.back().action;
369 // SYS messages must not be redispached, and their text content is not
370 // dispatched either.
371 bool handled = framework_handled || IsSysAction(last_action);
372
373 if (handled) {
374 return;
375 }
376
377 // Only WM_CHAR should be treated as characters. WM_SYS*CHAR are not part of
378 // text input, and WM_DEADCHAR will be incorporated into a later WM_CHAR with
379 // the full character.
380 if (last_action == WM_CHAR) {
381 DispatchText(*event);
382 }
383
384 RedispatchEvent(std::move(event));
385}
386
387void KeyboardManager::DispatchText(const PendingEvent& event) {
388 // Check if the character is printable based on the last wparam, which works
389 // even if the last wparam is a low surrogate, because the only unprintable
390 // keys defined by `IsPrintable` are certain characters at lower ASCII range.
391 // These ASCII control characters are sent as WM_CHAR events for all control
392 // key shortcuts.
393 FML_DCHECK(!event.session.empty());
394 bool is_printable = IsPrintable(event.session.back().wparam);
395 bool valid = event.character != 0 && is_printable;
396 if (valid) {
397 auto text = EncodeUtf16(event.character);
398 window_delegate_->OnText(text);
399 }
400}
401
402UINT KeyboardManager::PeekNextMessageType(UINT wMsgFilterMin,
403 UINT wMsgFilterMax) {
404 MSG next_message;
405 BOOL has_msg = window_delegate_->Win32PeekMessage(
406 &next_message, wMsgFilterMin, wMsgFilterMax, PM_NOREMOVE);
407 if (!has_msg) {
408 return 0;
409 }
410 return next_message.message;
411}
412
413} // namespace flutter
virtual UINT Win32DispatchMessage(UINT Msg, WPARAM wParam, LPARAM lParam)=0
virtual void OnText(const std::u16string &text)=0
virtual BOOL Win32PeekMessage(LPMSG lpMsg, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)=0
virtual void OnKey(int key, int scancode, int action, char32_t character, bool extended, bool was_down, KeyEventCallback callback)=0
virtual uint32_t Win32MapVkToChar(uint32_t virtual_key)=0
KeyboardManager(WindowDelegate *delegate)
bool HandleMessage(UINT const message, WPARAM const wparam, LPARAM const lparam)
virtual void RedispatchEvent(std::unique_ptr< PendingEvent > event)
#define FATAL(error)
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event
GAsyncResult * result
#define FML_LOG(severity)
Definition logging.h:82
#define FML_DCHECK(condition)
Definition logging.h:103
std::u16string text
Win32Message message
std::u16string EncodeUtf16(char32_t character)
constexpr int kDeadKeyCharMask
#define ERROR(message)
int BOOL
struct tagMSG MSG
unsigned int UINT
LONG_PTR LPARAM
UINT_PTR WPARAM