diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 573bb02d957..149cdd14608 100755 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -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 diff --git a/engine/src/flutter/shell/platform/windows/BUILD.gn b/engine/src/flutter/shell/platform/windows/BUILD.gn index e5f99df322d..a19418e2aa7 100644 --- a/engine/src/flutter/shell/platform/windows/BUILD.gn +++ b/engine/src/flutter/shell/platform/windows/BUILD.gn @@ -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", diff --git a/engine/src/flutter/shell/platform/windows/flutter_window_win32_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_window_win32_unittests.cc index a96dfc85544..1b4a77f1364 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_window_win32_unittests.cc @@ -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); diff --git a/engine/src/flutter/shell/platform/windows/keyboard_manager_win32.cc b/engine/src/flutter/shell/platform/windows/keyboard_manager_win32.cc new file mode 100644 index 00000000000..aadca7ec697 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/keyboard_manager_win32.cc @@ -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 +#include + +#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(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(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 diff --git a/engine/src/flutter/shell/platform/windows/keyboard_manager_win32.h b/engine/src/flutter/shell/platform/windows/keyboard_manager_win32.h new file mode 100644 index 00000000000..a542b02910f --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/keyboard_manager_win32.h @@ -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 +#include + +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 text_for_scancode_on_redispatch_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_ diff --git a/engine/src/flutter/shell/platform/windows/keyboard_unittests.cc b/engine/src/flutter/shell/platform/windows/keyboard_win32_unittests.cc similarity index 92% rename from engine/src/flutter/shell/platform/windows/keyboard_unittests.cc rename to engine/src/flutter/shell/platform/windows/keyboard_win32_unittests.cc index 8ee9c1d9147..2be68850578 100644 --- a/engine/src/flutter/shell/platform/windows/keyboard_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/keyboard_win32_unittests.cc @@ -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 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(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 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 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>()), + 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 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 pending_responds_; TestKeystate key_state_; }; @@ -261,16 +243,15 @@ std::unique_ptr GetTestEngine(); class KeyboardTester { public: explicit KeyboardTester() { - view_ = std::make_unique(); - view_->SetEngine(std::move(GetTestEngine())); - window_ = std::make_unique( + view_ = std::make_unique( [](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(view_.get()); } void SetKeyState(uint32_t key, bool pressed, bool toggled_on) { @@ -308,7 +289,7 @@ class KeyboardTester { private: std::unique_ptr view_; - std::unique_ptr window_; + std::unique_ptr window_; }; bool KeyboardTester::test_response = false; diff --git a/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.cc b/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.cc index 4376c277db1..a44bc0972a4 100644 --- a/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.cc +++ b/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.cc @@ -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, diff --git a/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.h b/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.h index 6c77ab1e85c..2d0839bb656 100644 --- a/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.h +++ b/engine/src/flutter/shell/platform/windows/testing/mock_window_win32.h @@ -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 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 diff --git a/engine/src/flutter/shell/platform/windows/testing/test_keyboard.cc b/engine/src/flutter/shell/platform/windows/testing/test_keyboard.cc index 5cedde95e8e..01971f936bc 100644 --- a/engine/src/flutter/shell/platform/windows/testing/test_keyboard.cc +++ b/engine/src/flutter/shell/platform/windows/testing/test_keyboard.cc @@ -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) { diff --git a/engine/src/flutter/shell/platform/windows/testing/test_keyboard.h b/engine/src/flutter/shell/platform/windows/testing/test_keyboard.h index e584f3b22aa..39d13c1dcd3 100644 --- a/engine/src/flutter/shell/platform/windows/testing/test_keyboard.h +++ b/engine/src/flutter/shell/platform/windows/testing/test_keyboard.h @@ -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; diff --git a/engine/src/flutter/shell/platform/windows/window_win32.cc b/engine/src/flutter/shell/platform/windows/window_win32.cc index 55995ff0b0a..177d0043fc5 100644 --- a/engine/src/flutter/shell/platform/windows/window_win32.cc +++ b/engine/src/flutter/shell/platform/windows/window_win32.cc @@ -64,6 +64,7 @@ WindowWin32::WindowWin32( if (text_input_manager_ == nullptr) { text_input_manager_ = std::make_unique(); } + keyboard_manager_ = std::make_unique(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(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( 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 diff --git a/engine/src/flutter/shell/platform/windows/window_win32.h b/engine/src/flutter/shell/platform/windows/window_win32.h index b8d97011d57..6b8d7220cc2 100644 --- a/engine/src/flutter/shell/platform/windows/window_win32.h +++ b/engine/src/flutter/shell/platform/windows/window_win32.h @@ -14,6 +14,7 @@ #include #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 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 text_input_manager_; + // Manages IME state. + std::unique_ptr keyboard_manager_; + // Used for temporarily storing the WM_TOUCH-provided touch points. std::vector touch_points_;