forked from flutter/engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Win32, Keyboard] Abstract KeyboardManager from FlutterWindowWin32 (f…
- Loading branch information
Showing
12 changed files
with
402 additions
and
267 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include <assert.h> | ||
#include <string> | ||
|
||
#include "keyboard_manager_win32.h" | ||
|
||
#include "keyboard_win32_common.h" | ||
|
||
namespace flutter { | ||
|
||
static char32_t CodePointFromSurrogatePair(wchar_t high, wchar_t low) { | ||
return 0x10000 + ((static_cast<char32_t>(high) & 0x000003FF) << 10) + | ||
(low & 0x3FF); | ||
} | ||
|
||
static uint16_t ResolveKeyCode(uint16_t original, | ||
bool extended, | ||
uint8_t scancode) { | ||
switch (original) { | ||
case VK_SHIFT: | ||
case VK_LSHIFT: | ||
return MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX); | ||
case VK_MENU: | ||
case VK_LMENU: | ||
return extended ? VK_RMENU : VK_LMENU; | ||
case VK_CONTROL: | ||
case VK_LCONTROL: | ||
return extended ? VK_RCONTROL : VK_LCONTROL; | ||
default: | ||
return original; | ||
} | ||
} | ||
|
||
static bool IsPrintable(uint32_t c) { | ||
constexpr char32_t kMinPrintable = ' '; | ||
constexpr char32_t kDelete = 0x7F; | ||
return c >= kMinPrintable && c != kDelete; | ||
} | ||
|
||
KeyboardManagerWin32::KeyboardManagerWin32(WindowDelegate* delegate) | ||
: window_delegate_(delegate) {} | ||
|
||
bool KeyboardManagerWin32::HandleMessage(UINT const message, | ||
WPARAM const wparam, | ||
LPARAM const lparam) { | ||
switch (message) { | ||
case WM_DEADCHAR: | ||
case WM_SYSDEADCHAR: | ||
case WM_CHAR: | ||
case WM_SYSCHAR: { | ||
static wchar_t s_pending_high_surrogate = 0; | ||
|
||
wchar_t character = static_cast<wchar_t>(wparam); | ||
std::u16string text({character}); | ||
char32_t code_point = character; | ||
if (IS_HIGH_SURROGATE(character)) { | ||
// Save to send later with the trailing surrogate. | ||
s_pending_high_surrogate = character; | ||
} else if (IS_LOW_SURROGATE(character) && s_pending_high_surrogate != 0) { | ||
text.insert(text.begin(), s_pending_high_surrogate); | ||
// Merge the surrogate pairs for the key event. | ||
code_point = | ||
CodePointFromSurrogatePair(s_pending_high_surrogate, character); | ||
s_pending_high_surrogate = 0; | ||
} | ||
|
||
const unsigned int scancode = (lparam >> 16) & 0xff; | ||
|
||
// All key presses that generate a character should be sent from | ||
// WM_CHAR. In order to send the full key press information, the keycode | ||
// is persisted in keycode_for_char_message_ obtained from WM_KEYDOWN. | ||
// | ||
// A high surrogate is always followed by a low surrogate, while a | ||
// non-surrogate character always appears alone. Filter out high | ||
// surrogates so that it's the low surrogate message that triggers | ||
// the onKey, asks if the framework handles it (which can only be done | ||
// once), and calls OnText during the redispatched messages. | ||
if (keycode_for_char_message_ != 0 && !IS_HIGH_SURROGATE(character)) { | ||
const bool extended = ((lparam >> 24) & 0x01) == 0x01; | ||
const bool was_down = lparam & 0x40000000; | ||
// Certain key combinations yield control characters as WM_CHAR's | ||
// lParam. For example, 0x01 for Ctrl-A. Filter these characters. See | ||
// https://docs.microsoft.com/en-us/windows/win32/learnwin32/accelerator-tables | ||
char32_t event_character; | ||
if (message == WM_DEADCHAR || message == WM_SYSDEADCHAR) { | ||
// Mask the resulting char with kDeadKeyCharMask anyway, because in | ||
// rare cases the bit is *not* set (US INTL Shift-6 circumflex, see | ||
// https://github.com/flutter/flutter/issues/92654 .) | ||
event_character = | ||
window_delegate_->Win32MapVkToChar(keycode_for_char_message_) | | ||
kDeadKeyCharMask; | ||
} else { | ||
event_character = IsPrintable(code_point) ? code_point : 0; | ||
} | ||
bool handled = window_delegate_->OnKey( | ||
keycode_for_char_message_, scancode, | ||
message == WM_SYSCHAR ? WM_SYSKEYDOWN : WM_KEYDOWN, event_character, | ||
extended, was_down); | ||
keycode_for_char_message_ = 0; | ||
if (handled) { | ||
// If the OnKey handler handles the message, then return so we don't | ||
// pass it to OnText, because handling the message indicates that | ||
// OnKey either just sent it to the framework to be processed. | ||
// | ||
// This message will be redispatched if not handled by the framework, | ||
// during which the OnText (below) might be reached. However, if the | ||
// original message was preceded by dead chars (such as ^ and e | ||
// yielding ê), then since the redispatched message is no longer | ||
// preceded by the dead char, the text will be wrong. Therefore we | ||
// record the text here for the redispached event to use. | ||
if (message == WM_CHAR) { | ||
text_for_scancode_on_redispatch_[scancode] = text; | ||
} | ||
|
||
// For system characters, always pass them to the default WndProc so | ||
// that system keys like the ALT-TAB are processed correctly. | ||
if (message == WM_SYSCHAR) { | ||
break; | ||
} | ||
return true; | ||
} | ||
} | ||
|
||
// Of the messages handled here, only WM_CHAR should be treated as | ||
// characters. WM_SYS*CHAR are not part of text input, and WM_DEADCHAR | ||
// will be incorporated into a later WM_CHAR with the full character. | ||
// Also filter out: | ||
// - Lead surrogates, which like dead keys will be send once combined. | ||
// - ASCII control characters, which are sent as WM_CHAR events for all | ||
// control key shortcuts. | ||
if (message == WM_CHAR && s_pending_high_surrogate == 0 && | ||
IsPrintable(character)) { | ||
auto found_text_iter = text_for_scancode_on_redispatch_.find(scancode); | ||
if (found_text_iter != text_for_scancode_on_redispatch_.end()) { | ||
text = found_text_iter->second; | ||
text_for_scancode_on_redispatch_.erase(found_text_iter); | ||
} | ||
window_delegate_->OnText(text); | ||
} | ||
return true; | ||
} | ||
|
||
case WM_KEYDOWN: | ||
case WM_SYSKEYDOWN: | ||
case WM_KEYUP: | ||
case WM_SYSKEYUP: { | ||
const bool is_keydown_message = | ||
(message == WM_KEYDOWN || message == WM_SYSKEYDOWN); | ||
// Check if this key produces a character. If so, the key press should | ||
// be sent with the character produced at WM_CHAR. Store the produced | ||
// keycode (it's not accessible from WM_CHAR) to be used in WM_CHAR. | ||
// | ||
// Messages with Control or Win modifiers down are never considered as | ||
// character messages. This allows key combinations such as "CTRL + Digit" | ||
// to properly produce key down events even though `MapVirtualKey` returns | ||
// a valid character. See https://github.com/flutter/flutter/issues/85587. | ||
unsigned int character = window_delegate_->Win32MapVkToChar(wparam); | ||
UINT next_key_message = PeekNextMessageType(WM_KEYFIRST, WM_KEYLAST); | ||
bool has_wm_char = | ||
(next_key_message == WM_DEADCHAR || | ||
next_key_message == WM_SYSDEADCHAR || next_key_message == WM_CHAR || | ||
next_key_message == WM_SYSCHAR); | ||
if (character > 0 && is_keydown_message && has_wm_char) { | ||
keycode_for_char_message_ = wparam; | ||
return true; | ||
} | ||
unsigned int keyCode(wparam); | ||
const uint8_t scancode = (lparam >> 16) & 0xff; | ||
const bool extended = ((lparam >> 24) & 0x01) == 0x01; | ||
// If the key is a modifier, get its side. | ||
keyCode = ResolveKeyCode(keyCode, extended, scancode); | ||
const bool was_down = lparam & 0x40000000; | ||
bool is_syskey = message == WM_SYSKEYDOWN || message == WM_SYSKEYUP; | ||
const int action = is_keydown_message | ||
? (is_syskey ? WM_SYSKEYDOWN : WM_KEYDOWN) | ||
: (is_syskey ? WM_SYSKEYUP : WM_KEYUP); | ||
if (window_delegate_->OnKey(keyCode, scancode, action, 0, extended, | ||
was_down)) { | ||
// For system keys, always pass them to the default WndProc so that keys | ||
// like the ALT-TAB or Kanji switches are processed correctly. | ||
if (is_syskey) { | ||
break; | ||
} | ||
return true; | ||
} | ||
break; | ||
} | ||
default: | ||
assert(false); | ||
} | ||
return false; | ||
} | ||
|
||
UINT KeyboardManagerWin32::PeekNextMessageType(UINT wMsgFilterMin, | ||
UINT wMsgFilterMax) { | ||
MSG next_message; | ||
BOOL has_msg = window_delegate_->Win32PeekMessage( | ||
&next_message, wMsgFilterMin, wMsgFilterMax, PM_NOREMOVE); | ||
if (!has_msg) { | ||
return 0; | ||
} | ||
return next_message.message; | ||
} | ||
|
||
} // namespace flutter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_ | ||
#define FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_ | ||
|
||
#include <windows.h> | ||
#include <map> | ||
|
||
namespace flutter { | ||
|
||
// Handles keyboard and text messages on Win32. | ||
// | ||
// |KeyboardManagerWin32| consumes raw Win32 messages related to key and chars, | ||
// and converts them to |OnKey| or |OnText| calls suitable for | ||
// |KeyboardKeyHandler|. | ||
// | ||
// |KeyboardManagerWin32| requires a |WindowDelegate| to define how to | ||
// access Win32 system calls (to allow mocking) and where to send the results | ||
// of |OnKey| and |OnText| to. | ||
// | ||
// Typically, |KeyboardManagerWin32| is owned by a |WindowWin32|, which also | ||
// implements the window delegate. The |OnKey| and |OnText| results are | ||
// passed to those of |WindowWin32|'s, and consequently, those of | ||
// |FlutterWindowsView|'s. | ||
class KeyboardManagerWin32 { | ||
public: | ||
// Define how the keyboard manager accesses Win32 system calls (to allow | ||
// mocking) and sends the results of |OnKey| and |OnText|. | ||
// | ||
// Typically implemented by |WindowWin32|. | ||
class WindowDelegate { | ||
public: | ||
virtual ~WindowDelegate() = default; | ||
|
||
// Called when text input occurs. | ||
virtual void OnText(const std::u16string& text) = 0; | ||
|
||
// Called when raw keyboard input occurs. | ||
// | ||
// Returns true if the event was handled, indicating that DefWindowProc | ||
// should not be called on the event by the main message loop. | ||
virtual bool OnKey(int key, | ||
int scancode, | ||
int action, | ||
char32_t character, | ||
bool extended, | ||
bool was_down) = 0; | ||
|
||
// Win32's PeekMessage. | ||
// | ||
// Used to process key messages. | ||
virtual BOOL Win32PeekMessage(LPMSG lpMsg, | ||
UINT wMsgFilterMin, | ||
UINT wMsgFilterMax, | ||
UINT wRemoveMsg) = 0; | ||
|
||
// Win32's MapVirtualKey(*, MAPVK_VK_TO_CHAR). | ||
// | ||
// Used to process key messages. | ||
virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) = 0; | ||
}; | ||
|
||
KeyboardManagerWin32(WindowDelegate* delegate); | ||
|
||
// Processes Win32 messages related to keyboard and text. | ||
// | ||
// All messages related to keyboard and text should be sent here without | ||
// pre-processing, including WM_{SYS,}KEY{DOWN,UP} and WM_{SYS,}{DEAD,}CHAR. | ||
// Other message types will trigger assertion error. | ||
// | ||
// |HandleMessage| returns true if Flutter keyboard system decides to handle | ||
// this message synchronously. It doesn't mean that the Flutter framework | ||
// handles it, which is reported asynchronously later. Not handling this | ||
// message here usually means that this message is a redispatched message, | ||
// but there are other rare cases too. |WindowWin32| should forward unhandled | ||
// messages to |DefWindowProc|. | ||
bool HandleMessage(UINT const message, | ||
WPARAM const wparam, | ||
LPARAM const lparam); | ||
|
||
private: | ||
// Returns the type of the next WM message. | ||
// | ||
// The parameters limits the range of interested messages. See Win32's | ||
// |PeekMessage| for information. | ||
// | ||
// If there's no message, returns 0. | ||
UINT PeekNextMessageType(UINT wMsgFilterMin, UINT wMsgFilterMax); | ||
|
||
WindowDelegate* window_delegate_; | ||
|
||
// Keeps track of the last key code produced by a WM_KEYDOWN or WM_SYSKEYDOWN | ||
// message. | ||
int keycode_for_char_message_ = 0; | ||
|
||
std::map<uint16_t, std::u16string> text_for_scancode_on_redispatch_; | ||
}; | ||
|
||
} // namespace flutter | ||
|
||
#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_ |
Oops, something went wrong.