mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Win32, Keyboard] Abstract KeyboardManager from FlutterWindowWin32 (flutter/engine#30296)
This commit is contained in:
parent
0f89ed5b2a
commit
3fface0f66
@ -1761,8 +1761,10 @@ FILE: ../../../flutter/shell/platform/windows/keyboard_key_embedder_handler_unit
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler.h
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_key_handler_unittests.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_unittests.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_manager_win32.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_manager_win32.h
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_win32_common.h
|
||||
FILE: ../../../flutter/shell/platform/windows/keyboard_win32_unittests.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/platform_handler.cc
|
||||
FILE: ../../../flutter/shell/platform/windows/platform_handler.h
|
||||
FILE: ../../../flutter/shell/platform/windows/platform_handler_unittests.cc
|
||||
|
||||
@ -72,6 +72,8 @@ source_set("flutter_windows_source") {
|
||||
"keyboard_key_embedder_handler.h",
|
||||
"keyboard_key_handler.cc",
|
||||
"keyboard_key_handler.h",
|
||||
"keyboard_manager_win32.cc",
|
||||
"keyboard_manager_win32.h",
|
||||
"keyboard_win32_common.h",
|
||||
"platform_handler.cc",
|
||||
"platform_handler.h",
|
||||
@ -261,7 +263,7 @@ executable("flutter_windows_unittests") {
|
||||
"keyboard_key_channel_handler_unittests.cc",
|
||||
"keyboard_key_embedder_handler_unittests.cc",
|
||||
"keyboard_key_handler_unittests.cc",
|
||||
"keyboard_unittests.cc",
|
||||
"keyboard_win32_unittests.cc",
|
||||
"platform_handler_unittests.cc",
|
||||
"testing/flutter_window_win32_test.cc",
|
||||
"testing/flutter_window_win32_test.h",
|
||||
|
||||
@ -122,7 +122,7 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32,
|
||||
LRESULT InjectWindowMessage(UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) {
|
||||
return Win32SendMessage(NULL, message, wparam, lparam);
|
||||
return Win32SendMessage(message, wparam, lparam);
|
||||
}
|
||||
|
||||
void InjectMessages(int count, Win32Message message1, ...) {
|
||||
@ -156,11 +156,10 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32,
|
||||
|
||||
protected:
|
||||
virtual BOOL Win32PeekMessage(LPMSG lpMsg,
|
||||
HWND hWnd,
|
||||
UINT wMsgFilterMin,
|
||||
UINT wMsgFilterMax,
|
||||
UINT wRemoveMsg) override {
|
||||
return MockMessageQueue::Win32PeekMessage(lpMsg, hWnd, wMsgFilterMin,
|
||||
return MockMessageQueue::Win32PeekMessage(lpMsg, wMsgFilterMin,
|
||||
wMsgFilterMax, wRemoveMsg);
|
||||
}
|
||||
|
||||
@ -172,8 +171,7 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32,
|
||||
}
|
||||
|
||||
private:
|
||||
LRESULT Win32SendMessage(HWND hWnd,
|
||||
UINT const message,
|
||||
LRESULT Win32SendMessage(UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) override {
|
||||
return HandleMessage(message, wparam, lparam);
|
||||
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -5,11 +5,12 @@
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/embedder/test_utils/key_codes.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_engine.h"
|
||||
#include "flutter/shell/platform/windows/flutter_windows_view.h"
|
||||
#include "flutter/shell/platform/windows/keyboard_key_channel_handler.h"
|
||||
#include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h"
|
||||
#include "flutter/shell/platform/windows/keyboard_key_handler.h"
|
||||
#include "flutter/shell/platform/windows/keyboard_manager_win32.h"
|
||||
#include "flutter/shell/platform/windows/testing/engine_modifier.h"
|
||||
#include "flutter/shell/platform/windows/testing/flutter_window_win32_test.h"
|
||||
#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h"
|
||||
#include "flutter/shell/platform/windows/testing/test_keyboard.h"
|
||||
|
||||
@ -58,54 +59,28 @@ uint32_t LayoutFrench(uint32_t virtual_key) {
|
||||
}
|
||||
}
|
||||
|
||||
class MockFlutterWindowWin32 : public FlutterWindowWin32,
|
||||
public MockMessageQueue {
|
||||
class MockKeyboardManagerWin32Delegate
|
||||
: public KeyboardManagerWin32::WindowDelegate,
|
||||
public MockMessageQueue {
|
||||
public:
|
||||
typedef std::function<void(const std::u16string& text)> U16StringHandler;
|
||||
|
||||
MockFlutterWindowWin32(U16StringHandler on_text)
|
||||
: FlutterWindowWin32(800, 600),
|
||||
on_text_(std::move(on_text)),
|
||||
map_vk_to_char_(LayoutDefault) {
|
||||
ON_CALL(*this, GetDpiScale())
|
||||
.WillByDefault(Return(this->FlutterWindowWin32::GetDpiScale()));
|
||||
MockKeyboardManagerWin32Delegate(WindowBindingHandlerDelegate* view)
|
||||
: view_(view), map_vk_to_char_(LayoutDefault) {
|
||||
keyboard_manager_ = std::make_unique<KeyboardManagerWin32>(this);
|
||||
}
|
||||
virtual ~MockFlutterWindowWin32() {}
|
||||
virtual ~MockKeyboardManagerWin32Delegate() {}
|
||||
|
||||
// Prevent copying.
|
||||
MockFlutterWindowWin32(MockFlutterWindowWin32 const&) = delete;
|
||||
MockFlutterWindowWin32& operator=(MockFlutterWindowWin32 const&) = delete;
|
||||
|
||||
// Wrapper for GetCurrentDPI() which is a protected method.
|
||||
UINT GetDpi() { return GetCurrentDPI(); }
|
||||
|
||||
LRESULT Win32DefWindowProc(HWND hWnd,
|
||||
UINT Msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam) override {
|
||||
return kWmResultDefault;
|
||||
// |WindowWin32|
|
||||
bool OnKey(int key,
|
||||
int scancode,
|
||||
int action,
|
||||
char32_t character,
|
||||
bool extended,
|
||||
bool was_down) override {
|
||||
return view_->OnKey(key, scancode, action, character, extended, was_down);
|
||||
}
|
||||
|
||||
// Simulates a WindowProc message from the OS.
|
||||
LRESULT InjectWindowMessage(UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) {
|
||||
return Win32SendMessage(NULL, message, wparam, lparam);
|
||||
}
|
||||
|
||||
void OnText(const std::u16string& text) override { on_text_(text); }
|
||||
|
||||
MOCK_METHOD1(OnDpiScale, void(unsigned int));
|
||||
MOCK_METHOD2(OnResize, void(unsigned int, unsigned int));
|
||||
MOCK_METHOD2(OnPointerMove, void(double, double));
|
||||
MOCK_METHOD3(OnPointerDown, void(double, double, UINT));
|
||||
MOCK_METHOD3(OnPointerUp, void(double, double, UINT));
|
||||
MOCK_METHOD0(OnPointerLeave, void());
|
||||
MOCK_METHOD0(OnSetCursor, void());
|
||||
MOCK_METHOD2(OnScroll, void(double, double));
|
||||
MOCK_METHOD0(GetDpiScale, float());
|
||||
MOCK_METHOD0(IsVisible, bool());
|
||||
MOCK_METHOD1(UpdateCursorRect, void(const Rect&));
|
||||
// |WindowWin32|
|
||||
void OnText(const std::u16string& text) override { view_->OnText(text); }
|
||||
|
||||
void SetLayout(MapVkToCharHandler map_vk_to_char) {
|
||||
map_vk_to_char_ =
|
||||
@ -114,11 +89,10 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32,
|
||||
|
||||
protected:
|
||||
virtual BOOL Win32PeekMessage(LPMSG lpMsg,
|
||||
HWND hWnd,
|
||||
UINT wMsgFilterMin,
|
||||
UINT wMsgFilterMax,
|
||||
UINT wRemoveMsg) override {
|
||||
return MockMessageQueue::Win32PeekMessage(lpMsg, hWnd, wMsgFilterMin,
|
||||
return MockMessageQueue::Win32PeekMessage(lpMsg, wMsgFilterMin,
|
||||
wMsgFilterMax, wRemoveMsg);
|
||||
}
|
||||
|
||||
@ -126,17 +100,20 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32,
|
||||
return map_vk_to_char_(virtual_key);
|
||||
}
|
||||
|
||||
virtual LRESULT Win32SendMessage(UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) override {
|
||||
return keyboard_manager_->HandleMessage(message, wparam, lparam)
|
||||
? 0
|
||||
: kWmResultDefault;
|
||||
}
|
||||
|
||||
private:
|
||||
U16StringHandler on_text_;
|
||||
WindowBindingHandlerDelegate* view_;
|
||||
|
||||
std::unique_ptr<KeyboardManagerWin32> keyboard_manager_;
|
||||
|
||||
MapVkToCharHandler map_vk_to_char_;
|
||||
|
||||
LRESULT Win32SendMessage(HWND hWnd,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) override {
|
||||
return HandleMessage(message, wparam, lparam);
|
||||
}
|
||||
};
|
||||
|
||||
class TestKeystate {
|
||||
@ -166,17 +143,21 @@ typedef struct {
|
||||
// to register the keyboard hook handlers that can be spied upon.
|
||||
class TestFlutterWindowsView : public FlutterWindowsView {
|
||||
public:
|
||||
TestFlutterWindowsView()
|
||||
typedef std::function<void(const std::u16string& text)> U16StringHandler;
|
||||
|
||||
TestFlutterWindowsView(U16StringHandler on_text)
|
||||
// The WindowBindingHandler is used for window size and such, and doesn't
|
||||
// affect keyboard.
|
||||
: FlutterWindowsView(
|
||||
std::make_unique<::testing::NiceMock<MockWindowBindingHandler>>()),
|
||||
on_text_(std::move(on_text)),
|
||||
redispatch_char(0) {}
|
||||
|
||||
uint32_t redispatch_char;
|
||||
|
||||
int InjectPendingEvents(MockFlutterWindowWin32* win32window,
|
||||
uint32_t redispatch_char) {
|
||||
void OnText(const std::u16string& text) override { on_text_(text); }
|
||||
|
||||
int InjectPendingEvents(MockMessageQueue* queue, uint32_t redispatch_char) {
|
||||
std::vector<Win32Message> messages;
|
||||
int num_pending_responds = pending_responds_.size();
|
||||
for (const SendInputInfo& input : pending_responds_) {
|
||||
@ -199,7 +180,7 @@ class TestFlutterWindowsView : public FlutterWindowsView {
|
||||
}
|
||||
}
|
||||
|
||||
win32window->InjectMessageList(messages.size(), messages.data());
|
||||
queue->InjectMessageList(messages.size(), messages.data());
|
||||
pending_responds_.clear();
|
||||
return num_pending_responds;
|
||||
}
|
||||
@ -227,6 +208,7 @@ class TestFlutterWindowsView : public FlutterWindowsView {
|
||||
return 1;
|
||||
}
|
||||
|
||||
U16StringHandler on_text_;
|
||||
std::vector<SendInputInfo> pending_responds_;
|
||||
TestKeystate key_state_;
|
||||
};
|
||||
@ -261,16 +243,15 @@ std::unique_ptr<FlutterWindowsEngine> GetTestEngine();
|
||||
class KeyboardTester {
|
||||
public:
|
||||
explicit KeyboardTester() {
|
||||
view_ = std::make_unique<TestFlutterWindowsView>();
|
||||
view_->SetEngine(std::move(GetTestEngine()));
|
||||
window_ = std::make_unique<MockFlutterWindowWin32>(
|
||||
view_ = std::make_unique<TestFlutterWindowsView>(
|
||||
[](const std::u16string& text) {
|
||||
key_calls.push_back(KeyCall{
|
||||
.type = kKeyCallOnText,
|
||||
.text = text,
|
||||
});
|
||||
});
|
||||
window_->SetView(view_.get());
|
||||
view_->SetEngine(std::move(GetTestEngine()));
|
||||
window_ = std::make_unique<MockKeyboardManagerWin32Delegate>(view_.get());
|
||||
}
|
||||
|
||||
void SetKeyState(uint32_t key, bool pressed, bool toggled_on) {
|
||||
@ -308,7 +289,7 @@ class KeyboardTester {
|
||||
|
||||
private:
|
||||
std::unique_ptr<TestFlutterWindowsView> view_;
|
||||
std::unique_ptr<MockFlutterWindowWin32> window_;
|
||||
std::unique_ptr<MockKeyboardManagerWin32Delegate> window_;
|
||||
};
|
||||
|
||||
bool KeyboardTester::test_response = false;
|
||||
@ -30,11 +30,16 @@ LRESULT MockWin32Window::InjectWindowMessage(UINT const message,
|
||||
return HandleMessage(message, wparam, lparam);
|
||||
}
|
||||
|
||||
LRESULT MockWin32Window::Win32SendMessage(HWND hWnd,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) {
|
||||
return HandleMessage(message, wparam, lparam);
|
||||
void MockWin32Window::InjectMessageList(int count,
|
||||
const Win32Message* messages) {
|
||||
for (int message_id = 0; message_id < count; message_id += 1) {
|
||||
const Win32Message& message = messages[message_id];
|
||||
LRESULT result =
|
||||
InjectWindowMessage(message.message, message.wParam, message.lParam);
|
||||
if (message.expected_result != kWmResultDontCheck) {
|
||||
EXPECT_EQ(result, message.expected_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MockWin32Window::CallOnImeComposition(UINT const message,
|
||||
|
||||
@ -15,7 +15,7 @@ namespace flutter {
|
||||
namespace testing {
|
||||
|
||||
/// Mock for the |WindowWin32| base class.
|
||||
class MockWin32Window : public WindowWin32, public MockMessageQueue {
|
||||
class MockWin32Window : public WindowWin32 {
|
||||
public:
|
||||
MockWin32Window();
|
||||
MockWin32Window(std::unique_ptr<TextInputManagerWin32> text_input_manager);
|
||||
@ -33,6 +33,8 @@ class MockWin32Window : public WindowWin32, public MockMessageQueue {
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam);
|
||||
|
||||
void InjectMessageList(int count, const Win32Message* messages);
|
||||
|
||||
MOCK_METHOD1(OnDpiScale, void(unsigned int));
|
||||
MOCK_METHOD2(OnResize, void(unsigned int, unsigned int));
|
||||
MOCK_METHOD4(OnPointerMove,
|
||||
@ -61,11 +63,6 @@ class MockWin32Window : public WindowWin32, public MockMessageQueue {
|
||||
|
||||
protected:
|
||||
LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
LRESULT Win32SendMessage(HWND hWnd,
|
||||
UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) override;
|
||||
};
|
||||
|
||||
} // namespace testing
|
||||
|
||||
@ -183,8 +183,8 @@ void MockMessageQueue::InjectMessageList(int count,
|
||||
while (!_pending_messages.empty()) {
|
||||
Win32Message message = _pending_messages.front();
|
||||
_pending_messages.pop_front();
|
||||
LRESULT result = Win32SendMessage(message.hWnd, message.message,
|
||||
message.wParam, message.lParam);
|
||||
LRESULT result =
|
||||
Win32SendMessage(message.message, message.wParam, message.lParam);
|
||||
if (message.expected_result != kWmResultDontCheck) {
|
||||
EXPECT_EQ(result, message.expected_result);
|
||||
}
|
||||
@ -192,7 +192,6 @@ void MockMessageQueue::InjectMessageList(int count,
|
||||
}
|
||||
|
||||
BOOL MockMessageQueue::Win32PeekMessage(LPMSG lpMsg,
|
||||
HWND hWnd,
|
||||
UINT wMsgFilterMin,
|
||||
UINT wMsgFilterMax,
|
||||
UINT wRemoveMsg) {
|
||||
|
||||
@ -64,14 +64,12 @@ class MockMessageQueue {
|
||||
//
|
||||
// See Win32's |PeekMessage| for documentation.
|
||||
BOOL Win32PeekMessage(LPMSG lpMsg,
|
||||
HWND hWnd,
|
||||
UINT wMsgFilterMin,
|
||||
UINT wMsgFilterMax,
|
||||
UINT wRemoveMsg);
|
||||
|
||||
protected:
|
||||
virtual LRESULT Win32SendMessage(HWND hWnd,
|
||||
UINT const message,
|
||||
virtual LRESULT Win32SendMessage(UINT const message,
|
||||
WPARAM const wparam,
|
||||
LPARAM const lparam) = 0;
|
||||
|
||||
|
||||
@ -64,6 +64,7 @@ WindowWin32::WindowWin32(
|
||||
if (text_input_manager_ == nullptr) {
|
||||
text_input_manager_ = std::make_unique<TextInputManagerWin32>();
|
||||
}
|
||||
keyboard_manager_ = std::make_unique<KeyboardManagerWin32>(this);
|
||||
}
|
||||
|
||||
WindowWin32::~WindowWin32() {
|
||||
@ -480,136 +481,12 @@ WindowWin32::HandleMessage(UINT const 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 =
|
||||
Win32MapVkToChar(keycode_for_char_message_) | kDeadKeyCharMask;
|
||||
} else {
|
||||
event_character = IsPrintable(code_point) ? code_point : 0;
|
||||
}
|
||||
bool handled = 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 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
OnText(text);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
case WM_SYSCHAR:
|
||||
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 = 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 0;
|
||||
}
|
||||
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 (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;
|
||||
}
|
||||
if (keyboard_manager_->HandleMessage(message, wparam, lparam)) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
@ -650,16 +527,6 @@ void WindowWin32::HandleResize(UINT width, UINT height) {
|
||||
OnResize(width, height);
|
||||
}
|
||||
|
||||
UINT WindowWin32::PeekNextMessageType(UINT wMsgFilterMin, UINT wMsgFilterMax) {
|
||||
MSG next_message;
|
||||
BOOL has_msg = Win32PeekMessage(&next_message, window_handle_, wMsgFilterMin,
|
||||
wMsgFilterMax, PM_NOREMOVE);
|
||||
if (!has_msg) {
|
||||
return 0;
|
||||
}
|
||||
return next_message.message;
|
||||
}
|
||||
|
||||
WindowWin32* WindowWin32::GetThisFromHandle(HWND const window) noexcept {
|
||||
return reinterpret_cast<WindowWin32*>(
|
||||
GetWindowLongPtr(window, GWLP_USERDATA));
|
||||
@ -669,19 +536,19 @@ LRESULT WindowWin32::Win32DefWindowProc(HWND hWnd,
|
||||
UINT Msg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
return DefWindowProc(hWnd, Msg, wParam, lParam);
|
||||
return ::DefWindowProc(hWnd, Msg, wParam, lParam);
|
||||
}
|
||||
|
||||
BOOL WindowWin32::Win32PeekMessage(LPMSG lpMsg,
|
||||
HWND hWnd,
|
||||
UINT wMsgFilterMin,
|
||||
UINT wMsgFilterMax,
|
||||
UINT wRemoveMsg) {
|
||||
return PeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
|
||||
return ::PeekMessage(lpMsg, window_handle_, wMsgFilterMin, wMsgFilterMax,
|
||||
wRemoveMsg);
|
||||
}
|
||||
|
||||
uint32_t WindowWin32::Win32MapVkToChar(uint32_t virtual_key) {
|
||||
return MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR);
|
||||
return ::MapVirtualKey(virtual_key, MAPVK_VK_TO_CHAR);
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/windows/keyboard_manager_win32.h"
|
||||
#include "flutter/shell/platform/windows/sequential_id_generator.h"
|
||||
#include "flutter/shell/platform/windows/text_input_manager_win32.h"
|
||||
#include "flutter/third_party/accessibility/gfx/native_widget_types.h"
|
||||
@ -23,7 +24,7 @@ namespace flutter {
|
||||
// A class abstraction for a high DPI aware Win32 Window. Intended to be
|
||||
// inherited from by classes that wish to specialize with custom
|
||||
// rendering and input handling.
|
||||
class WindowWin32 {
|
||||
class WindowWin32 : public KeyboardManagerWin32::WindowDelegate {
|
||||
public:
|
||||
WindowWin32();
|
||||
WindowWin32(std::unique_ptr<TextInputManagerWin32> text_input_manager);
|
||||
@ -38,6 +39,15 @@ class WindowWin32 {
|
||||
|
||||
HWND GetWindowHandle();
|
||||
|
||||
// |KeyboardManagerWin32::WindowDelegate|
|
||||
virtual BOOL Win32PeekMessage(LPMSG lpMsg,
|
||||
UINT wMsgFilterMin,
|
||||
UINT wMsgFilterMax,
|
||||
UINT wRemoveMsg) override;
|
||||
|
||||
// |KeyboardManagerWin32::WindowDelegate|
|
||||
virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) override;
|
||||
|
||||
protected:
|
||||
// Converts a c string to a wide unicode string.
|
||||
std::wstring NarrowToWide(const char* source);
|
||||
@ -109,20 +119,6 @@ class WindowWin32 {
|
||||
// Called when the cursor should be set for the client area.
|
||||
virtual void OnSetCursor() = 0;
|
||||
|
||||
// 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;
|
||||
|
||||
// Called when the OS requests a COM object.
|
||||
//
|
||||
// The primary use of this function is to supply Windows with wrapped
|
||||
@ -205,20 +201,6 @@ class WindowWin32 {
|
||||
WPARAM wParam,
|
||||
LPARAM lParam);
|
||||
|
||||
// Win32's PeekMessage.
|
||||
//
|
||||
// Used to process key messages. Exposed for dependency injection.
|
||||
virtual BOOL Win32PeekMessage(LPMSG lpMsg,
|
||||
HWND hWnd,
|
||||
UINT wMsgFilterMin,
|
||||
UINT wMsgFilterMax,
|
||||
UINT wRemoveMsg);
|
||||
|
||||
// Win32's MapVirtualKey(*, MAPVK_VK_TO_CHAR).
|
||||
//
|
||||
// Used to process key messages. Exposed for dependency injection.
|
||||
virtual uint32_t Win32MapVkToChar(uint32_t virtual_key);
|
||||
|
||||
// Returns the root view accessibility node, or nullptr if none.
|
||||
virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0;
|
||||
|
||||
@ -232,16 +214,6 @@ class WindowWin32 {
|
||||
// Stores new width and height and calls |OnResize| to notify inheritors
|
||||
void HandleResize(UINT width, UINT height);
|
||||
|
||||
// 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.
|
||||
//
|
||||
// The behavior can be mocked by replacing |Win32PeekMessage|.
|
||||
UINT PeekNextMessageType(UINT wMsgFilterMin, UINT wMsgFilterMax);
|
||||
|
||||
// Retrieves a class instance pointer for |window|
|
||||
static WindowWin32* GetThisFromHandle(HWND const window) noexcept;
|
||||
|
||||
@ -271,6 +243,9 @@ class WindowWin32 {
|
||||
// Manages IME state.
|
||||
std::unique_ptr<TextInputManagerWin32> text_input_manager_;
|
||||
|
||||
// Manages IME state.
|
||||
std::unique_ptr<KeyboardManagerWin32> keyboard_manager_;
|
||||
|
||||
// Used for temporarily storing the WM_TOUCH-provided touch points.
|
||||
std::vector<TOUCHINPUT> touch_points_;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user