From 91b9c61fd67fba74632387104dccb79931e9cc9d Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 5 Aug 2021 19:06:35 -0700 Subject: [PATCH 01/30] Basic changes --- lib/ui/key.dart | 10 +- shell/platform/windows/BUILD.gn | 3 + .../windows/flutter_window_win32_unittests.cc | 32 +- .../platform/windows/flutter_windows_view.cc | 27 +- .../windows/keyboard_key_embedder_handler.cc | 4 +- .../platform/windows/keyboard_key_handler.cc | 4 +- shell/platform/windows/keyboard_unittests.cc | 390 ++++++++++++++++++ .../platform/windows/testing/test_keyboard.cc | 21 + .../platform/windows/testing/test_keyboard.h | 74 ++++ shell/platform/windows/window_win32.cc | 51 ++- 10 files changed, 569 insertions(+), 47 deletions(-) create mode 100644 shell/platform/windows/keyboard_unittests.cc create mode 100644 shell/platform/windows/testing/test_keyboard.cc create mode 100644 shell/platform/windows/testing/test_keyboard.h diff --git a/lib/ui/key.dart b/lib/ui/key.dart index 50e07703fb977..ac6b8b7755a90 100644 --- a/lib/ui/key.dart +++ b/lib/ui/key.dart @@ -149,9 +149,17 @@ class KeyData { } } + String? _quotedCharCode() { + if (character == null) + return ''; + final Iterable hexChars = character!.codeUnits + .map((int code) => code.toRadixString(16).padLeft(2, '0')); + return '0x${hexChars.join(' ')}'; + } + @override String toString() => 'KeyData(key ${_typeToString(type)}, physical: 0x${physical.toRadixString(16)}, ' - 'logical: ${_logicalToString()}, character: ${_escapeCharacter()})'; + 'logical: ${_logicalToString()}, character: ${_escapeCharacter()}${_quotedCharCode()}${synthesized ? ', synthesized' : ''})'; /// Returns a complete textual description of the information in this object. String toStringFull() { diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 4ed1b0a8e7cfe..3a9f5fa0c3c20 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -243,12 +243,15 @@ executable("flutter_windows_unittests") { "keyboard_key_channel_handler_unittests.cc", "keyboard_key_embedder_handler_unittests.cc", "keyboard_key_handler_unittests.cc", + "keyboard_unittests.cc", "testing/flutter_window_win32_test.cc", "testing/flutter_window_win32_test.h", "testing/mock_window_binding_handler.cc", "testing/mock_window_binding_handler.h", "testing/mock_window_win32.cc", "testing/mock_window_win32.h", + "testing/test_keyboard.cc", + "testing/test_keyboard.h", "text_input_plugin_unittest.cc", "window_proc_delegate_manager_win32_unittests.cc", "window_win32_unittests.cc", diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index 7f01ed5c9930e..ddf86d82eff5a 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -30,8 +30,8 @@ namespace { // Creates a valid Windows LPARAM for WM_KEYDOWN and WM_CHAR from parameters // given. static LPARAM CreateKeyEventLparam(USHORT scancode, - bool extended = false, - bool was_down = 1, + bool extended, + bool was_down, USHORT repeat_count = 1, bool context_code = 0, bool transition_state = 1) { @@ -215,7 +215,11 @@ class TestFlutterWindowsView : public FlutterWindowsView { } protected: - void RegisterKeyboardHandlers(flutter::BinaryMessenger* messenger) override { + void RegisterKeyboardHandlers( + flutter::BinaryMessenger* messenger, + flutter::KeyboardKeyHandler::EventDispatcher dispatch_event, + flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state) + override { auto spy_key_event_handler = std::make_unique( messenger, [this](UINT cInputs, LPINPUT pInputs, int cbSize) -> UINT { return this->SendInput(cInputs, pInputs, cbSize); @@ -235,8 +239,9 @@ class TestFlutterWindowsView : public FlutterWindowsView { const KEYBDINPUT kbdinput = pInputs->ki; const UINT message = (kbdinput.dwFlags & KEYEVENTF_KEYUP) ? WM_KEYUP : WM_KEYDOWN; + const bool is_key_up = kbdinput.dwFlags & KEYEVENTF_KEYUP; const LPARAM lparam = CreateKeyEventLparam( - kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY); + kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY, is_key_up); // Windows would normally fill in the virtual key code for us, so we // simulate it for the test with the key we know is in the test. The // KBDINPUT we're passed doesn't have it filled in (on purpose, so that @@ -345,8 +350,7 @@ TEST(FlutterWindowWin32Test, NonPrintableKeyDownPropagation) { TestFlutterWindowsView flutter_windows_view( std::move(window_binding_handler), virtual_key, false /* is_printable */); win32window.SetView(&flutter_windows_view); - LPARAM lparam = CreateKeyEventLparam(scan_code, false /* extended */, - false /* PrevState */); + LPARAM lparam = CreateKeyEventLparam(scan_code, false, false); // Test an event not handled by the framework { @@ -406,16 +410,15 @@ TEST(FlutterWindowWin32Test, CharKeyDownPropagation) { TestFlutterWindowsView flutter_windows_view( std::move(window_binding_handler), virtual_key, true /* is_printable */); win32window.SetView(&flutter_windows_view); - LPARAM lparam = CreateKeyEventLparam(scan_code, false /* extended */, - true /* PrevState */); + LPARAM lparam = CreateKeyEventLparam(scan_code, false, false); + flutter_windows_view.SetEngine(std::move(GetTestEngine())); // Test an event not handled by the framework { test_response = false; - flutter_windows_view.SetEngine(std::move(GetTestEngine())); EXPECT_CALL(*flutter_windows_view.key_event_handler, KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character, - false, true)) + false, false)) .Times(2) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, @@ -433,14 +436,13 @@ TEST(FlutterWindowWin32Test, CharKeyDownPropagation) { EXPECT_EQ(win32window.InjectWindowMessage(WM_CHAR, virtual_key, lparam), 0); flutter_windows_view.InjectPendingEvents(&win32window); } - return; // Test an event handled by the framework { test_response = true; EXPECT_CALL(*flutter_windows_view.key_event_handler, KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character, - false /* is_printable */, true)) + false, false)) .Times(1) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, @@ -471,7 +473,7 @@ TEST(FlutterWindowWin32Test, ModifierKeyDownPropagation) { TestFlutterWindowsView flutter_windows_view( std::move(window_binding_handler), virtual_key, false /* is_printable */); win32window.SetView(&flutter_windows_view); - LPARAM lparam = CreateKeyEventLparam(scan_code); + LPARAM lparam = CreateKeyEventLparam(scan_code, false, false); // Test an event not handled by the framework { @@ -479,7 +481,7 @@ TEST(FlutterWindowWin32Test, ModifierKeyDownPropagation) { flutter_windows_view.SetEngine(std::move(GetTestEngine())); EXPECT_CALL(*flutter_windows_view.key_event_handler, KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character, - false /* extended */, true)) + false /* extended */, false)) .Times(2) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, @@ -500,7 +502,7 @@ TEST(FlutterWindowWin32Test, ModifierKeyDownPropagation) { test_response = true; EXPECT_CALL(*flutter_windows_view.key_event_handler, KeyboardHook(_, virtual_key, scan_code, WM_KEYDOWN, character, - false /* extended */, true)) + false /* extended */, false)) .Times(1) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.text_input_plugin, diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 55cbc43299dce..fee32e5f7da18 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -55,7 +55,17 @@ void FlutterWindowsView::SetEngine( // Set up the system channel handlers. auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); - RegisterKeyboardHandlers(internal_plugin_messenger); +#ifdef WINUWP + flutter::KeyboardKeyHandler::EventDispatcher dispatch_event = nullptr; + flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = + nullptr; +#else + flutter::KeyboardKeyHandler::EventDispatcher dispatch_event = SendInput; + flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = + GetKeyState; +#endif + RegisterKeyboardHandlers(internal_plugin_messenger, dispatch_event, + get_key_state); platform_handler_ = PlatformHandler::Create(internal_plugin_messenger, this); cursor_handler_ = std::make_unique( internal_plugin_messenger, binding_handler_.get()); @@ -67,7 +77,9 @@ void FlutterWindowsView::SetEngine( } void FlutterWindowsView::RegisterKeyboardHandlers( - flutter::BinaryMessenger* messenger) { + flutter::BinaryMessenger* messenger, + flutter::KeyboardKeyHandler::EventDispatcher dispatch_event, + flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state) { // There must be only one handler that receives |SendInput|, i.e. only one // handler that might redispatch events. (See the documentation of // |KeyboardKeyHandler| to learn about redispatching.) @@ -76,17 +88,8 @@ void FlutterWindowsView::RegisterKeyboardHandlers( // of the event. In order to allow the same real event in the future, the // handler is "toggled" when events pass through, therefore the redispatching // algorithm does not allow more than 1 handler that takes |SendInput|. -#ifdef WINUWP - flutter::KeyboardKeyHandler::EventDispatcher redispatch_event = nullptr; - flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = - nullptr; -#else - flutter::KeyboardKeyHandler::EventDispatcher redispatch_event = SendInput; - flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = - GetKeyState; -#endif auto key_handler = - std::make_unique(redispatch_event); + std::make_unique(dispatch_event); key_handler->AddDelegate(std::make_unique( [this](const FlutterKeyEvent& event, FlutterKeyEventCallback callback, void* user_data) { diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index b1aa06d1bbddb..1c4bf4d2037ae 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -267,10 +267,10 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( continue; } assert(key_info.logical_key != 0); - SHORT state = get_key_state_(virtual_key); // Check toggling state first, because it might alter pressing state. if (key_info.check_toggled) { + SHORT state = get_key_state_(virtual_key); bool should_toggled = state & kStateMaskToggled; if (virtual_key == toggle_virtual_key) { key_info.toggled_on = !key_info.toggled_on; @@ -312,8 +312,8 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates() { continue; } assert(key_info.logical_key != 0); - SHORT state = get_key_state_(virtual_key); if (key_info.check_pressed) { + SHORT state = get_key_state_(virtual_key); auto recorded_pressed_iter = pressingRecords_.find(key_info.physical_key); bool recorded_pressed = recorded_pressed_iter != pressingRecords_.end(); bool should_pressed = state & kStateMaskPressed; diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index 4264039d658aa..0f80687cbba73 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -62,7 +62,7 @@ static bool IsKeyDownAltRight(int action, int virtual_key, bool extended) { #ifdef WINUWP return false; #else - return virtual_key == VK_LMENU && extended && action == WM_KEYDOWN; + return virtual_key == VK_RMENU && extended && action == WM_KEYDOWN; #endif } @@ -73,7 +73,7 @@ static bool IsKeyUpAltRight(int action, int virtual_key, bool extended) { #ifdef WINUWP return false; #else - return virtual_key == VK_LMENU && extended && action == WM_KEYUP; + return virtual_key == VK_RMENU && extended && action == WM_KEYUP; #endif } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc new file mode 100644 index 0000000000000..7a98fb6ee00d4 --- /dev/null +++ b/shell/platform/windows/keyboard_unittests.cc @@ -0,0 +1,390 @@ +// 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 "flutter/shell/platform/common/json_message_codec.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/embedder/test_utils/key_codes.h" +#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" +#include "flutter/shell/platform/windows/flutter_windows_engine.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/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" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include + +using testing::_; +using testing::Invoke; +using testing::Return; +using namespace ::flutter::testing::keycodes; + +namespace flutter { +namespace testing { + +namespace { + +constexpr SHORT kStateMaskToggled = 0x01; +constexpr SHORT kStateMaskPressed = 0x80; + +struct SimulatedEvent { + UINT message; + WPARAM wparam; + LPARAM lparam; +}; + +static LPARAM CreateKeyEventLparam(USHORT scancode, + bool extended, + bool was_down, + USHORT repeat_count = 1, + bool context_code = 0, + bool transition_state = 1) { + return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | + (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | + (LPARAM(scancode) << 16) | LPARAM(repeat_count)); +} + +class MockFlutterWindowWin32 : public FlutterWindowWin32 { + public: + typedef std::function U16StringHandler; + + MockFlutterWindowWin32(U16StringHandler on_text) + : FlutterWindowWin32(800, 600), on_text_(std::move(on_text)) { + ON_CALL(*this, GetDpiScale()) + .WillByDefault(Return(this->FlutterWindowWin32::GetDpiScale())); + } + virtual ~MockFlutterWindowWin32() {} + + // Prevent copying. + MockFlutterWindowWin32(MockFlutterWindowWin32 const&) = delete; + MockFlutterWindowWin32& operator=(MockFlutterWindowWin32 const&) = delete; + + // Wrapper for GetCurrentDPI() which is a protected method. + UINT GetDpi() { return GetCurrentDPI(); } + + // Simulates a WindowProc message from the OS. + LRESULT InjectWindowMessage(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + return HandleMessage(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_METHOD4(DefaultWindowProc, LRESULT(HWND, UINT, WPARAM, LPARAM)); + MOCK_METHOD0(GetDpiScale, float()); + MOCK_METHOD0(IsVisible, bool()); + MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); + + private: + U16StringHandler on_text_; +}; + +class TestKeystate { + public: + void Set(int virtual_key, bool pressed, bool toggled_on = false) { + state_[virtual_key] = (pressed ? kStateMaskPressed : 0) | + (toggled_on ? kStateMaskToggled : 0); + } + + SHORT Get(int virtual_key) { return state_[virtual_key]; } + + KeyboardKeyEmbedderHandler::GetKeyStateHandler Getter() { + return [this](int virtual_key) { return Get(virtual_key); }; + } + + private: + std::map state_; +}; + +// A FlutterWindowsView that overrides the RegisterKeyboardHandlers function +// to register the keyboard hook handlers that can be spied upon. +class TestFlutterWindowsView : public FlutterWindowsView { + public: + TestFlutterWindowsView() + // The WindowBindingHandler is used for window size and such, and doesn't + // affect keyboard. + : FlutterWindowsView( + std::make_unique<::testing::NiceMock>()), + is_printable(true) {} + + void InjectPendingEvents(MockFlutterWindowWin32* win32window) { + while (pending_responds_.size() > 0) { + SimulatedEvent event = pending_responds_.front(); + win32window->InjectWindowMessage(event.message, event.wparam, + event.lparam); + pending_responds_.pop_front(); + } + } + + bool is_printable; + + protected: + void RegisterKeyboardHandlers( + BinaryMessenger* messenger, + KeyboardKeyHandler::EventDispatcher dispatch_event, + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state) override { + FlutterWindowsView::RegisterKeyboardHandlers( + messenger, + [this](UINT cInputs, LPINPUT pInputs, int cbSize) -> UINT { + return this->SendInput(cInputs, pInputs, cbSize); + }, + key_state_.Getter()); + } + + private: + UINT SendInput(UINT cInputs, LPINPUT pInputs, int cbSize) { + // Simulate the event loop by just sending the event sent to + // "SendInput" directly to the window. + const KEYBDINPUT kbdinput = pInputs->ki; + const UINT message = + (kbdinput.dwFlags & KEYEVENTF_KEYUP) ? WM_KEYUP : WM_KEYDOWN; + const bool is_key_up = kbdinput.dwFlags & KEYEVENTF_KEYUP; + const LPARAM lparam = CreateKeyEventLparam( + kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY, is_key_up); + pending_responds_.push_back(SimulatedEvent{message, kbdinput.wVk, lparam}); + if (is_printable && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { + pending_responds_.push_back( + SimulatedEvent{WM_CHAR, kbdinput.wVk, lparam}); + } + return 1; + } + + std::deque pending_responds_; + TestKeystate key_state_; +}; + +// A struct to use as a FlutterPlatformMessageResponseHandle so it can keep the +// callbacks and user data passed to the engine's +// PlatformMessageCreateResponseHandle for use in the SendPlatformMessage +// overridden function. +struct TestResponseHandle { + FlutterDesktopBinaryReply callback; + void* user_data; +}; + +static bool test_response = false; + +typedef enum { + kKeyCallOnKey, + kKeyCallOnText, +} KeyCallType; + +typedef struct { + KeyCallType type; + + // Only one of the following fields should be assigned. + FlutterKeyEvent key_event; + std::u16string text; +} KeyCall; + +static std::vector key_calls; + +void clear_key_calls() { + for (KeyCall& key_call : key_calls) { + if (key_call.type == kKeyCallOnKey && + key_call.key_event.character != nullptr) { + delete[] key_call.key_event.character; + } + } + key_calls.clear(); +} + +std::unique_ptr> keyHandlingResponse(bool handled) { + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + document.SetObject(); + document.AddMember("handled", test_response, allocator); + return flutter::JsonMessageCodec::GetInstance().EncodeMessage(document); +} + +// Returns an engine instance configured with dummy project path values, and +// overridden methods for sending platform messages, so that the engine can +// respond as if the framework were connected. +std::unique_ptr GetTestEngine() { + FlutterDesktopEngineProperties properties = {}; + properties.assets_path = L"C:\\foo\\flutter_assets"; + properties.icu_data_path = L"C:\\foo\\icudtl.dat"; + properties.aot_library_path = L"C:\\foo\\aot.so"; + FlutterProjectBundle project(properties); + auto engine = std::make_unique(project); + + EngineModifier modifier(engine.get()); + + // This mock handles channel messages. + modifier.embedder_api().SendPlatformMessage = + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPlatformMessage* message) { + if (std::string(message->channel) == std::string("flutter/settings")) { + return kSuccess; + } + if (std::string(message->channel) == std::string("flutter/keyevent")) { + auto response = keyHandlingResponse(true); + const TestResponseHandle* response_handle = + reinterpret_cast( + message->response_handle); + if (response_handle->callback != nullptr) { + response_handle->callback(response->data(), response->size(), + response_handle->user_data); + } + return kSuccess; + } + return kSuccess; + }; + + // This mock handles key events sent through the embedder API, + // and records it in `key_calls`. + modifier.embedder_api().SendKeyEvent = + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + FlutterKeyEvent clone_event = *event; + clone_event.character = event->character == nullptr + ? nullptr + : clone_string(event->character); + key_calls.push_back(KeyCall{ + .type = kKeyCallOnKey, + .key_event = clone_event, + }); + if (callback != nullptr) { + callback(test_response, user_data); + } + return kSuccess; + }; + + // The following mocks enable channel mocking. + modifier.embedder_api().PlatformMessageCreateResponseHandle = + [](auto engine, auto data_callback, auto user_data, auto response_out) { + TestResponseHandle* response_handle = new TestResponseHandle(); + response_handle->user_data = user_data; + response_handle->callback = data_callback; + *response_out = reinterpret_cast( + response_handle); + return kSuccess; + }; + + modifier.embedder_api().PlatformMessageReleaseResponseHandle = + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterPlatformMessageResponseHandle* response) { + const TestResponseHandle* response_handle = + reinterpret_cast(response); + delete response_handle; + return kSuccess; + }; + + // The following mocks allows RunWithEntrypoint to be run, which creates a + // non-empty FlutterEngine and enables SendKeyEvent. + + modifier.embedder_api().Run = + [](size_t version, const FlutterRendererConfig* config, + const FlutterProjectArgs* args, void* user_data, + FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) { + *engine_out = reinterpret_cast(1); + + return kSuccess; + }; + modifier.embedder_api().UpdateLocales = + [](auto engine, const FlutterLocale** locales, size_t locales_count) { + return kSuccess; + }; + modifier.embedder_api().SendWindowMetricsEvent = + [](auto engine, const FlutterWindowMetricsEvent* event) { + return kSuccess; + }; + modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; }; + + engine->RunWithEntrypoint(nullptr); + return engine; +} + +class KeyboardTester { + public: + explicit KeyboardTester() { + view_ = std::make_unique(); + view_->SetEngine(std::move(GetTestEngine())); + window_ = std::make_unique( + [](const std::u16string& text) { + key_calls.push_back(KeyCall{ + .type = kKeyCallOnText, + .text = text, + }); + }); + window_->SetView(view_.get()); + } + + KeyboardTester& Responding(bool response) { + test_response = response; + return *this; + } + + LRESULT InjectWindowMessage(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + return window_->InjectWindowMessage(message, wparam, lparam); + } + + private: + std::unique_ptr view_; + std::unique_ptr window_; +}; + +constexpr uint64_t kScanCodeKeyA = 0x1e; +// constexpr uint64_t kScanCodeNumpad1 = 0x4f; +// constexpr uint64_t kScanCodeNumLock = 0x45; +// constexpr uint64_t kScanCodeControl = 0x1d; +// constexpr uint64_t kScanCodeShiftLeft = 0x2a; +// constexpr uint64_t kScanCodeShiftRight = 0x36; + +constexpr uint64_t kVirtualKeyA = 0x41; + +constexpr bool kExtended = true; +constexpr bool kNotExtended = false; +constexpr bool kWasDown = true; +constexpr bool kWasUp = false; +constexpr bool kSynthesized = true; +constexpr bool kNotSynthesized = false; + +} // namespace + +#define EXPECT_CALL_IS_EVENT(_key_call, ...) \ + EXPECT_EQ(_key_call.type, kKeyCallOnKey); \ + EXPECT_EVENT_EQUALS(_key_call.key_event, __VA_ARGS__); + +TEST(KeyboardTest, LowerCaseA) { + KeyboardTester tester; + + tester.Responding(true).InjectWindowMessage( + WM_KEYDOWN, kVirtualKeyA, + CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, + kLogicalKeyA, "A", kNotSynthesized); + clear_key_calls(); + + tester.Responding(true).InjectWindowMessage( + WM_KEYUP, kVirtualKeyA, + CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasDown)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, + kLogicalKeyA, "", kNotSynthesized); + clear_key_calls(); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc new file mode 100644 index 0000000000000..a28aa72067c7f --- /dev/null +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -0,0 +1,21 @@ +// 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 "flutter/shell/platform/windows/testing/test_keyboard.h" + +namespace flutter { +namespace testing { + +char* clone_string(const char* string) { + if (string == nullptr) { + return nullptr; + } + size_t len = strlen(string); + char* result = new char[len + 1]; + strcpy(result, string); + return result; +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/testing/test_keyboard.h b/shell/platform/windows/testing/test_keyboard.h new file mode 100644 index 0000000000000..a7728ce015b03 --- /dev/null +++ b/shell/platform/windows/testing/test_keyboard.h @@ -0,0 +1,74 @@ +// 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_TESTING_TEST_KEYBOARD_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_TEST_KEYBOARD_H_ + +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +#include "gtest/gtest.h" + +#define _RETURN_IF_NOT_EQUALS(val1, val2) \ + if ((val1) != (val2)) { \ + return ::testing::AssertionFailure() \ + << "Expected equality of these values:\n " #val1 "\n To be: " \ + << val2 << "\n Actual: \n " << val1; \ + } + +namespace flutter { +namespace testing { + +namespace { + +std::string _print_character(const char* s) { + if (s == nullptr) { + return "nullptr"; + } + return std::string("\"") + s + "\""; +} + +::testing::AssertionResult _EventEquals(const char* expr_event, + const char* expr_expected, + const FlutterKeyEvent& event, + const FlutterKeyEvent& expected) { + _RETURN_IF_NOT_EQUALS(event.struct_size, sizeof(FlutterKeyEvent)); + _RETURN_IF_NOT_EQUALS(event.type, expected.type); + _RETURN_IF_NOT_EQUALS(event.physical, expected.physical); + _RETURN_IF_NOT_EQUALS(event.logical, expected.logical); + if ((event.character == nullptr) != (expected.character == nullptr) || + strcmp(event.character, expected.character) != 0) { + return ::testing::AssertionFailure() + << "Expected equality of these values:\n expected.character\n " + << _print_character(expected.character) << "\n Actual: \n " + << _print_character(event.character); + } + _RETURN_IF_NOT_EQUALS(event.synthesized, expected.synthesized); + return ::testing::AssertionSuccess(); +} + +} // namespace + +// Clone string onto the heap. +// +// If #string is nullptr, returns nullptr. Otherwise, the returned pointer must +// be freed with delete[]. +char* clone_string(const char* string); + +} // namespace testing +} // namespace flutter + +#define EXPECT_EVENT_EQUALS(_target, _type, _physical, _logical, _character, \ + _synthesized) \ + EXPECT_PRED_FORMAT2(_EventEquals, _target, \ + (FlutterKeyEvent{ \ + .type = _type, \ + .physical = _physical, \ + .logical = _logical, \ + .character = _character, \ + .synthesized = _synthesized, \ + })); + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_TEST_KEYBOARD_H_ diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index f7c8e231a3a48..1ed1333f45f16 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -172,6 +172,25 @@ void WindowWin32::UpdateCursorRect(const Rect& rect) { text_input_manager_.UpdateCaretRect(rect); } +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; + } +} + LRESULT WindowWin32::HandleMessage(UINT const message, WPARAM const wparam, @@ -313,6 +332,9 @@ WindowWin32::HandleMessage(UINT const message, case WM_SYSDEADCHAR: case WM_CHAR: case WM_SYSCHAR: { + if (ignore_next_event) { + break; + } static wchar_t s_pending_high_surrogate = 0; wchar_t character = static_cast(wparam); @@ -329,10 +351,11 @@ WindowWin32::HandleMessage(UINT const message, s_pending_high_surrogate = 0; } - // All key presses that generate a character should be sent from + // Key presses that generate a non-surrogate 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. - if (keycode_for_char_message_ != 0) { + if (keycode_for_char_message_ != 0 && + (IS_HIGH_SURROGATE(character) || IS_LOW_SURROGATE(character))) { const unsigned int scancode = (lparam >> 16) & 0xff; const bool extended = ((lparam >> 24) & 0x01) == 0x01; const bool was_down = lparam & 0x40000000; @@ -366,19 +389,16 @@ WindowWin32::HandleMessage(UINT const message, case WM_SYSKEYDOWN: case WM_KEYUP: case WM_SYSKEYUP: + handled_for_char_message_ = false; 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 + // Check if this key produces a surrogate. If so, the key press should + // be resolved 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 = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR); - if (character > 0 && is_keydown_message && GetKeyState(VK_CONTROL) >= 0 && - GetKeyState(VK_LWIN) >= 0 && GetKeyState(VK_RWIN) >= 0) { + keycode_for_char_message_ = 0; + if (character > 0 && is_keydown_message && + (IS_HIGH_SURROGATE(character) || IS_LOW_SURROGATE(character))) { keycode_for_char_message_ = wparam; break; } @@ -386,13 +406,14 @@ WindowWin32::HandleMessage(UINT const message, const unsigned int scancode = (lparam >> 16) & 0xff; const bool extended = ((lparam >> 24) & 0x01) == 0x01; // If the key is a modifier, get its side. - if (keyCode == VK_SHIFT || keyCode == VK_MENU || keyCode == VK_CONTROL) { - keyCode = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX); - } + keyCode = ResolveKeyCode(keyCode, extended, scancode); const int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; const bool was_down = lparam & 0x40000000; - if (OnKey(keyCode, scancode, action, 0, extended, was_down)) { + if (OnKey(keyCode, scancode, action, character, extended, was_down)) { + handled_for_char_message_ = true; return 0; + } else { + ignore_next_event = true; } break; } From 1cf7636d35125bc58e818f49055281954da0cb74 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 6 Aug 2021 00:52:37 -0700 Subject: [PATCH 02/30] Fix build --- shell/platform/windows/flutter_windows_view.h | 7 ++++++- shell/platform/windows/window_win32.cc | 7 ------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index f70d1b1aa62d6..32f5bbec9176d 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -19,6 +19,8 @@ #include "flutter/shell/platform/windows/cursor_handler.h" #include "flutter/shell/platform/windows/flutter_windows_engine.h" #include "flutter/shell/platform/windows/keyboard_handler_base.h" +#include "flutter/shell/platform/windows/keyboard_key_handler.h" +#include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h" #include "flutter/shell/platform/windows/platform_handler.h" #include "flutter/shell/platform/windows/public/flutter_windows.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" @@ -144,7 +146,10 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, protected: // Called to create the keyboard hook handlers. - virtual void RegisterKeyboardHandlers(flutter::BinaryMessenger* messenger); + virtual void RegisterKeyboardHandlers( + flutter::BinaryMessenger* messenger, + flutter::KeyboardKeyHandler::EventDispatcher dispatch_event, + flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state); // Used by RegisterKeyboardHandlers to add a new keyboard hook handler. void AddKeyboardHandler( diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 1ed1333f45f16..292f91e0e13b6 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -332,9 +332,6 @@ WindowWin32::HandleMessage(UINT const message, case WM_SYSDEADCHAR: case WM_CHAR: case WM_SYSCHAR: { - if (ignore_next_event) { - break; - } static wchar_t s_pending_high_surrogate = 0; wchar_t character = static_cast(wparam); @@ -389,7 +386,6 @@ WindowWin32::HandleMessage(UINT const message, case WM_SYSKEYDOWN: case WM_KEYUP: case WM_SYSKEYUP: - handled_for_char_message_ = false; const bool is_keydown_message = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN); // Check if this key produces a surrogate. If so, the key press should @@ -410,10 +406,7 @@ WindowWin32::HandleMessage(UINT const message, const int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; const bool was_down = lparam & 0x40000000; if (OnKey(keyCode, scancode, action, character, extended, was_down)) { - handled_for_char_message_ = true; return 0; - } else { - ignore_next_event = true; } break; } From 8a7d365ec2341ba9bde1cd531314eea901b98198 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 6 Aug 2021 01:46:44 -0700 Subject: [PATCH 03/30] Revert some changes --- .../platform/windows/keyboard_key_handler.cc | 3 +- shell/platform/windows/keyboard_unittests.cc | 123 ++++++++++++------ shell/platform/windows/window_win32.cc | 19 +-- shell/platform/windows/window_win32.h | 2 +- 4 files changed, 97 insertions(+), 50 deletions(-) diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index 0f80687cbba73..c62668f132baa 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -261,7 +261,8 @@ void KeyboardKeyHandler::ResolvePendingEvent(uint64_t sequence_id, if (event.unreplied == 0) { std::unique_ptr event_ptr = std::move(*iter); pending_responds_.erase(iter); - if (!event_ptr->any_handled) { + const bool should_redispatch = !event_ptr->any_handled; + if (should_redispatch) { RedispatchEvent(std::move(event_ptr)); } } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 7a98fb6ee00d4..efeb9db48c76b 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -51,6 +51,8 @@ static LPARAM CreateKeyEventLparam(USHORT scancode, (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } +constexpr LRESULT kDefaultWindowProcBehavior = 0xDEADC0DE; + class MockFlutterWindowWin32 : public FlutterWindowWin32 { public: typedef std::function U16StringHandler; @@ -69,6 +71,13 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32 { // Wrapper for GetCurrentDPI() which is a protected method. UINT GetDpi() { return GetCurrentDPI(); } + LRESULT DefaultWindowProc(HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam) override { + return kDefaultWindowProcBehavior; + } + // Simulates a WindowProc message from the OS. LRESULT InjectWindowMessage(UINT const message, WPARAM const wparam, @@ -86,7 +95,6 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32 { MOCK_METHOD0(OnPointerLeave, void()); MOCK_METHOD0(OnSetCursor, void()); MOCK_METHOD2(OnScroll, void(double, double)); - MOCK_METHOD4(DefaultWindowProc, LRESULT(HWND, UINT, WPARAM, LPARAM)); MOCK_METHOD0(GetDpiScale, float()); MOCK_METHOD0(IsVisible, bool()); MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); @@ -126,8 +134,10 @@ class TestFlutterWindowsView : public FlutterWindowsView { void InjectPendingEvents(MockFlutterWindowWin32* win32window) { while (pending_responds_.size() > 0) { SimulatedEvent event = pending_responds_.front(); - win32window->InjectWindowMessage(event.message, event.wparam, - event.lparam); + EXPECT_EQ( + win32window->InjectWindowMessage(event.message, event.wparam, + event.lparam), + kDefaultWindowProcBehavior); pending_responds_.pop_front(); } } @@ -178,8 +188,6 @@ struct TestResponseHandle { void* user_data; }; -static bool test_response = false; - typedef enum { kKeyCallOnKey, kKeyCallOnText, @@ -205,11 +213,63 @@ void clear_key_calls() { key_calls.clear(); } +std::unique_ptr GetTestEngine(); + +class KeyboardTester { + public: + explicit KeyboardTester() { + view_ = std::make_unique(); + view_->SetEngine(std::move(GetTestEngine())); + window_ = std::make_unique( + [](const std::u16string& text) { + key_calls.push_back(KeyCall{ + .type = kKeyCallOnText, + .text = text, + }); + }); + window_->SetView(view_.get()); + } + + void Responding(bool response) { + test_response = response; + } + + void NextMessageShouldDefault() { + next_event_should_default_ = true; + } + + void InjectWindowMessage(UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + LRESULT expected_result = next_event_should_default_ ? + kDefaultWindowProcBehavior : 0; + EXPECT_EQ( + window_->InjectWindowMessage(message, wparam, lparam), + expected_result); + next_event_should_default_ = false; + } + + void InjectPendingEvents() { + view_->InjectPendingEvents(window_.get()); + } + + static bool test_response; + + private: + std::unique_ptr view_; + std::unique_ptr window_; + + bool next_event_should_default_ = false; +}; + + +bool KeyboardTester::test_response = false; + std::unique_ptr> keyHandlingResponse(bool handled) { rapidjson::Document document; auto& allocator = document.GetAllocator(); document.SetObject(); - document.AddMember("handled", test_response, allocator); + document.AddMember("handled", KeyboardTester::test_response, allocator); return flutter::JsonMessageCodec::GetInstance().EncodeMessage(document); } @@ -261,7 +321,7 @@ std::unique_ptr GetTestEngine() { .key_event = clone_event, }); if (callback != nullptr) { - callback(test_response, user_data); + callback(KeyboardTester::test_response, user_data); } return kSuccess; }; @@ -311,37 +371,6 @@ std::unique_ptr GetTestEngine() { return engine; } -class KeyboardTester { - public: - explicit KeyboardTester() { - view_ = std::make_unique(); - view_->SetEngine(std::move(GetTestEngine())); - window_ = std::make_unique( - [](const std::u16string& text) { - key_calls.push_back(KeyCall{ - .type = kKeyCallOnText, - .text = text, - }); - }); - window_->SetView(view_.get()); - } - - KeyboardTester& Responding(bool response) { - test_response = response; - return *this; - } - - LRESULT InjectWindowMessage(UINT const message, - WPARAM const wparam, - LPARAM const lparam) { - return window_->InjectWindowMessage(message, wparam, lparam); - } - - private: - std::unique_ptr view_; - std::unique_ptr window_; -}; - constexpr uint64_t kScanCodeKeyA = 0x1e; // constexpr uint64_t kScanCodeNumpad1 = 0x4f; // constexpr uint64_t kScanCodeNumLock = 0x45; @@ -366,17 +395,28 @@ constexpr bool kNotSynthesized = false; TEST(KeyboardTest, LowerCaseA) { KeyboardTester tester; + tester.Responding(true); - tester.Responding(true).InjectWindowMessage( + // US Keyboard layout + + // Press A + tester.InjectWindowMessage( WM_KEYDOWN, kVirtualKeyA, CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)); + tester.InjectWindowMessage( + WM_CHAR, kVirtualKeyA, + CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, kLogicalKeyA, "A", kNotSynthesized); clear_key_calls(); - tester.Responding(true).InjectWindowMessage( + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + + // Release A + tester.InjectWindowMessage( WM_KEYUP, kVirtualKeyA, CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasDown)); @@ -384,6 +424,9 @@ TEST(KeyboardTest, LowerCaseA) { EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, kLogicalKeyA, "", kNotSynthesized); clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); } } // namespace testing diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 292f91e0e13b6..7d305d2d6f34d 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -351,8 +351,7 @@ WindowWin32::HandleMessage(UINT const message, // Key presses that generate a non-surrogate 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. - if (keycode_for_char_message_ != 0 && - (IS_HIGH_SURROGATE(character) || IS_LOW_SURROGATE(character))) { + if (keycode_for_char_message_ != 0) { const unsigned int scancode = (lparam >> 16) & 0xff; const bool extended = ((lparam >> 24) & 0x01) == 0x01; const bool was_down = lparam & 0x40000000; @@ -388,13 +387,17 @@ WindowWin32::HandleMessage(UINT const message, case WM_SYSKEYUP: const bool is_keydown_message = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN); - // Check if this key produces a surrogate. If so, the key press should - // be resolved with the character produced at WM_CHAR. Store the produced + // 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 = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR); - keycode_for_char_message_ = 0; - if (character > 0 && is_keydown_message && - (IS_HIGH_SURROGATE(character) || IS_LOW_SURROGATE(character))) { + if (character > 0 && is_keydown_message && GetKeyState(VK_CONTROL) >= 0 && + GetKeyState(VK_LWIN) >= 0 && GetKeyState(VK_RWIN) >= 0) { keycode_for_char_message_ = wparam; break; } @@ -411,7 +414,7 @@ WindowWin32::HandleMessage(UINT const message, break; } - return DefWindowProc(window_handle_, message, wparam, result_lparam); + return DefaultWindowProc(window_handle_, message, wparam, result_lparam); } UINT WindowWin32::GetCurrentDPI() { diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index 37d8d9aa18719..20de5c7970bf2 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -159,7 +159,7 @@ class WindowWin32 { UINT GetCurrentHeight(); protected: - LRESULT DefaultWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + virtual LRESULT DefaultWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); private: // Release OS resources associated with window. From 2997fb881b393894ffed6d7272bf1f618131791d Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 6 Aug 2021 02:54:03 -0700 Subject: [PATCH 04/30] Fix tests (ATP) --- .../windows/flutter_window_win32_unittests.cc | 6 ++-- .../windows/keyboard_key_handler_unittests.cc | 28 +++++++++---------- shell/platform/windows/window_win32.cc | 7 ++--- .../windows/window_win32_unittests.cc | 2 +- 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index ddf86d82eff5a..dd1cc1c323856 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -366,7 +366,7 @@ TEST(FlutterWindowWin32Test, NonPrintableKeyDownPropagation) { .Times(1) .RetiresOnSaturation(); EXPECT_CALL(win32window, DefaultWindowProc(_, _, _, _)) - .Times(0) + .Times(1) .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _)) .Times(0); @@ -459,12 +459,12 @@ TEST(FlutterWindowWin32Test, CharKeyDownPropagation) { } } -// Tests key event propagation of modifier key down events. This are different +// Tests key event propagation of modifier key down events. This is different // from non-printable events in that they call MapVirtualKey, resulting in a // slightly different code path. TEST(FlutterWindowWin32Test, ModifierKeyDownPropagation) { constexpr WPARAM virtual_key = VK_LSHIFT; - constexpr WPARAM scan_code = 20; + constexpr WPARAM scan_code = 0x2a; constexpr char32_t character = 0; MockFlutterWindowWin32 win32window; std::deque pending_events; diff --git a/shell/platform/windows/keyboard_key_handler_unittests.cc b/shell/platform/windows/keyboard_key_handler_unittests.cc index d94a4b6e47778..5bd3c39464a98 100644 --- a/shell/platform/windows/keyboard_key_handler_unittests.cc +++ b/shell/platform/windows/keyboard_key_handler_unittests.cc @@ -470,7 +470,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN, 0, false, false), true); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0, true, false), true); EXPECT_EQ(redispatch_scancode, 0); @@ -489,7 +489,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN, 0, false, false), false); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0, true, false), false); @@ -498,7 +498,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { hook_history.clear(); // The key up event only causes a AltRight (extended AltLeft) up. - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, WM_KEYUP, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, 0, true, true), true); EXPECT_EQ(hook_history.size(), 1); @@ -523,7 +523,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYUP, 0, false, true), false); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, WM_KEYUP, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, 0, true, true), false); @@ -556,7 +556,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { false); // Key down AltRight. - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0, true, false), true); EXPECT_EQ(redispatch_scancode, 0); @@ -569,14 +569,14 @@ TEST(KeyboardKeyHandlerTest, AltGr) { hook_history.clear(); // Resolve redispatches. - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0, true, false), false); redispatch_scancode = 0; // Key up AltRight. - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, WM_KEYUP, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, 0, true, true), true); EXPECT_EQ(hook_history.size(), 1); @@ -601,7 +601,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYUP, 0, false, true), false); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, WM_KEYUP, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, 0, true, true), false); @@ -635,7 +635,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN, 0, false, false), true); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0, true, false), true); EXPECT_EQ(redispatch_scancode, 0); @@ -654,7 +654,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN, 0, false, false), false); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0, true, false), false); @@ -666,7 +666,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN, 0, false, true), true); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0, true, true), true); EXPECT_EQ(redispatch_scancode, 0); @@ -685,7 +685,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYDOWN, 0, false, false), false); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYDOWN, 0, true, false), false); @@ -694,7 +694,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { hook_history.clear(); // Key up AltRight. - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, WM_KEYUP, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, 0, true, true), true); EXPECT_EQ(hook_history.size(), 1); @@ -719,7 +719,7 @@ TEST(KeyboardKeyHandlerTest, AltGr) { EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LCONTROL, kScanCodeControlLeft, WM_KEYUP, 0, false, true), false); - EXPECT_EQ(handler.KeyboardHook(nullptr, VK_LMENU, kScanCodeAltLeft, WM_KEYUP, + EXPECT_EQ(handler.KeyboardHook(nullptr, VK_RMENU, kScanCodeAltLeft, WM_KEYUP, 0, true, true), false); diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 7d305d2d6f34d..ab05b369a18cf 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -179,7 +179,6 @@ static uint16_t ResolveKeyCode(uint16_t 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; @@ -348,7 +347,7 @@ WindowWin32::HandleMessage(UINT const message, s_pending_high_surrogate = 0; } - // Key presses that generate a non-surrogate should be sent from + // 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. if (keycode_for_char_message_ != 0) { @@ -399,10 +398,10 @@ WindowWin32::HandleMessage(UINT const message, if (character > 0 && is_keydown_message && GetKeyState(VK_CONTROL) >= 0 && GetKeyState(VK_LWIN) >= 0 && GetKeyState(VK_RWIN) >= 0) { keycode_for_char_message_ = wparam; - break; + return 0; } unsigned int keyCode(wparam); - const unsigned int scancode = (lparam >> 16) & 0xff; + 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); diff --git a/shell/platform/windows/window_win32_unittests.cc b/shell/platform/windows/window_win32_unittests.cc index ce54155cd7a96..31afb7e9b41ae 100644 --- a/shell/platform/windows/window_win32_unittests.cc +++ b/shell/platform/windows/window_win32_unittests.cc @@ -97,7 +97,7 @@ TEST(MockWin32Window, KeyDownWithCtrl) { // Expect OnKey, but not OnText, because Control + Key is not followed by // WM_CHAR - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, true)).Times(1); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 'A', false, true)).Times(1); EXPECT_CALL(window, OnText(_)).Times(0); window.InjectWindowMessage(WM_KEYDOWN, 65, lparam); From eac8a398519b948da45f0de49903ef8787a05925 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 6 Aug 2021 22:23:10 -0700 Subject: [PATCH 05/30] Win32Message and inject messagelist --- .../windows/flutter_window_win32_unittests.cc | 91 +++++++++++-------- shell/platform/windows/keyboard_unittests.cc | 86 +++++++++--------- .../windows/testing/mock_window_win32.cc | 53 +++++++++++ .../windows/testing/mock_window_win32.h | 47 +++++++++- .../platform/windows/testing/test_keyboard.cc | 38 ++++++++ .../platform/windows/testing/test_keyboard.h | 44 ++------- shell/platform/windows/window_win32.cc | 26 +++++- shell/platform/windows/window_win32.h | 11 ++- .../windows/window_win32_unittests.cc | 29 +++--- 9 files changed, 284 insertions(+), 141 deletions(-) diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index dd1cc1c323856..99a4d4b2eeb51 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -11,6 +11,7 @@ #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/mock_window_win32.h" #include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" @@ -40,14 +41,6 @@ static LPARAM CreateKeyEventLparam(USHORT scancode, (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } -// A struc to hold simulated events that will be delivered after the framework -// response is handled. -struct SimulatedEvent { - UINT message; - WPARAM wparam; - LPARAM lparam; -}; - // A key event handler that can be spied on while it forwards calls to the real // key event handler. class SpyKeyboardKeyHandler : public KeyboardHandlerBase { @@ -123,7 +116,7 @@ class SpyTextInputPlugin : public KeyboardHandlerBase, std::unique_ptr real_implementation_; }; -class MockFlutterWindowWin32 : public FlutterWindowWin32 { +class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueue { public: MockFlutterWindowWin32() : FlutterWindowWin32(800, 600) { ON_CALL(*this, GetDpiScale()) @@ -142,7 +135,19 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32 { LRESULT InjectWindowMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) { - return HandleMessage(message, wparam, lparam); + return Win32SendMessage(NULL, message, wparam, lparam); + } + + void InjectMessages(int count, Win32Message message1, ...) { + Win32Message messages[count]; + messages[0] = message1; + va_list args; + va_start(args, message1); + for (int i = 1; i < count; i += 1) { + messages[i] = va_arg(args, Win32Message); + } + va_end(args); + InjectMessageList(count, messages); } MOCK_METHOD1(OnDpiScale, void(unsigned int)); @@ -153,10 +158,29 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32 { MOCK_METHOD0(OnPointerLeave, void()); MOCK_METHOD0(OnSetCursor, void()); MOCK_METHOD2(OnScroll, void(double, double)); - MOCK_METHOD4(DefaultWindowProc, LRESULT(HWND, UINT, WPARAM, LPARAM)); MOCK_METHOD0(GetDpiScale, float()); MOCK_METHOD0(IsVisible, bool()); MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); + + protected: + virtual BOOL Win32PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) override { + return MockMessageQueue::Win32PeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + } + + LRESULT Win32DefWindowProc(HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam) override { + return kMockDefaultResult; + } + + private: + LRESULT Win32SendMessage(HWND hWnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) override { + return HandleMessage(message, wparam, lparam); + } }; class MockWindowBindingHandlerDelegate : public WindowBindingHandlerDelegate { @@ -206,12 +230,8 @@ class TestFlutterWindowsView : public FlutterWindowsView { SpyTextInputPlugin* text_input_plugin; void InjectPendingEvents(MockFlutterWindowWin32* win32window) { - while (pending_responds_.size() > 0) { - SimulatedEvent event = pending_responds_.front(); - win32window->InjectWindowMessage(event.message, event.wparam, - event.lparam); - pending_responds_.pop_front(); - } + win32window->InjectMessageList(pending_responds_.size(), pending_responds_.data()); + pending_responds_.clear(); } protected: @@ -246,15 +266,19 @@ class TestFlutterWindowsView : public FlutterWindowsView { // simulate it for the test with the key we know is in the test. The // KBDINPUT we're passed doesn't have it filled in (on purpose, so that // Windows will fill it in). - pending_responds_.push_back(SimulatedEvent{message, virtual_key_, lparam}); + // + // TODO(dkwingsmt): Don't check the message results for redispatched + // messages for now, because making them work takes non-trivial rework + // to our current structure. https://github.com/flutter/flutter/issues/87843 + // If this is resolved, change them to kMockDefaultResult. + pending_responds_.push_back(Win32Message{message, virtual_key_, lparam, kMockDontCheckResult}); if (is_printable_ && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { - pending_responds_.push_back( - SimulatedEvent{WM_CHAR, virtual_key_, lparam}); + pending_responds_.push_back(Win32Message{WM_CHAR, virtual_key_, lparam, kMockDontCheckResult}); } return 1; } - std::deque pending_responds_; + std::vector pending_responds_; WPARAM virtual_key_; bool is_printable_; }; @@ -344,7 +368,6 @@ TEST(FlutterWindowWin32Test, NonPrintableKeyDownPropagation) { constexpr WPARAM scan_code = 10; constexpr char32_t character = 0; MockFlutterWindowWin32 win32window; - std::deque pending_events; auto window_binding_handler = std::make_unique<::testing::NiceMock>(); TestFlutterWindowsView flutter_windows_view( @@ -365,15 +388,11 @@ TEST(FlutterWindowWin32Test, NonPrintableKeyDownPropagation) { KeyboardHook(_, _, _, _, _, _, _)) .Times(1) .RetiresOnSaturation(); - EXPECT_CALL(win32window, DefaultWindowProc(_, _, _, _)) - .Times(1) - .RetiresOnSaturation(); EXPECT_CALL(*flutter_windows_view.key_event_handler, TextHook(_, _)) .Times(0); EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _)) .Times(0); - EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam), - 0); + win32window.InjectMessages(1, Win32Message{WM_KEYDOWN, virtual_key, lparam}); flutter_windows_view.InjectPendingEvents(&win32window); } @@ -388,8 +407,7 @@ TEST(FlutterWindowWin32Test, NonPrintableKeyDownPropagation) { EXPECT_CALL(*flutter_windows_view.text_input_plugin, KeyboardHook(_, _, _, _, _, _, _)) .Times(0); - EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam), - 0); + win32window.InjectMessages(1, Win32Message{WM_KEYDOWN, virtual_key, lparam}); flutter_windows_view.InjectPendingEvents(&win32window); } } @@ -431,9 +449,10 @@ TEST(FlutterWindowWin32Test, CharKeyDownPropagation) { EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _)) .Times(1) .RetiresOnSaturation(); - EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam), - 0); - EXPECT_EQ(win32window.InjectWindowMessage(WM_CHAR, virtual_key, lparam), 0); + win32window.InjectMessages(2, + Win32Message{WM_KEYDOWN, virtual_key, lparam}, + Win32Message{WM_CHAR, virtual_key, lparam} + ); flutter_windows_view.InjectPendingEvents(&win32window); } @@ -452,9 +471,10 @@ TEST(FlutterWindowWin32Test, CharKeyDownPropagation) { .Times(0); EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _)) .Times(0); - EXPECT_EQ(win32window.InjectWindowMessage(WM_KEYDOWN, virtual_key, lparam), - 0); - EXPECT_EQ(win32window.InjectWindowMessage(WM_CHAR, virtual_key, lparam), 0); + win32window.InjectMessages(2, + Win32Message{WM_KEYDOWN, virtual_key, lparam}, + Win32Message{WM_CHAR, virtual_key, lparam} + ); flutter_windows_view.InjectPendingEvents(&win32window); } } @@ -467,7 +487,6 @@ TEST(FlutterWindowWin32Test, ModifierKeyDownPropagation) { constexpr WPARAM scan_code = 0x2a; constexpr char32_t character = 0; MockFlutterWindowWin32 win32window; - std::deque pending_events; auto window_binding_handler = std::make_unique<::testing::NiceMock>(); TestFlutterWindowsView flutter_windows_view( diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index efeb9db48c76b..0ddc312bba01b 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -13,13 +13,15 @@ #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/mock_window_win32.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include #include +#include +#include using testing::_; using testing::Invoke; @@ -34,12 +36,6 @@ namespace { constexpr SHORT kStateMaskToggled = 0x01; constexpr SHORT kStateMaskPressed = 0x80; -struct SimulatedEvent { - UINT message; - WPARAM wparam; - LPARAM lparam; -}; - static LPARAM CreateKeyEventLparam(USHORT scancode, bool extended, bool was_down, @@ -51,9 +47,7 @@ static LPARAM CreateKeyEventLparam(USHORT scancode, (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } -constexpr LRESULT kDefaultWindowProcBehavior = 0xDEADC0DE; - -class MockFlutterWindowWin32 : public FlutterWindowWin32 { +class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueue { public: typedef std::function U16StringHandler; @@ -71,18 +65,18 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32 { // Wrapper for GetCurrentDPI() which is a protected method. UINT GetDpi() { return GetCurrentDPI(); } - LRESULT DefaultWindowProc(HWND hWnd, + LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) override { - return kDefaultWindowProcBehavior; + return kMockDefaultResult; } // Simulates a WindowProc message from the OS. LRESULT InjectWindowMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) { - return HandleMessage(message, wparam, lparam); + return Win32SendMessage(NULL, message, wparam, lparam); } void OnText(const std::u16string& text) override { on_text_(text); } @@ -99,8 +93,19 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32 { MOCK_METHOD0(IsVisible, bool()); MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); + virtual BOOL Win32PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) override { + return MockMessageQueue::Win32PeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + } + private: U16StringHandler on_text_; + + LRESULT Win32SendMessage(HWND hWnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) override { + return HandleMessage(message, wparam, lparam); + } }; class TestKeystate { @@ -131,19 +136,13 @@ class TestFlutterWindowsView : public FlutterWindowsView { std::make_unique<::testing::NiceMock>()), is_printable(true) {} + bool is_printable; + void InjectPendingEvents(MockFlutterWindowWin32* win32window) { - while (pending_responds_.size() > 0) { - SimulatedEvent event = pending_responds_.front(); - EXPECT_EQ( - win32window->InjectWindowMessage(event.message, event.wparam, - event.lparam), - kDefaultWindowProcBehavior); - pending_responds_.pop_front(); - } + win32window->InjectMessageList(pending_responds_.size(), pending_responds_.data()); + pending_responds_.clear(); } - bool is_printable; - protected: void RegisterKeyboardHandlers( BinaryMessenger* messenger, @@ -167,15 +166,14 @@ class TestFlutterWindowsView : public FlutterWindowsView { const bool is_key_up = kbdinput.dwFlags & KEYEVENTF_KEYUP; const LPARAM lparam = CreateKeyEventLparam( kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY, is_key_up); - pending_responds_.push_back(SimulatedEvent{message, kbdinput.wVk, lparam}); + pending_responds_.push_back(Win32Message{message, kbdinput.wVk, lparam, kMockDefaultResult}); if (is_printable && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { - pending_responds_.push_back( - SimulatedEvent{WM_CHAR, kbdinput.wVk, lparam}); + pending_responds_.push_back(Win32Message{WM_CHAR, kbdinput.wVk, lparam, kMockDefaultResult}); } return 1; } - std::deque pending_responds_; + std::vector pending_responds_; TestKeystate key_state_; }; @@ -238,15 +236,16 @@ class KeyboardTester { next_event_should_default_ = true; } - void InjectWindowMessage(UINT const message, - WPARAM const wparam, - LPARAM const lparam) { - LRESULT expected_result = next_event_should_default_ ? - kDefaultWindowProcBehavior : 0; - EXPECT_EQ( - window_->InjectWindowMessage(message, wparam, lparam), - expected_result); - next_event_should_default_ = false; + void InjectMessages(int count, Win32Message message1, ...) { + Win32Message messages[count]; + messages[0] = message1; + va_list args; + va_start(args, message1); + for (int i = 1; i < count; i += 1) { + messages[i] = va_arg(args, Win32Message); + } + va_end(args); + window_->InjectMessageList(count, messages); } void InjectPendingEvents() { @@ -400,12 +399,10 @@ TEST(KeyboardTest, LowerCaseA) { // US Keyboard layout // Press A - tester.InjectWindowMessage( - WM_KEYDOWN, kVirtualKeyA, - CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)); - tester.InjectWindowMessage( - WM_CHAR, kVirtualKeyA, - CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)); + tester.InjectMessages(2, + Win32Message{WM_KEYDOWN, kVirtualKeyA, CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)}, + Win32Message{WM_CHAR, kVirtualKeyA, CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)} + ); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, @@ -416,9 +413,8 @@ TEST(KeyboardTest, LowerCaseA) { EXPECT_EQ(key_calls.size(), 0); // Release A - tester.InjectWindowMessage( - WM_KEYUP, kVirtualKeyA, - CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasDown)); + tester.InjectMessages(1, + Win32Message{WM_KEYUP, kVirtualKeyA, CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasDown)}); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, diff --git a/shell/platform/windows/testing/mock_window_win32.cc b/shell/platform/windows/testing/mock_window_win32.cc index b3e0ac26e3a7c..9e80cdfd24e26 100644 --- a/shell/platform/windows/testing/mock_window_win32.cc +++ b/shell/platform/windows/testing/mock_window_win32.cc @@ -4,9 +4,48 @@ #include "flutter/shell/platform/windows/testing/mock_window_win32.h" +#include "gtest/gtest.h" + namespace flutter { namespace testing { +void MockMessageQueue::InjectMessageList(int count, const Win32Message* messages) { + for (int i = 0; i < count; i += 1) { + _pending_messages.push_back(messages[i]); + } + while (!_pending_messages.empty()) { + Win32Message message = _pending_messages.front(); + _pending_messages.pop_front(); + LRESULT result = Win32SendMessage(message.hWnd, message.message, message.wparam, message.lparam); + if (message.expected_result != kMockDontCheckResult) { + EXPECT_EQ(result, message.expected_result); + } + } +} + +BOOL MockMessageQueue::Win32PeekMessage(LPMSG lpMsg, + HWND hWnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg) { + for (auto iter = _pending_messages.begin(); + iter != _pending_messages.end(); + ++iter) { + if (iter->message >= wMsgFilterMin && iter->message <= wMsgFilterMax) { + *lpMsg = MSG{ + .message = iter->message, + .wParam = iter->wparam, + .lParam = iter->lparam, + }; + if ((wRemoveMsg & PM_REMOVE) == PM_REMOVE) { + _pending_messages.erase(iter); + } + return TRUE; + } + } + return FALSE; +} + MockWin32Window::MockWin32Window() : WindowWin32(){}; MockWin32Window::~MockWin32Window() = default; @@ -15,11 +54,25 @@ UINT MockWin32Window::GetDpi() { return GetCurrentDPI(); } +LRESULT MockWin32Window::Win32DefWindowProc(HWND hWnd, + UINT Msg, + WPARAM wParam, + LPARAM lParam) { + return kMockDefaultResult; +} + LRESULT MockWin32Window::InjectWindowMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) { return HandleMessage(message, wparam, lparam); } +LRESULT MockWin32Window::Win32SendMessage(HWND hWnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) { + return HandleMessage(message, wparam, lparam); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/mock_window_win32.h b/shell/platform/windows/testing/mock_window_win32.h index e1191a01de499..010c983af8d0f 100644 --- a/shell/platform/windows/testing/mock_window_win32.h +++ b/shell/platform/windows/testing/mock_window_win32.h @@ -6,15 +6,51 @@ #define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WIN32_WINDOW_H_ #include +#include #include "flutter/shell/platform/windows/window_win32.h" +#include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "gmock/gmock.h" namespace flutter { namespace testing { +constexpr LRESULT kMockDefaultResult = 0xDEADC0DE; +constexpr LRESULT kMockDontCheckResult = 0xFFFF1234; + +// A struc to hold simulated events that will be delivered after the framework +// response is handled. +struct Win32Message { + UINT message; + WPARAM wparam; + LPARAM lparam; + LRESULT expected_result; + HWND hWnd; +}; + +class MockMessageQueue { + public: + // Simulates a WindowProc message from the OS. + void InjectMessageList(int count, const Win32Message* messages); + + BOOL Win32PeekMessage(LPMSG lpMsg, + HWND hWnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg); + + protected: + virtual LRESULT Win32SendMessage(HWND hWnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) = 0; + + std::list _pending_messages; +}; + /// Mock for the |WindowWin32| base class. -class MockWin32Window : public WindowWin32 { +class MockWin32Window : public WindowWin32, + public MockMessageQueue { public: MockWin32Window(); virtual ~MockWin32Window(); @@ -31,6 +67,8 @@ class MockWin32Window : public WindowWin32 { WPARAM const wparam, LPARAM const lparam); + LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + MOCK_METHOD1(OnDpiScale, void(unsigned int)); MOCK_METHOD2(OnResize, void(unsigned int, unsigned int)); MOCK_METHOD2(OnPointerMove, void(double, double)); @@ -45,7 +83,12 @@ class MockWin32Window : public WindowWin32 { MOCK_METHOD0(OnComposeCommit, void()); MOCK_METHOD0(OnComposeEnd, void()); MOCK_METHOD2(OnComposeChange, void(const std::u16string&, int)); - MOCK_METHOD4(DefaultWindowProc, LRESULT(HWND, UINT, WPARAM, LPARAM)); + + protected: + LRESULT Win32SendMessage(HWND hWnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) override; }; } // namespace testing diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index a28aa72067c7f..5abf094dfc883 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -4,6 +4,8 @@ #include "flutter/shell/platform/windows/testing/test_keyboard.h" +#include + namespace flutter { namespace testing { @@ -17,5 +19,41 @@ char* clone_string(const char* string) { return result; } +namespace { +std::string _print_character(const char* s) { + if (s == nullptr) { + return "nullptr"; + } + return std::string("\"") + s + "\""; +} +} // namespace + +#define _RETURN_IF_NOT_EQUALS(val1, val2) \ + if ((val1) != (val2)) { \ + return ::testing::AssertionFailure() \ + << "Expected equality of these values:\n " #val1 "\n To be: " \ + << val2 << "\n Actual: \n " << val1; \ + } + +::testing::AssertionResult _EventEquals(const char* expr_event, + const char* expr_expected, + const FlutterKeyEvent& event, + const FlutterKeyEvent& expected) { + _RETURN_IF_NOT_EQUALS(event.struct_size, sizeof(FlutterKeyEvent)); + _RETURN_IF_NOT_EQUALS(event.type, expected.type); + _RETURN_IF_NOT_EQUALS(event.physical, expected.physical); + _RETURN_IF_NOT_EQUALS(event.logical, expected.logical); + if ((event.character == nullptr) != (expected.character == nullptr) || + strcmp(event.character, expected.character) != 0) { + return ::testing::AssertionFailure() + << "Expected equality of these values:\n expected.character\n " + << _print_character(expected.character) << "\n Actual: \n " + << _print_character(event.character); + } + _RETURN_IF_NOT_EQUALS(event.synthesized, expected.synthesized); + return ::testing::AssertionSuccess(); +} + + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/test_keyboard.h b/shell/platform/windows/testing/test_keyboard.h index a7728ce015b03..43695271cbf6e 100644 --- a/shell/platform/windows/testing/test_keyboard.h +++ b/shell/platform/windows/testing/test_keyboard.h @@ -5,58 +5,28 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_TEST_KEYBOARD_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_TEST_KEYBOARD_H_ +#include + #include #include "flutter/shell/platform/embedder/embedder.h" #include "gtest/gtest.h" -#define _RETURN_IF_NOT_EQUALS(val1, val2) \ - if ((val1) != (val2)) { \ - return ::testing::AssertionFailure() \ - << "Expected equality of these values:\n " #val1 "\n To be: " \ - << val2 << "\n Actual: \n " << val1; \ - } - namespace flutter { namespace testing { -namespace { - -std::string _print_character(const char* s) { - if (s == nullptr) { - return "nullptr"; - } - return std::string("\"") + s + "\""; -} - -::testing::AssertionResult _EventEquals(const char* expr_event, - const char* expr_expected, - const FlutterKeyEvent& event, - const FlutterKeyEvent& expected) { - _RETURN_IF_NOT_EQUALS(event.struct_size, sizeof(FlutterKeyEvent)); - _RETURN_IF_NOT_EQUALS(event.type, expected.type); - _RETURN_IF_NOT_EQUALS(event.physical, expected.physical); - _RETURN_IF_NOT_EQUALS(event.logical, expected.logical); - if ((event.character == nullptr) != (expected.character == nullptr) || - strcmp(event.character, expected.character) != 0) { - return ::testing::AssertionFailure() - << "Expected equality of these values:\n expected.character\n " - << _print_character(expected.character) << "\n Actual: \n " - << _print_character(event.character); - } - _RETURN_IF_NOT_EQUALS(event.synthesized, expected.synthesized); - return ::testing::AssertionSuccess(); -} - -} // namespace - // Clone string onto the heap. // // If #string is nullptr, returns nullptr. Otherwise, the returned pointer must // be freed with delete[]. char* clone_string(const char* string); +::testing::AssertionResult _EventEquals(const char* expr_event, + const char* expr_expected, + const FlutterKeyEvent& event, + const FlutterKeyEvent& expected); + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index ab05b369a18cf..5d3902862e9af 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -378,7 +378,7 @@ WindowWin32::HandleMessage(UINT const message, character >= u' ') { OnText(text); } - break; + return 0; } case WM_KEYDOWN: case WM_SYSKEYDOWN: @@ -395,8 +395,16 @@ WindowWin32::HandleMessage(UINT const message, // to properly produce key down events even though `MapVirtualKey` returns // a valid character. See https://github.com/flutter/flutter/issues/85587. unsigned int character = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR); - if (character > 0 && is_keydown_message && GetKeyState(VK_CONTROL) >= 0 && - GetKeyState(VK_LWIN) >= 0 && GetKeyState(VK_RWIN) >= 0) { + bool has_wm_char; + { + MSG next_message; + BOOL has_msg = Win32PeekMessage(&next_message, window_handle_, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE); + has_wm_char = has_msg && ( + next_message.message == WM_DEADCHAR || + next_message.message == WM_SYSDEADCHAR || + next_message.message == WM_CHAR || + next_message.message == WM_SYSCHAR); + } keycode_for_char_message_ = wparam; return 0; } @@ -413,7 +421,7 @@ WindowWin32::HandleMessage(UINT const message, break; } - return DefaultWindowProc(window_handle_, message, wparam, result_lparam); + return Win32DefWindowProc(window_handle_, message, wparam, result_lparam); } UINT WindowWin32::GetCurrentDPI() { @@ -452,11 +460,19 @@ WindowWin32* WindowWin32::GetThisFromHandle(HWND const window) noexcept { GetWindowLongPtr(window, GWLP_USERDATA)); } -LRESULT WindowWin32::DefaultWindowProc(HWND hWnd, +LRESULT WindowWin32::Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM 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); +} + } // namespace flutter diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index 20de5c7970bf2..603caff0b4b3c 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -159,7 +159,16 @@ class WindowWin32 { UINT GetCurrentHeight(); protected: - virtual LRESULT DefaultWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + // Win32's DefWindowProc. + // + // Used as the fallback behavior of HandleMessage. Exposed for dependency + // injection. + virtual LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, 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); private: // Release OS resources associated with window. diff --git a/shell/platform/windows/window_win32_unittests.cc b/shell/platform/windows/window_win32_unittests.cc index 31afb7e9b41ae..44c26e1fa29ff 100644 --- a/shell/platform/windows/window_win32_unittests.cc +++ b/shell/platform/windows/window_win32_unittests.cc @@ -73,15 +73,14 @@ TEST(MockWin32Window, KeyUp) { TEST(MockWin32Window, KeyDownPrintable) { MockWin32Window window; - LPARAM lparam = CreateKeyEventLparam(30); - // OnKey shouldn't be called until the WM_CHAR message. - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, true)).Times(0); - // send a "A" key down event. - window.InjectWindowMessage(WM_KEYDOWN, 65, lparam); + LPARAM lparam = CreateKeyEventLparam(30, false, false); - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, true)).Times(1); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, false)).Times(1); EXPECT_CALL(window, OnText(_)).Times(1); - window.InjectWindowMessage(WM_CHAR, 65, lparam); + Win32Message messages[] = { + {WM_KEYDOWN, 65, lparam, kMockDontCheckResult}, + {WM_CHAR, 65, lparam, kMockDontCheckResult}}; + window.InjectMessageList(2, messages); } TEST(MockWin32Window, KeyDownWithCtrl) { @@ -115,16 +114,16 @@ TEST(MockWin32Window, KeyDownWithCtrlToggled) { keyboard_state[VK_CONTROL] = 1; SetKeyboardState(keyboard_state); - LPARAM lparam = CreateKeyEventLparam(30); + LPARAM lparam = CreateKeyEventLparam(30, false, false); - // OnKey shouldn't be called until the WM_CHAR message. - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, true)).Times(0); - // send a "A" key down event. - window.InjectWindowMessage(WM_KEYDOWN, 65, lparam); - - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, true)).Times(1); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, false)).Times(1); EXPECT_CALL(window, OnText(_)).Times(1); - window.InjectWindowMessage(WM_CHAR, 65, lparam); + + // send a "A" key down event. + Win32Message messages[] = { + {WM_KEYDOWN, 65, lparam, kMockDontCheckResult}, + {WM_CHAR, 65, lparam, kMockDontCheckResult}}; + window.InjectMessageList(2, messages); memset(keyboard_state, 0, 256); SetKeyboardState(keyboard_state); From bf0177977a5cd7b55e5985816e58a1413e2f8a3d Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sat, 7 Aug 2021 03:50:13 -0700 Subject: [PATCH 06/30] wm builders --- shell/platform/windows/BUILD.gn | 2 + .../windows/flutter_window_win32_unittests.cc | 8 +- shell/platform/windows/keyboard_unittests.cc | 19 ++- .../windows/testing/mock_window_win32.cc | 10 +- .../windows/testing/mock_window_win32.h | 14 +- shell/platform/windows/testing/wm_builders.cc | 56 ++++++++ shell/platform/windows/testing/wm_builders.h | 120 ++++++++++++++++++ .../windows/window_win32_unittests.cc | 8 +- 8 files changed, 200 insertions(+), 37 deletions(-) create mode 100644 shell/platform/windows/testing/wm_builders.cc create mode 100644 shell/platform/windows/testing/wm_builders.h diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 3a9f5fa0c3c20..8354a7d858b0d 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -252,6 +252,8 @@ executable("flutter_windows_unittests") { "testing/mock_window_win32.h", "testing/test_keyboard.cc", "testing/test_keyboard.h", + "testing/wm_builders.cc", + "testing/wm_builders.h", "text_input_plugin_unittest.cc", "window_proc_delegate_manager_win32_unittests.cc", "window_win32_unittests.cc", diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index 99a4d4b2eeb51..e81146614a100 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -171,7 +171,7 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueu UINT Msg, WPARAM wParam, LPARAM lParam) override { - return kMockDefaultResult; + return kWmResultDefault; } private: @@ -270,10 +270,10 @@ class TestFlutterWindowsView : public FlutterWindowsView { // TODO(dkwingsmt): Don't check the message results for redispatched // messages for now, because making them work takes non-trivial rework // to our current structure. https://github.com/flutter/flutter/issues/87843 - // If this is resolved, change them to kMockDefaultResult. - pending_responds_.push_back(Win32Message{message, virtual_key_, lparam, kMockDontCheckResult}); + // If this is resolved, change them to kWmResultDefault. + pending_responds_.push_back(Win32Message{message, virtual_key_, lparam, kWmResultDontCheck}); if (is_printable_ && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { - pending_responds_.push_back(Win32Message{WM_CHAR, virtual_key_, lparam, kMockDontCheckResult}); + pending_responds_.push_back(Win32Message{WM_CHAR, virtual_key_, lparam, kWmResultDontCheck}); } return 1; } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 0ddc312bba01b..3875ec5b8aa08 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -41,7 +41,7 @@ static LPARAM CreateKeyEventLparam(USHORT scancode, bool was_down, USHORT repeat_count = 1, bool context_code = 0, - bool transition_state = 1) { + bool transition_state = 0) { return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | (LPARAM(scancode) << 16) | LPARAM(repeat_count)); @@ -69,7 +69,7 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueu UINT Msg, WPARAM wParam, LPARAM lParam) override { - return kMockDefaultResult; + return kWmResultDefault; } // Simulates a WindowProc message from the OS. @@ -166,9 +166,9 @@ class TestFlutterWindowsView : public FlutterWindowsView { const bool is_key_up = kbdinput.dwFlags & KEYEVENTF_KEYUP; const LPARAM lparam = CreateKeyEventLparam( kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY, is_key_up); - pending_responds_.push_back(Win32Message{message, kbdinput.wVk, lparam, kMockDefaultResult}); + pending_responds_.push_back(Win32Message{message, kbdinput.wVk, lparam, kWmResultDefault}); if (is_printable && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { - pending_responds_.push_back(Win32Message{WM_CHAR, kbdinput.wVk, lparam, kMockDefaultResult}); + pending_responds_.push_back(Win32Message{WM_CHAR, kbdinput.wVk, lparam, kWmResultDefault}); } return 1; } @@ -379,10 +379,6 @@ constexpr uint64_t kScanCodeKeyA = 0x1e; constexpr uint64_t kVirtualKeyA = 0x41; -constexpr bool kExtended = true; -constexpr bool kNotExtended = false; -constexpr bool kWasDown = true; -constexpr bool kWasUp = false; constexpr bool kSynthesized = true; constexpr bool kNotSynthesized = false; @@ -400,8 +396,8 @@ TEST(KeyboardTest, LowerCaseA) { // Press A tester.InjectMessages(2, - Win32Message{WM_KEYDOWN, kVirtualKeyA, CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)}, - Win32Message{WM_CHAR, kVirtualKeyA, CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasUp)} + WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build(kWmResultZero), + WmCharInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build(kWmResultZero) ); EXPECT_EQ(key_calls.size(), 1); @@ -414,7 +410,8 @@ TEST(KeyboardTest, LowerCaseA) { // Release A tester.InjectMessages(1, - Win32Message{WM_KEYUP, kVirtualKeyA, CreateKeyEventLparam(kScanCodeKeyA, kNotExtended, kWasDown)}); + WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build(kWmResultZero) + ); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, diff --git a/shell/platform/windows/testing/mock_window_win32.cc b/shell/platform/windows/testing/mock_window_win32.cc index 9e80cdfd24e26..37a91f350d8ff 100644 --- a/shell/platform/windows/testing/mock_window_win32.cc +++ b/shell/platform/windows/testing/mock_window_win32.cc @@ -16,8 +16,8 @@ void MockMessageQueue::InjectMessageList(int count, const Win32Message* messages while (!_pending_messages.empty()) { Win32Message message = _pending_messages.front(); _pending_messages.pop_front(); - LRESULT result = Win32SendMessage(message.hWnd, message.message, message.wparam, message.lparam); - if (message.expected_result != kMockDontCheckResult) { + LRESULT result = Win32SendMessage(message.hWnd, message.message, message.wParam, message.lParam); + if (message.expected_result != kWmResultDontCheck) { EXPECT_EQ(result, message.expected_result); } } @@ -34,8 +34,8 @@ BOOL MockMessageQueue::Win32PeekMessage(LPMSG lpMsg, if (iter->message >= wMsgFilterMin && iter->message <= wMsgFilterMax) { *lpMsg = MSG{ .message = iter->message, - .wParam = iter->wparam, - .lParam = iter->lparam, + .wParam = iter->wParam, + .lParam = iter->lParam, }; if ((wRemoveMsg & PM_REMOVE) == PM_REMOVE) { _pending_messages.erase(iter); @@ -58,7 +58,7 @@ LRESULT MockWin32Window::Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { - return kMockDefaultResult; + return kWmResultDefault; } LRESULT MockWin32Window::InjectWindowMessage(UINT const message, diff --git a/shell/platform/windows/testing/mock_window_win32.h b/shell/platform/windows/testing/mock_window_win32.h index 010c983af8d0f..f01a183e498b7 100644 --- a/shell/platform/windows/testing/mock_window_win32.h +++ b/shell/platform/windows/testing/mock_window_win32.h @@ -10,24 +10,12 @@ #include "flutter/shell/platform/windows/window_win32.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" +#include "flutter/shell/platform/windows/testing/wm_builders.h" #include "gmock/gmock.h" namespace flutter { namespace testing { -constexpr LRESULT kMockDefaultResult = 0xDEADC0DE; -constexpr LRESULT kMockDontCheckResult = 0xFFFF1234; - -// A struc to hold simulated events that will be delivered after the framework -// response is handled. -struct Win32Message { - UINT message; - WPARAM wparam; - LPARAM lparam; - LRESULT expected_result; - HWND hWnd; -}; - class MockMessageQueue { public: // Simulates a WindowProc message from the OS. diff --git a/shell/platform/windows/testing/wm_builders.cc b/shell/platform/windows/testing/wm_builders.cc new file mode 100644 index 0000000000000..3658953f0c37a --- /dev/null +++ b/shell/platform/windows/testing/wm_builders.cc @@ -0,0 +1,56 @@ +// 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 "flutter/shell/platform/windows/testing/wm_builders.h" + +namespace flutter { +namespace testing { + +Win32Message WmKeyDownInfo::Build(LRESULT expected_result, HWND hWnd) { + uint32_t lParam = (repeat_count << 0) | + (scan_code << 16) | + (extended << 24) | + (prev_state << 30); + return Win32Message{ + .message = WM_KEYDOWN, + .wParam = key, + .lParam = lParam, + .expected_result = expected_result, + .hWnd = hWnd, + }; +} + +Win32Message WmKeyUpInfo::Build(LRESULT expected_result, HWND hWnd) { + uint32_t lParam = (1 /* repeat_count */ << 0) | + (scan_code << 16) | + (extended << 24) | + (1 /* prev_state */ << 30) | + (1 /* transition */ << 31); + return Win32Message{ + .message = WM_KEYUP, + .wParam = key, + .lParam = lParam, + .expected_result = expected_result, + .hWnd = hWnd, + }; +} + +Win32Message WmCharInfo::Build(LRESULT expected_result, HWND hWnd) { + uint32_t lParam = (repeat_count << 0) | + (scan_code << 16) | + (extended << 24) | + (context << 30) | + (prev_state << 30) | + (transition << 31); + return Win32Message{ + .message = WM_CHAR, + .wParam = char_code, + .lParam = lParam, + .expected_result = expected_result, + .hWnd = hWnd, + }; +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/testing/wm_builders.h b/shell/platform/windows/testing/wm_builders.h new file mode 100644 index 0000000000000..2f9c6b68a1885 --- /dev/null +++ b/shell/platform/windows/testing/wm_builders.h @@ -0,0 +1,120 @@ +// 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_TESTING_WM_BUILDERS_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WM_BUILDERS_H_ + +#include +#include +#include + +namespace flutter { +namespace testing { + +constexpr LRESULT kWmResultZero = 0; +constexpr LRESULT kWmResultDefault = 0xDEADC0DE; +constexpr LRESULT kWmResultDontCheck = 0xFFFF1234; + +// A struc to hold simulated events that will be delivered after the framework +// response is handled. +struct Win32Message { + UINT message; + WPARAM wParam; + LPARAM lParam; + LRESULT expected_result; + HWND hWnd; +}; + +typedef enum { + kNotExtended = 0, + kExtended = 1, +} WmFieldExtended; + +typedef enum { + kNoContext = 0, + kAltHeld = 1, +} WmFieldContext; + +typedef enum { + kWasUp = 0, + kWasDown = 1, +} WmFieldPrevState; + +typedef enum { + kBeingReleased = 0, + kBeingPressed = 1, +} WmFieldTransitionState; + + +// WM_KEYDOWN messages. +// +// See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown. +typedef struct { + uint32_t key; + + uint8_t scan_code; + + WmFieldExtended extended; + + WmFieldPrevState prev_state; + + // WmFieldTransitionState transition; // Always 0. + + // WmFieldContext context; // Always 0. + + uint16_t repeat_count = 1; + + Win32Message Build(LRESULT expected_result = kWmResultDontCheck, HWND hWnd = NULL); +} WmKeyDownInfo; + +// Win32Message BuildMessage(WmKeyDownInfo info, LRESULT expected_result = kWmResultDontCheck, HWND hWnd = NULL); + + +// WM_KEYUP messages. +// +// See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keyup. +typedef struct { + uint32_t key; + + uint8_t scan_code; + + WmFieldExtended extended; + + // WmFieldPrevState prev_state; // Always 1. + + // WmFieldTransitionState transition; // Always 1. + + // WmFieldContext context; // Always 0. + + // uint16_t repeat_count; // Always 1. + + Win32Message Build(LRESULT expected_result = kWmResultDontCheck, HWND hWnd = NULL); +} WmKeyUpInfo; + + +// WM_CHAR messages. +// +// See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-char. +typedef struct { + uint32_t char_code; + + uint8_t scan_code; + + WmFieldExtended extended; + + WmFieldPrevState prev_state; + + WmFieldTransitionState transition; + + WmFieldContext context; + + uint16_t repeat_count = 1; + + Win32Message Build(LRESULT expected_result = kWmResultDontCheck, HWND hWnd = NULL); +} WmCharInfo; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WM_BUILDERS_H_ diff --git a/shell/platform/windows/window_win32_unittests.cc b/shell/platform/windows/window_win32_unittests.cc index 44c26e1fa29ff..8a6a709118376 100644 --- a/shell/platform/windows/window_win32_unittests.cc +++ b/shell/platform/windows/window_win32_unittests.cc @@ -78,8 +78,8 @@ TEST(MockWin32Window, KeyDownPrintable) { EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, false)).Times(1); EXPECT_CALL(window, OnText(_)).Times(1); Win32Message messages[] = { - {WM_KEYDOWN, 65, lparam, kMockDontCheckResult}, - {WM_CHAR, 65, lparam, kMockDontCheckResult}}; + {WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, + {WM_CHAR, 65, lparam, kWmResultDontCheck}}; window.InjectMessageList(2, messages); } @@ -121,8 +121,8 @@ TEST(MockWin32Window, KeyDownWithCtrlToggled) { // send a "A" key down event. Win32Message messages[] = { - {WM_KEYDOWN, 65, lparam, kMockDontCheckResult}, - {WM_CHAR, 65, lparam, kMockDontCheckResult}}; + {WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, + {WM_CHAR, 65, lparam, kWmResultDontCheck}}; window.InjectMessageList(2, messages); memset(keyboard_state, 0, 256); From aa9a1ef588a628ffc78668656b7fd6c78b403fa1 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sat, 7 Aug 2021 03:52:36 -0700 Subject: [PATCH 07/30] Format --- .../windows/flutter_window_win32_unittests.cc | 39 ++++++++----- shell/platform/windows/flutter_windows_view.h | 8 +-- shell/platform/windows/keyboard_unittests.cc | 58 ++++++++++--------- .../windows/testing/mock_window_win32.cc | 17 +++--- .../windows/testing/mock_window_win32.h | 5 +- .../platform/windows/testing/test_keyboard.cc | 3 +- shell/platform/windows/testing/wm_builders.cc | 48 +++++++-------- shell/platform/windows/testing/wm_builders.h | 15 ++--- shell/platform/windows/window_win32.cc | 48 +++++++-------- shell/platform/windows/window_win32.h | 11 +++- .../windows/window_win32_unittests.cc | 10 ++-- 11 files changed, 135 insertions(+), 127 deletions(-) diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index e81146614a100..2df9f20c4ff27 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -116,7 +116,8 @@ class SpyTextInputPlugin : public KeyboardHandlerBase, std::unique_ptr real_implementation_; }; -class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueue { +class MockFlutterWindowWin32 : public FlutterWindowWin32, + public MockMessageQueue { public: MockFlutterWindowWin32() : FlutterWindowWin32(800, 600) { ON_CALL(*this, GetDpiScale()) @@ -163,8 +164,13 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueu MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); protected: - virtual BOOL Win32PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) override { - return MockMessageQueue::Win32PeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + virtual BOOL Win32PeekMessage(LPMSG lpMsg, + HWND hWnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg) override { + return MockMessageQueue::Win32PeekMessage(lpMsg, hWnd, wMsgFilterMin, + wMsgFilterMax, wRemoveMsg); } LRESULT Win32DefWindowProc(HWND hWnd, @@ -230,7 +236,8 @@ class TestFlutterWindowsView : public FlutterWindowsView { SpyTextInputPlugin* text_input_plugin; void InjectPendingEvents(MockFlutterWindowWin32* win32window) { - win32window->InjectMessageList(pending_responds_.size(), pending_responds_.data()); + win32window->InjectMessageList(pending_responds_.size(), + pending_responds_.data()); pending_responds_.clear(); } @@ -271,9 +278,11 @@ class TestFlutterWindowsView : public FlutterWindowsView { // messages for now, because making them work takes non-trivial rework // to our current structure. https://github.com/flutter/flutter/issues/87843 // If this is resolved, change them to kWmResultDefault. - pending_responds_.push_back(Win32Message{message, virtual_key_, lparam, kWmResultDontCheck}); + pending_responds_.push_back( + Win32Message{message, virtual_key_, lparam, kWmResultDontCheck}); if (is_printable_ && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { - pending_responds_.push_back(Win32Message{WM_CHAR, virtual_key_, lparam, kWmResultDontCheck}); + pending_responds_.push_back( + Win32Message{WM_CHAR, virtual_key_, lparam, kWmResultDontCheck}); } return 1; } @@ -392,7 +401,8 @@ TEST(FlutterWindowWin32Test, NonPrintableKeyDownPropagation) { .Times(0); EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _)) .Times(0); - win32window.InjectMessages(1, Win32Message{WM_KEYDOWN, virtual_key, lparam}); + win32window.InjectMessages(1, + Win32Message{WM_KEYDOWN, virtual_key, lparam}); flutter_windows_view.InjectPendingEvents(&win32window); } @@ -407,7 +417,8 @@ TEST(FlutterWindowWin32Test, NonPrintableKeyDownPropagation) { EXPECT_CALL(*flutter_windows_view.text_input_plugin, KeyboardHook(_, _, _, _, _, _, _)) .Times(0); - win32window.InjectMessages(1, Win32Message{WM_KEYDOWN, virtual_key, lparam}); + win32window.InjectMessages(1, + Win32Message{WM_KEYDOWN, virtual_key, lparam}); flutter_windows_view.InjectPendingEvents(&win32window); } } @@ -449,10 +460,8 @@ TEST(FlutterWindowWin32Test, CharKeyDownPropagation) { EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _)) .Times(1) .RetiresOnSaturation(); - win32window.InjectMessages(2, - Win32Message{WM_KEYDOWN, virtual_key, lparam}, - Win32Message{WM_CHAR, virtual_key, lparam} - ); + win32window.InjectMessages(2, Win32Message{WM_KEYDOWN, virtual_key, lparam}, + Win32Message{WM_CHAR, virtual_key, lparam}); flutter_windows_view.InjectPendingEvents(&win32window); } @@ -471,10 +480,8 @@ TEST(FlutterWindowWin32Test, CharKeyDownPropagation) { .Times(0); EXPECT_CALL(*flutter_windows_view.text_input_plugin, TextHook(_, _)) .Times(0); - win32window.InjectMessages(2, - Win32Message{WM_KEYDOWN, virtual_key, lparam}, - Win32Message{WM_CHAR, virtual_key, lparam} - ); + win32window.InjectMessages(2, Win32Message{WM_KEYDOWN, virtual_key, lparam}, + Win32Message{WM_CHAR, virtual_key, lparam}); flutter_windows_view.InjectPendingEvents(&win32window); } } diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 32f5bbec9176d..f3432c0575062 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -19,8 +19,8 @@ #include "flutter/shell/platform/windows/cursor_handler.h" #include "flutter/shell/platform/windows/flutter_windows_engine.h" #include "flutter/shell/platform/windows/keyboard_handler_base.h" -#include "flutter/shell/platform/windows/keyboard_key_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/platform_handler.h" #include "flutter/shell/platform/windows/public/flutter_windows.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" @@ -147,9 +147,9 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, protected: // Called to create the keyboard hook handlers. virtual void RegisterKeyboardHandlers( - flutter::BinaryMessenger* messenger, - flutter::KeyboardKeyHandler::EventDispatcher dispatch_event, - flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state); + flutter::BinaryMessenger* messenger, + flutter::KeyboardKeyHandler::EventDispatcher dispatch_event, + flutter::KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state); // Used by RegisterKeyboardHandlers to add a new keyboard hook handler. void AddKeyboardHandler( diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 3875ec5b8aa08..3d98030dd4835 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -19,8 +19,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include #include +#include #include using testing::_; @@ -47,7 +47,8 @@ static LPARAM CreateKeyEventLparam(USHORT scancode, (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } -class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueue { +class MockFlutterWindowWin32 : public FlutterWindowWin32, + public MockMessageQueue { public: typedef std::function U16StringHandler; @@ -66,9 +67,9 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueu UINT GetDpi() { return GetCurrentDPI(); } LRESULT Win32DefWindowProc(HWND hWnd, - UINT Msg, - WPARAM wParam, - LPARAM lParam) override { + UINT Msg, + WPARAM wParam, + LPARAM lParam) override { return kWmResultDefault; } @@ -93,8 +94,13 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, public MockMessageQueu MOCK_METHOD0(IsVisible, bool()); MOCK_METHOD1(UpdateCursorRect, void(const Rect&)); - virtual BOOL Win32PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) override { - return MockMessageQueue::Win32PeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + virtual BOOL Win32PeekMessage(LPMSG lpMsg, + HWND hWnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg) override { + return MockMessageQueue::Win32PeekMessage(lpMsg, hWnd, wMsgFilterMin, + wMsgFilterMax, wRemoveMsg); } private: @@ -139,7 +145,8 @@ class TestFlutterWindowsView : public FlutterWindowsView { bool is_printable; void InjectPendingEvents(MockFlutterWindowWin32* win32window) { - win32window->InjectMessageList(pending_responds_.size(), pending_responds_.data()); + win32window->InjectMessageList(pending_responds_.size(), + pending_responds_.data()); pending_responds_.clear(); } @@ -166,9 +173,11 @@ class TestFlutterWindowsView : public FlutterWindowsView { const bool is_key_up = kbdinput.dwFlags & KEYEVENTF_KEYUP; const LPARAM lparam = CreateKeyEventLparam( kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY, is_key_up); - pending_responds_.push_back(Win32Message{message, kbdinput.wVk, lparam, kWmResultDefault}); + pending_responds_.push_back( + Win32Message{message, kbdinput.wVk, lparam, kWmResultDefault}); if (is_printable && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { - pending_responds_.push_back(Win32Message{WM_CHAR, kbdinput.wVk, lparam, kWmResultDefault}); + pending_responds_.push_back( + Win32Message{WM_CHAR, kbdinput.wVk, lparam, kWmResultDefault}); } return 1; } @@ -228,13 +237,9 @@ class KeyboardTester { window_->SetView(view_.get()); } - void Responding(bool response) { - test_response = response; - } + void Responding(bool response) { test_response = response; } - void NextMessageShouldDefault() { - next_event_should_default_ = true; - } + void NextMessageShouldDefault() { next_event_should_default_ = true; } void InjectMessages(int count, Win32Message message1, ...) { Win32Message messages[count]; @@ -248,9 +253,7 @@ class KeyboardTester { window_->InjectMessageList(count, messages); } - void InjectPendingEvents() { - view_->InjectPendingEvents(window_.get()); - } + void InjectPendingEvents() { view_->InjectPendingEvents(window_.get()); } static bool test_response; @@ -261,7 +264,6 @@ class KeyboardTester { bool next_event_should_default_ = false; }; - bool KeyboardTester::test_response = false; std::unique_ptr> keyHandlingResponse(bool handled) { @@ -395,10 +397,12 @@ TEST(KeyboardTest, LowerCaseA) { // US Keyboard layout // Press A - tester.InjectMessages(2, - WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build(kWmResultZero), - WmCharInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build(kWmResultZero) - ); + tester.InjectMessages( + 2, + WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, @@ -409,9 +413,9 @@ TEST(KeyboardTest, LowerCaseA) { EXPECT_EQ(key_calls.size(), 0); // Release A - tester.InjectMessages(1, - WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build(kWmResultZero) - ); + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, diff --git a/shell/platform/windows/testing/mock_window_win32.cc b/shell/platform/windows/testing/mock_window_win32.cc index 37a91f350d8ff..371e504aa95c8 100644 --- a/shell/platform/windows/testing/mock_window_win32.cc +++ b/shell/platform/windows/testing/mock_window_win32.cc @@ -9,14 +9,16 @@ namespace flutter { namespace testing { -void MockMessageQueue::InjectMessageList(int count, const Win32Message* messages) { +void MockMessageQueue::InjectMessageList(int count, + const Win32Message* messages) { for (int i = 0; i < count; i += 1) { _pending_messages.push_back(messages[i]); } 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.hWnd, message.message, + message.wParam, message.lParam); if (message.expected_result != kWmResultDontCheck) { EXPECT_EQ(result, message.expected_result); } @@ -28,14 +30,13 @@ BOOL MockMessageQueue::Win32PeekMessage(LPMSG lpMsg, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) { - for (auto iter = _pending_messages.begin(); - iter != _pending_messages.end(); - ++iter) { + for (auto iter = _pending_messages.begin(); iter != _pending_messages.end(); + ++iter) { if (iter->message >= wMsgFilterMin && iter->message <= wMsgFilterMax) { *lpMsg = MSG{ - .message = iter->message, - .wParam = iter->wParam, - .lParam = iter->lParam, + .message = iter->message, + .wParam = iter->wParam, + .lParam = iter->lParam, }; if ((wRemoveMsg & PM_REMOVE) == PM_REMOVE) { _pending_messages.erase(iter); diff --git a/shell/platform/windows/testing/mock_window_win32.h b/shell/platform/windows/testing/mock_window_win32.h index f01a183e498b7..86d42741c9e1e 100644 --- a/shell/platform/windows/testing/mock_window_win32.h +++ b/shell/platform/windows/testing/mock_window_win32.h @@ -8,9 +8,9 @@ #include #include -#include "flutter/shell/platform/windows/window_win32.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "flutter/shell/platform/windows/testing/wm_builders.h" +#include "flutter/shell/platform/windows/window_win32.h" #include "gmock/gmock.h" namespace flutter { @@ -37,8 +37,7 @@ class MockMessageQueue { }; /// Mock for the |WindowWin32| base class. -class MockWin32Window : public WindowWin32, - public MockMessageQueue { +class MockWin32Window : public WindowWin32, public MockMessageQueue { public: MockWin32Window(); virtual ~MockWin32Window(); diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index 5abf094dfc883..f9f45fbb4adb0 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -26,7 +26,7 @@ std::string _print_character(const char* s) { } return std::string("\"") + s + "\""; } -} // namespace +} // namespace #define _RETURN_IF_NOT_EQUALS(val1, val2) \ if ((val1) != (val2)) { \ @@ -54,6 +54,5 @@ ::testing::AssertionResult _EventEquals(const char* expr_event, return ::testing::AssertionSuccess(); } - } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/wm_builders.cc b/shell/platform/windows/testing/wm_builders.cc index 3658953f0c37a..e2da5317627d1 100644 --- a/shell/platform/windows/testing/wm_builders.cc +++ b/shell/platform/windows/testing/wm_builders.cc @@ -8,47 +8,39 @@ namespace flutter { namespace testing { Win32Message WmKeyDownInfo::Build(LRESULT expected_result, HWND hWnd) { - uint32_t lParam = (repeat_count << 0) | - (scan_code << 16) | - (extended << 24) | + uint32_t lParam = (repeat_count << 0) | (scan_code << 16) | (extended << 24) | (prev_state << 30); return Win32Message{ - .message = WM_KEYDOWN, - .wParam = key, - .lParam = lParam, - .expected_result = expected_result, - .hWnd = hWnd, + .message = WM_KEYDOWN, + .wParam = key, + .lParam = lParam, + .expected_result = expected_result, + .hWnd = hWnd, }; } Win32Message WmKeyUpInfo::Build(LRESULT expected_result, HWND hWnd) { - uint32_t lParam = (1 /* repeat_count */ << 0) | - (scan_code << 16) | - (extended << 24) | - (1 /* prev_state */ << 30) | + uint32_t lParam = (1 /* repeat_count */ << 0) | (scan_code << 16) | + (extended << 24) | (1 /* prev_state */ << 30) | (1 /* transition */ << 31); return Win32Message{ - .message = WM_KEYUP, - .wParam = key, - .lParam = lParam, - .expected_result = expected_result, - .hWnd = hWnd, + .message = WM_KEYUP, + .wParam = key, + .lParam = lParam, + .expected_result = expected_result, + .hWnd = hWnd, }; } Win32Message WmCharInfo::Build(LRESULT expected_result, HWND hWnd) { - uint32_t lParam = (repeat_count << 0) | - (scan_code << 16) | - (extended << 24) | - (context << 30) | - (prev_state << 30) | - (transition << 31); + uint32_t lParam = (repeat_count << 0) | (scan_code << 16) | (extended << 24) | + (context << 30) | (prev_state << 30) | (transition << 31); return Win32Message{ - .message = WM_CHAR, - .wParam = char_code, - .lParam = lParam, - .expected_result = expected_result, - .hWnd = hWnd, + .message = WM_CHAR, + .wParam = char_code, + .lParam = lParam, + .expected_result = expected_result, + .hWnd = hWnd, }; } diff --git a/shell/platform/windows/testing/wm_builders.h b/shell/platform/windows/testing/wm_builders.h index 2f9c6b68a1885..6c2173f47e43c 100644 --- a/shell/platform/windows/testing/wm_builders.h +++ b/shell/platform/windows/testing/wm_builders.h @@ -46,7 +46,6 @@ typedef enum { kBeingPressed = 1, } WmFieldTransitionState; - // WM_KEYDOWN messages. // // See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown. @@ -65,11 +64,12 @@ typedef struct { uint16_t repeat_count = 1; - Win32Message Build(LRESULT expected_result = kWmResultDontCheck, HWND hWnd = NULL); + Win32Message Build(LRESULT expected_result = kWmResultDontCheck, + HWND hWnd = NULL); } WmKeyDownInfo; -// Win32Message BuildMessage(WmKeyDownInfo info, LRESULT expected_result = kWmResultDontCheck, HWND hWnd = NULL); - +// Win32Message BuildMessage(WmKeyDownInfo info, LRESULT expected_result = +// kWmResultDontCheck, HWND hWnd = NULL); // WM_KEYUP messages. // @@ -89,10 +89,10 @@ typedef struct { // uint16_t repeat_count; // Always 1. - Win32Message Build(LRESULT expected_result = kWmResultDontCheck, HWND hWnd = NULL); + Win32Message Build(LRESULT expected_result = kWmResultDontCheck, + HWND hWnd = NULL); } WmKeyUpInfo; - // WM_CHAR messages. // // See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-char. @@ -111,7 +111,8 @@ typedef struct { uint16_t repeat_count = 1; - Win32Message Build(LRESULT expected_result = kWmResultDontCheck, HWND hWnd = NULL); + Win32Message Build(LRESULT expected_result = kWmResultDontCheck, + HWND hWnd = NULL); } WmCharInfo; } // namespace testing diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 5d3902862e9af..a2bdcd182952b 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -398,30 +398,30 @@ WindowWin32::HandleMessage(UINT const message, bool has_wm_char; { MSG next_message; - BOOL has_msg = Win32PeekMessage(&next_message, window_handle_, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE); - has_wm_char = has_msg && ( - next_message.message == WM_DEADCHAR || - next_message.message == WM_SYSDEADCHAR || - next_message.message == WM_CHAR || - next_message.message == WM_SYSCHAR); + BOOL has_msg = Win32PeekMessage(&next_message, window_handle_, + WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE); + has_wm_char = has_msg && (next_message.message == WM_DEADCHAR || + next_message.message == WM_SYSDEADCHAR || + next_message.message == WM_CHAR || + next_message.message == WM_SYSCHAR); } - 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 int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; - const bool was_down = lparam & 0x40000000; - if (OnKey(keyCode, scancode, action, character, extended, was_down)) { - return 0; - } - break; + 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 int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; + const bool was_down = lparam & 0x40000000; + if (OnKey(keyCode, scancode, action, character, extended, was_down)) { + return 0; } + break; +} - return Win32DefWindowProc(window_handle_, message, wparam, result_lparam); +return Win32DefWindowProc(window_handle_, message, wparam, result_lparam); } UINT WindowWin32::GetCurrentDPI() { @@ -461,9 +461,9 @@ WindowWin32* WindowWin32::GetThisFromHandle(HWND const window) noexcept { } LRESULT WindowWin32::Win32DefWindowProc(HWND hWnd, - UINT Msg, - WPARAM wParam, - LPARAM lParam) { + UINT Msg, + WPARAM wParam, + LPARAM lParam) { return DefWindowProc(hWnd, Msg, wParam, lParam); } diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index 603caff0b4b3c..6fc27e9ddfb3c 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -163,12 +163,19 @@ class WindowWin32 { // // Used as the fallback behavior of HandleMessage. Exposed for dependency // injection. - virtual LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + virtual LRESULT Win32DefWindowProc(HWND hWnd, + UINT Msg, + 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); + virtual BOOL Win32PeekMessage(LPMSG lpMsg, + HWND hWnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg); private: // Release OS resources associated with window. diff --git a/shell/platform/windows/window_win32_unittests.cc b/shell/platform/windows/window_win32_unittests.cc index 8a6a709118376..ef81d07ef247c 100644 --- a/shell/platform/windows/window_win32_unittests.cc +++ b/shell/platform/windows/window_win32_unittests.cc @@ -77,9 +77,8 @@ TEST(MockWin32Window, KeyDownPrintable) { EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, false)).Times(1); EXPECT_CALL(window, OnText(_)).Times(1); - Win32Message messages[] = { - {WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, - {WM_CHAR, 65, lparam, kWmResultDontCheck}}; + Win32Message messages[] = {{WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, + {WM_CHAR, 65, lparam, kWmResultDontCheck}}; window.InjectMessageList(2, messages); } @@ -120,9 +119,8 @@ TEST(MockWin32Window, KeyDownWithCtrlToggled) { EXPECT_CALL(window, OnText(_)).Times(1); // send a "A" key down event. - Win32Message messages[] = { - {WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, - {WM_CHAR, 65, lparam, kWmResultDontCheck}}; + Win32Message messages[] = {{WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, + {WM_CHAR, 65, lparam, kWmResultDontCheck}}; window.InjectMessageList(2, messages); memset(keyboard_state, 0, 256); From 1cca6fca4aaefffd1d1b727fbdd7e97e4ec7ef91 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sat, 7 Aug 2021 03:58:25 -0700 Subject: [PATCH 08/30] Correct --- shell/platform/windows/window_win32.cc | 31 +++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index a2bdcd182952b..cb5f41e45d0e1 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -405,23 +405,24 @@ WindowWin32::HandleMessage(UINT const message, next_message.message == WM_CHAR || next_message.message == WM_SYSCHAR); } - 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 int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; - const bool was_down = lparam & 0x40000000; - if (OnKey(keyCode, scancode, action, character, extended, was_down)) { - return 0; + 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 int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; + const bool was_down = lparam & 0x40000000; + if (OnKey(keyCode, scancode, action, character, extended, was_down)) { + return 0; + } + break; } - break; -} -return Win32DefWindowProc(window_handle_, message, wparam, result_lparam); + return Win32DefWindowProc(window_handle_, message, wparam, result_lparam); } UINT WindowWin32::GetCurrentDPI() { From 84e61b73033c029867e40ae153cc01e1416bd1a7 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 9 Aug 2021 02:19:12 -0700 Subject: [PATCH 09/30] LowerCaseAUnhandled --- shell/platform/windows/keyboard_unittests.cc | 114 ++++++++++++++----- 1 file changed, 85 insertions(+), 29 deletions(-) diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 3d98030dd4835..de4dadd185de1 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -131,6 +131,12 @@ class TestKeystate { std::map state_; }; +typedef struct { + UINT cInputs; + KEYBDINPUT kbdinput; + int cbSize; +} SendInputInfo; + // A FlutterWindowsView that overrides the RegisterKeyboardHandlers function // to register the keyboard hook handlers that can be spied upon. class TestFlutterWindowsView : public FlutterWindowsView { @@ -140,13 +146,33 @@ class TestFlutterWindowsView : public FlutterWindowsView { // affect keyboard. : FlutterWindowsView( std::make_unique<::testing::NiceMock>()), - is_printable(true) {} - - bool is_printable; + redispatch_char(0) {} + + uint32_t redispatch_char; + + void InjectPendingEvents(MockFlutterWindowWin32* win32window, uint32_t redispatch_char) { + std::vector messages; + for (const SendInputInfo& input : pending_responds_) { + const KEYBDINPUT kbdinput = input.kbdinput; + const UINT message = + (kbdinput.dwFlags & KEYEVENTF_KEYUP) ? WM_KEYUP : WM_KEYDOWN; + const bool is_key_up = kbdinput.dwFlags & KEYEVENTF_KEYUP; + const LPARAM lparam = CreateKeyEventLparam( + kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY, is_key_up); + // TODO(dkwingsmt): Don't check the message results for redispatched + // messages for now, because making them work takes non-trivial rework + // to our current structure. https://github.com/flutter/flutter/issues/87843 + // If this is resolved, change them to kWmResultDefault. + messages.push_back( + Win32Message{message, kbdinput.wVk, lparam, kWmResultDontCheck}); + if (redispatch_char != 0 && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { + messages.push_back( + Win32Message{WM_CHAR, redispatch_char, lparam, kWmResultDontCheck}); + } + } - void InjectPendingEvents(MockFlutterWindowWin32* win32window) { - win32window->InjectMessageList(pending_responds_.size(), - pending_responds_.data()); + win32window->InjectMessageList(messages.size(), + messages.data()); pending_responds_.clear(); } @@ -165,24 +191,11 @@ class TestFlutterWindowsView : public FlutterWindowsView { private: UINT SendInput(UINT cInputs, LPINPUT pInputs, int cbSize) { - // Simulate the event loop by just sending the event sent to - // "SendInput" directly to the window. - const KEYBDINPUT kbdinput = pInputs->ki; - const UINT message = - (kbdinput.dwFlags & KEYEVENTF_KEYUP) ? WM_KEYUP : WM_KEYDOWN; - const bool is_key_up = kbdinput.dwFlags & KEYEVENTF_KEYUP; - const LPARAM lparam = CreateKeyEventLparam( - kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY, is_key_up); - pending_responds_.push_back( - Win32Message{message, kbdinput.wVk, lparam, kWmResultDefault}); - if (is_printable && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { - pending_responds_.push_back( - Win32Message{WM_CHAR, kbdinput.wVk, lparam, kWmResultDefault}); - } + pending_responds_.push_back({cInputs, pInputs->ki, cbSize}); return 1; } - std::vector pending_responds_; + std::vector pending_responds_; TestKeystate key_state_; }; @@ -239,8 +252,6 @@ class KeyboardTester { void Responding(bool response) { test_response = response; } - void NextMessageShouldDefault() { next_event_should_default_ = true; } - void InjectMessages(int count, Win32Message message1, ...) { Win32Message messages[count]; messages[0] = message1; @@ -253,15 +264,15 @@ class KeyboardTester { window_->InjectMessageList(count, messages); } - void InjectPendingEvents() { view_->InjectPendingEvents(window_.get()); } + void InjectPendingEvents(uint32_t redispatch_char = 0) { + view_->InjectPendingEvents(window_.get(), redispatch_char); + } static bool test_response; private: std::unique_ptr view_; std::unique_ptr window_; - - bool next_event_should_default_ = false; }; bool KeyboardTester::test_response = false; @@ -386,11 +397,18 @@ constexpr bool kNotSynthesized = false; } // namespace +// Define compound `expect` in macros. If they're defined in functions, the +// stacktrace wouldn't print where the function is called in the unit tests. + #define EXPECT_CALL_IS_EVENT(_key_call, ...) \ EXPECT_EQ(_key_call.type, kKeyCallOnKey); \ EXPECT_EVENT_EQUALS(_key_call.key_event, __VA_ARGS__); -TEST(KeyboardTest, LowerCaseA) { +#define EXPECT_CALL_IS_TEXT(_key_call, u16_string) \ + EXPECT_EQ(_key_call.type, kKeyCallOnText); \ + EXPECT_EQ(_key_call.text, u16_string); + +TEST(KeyboardTest, LowerCaseAHandled) { KeyboardTester tester; tester.Responding(true); @@ -401,16 +419,54 @@ TEST(KeyboardTest, LowerCaseA) { 2, WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( kWmResultZero), - WmCharInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( + WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, - kLogicalKeyA, "A", kNotSynthesized); + kLogicalKeyA, "a", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents('a'); + EXPECT_EQ(key_calls.size(), 0); + + // Release A + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, + kLogicalKeyA, "", kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); EXPECT_EQ(key_calls.size(), 0); +} + +TEST(KeyboardTest, LowerCaseAUnhandled) { + KeyboardTester tester; + tester.Responding(false); + + // US Keyboard layout + + // Press A + tester.InjectMessages( + 2, + WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, + kLogicalKeyA, "a", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents('a'); + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_TEXT(key_calls[0], u"a"); + clear_key_calls(); // Release A tester.InjectMessages( From 770406f216439645c3060a09793d652e8c5548a1 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 9 Aug 2021 02:53:28 -0700 Subject: [PATCH 10/30] ShiftLeftKeyA --- shell/platform/windows/keyboard_unittests.cc | 93 ++++++++++++++++++-- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index de4dadd185de1..cd516abc8b104 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -116,19 +116,19 @@ class MockFlutterWindowWin32 : public FlutterWindowWin32, class TestKeystate { public: - void Set(int virtual_key, bool pressed, bool toggled_on = false) { + void Set(uint32_t virtual_key, bool pressed, bool toggled_on = false) { state_[virtual_key] = (pressed ? kStateMaskPressed : 0) | (toggled_on ? kStateMaskToggled : 0); } - SHORT Get(int virtual_key) { return state_[virtual_key]; } + SHORT Get(uint32_t virtual_key) { return state_[virtual_key]; } KeyboardKeyEmbedderHandler::GetKeyStateHandler Getter() { - return [this](int virtual_key) { return Get(virtual_key); }; + return [this](uint32_t virtual_key) { return Get(virtual_key); }; } private: - std::map state_; + std::map state_; }; typedef struct { @@ -176,6 +176,10 @@ class TestFlutterWindowsView : public FlutterWindowsView { pending_responds_.clear(); } + void SetKeyState(uint32_t key, bool pressed, bool toggled_on) { + key_state_.Set(key, pressed, toggled_on); + } + protected: void RegisterKeyboardHandlers( BinaryMessenger* messenger, @@ -250,6 +254,10 @@ class KeyboardTester { window_->SetView(view_.get()); } + void SetKeyState(uint32_t key, bool pressed, bool toggled_on) { + view_->SetKeyState(key, pressed, toggled_on); + } + void Responding(bool response) { test_response = response; } void InjectMessages(int count, Win32Message message1, ...) { @@ -264,6 +272,11 @@ class KeyboardTester { window_->InjectMessageList(count, messages); } + // Inject all events called with |SendInput| to the event queue, + // then process the event queue. + // + // If |redispatch_char| is not 0, then WM_KEYDOWN events will + // also redispatch a WM_CHAR event with that value as lparam. void InjectPendingEvents(uint32_t redispatch_char = 0) { view_->InjectPendingEvents(window_.get(), redispatch_char); } @@ -387,7 +400,7 @@ constexpr uint64_t kScanCodeKeyA = 0x1e; // constexpr uint64_t kScanCodeNumpad1 = 0x4f; // constexpr uint64_t kScanCodeNumLock = 0x45; // constexpr uint64_t kScanCodeControl = 0x1d; -// constexpr uint64_t kScanCodeShiftLeft = 0x2a; +constexpr uint64_t kScanCodeShiftLeft = 0x2a; // constexpr uint64_t kScanCodeShiftRight = 0x36; constexpr uint64_t kVirtualKeyA = 0x41; @@ -482,5 +495,75 @@ TEST(KeyboardTest, LowerCaseAUnhandled) { EXPECT_EQ(key_calls.size(), 0); } +TEST(KeyboardTest, ShiftLeftKeyA) { + KeyboardTester tester; + tester.Responding(false); + + // US Keyboard layout + + // Press ShiftLeft + tester.SetKeyState(VK_LSHIFT, true, true); + tester.InjectMessages( + 1, + WmKeyDownInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalShiftLeft, + kLogicalShiftLeft, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Press A + tester.InjectMessages( + 2, + WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{'A', kScanCodeKeyA, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, + kLogicalKeyA, "A", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents('A'); + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_TEXT(key_calls[0], u"A"); + clear_key_calls(); + + // Release ShiftLeft + tester.SetKeyState(VK_LSHIFT, false, true); + tester.InjectMessages( + 1, + WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalShiftLeft, + kLogicalShiftLeft, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Release A + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, + kLogicalKeyA, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); +} + } // namespace testing } // namespace flutter From 78499d530ce80eb0820c605eaa08ba702960e4bc Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 9 Aug 2021 03:14:41 -0700 Subject: [PATCH 11/30] Ctrl-A --- shell/platform/windows/keyboard_unittests.cc | 76 ++++++++++++++++++- .../platform/windows/testing/test_keyboard.cc | 4 +- shell/platform/windows/window_win32.cc | 14 +++- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index cd516abc8b104..f02e2890b1d96 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -399,7 +399,7 @@ std::unique_ptr GetTestEngine() { constexpr uint64_t kScanCodeKeyA = 0x1e; // constexpr uint64_t kScanCodeNumpad1 = 0x4f; // constexpr uint64_t kScanCodeNumLock = 0x45; -// constexpr uint64_t kScanCodeControl = 0x1d; +constexpr uint64_t kScanCodeControl = 0x1d; constexpr uint64_t kScanCodeShiftLeft = 0x2a; // constexpr uint64_t kScanCodeShiftRight = 0x36; @@ -495,6 +495,8 @@ TEST(KeyboardTest, LowerCaseAUnhandled) { EXPECT_EQ(key_calls.size(), 0); } +// Press Shift-A. This is special because Win32 gives 'A' as character for the +// KeyA press. TEST(KeyboardTest, ShiftLeftKeyA) { KeyboardTester tester; tester.Responding(false); @@ -565,5 +567,77 @@ TEST(KeyboardTest, ShiftLeftKeyA) { EXPECT_EQ(key_calls.size(), 0); } +// Press Ctrl-A. This is special because Win32 gives 0x01 as character for the +// KeyA press. +TEST(KeyboardTest, CtrlLeftKeyA) { + KeyboardTester tester; + tester.Responding(false); + + // US Keyboard layout + + // Press ControlLeft + tester.SetKeyState(VK_LCONTROL, true, true); + tester.InjectMessages( + 1, + WmKeyDownInfo{VK_CONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalControlLeft, + kLogicalControlLeft, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Press A + tester.InjectMessages( + 2, + WmKeyDownInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{0x01, kScanCodeKeyA, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, + kLogicalKeyA, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(0); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Release A + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualKeyA, kScanCodeKeyA, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, + kLogicalKeyA, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + + // Release ControlLeft + tester.SetKeyState(VK_LCONTROL, false, true); + tester.InjectMessages( + 1, + WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalControlLeft, + kLogicalControlLeft, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index f9f45fbb4adb0..46ca78f133246 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -31,8 +31,8 @@ std::string _print_character(const char* s) { #define _RETURN_IF_NOT_EQUALS(val1, val2) \ if ((val1) != (val2)) { \ return ::testing::AssertionFailure() \ - << "Expected equality of these values:\n " #val1 "\n To be: " \ - << val2 << "\n Actual: \n " << val1; \ + << "Expected equality of these values:\n " #val1 "\n " \ + << val2 << "\n Actual: \n " << val1; \ } ::testing::AssertionResult _EventEquals(const char* expr_event, diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index cb5f41e45d0e1..bb1d728a00656 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -190,6 +190,12 @@ static uint16_t ResolveKeyCode(uint16_t original, } } +static bool IsPrintable(uint32_t c) { + constexpr char32_t min_printable = ' '; + constexpr char32_t del = 0x7F; + return c >= min_printable && c != del; +} + LRESULT WindowWin32::HandleMessage(UINT const message, WPARAM const wparam, @@ -354,8 +360,12 @@ WindowWin32::HandleMessage(UINT const message, const unsigned int scancode = (lparam >> 16) & 0xff; 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 + const char32_t event_character = IsPrintable(code_point) ? code_point : 0; bool handled = OnKey(keycode_for_char_message_, scancode, WM_KEYDOWN, - code_point, extended, was_down); + 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 @@ -375,7 +385,7 @@ WindowWin32::HandleMessage(UINT const message, // - ASCII control characters, which are sent as WM_CHAR events for all // control key shortcuts. if (message == WM_CHAR && s_pending_high_surrogate == 0 && - character >= u' ') { + IsPrintable(character)) { OnText(text); } return 0; From cc9577ca22eaf7a94ce8ad950d0b9e78cf9b9272 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 9 Aug 2021 03:50:08 -0700 Subject: [PATCH 12/30] Ctrl-1 --- shell/platform/windows/keyboard_unittests.cc | 69 +++++++++++++++++++ shell/platform/windows/window_win32.cc | 2 +- .../windows/window_win32_unittests.cc | 6 +- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index f02e2890b1d96..b8430da76d70d 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -397,12 +397,14 @@ std::unique_ptr GetTestEngine() { } constexpr uint64_t kScanCodeKeyA = 0x1e; +constexpr uint64_t kScanCodeDigit1 = 0x02; // constexpr uint64_t kScanCodeNumpad1 = 0x4f; // constexpr uint64_t kScanCodeNumLock = 0x45; constexpr uint64_t kScanCodeControl = 0x1d; constexpr uint64_t kScanCodeShiftLeft = 0x2a; // constexpr uint64_t kScanCodeShiftRight = 0x36; +constexpr uint64_t kVirtualDigit1 = 0x31; constexpr uint64_t kVirtualKeyA = 0x41; constexpr bool kSynthesized = true; @@ -636,7 +638,74 @@ TEST(KeyboardTest, CtrlLeftKeyA) { tester.InjectPendingEvents(); EXPECT_EQ(key_calls.size(), 0); clear_key_calls(); +} + +// Press Ctrl-1. This is special because it yields no WM_CHAR for the 1. +TEST(KeyboardTest, CtrlLeftDigit1) { + KeyboardTester tester; + tester.Responding(false); + + // US Keyboard layout + + // Press ControlLeft + tester.SetKeyState(VK_LCONTROL, true, true); + tester.InjectMessages( + 1, + WmKeyDownInfo{VK_CONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalControlLeft, + kLogicalControlLeft, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Press 1 + tester.InjectMessages( + 1, + WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit1, + kLogicalDigit1, "", kNotSynthesized); + clear_key_calls(); + tester.InjectPendingEvents(0); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Release 1 + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit1, + kLogicalDigit1, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + + // Release ControlLeft + tester.SetKeyState(VK_LCONTROL, false, true); + tester.InjectMessages( + 1, + WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalControlLeft, + kLogicalControlLeft, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); } } // namespace testing diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index bb1d728a00656..9f49ff9aebed1 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -426,7 +426,7 @@ WindowWin32::HandleMessage(UINT const message, keyCode = ResolveKeyCode(keyCode, extended, scancode); const int action = is_keydown_message ? WM_KEYDOWN : WM_KEYUP; const bool was_down = lparam & 0x40000000; - if (OnKey(keyCode, scancode, action, character, extended, was_down)) { + if (OnKey(keyCode, scancode, action, 0, extended, was_down)) { return 0; } break; diff --git a/shell/platform/windows/window_win32_unittests.cc b/shell/platform/windows/window_win32_unittests.cc index ef81d07ef247c..9b2beba8592b1 100644 --- a/shell/platform/windows/window_win32_unittests.cc +++ b/shell/platform/windows/window_win32_unittests.cc @@ -75,7 +75,7 @@ TEST(MockWin32Window, KeyDownPrintable) { MockWin32Window window; LPARAM lparam = CreateKeyEventLparam(30, false, false); - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, false)).Times(1); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, false)).Times(1); EXPECT_CALL(window, OnText(_)).Times(1); Win32Message messages[] = {{WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, {WM_CHAR, 65, lparam, kWmResultDontCheck}}; @@ -95,7 +95,7 @@ TEST(MockWin32Window, KeyDownWithCtrl) { // Expect OnKey, but not OnText, because Control + Key is not followed by // WM_CHAR - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 'A', false, true)).Times(1); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, true)).Times(1); EXPECT_CALL(window, OnText(_)).Times(0); window.InjectWindowMessage(WM_KEYDOWN, 65, lparam); @@ -115,7 +115,7 @@ TEST(MockWin32Window, KeyDownWithCtrlToggled) { LPARAM lparam = CreateKeyEventLparam(30, false, false); - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 65, false, false)).Times(1); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, false)).Times(1); EXPECT_CALL(window, OnText(_)).Times(1); // send a "A" key down event. From b11134bba53edee9fedd61bf0b1b1dd2457d0d85 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 9 Aug 2021 03:56:14 -0700 Subject: [PATCH 13/30] Digit1OnFrenchLayout --- shell/platform/windows/keyboard_unittests.cc | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index b8430da76d70d..94d3ee9f06a07 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -708,5 +708,44 @@ TEST(KeyboardTest, CtrlLeftDigit1) { clear_key_calls(); } +// Press 1 on a French keyboard. This is special because it yields a WM_CHAR with char_code '&'. +TEST(KeyboardTest, Digit1OnFrenchLayout) { + KeyboardTester tester; + tester.Responding(false); + + // French Keyboard layout + + // Press 1 + tester.InjectMessages( + 2, + WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{'&', kScanCodeDigit1, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit1, + kLogicalDigit1, "&", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents('&'); + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_TEXT(key_calls[0], u"&"); + clear_key_calls(); + + // Release 1 + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit1, + kLogicalDigit1, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); +} + } // namespace testing } // namespace flutter From 68e15122c3e5f6de6dbf666e8c193a65988e8366 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 9 Aug 2021 16:53:05 -0700 Subject: [PATCH 14/30] AltGrKeyQ --- shell/platform/windows/keyboard_unittests.cc | 83 ++++++++++++++++++- shell/platform/windows/testing/wm_builders.cc | 14 ++++ shell/platform/windows/testing/wm_builders.h | 19 +++++ 3 files changed, 115 insertions(+), 1 deletion(-) diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 94d3ee9f06a07..e8449b4c70df0 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -397,15 +397,18 @@ std::unique_ptr GetTestEngine() { } constexpr uint64_t kScanCodeKeyA = 0x1e; +constexpr uint64_t kScanCodeKeyQ = 0x10; constexpr uint64_t kScanCodeDigit1 = 0x02; // constexpr uint64_t kScanCodeNumpad1 = 0x4f; // constexpr uint64_t kScanCodeNumLock = 0x45; constexpr uint64_t kScanCodeControl = 0x1d; +constexpr uint64_t kScanCodeAlt= 0x38; constexpr uint64_t kScanCodeShiftLeft = 0x2a; // constexpr uint64_t kScanCodeShiftRight = 0x36; constexpr uint64_t kVirtualDigit1 = 0x31; constexpr uint64_t kVirtualKeyA = 0x41; +constexpr uint64_t kVirtualKeyQ = 0x51; constexpr bool kSynthesized = true; constexpr bool kNotSynthesized = false; @@ -708,7 +711,8 @@ TEST(KeyboardTest, CtrlLeftDigit1) { clear_key_calls(); } -// Press 1 on a French keyboard. This is special because it yields a WM_CHAR with char_code '&'. +// Press 1 on a French keyboard. This is special because it yields WM_CHAR +// with char_code '&'. TEST(KeyboardTest, Digit1OnFrenchLayout) { KeyboardTester tester; tester.Responding(false); @@ -747,5 +751,82 @@ TEST(KeyboardTest, Digit1OnFrenchLayout) { EXPECT_EQ(key_calls.size(), 0); } +// This tests AltGr-Q on a German keyboard, which should print '@'. +TEST(KeyboardTest, AltGrKeyQOnGermanLayout) { + KeyboardTester tester; + tester.Responding(false); + + // German Keyboard layout + + // Press AltGr, which Win32 precedes with a ContrlLeft down. + tester.SetKeyState(VK_LCONTROL, true, true); + tester.InjectMessages( + 2, + WmKeyDownInfo{VK_LCONTROL, kScanCodeControl, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmKeyDownInfo{VK_MENU, kScanCodeAlt, kExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 2); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalControlLeft, + kLogicalControlLeft, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeDown, kPhysicalAltRight, + kLogicalAltRight, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Press Q + tester.InjectMessages( + 2, + WmKeyDownInfo{kVirtualKeyQ, kScanCodeKeyQ, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{'@', kScanCodeKeyQ, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyQ, + kLogicalKeyQ, "@", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents('@'); + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_TEXT(key_calls[0], u"@"); + clear_key_calls(); + + // Release Q + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualKeyQ, kScanCodeKeyQ, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyQ, + kLogicalKeyQ, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + + // Release AltGr. Win32 doesn't dispatch ControlLeft up. Instead Flutter will + // dispatch one. + tester.InjectMessages( + 1, WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalAltRight, + kLogicalAltRight, "", kNotSynthesized); + clear_key_calls(); + + tester.SetKeyState(VK_LCONTROL, false, false); + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalControlLeft, + kLogicalControlLeft, "", kNotSynthesized); + clear_key_calls(); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/wm_builders.cc b/shell/platform/windows/testing/wm_builders.cc index e2da5317627d1..4e78f29fafd80 100644 --- a/shell/platform/windows/testing/wm_builders.cc +++ b/shell/platform/windows/testing/wm_builders.cc @@ -44,5 +44,19 @@ Win32Message WmCharInfo::Build(LRESULT expected_result, HWND hWnd) { }; } +Win32Message WmSysKeyUpInfo::Build(LRESULT expected_result, HWND hWnd) { + uint32_t lParam = (1 /* repeat_count */ << 0) | (scan_code << 16) | + (extended << 24) | (context << 29) | + (1 /* prev_state */ << 30) | + (1 /* transition */ << 31); + return Win32Message{ + .message = WM_SYSKEYUP, + .wParam = key, + .lParam = lParam, + .expected_result = expected_result, + .hWnd = hWnd, + }; +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/wm_builders.h b/shell/platform/windows/testing/wm_builders.h index 6c2173f47e43c..d1fe65e4da440 100644 --- a/shell/platform/windows/testing/wm_builders.h +++ b/shell/platform/windows/testing/wm_builders.h @@ -115,6 +115,25 @@ typedef struct { HWND hWnd = NULL); } WmCharInfo; +typedef struct { + uint32_t key; + + uint8_t scan_code; + + WmFieldExtended extended; + + // WmFieldPrevState prev_state; // Always 1. + + // WmFieldTransitionState transition; // Always 1. + + WmFieldContext context; + + // uint16_t repeat_count; // Always 1. + + Win32Message Build(LRESULT expected_result = kWmResultDontCheck, + HWND hWnd = NULL); +} WmSysKeyUpInfo; + } // namespace testing } // namespace flutter From 32de55aab1313b0f8756013b2b5e22279be7b88a Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 03:34:12 -0700 Subject: [PATCH 15/30] Dead key E --- .../platform/windows/keyboard_key_handler.cc | 7 +- shell/platform/windows/keyboard_unittests.cc | 75 +++++++++++++++++++ shell/platform/windows/testing/wm_builders.cc | 12 +++ shell/platform/windows/testing/wm_builders.h | 25 +++++++ shell/platform/windows/window_win32.cc | 26 +++++-- shell/platform/windows/window_win32.h | 3 + 6 files changed, 141 insertions(+), 7 deletions(-) diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index c62668f132baa..f1ed98147d780 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -220,8 +220,10 @@ bool KeyboardKeyHandler::KeyboardHook(FlutterWindowsView* view, } pending_responds_.push_back(std::move(incoming)); + const bool is_deadchar = (character & 0x80000000) != 0; + const char32_t event_character = is_deadchar ? 0 : character; for (const auto& delegate : delegates_) { - delegate->KeyboardHook(key, scancode, action, character, extended, was_down, + delegate->KeyboardHook(key, scancode, action, event_character, extended, was_down, [sequence_id, this](bool handled) { ResolvePendingEvent(sequence_id, handled); }); @@ -261,7 +263,8 @@ void KeyboardKeyHandler::ResolvePendingEvent(uint64_t sequence_id, if (event.unreplied == 0) { std::unique_ptr event_ptr = std::move(*iter); pending_responds_.erase(iter); - const bool should_redispatch = !event_ptr->any_handled; + const bool is_deadchar = (event_ptr->character & 0x80000000) != 0; + const bool should_redispatch = !event_ptr->any_handled && !is_deadchar; if (should_redispatch) { RedispatchEvent(std::move(event_ptr)); } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index e8449b4c70df0..8f631f06cf5c0 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -397,6 +397,7 @@ std::unique_ptr GetTestEngine() { } constexpr uint64_t kScanCodeKeyA = 0x1e; +constexpr uint64_t kScanCodeKeyE = 0x12; constexpr uint64_t kScanCodeKeyQ = 0x10; constexpr uint64_t kScanCodeDigit1 = 0x02; // constexpr uint64_t kScanCodeNumpad1 = 0x4f; @@ -405,9 +406,11 @@ constexpr uint64_t kScanCodeControl = 0x1d; constexpr uint64_t kScanCodeAlt= 0x38; constexpr uint64_t kScanCodeShiftLeft = 0x2a; // constexpr uint64_t kScanCodeShiftRight = 0x36; +constexpr uint64_t kScanCodeBracketLeft = 0x1a; constexpr uint64_t kVirtualDigit1 = 0x31; constexpr uint64_t kVirtualKeyA = 0x41; +constexpr uint64_t kVirtualKeyE = 0x45; constexpr uint64_t kVirtualKeyQ = 0x51; constexpr bool kSynthesized = true; @@ -828,5 +831,77 @@ TEST(KeyboardTest, AltGrKeyQOnGermanLayout) { clear_key_calls(); } +// This tests dead key ^ then E on a French keyboard, which should be combined +// into ê. +TEST(KeyboardTest, DeadCaretKeyThenKeyEOnFrench) { + KeyboardTester tester; + tester.Responding(false); + + // French Keyboard layout + + // Press ^¨ (US: Left bracket) + tester.InjectMessages( + 2, + WmKeyDownInfo{0xDD, kScanCodeBracketLeft, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmDeadCharInfo{'^', kScanCodeBracketLeft, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalBracketLeft, + kLogicalBracketRight, "]", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(0); // No WM_DEADCHAR messages sent here. + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Release ^¨ + tester.InjectMessages( + 1, + WmKeyUpInfo{0xDD, kScanCodeBracketLeft, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalBracketLeft, + kLogicalBracketRight, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Press E + tester.InjectMessages( + 2, + WmKeyDownInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{0xEA, kScanCodeKeyE, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyE, + kLogicalKeyE, "ê", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(0xEA); // The redispatched event uses unmodified 'e' + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_TEXT(key_calls[0], u"ê"); + clear_key_calls(); + + // Release E + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualKeyE, kScanCodeKeyE, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyE, + kLogicalKeyE, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/wm_builders.cc b/shell/platform/windows/testing/wm_builders.cc index 4e78f29fafd80..ce429560a1064 100644 --- a/shell/platform/windows/testing/wm_builders.cc +++ b/shell/platform/windows/testing/wm_builders.cc @@ -58,5 +58,17 @@ Win32Message WmSysKeyUpInfo::Build(LRESULT expected_result, HWND hWnd) { }; } +Win32Message WmDeadCharInfo::Build(LRESULT expected_result, HWND hWnd) { + uint32_t lParam = (repeat_count << 0) | (scan_code << 16) | (extended << 24) | + (context << 30) | (prev_state << 30) | (transition << 31); + return Win32Message{ + .message = WM_DEADCHAR, + .wParam = char_code, + .lParam = lParam, + .expected_result = expected_result, + .hWnd = hWnd, + }; +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/wm_builders.h b/shell/platform/windows/testing/wm_builders.h index d1fe65e4da440..56a0b3e29858c 100644 --- a/shell/platform/windows/testing/wm_builders.h +++ b/shell/platform/windows/testing/wm_builders.h @@ -115,6 +115,9 @@ typedef struct { HWND hWnd = NULL); } WmCharInfo; +// WM_SYSKEYUP messages. +// +// See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-syskeyup. typedef struct { uint32_t key; @@ -134,6 +137,28 @@ typedef struct { HWND hWnd = NULL); } WmSysKeyUpInfo; +// WM_DEADCHAR messages. +// +// See https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-deadchar. +typedef struct { + uint32_t char_code; + + uint8_t scan_code; + + WmFieldExtended extended; + + WmFieldPrevState prev_state; + + WmFieldTransitionState transition; + + WmFieldContext context; + + uint16_t repeat_count = 1; + + Win32Message Build(LRESULT expected_result = kWmResultDontCheck, + HWND hWnd = NULL); +} WmDeadCharInfo; + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 9f49ff9aebed1..adad259f5a69b 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -353,26 +353,37 @@ WindowWin32::HandleMessage(UINT const message, 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. if (keycode_for_char_message_ != 0) { - const unsigned int scancode = (lparam >> 16) & 0xff; 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 - const char32_t event_character = IsPrintable(code_point) ? code_point : 0; + const char32_t event_character = + (message == WM_DEADCHAR || message == WM_SYSDEADCHAR) ? MapVirtualKey(keycode_for_char_message_, MAPVK_VK_TO_CHAR) : + IsPrintable(code_point) ? code_point : 0; bool handled = OnKey(keycode_for_char_message_, scancode, 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, or the - // framework handled the key in its response, so it shouldn't also be - // added as text. + // 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; + } return 0; } } @@ -386,6 +397,11 @@ WindowWin32::HandleMessage(UINT const message, // 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; diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index 6fc27e9ddfb3c..95d0440224614 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -210,6 +211,8 @@ class WindowWin32 { // message. int keycode_for_char_message_ = 0; + std::map text_for_scancode_on_redispatch_; + // Manages IME state. TextInputManagerWin32 text_input_manager_; }; From b7a78c79ef7c1cb89792968d2197a8260a404d66 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 03:53:28 -0700 Subject: [PATCH 16/30] ^1 --- shell/platform/windows/keyboard_unittests.cc | 83 +++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 8f631f06cf5c0..803b156818fbf 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -755,7 +755,7 @@ TEST(KeyboardTest, Digit1OnFrenchLayout) { } // This tests AltGr-Q on a German keyboard, which should print '@'. -TEST(KeyboardTest, AltGrKeyQOnGermanLayout) { +TEST(KeyboardTest, AltGrModifiedKey) { KeyboardTester tester; tester.Responding(false); @@ -833,7 +833,7 @@ TEST(KeyboardTest, AltGrKeyQOnGermanLayout) { // This tests dead key ^ then E on a French keyboard, which should be combined // into ê. -TEST(KeyboardTest, DeadCaretKeyThenKeyEOnFrench) { +TEST(KeyboardTest, DeadKeyThatCombines) { KeyboardTester tester; tester.Responding(false); @@ -903,5 +903,84 @@ TEST(KeyboardTest, DeadCaretKeyThenKeyEOnFrench) { EXPECT_EQ(key_calls.size(), 0); } +// This tests dead key ^ then & (US: 1) on a French keyboard, which do not +// combine and should output "^$". +TEST(KeyboardTest, DeadKeyThatDoesNotCombine) { + KeyboardTester tester; + tester.Responding(false); + + // French Keyboard layout + + // Press ^¨ (US: Left bracket) + tester.InjectMessages( + 2, + WmKeyDownInfo{0xDD, kScanCodeBracketLeft, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmDeadCharInfo{'^', kScanCodeBracketLeft, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalBracketLeft, + kLogicalBracketRight, "]", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(0); // No WM_DEADCHAR messages sent here. + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Release ^¨ + tester.InjectMessages( + 1, + WmKeyUpInfo{0xDD, kScanCodeBracketLeft, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalBracketLeft, + kLogicalBracketRight, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Press 1 + tester.InjectMessages( + 3, + WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{'^', kScanCodeDigit1, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{'&', kScanCodeDigit1, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 2); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit1, + kLogicalDigit1, "^", kNotSynthesized); + EXPECT_CALL_IS_TEXT(key_calls[1], u"^"); + clear_key_calls(); + + tester.InjectPendingEvents('&'); + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_TEXT(key_calls[0], u"&"); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); + + // Release 1 + tester.InjectMessages( + 1, WmKeyUpInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalDigit1, + kLogicalDigit1, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); +} + } // namespace testing } // namespace flutter From 4432f98afcfa88de53595735620dbe78eb877f28 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 05:50:18 -0700 Subject: [PATCH 17/30] Multibyte --- .../windows/keyboard_key_embedder_handler.cc | 15 ++++- shell/platform/windows/keyboard_unittests.cc | 62 +++++++++++++++++++ shell/platform/windows/window_win32.cc | 8 ++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index 1c4bf4d2037ae..d378e2823c260 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -388,8 +388,19 @@ void KeyboardKeyEmbedderHandler::ConvertUtf32ToUtf8_(char* out, char32_t ch) { out[0] = '\0'; return; } - // TODO: Correctly handle UTF-32 - std::wstring text({static_cast(ch)}); + assert(!(0xD800 <= ch && ch <= 0xDFFF)); + std::wstring text; + // Encode char32 into UTF-16. See https://en.wikipedia.org/wiki/UTF-16. + if ((0x0000 <= ch && ch <= 0xD7FF) || + (0xE000 <= ch && ch <= 0xFFFF)) { + text.push_back(static_cast(ch)); + } else { // 0x10000 <= ch && ch <= 0x10FFFF + const uint32_t offset = ch - 0x10000; + const uint16_t high_offset = (offset >> 10) & 0x3FF; + const uint16_t low_offset = offset & 0x3FF; + text.push_back(static_cast(high_offset + 0xD800)); + text.push_back(static_cast(low_offset + 0xDC00)); + } strcpy_s(out, kCharacterCacheSize, Utf8FromUtf16(text).c_str()); } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 803b156818fbf..ffac3cf0e995c 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -399,6 +399,7 @@ std::unique_ptr GetTestEngine() { constexpr uint64_t kScanCodeKeyA = 0x1e; constexpr uint64_t kScanCodeKeyE = 0x12; constexpr uint64_t kScanCodeKeyQ = 0x10; +constexpr uint64_t kScanCodeKeyW = 0x11; constexpr uint64_t kScanCodeDigit1 = 0x02; // constexpr uint64_t kScanCodeNumpad1 = 0x4f; // constexpr uint64_t kScanCodeNumLock = 0x45; @@ -412,6 +413,7 @@ constexpr uint64_t kVirtualDigit1 = 0x31; constexpr uint64_t kVirtualKeyA = 0x41; constexpr uint64_t kVirtualKeyE = 0x45; constexpr uint64_t kVirtualKeyQ = 0x51; +constexpr uint64_t kVirtualKeyW = 0x57; constexpr bool kSynthesized = true; constexpr bool kNotSynthesized = false; @@ -982,5 +984,65 @@ TEST(KeyboardTest, DeadKeyThatDoesNotCombine) { EXPECT_EQ(key_calls.size(), 0); } +// This tests when the resulting character needs to be combined with surrogates. +TEST(KeyboardTest, MultibyteCharacter) { + KeyboardTester tester; + tester.Responding(false); + + // Gothic Keyboard layout. (We need a layout that yields non-BMP characters + // without IME, which that is actually very rare.) + + // Press key W of a US keyboard, which should yield character '𐍅'. + tester.InjectMessages( + 3, + WmKeyDownInfo{kVirtualKeyW, kScanCodeKeyW, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{0xd800, kScanCodeKeyW, kNotExtended, kWasUp}.Build( + kWmResultZero), + WmCharInfo{0xdf45, kScanCodeKeyW, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + const char* st = key_calls[0].key_event.character; + printf("CH: "); + while (*st != 0) { + printf("0x%02x ", *st); + st++; + } + printf(".\n");fflush(stdout); + + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyW, + kLogicalKeyW, "𐍅", kNotSynthesized); + clear_key_calls(); + + // Inject the redispatched high surrogate. + tester.InjectPendingEvents(0xd800); + // Manually inject the redispatched low surrogate. + tester.InjectMessages( + 1, + WmCharInfo{0xdf45, kScanCodeKeyW, kNotExtended, kWasUp}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_TEXT(key_calls[0], u"𐍅"); + clear_key_calls(); + + // Release W + tester.InjectMessages( + 1, + WmKeyUpInfo{kVirtualKeyW, kScanCodeKeyW, kNotExtended}.Build( + kWmResultZero)); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyW, + kLogicalKeyW, "", kNotSynthesized); + clear_key_calls(); + + tester.InjectPendingEvents(); + EXPECT_EQ(key_calls.size(), 0); + clear_key_calls(); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index adad259f5a69b..537875179da4e 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -358,7 +358,13 @@ WindowWin32::HandleMessage(UINT const message, // 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. - if (keycode_for_char_message_ != 0) { + // + // A high surrogate is always followed by a low surrogate, while a + // non-surrogate character always appear alone. Filter out high + // surrogates out so that it's the low surrogate message that triggers + // the onKey, asks if the framework handles (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 From dbd83071ef8b5be51d0e11211f9caa9675a3904a Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 05:56:29 -0700 Subject: [PATCH 18/30] Format --- .../windows/keyboard_key_embedder_handler.cc | 5 +- .../platform/windows/keyboard_key_handler.cc | 4 +- shell/platform/windows/keyboard_unittests.cc | 137 +++++++++--------- .../platform/windows/testing/test_keyboard.cc | 4 +- shell/platform/windows/testing/wm_builders.cc | 3 +- shell/platform/windows/window_win32.cc | 9 +- 6 files changed, 84 insertions(+), 78 deletions(-) diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index d378e2823c260..5db8d1a7f0189 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -391,10 +391,9 @@ void KeyboardKeyEmbedderHandler::ConvertUtf32ToUtf8_(char* out, char32_t ch) { assert(!(0xD800 <= ch && ch <= 0xDFFF)); std::wstring text; // Encode char32 into UTF-16. See https://en.wikipedia.org/wiki/UTF-16. - if ((0x0000 <= ch && ch <= 0xD7FF) || - (0xE000 <= ch && ch <= 0xFFFF)) { + if ((0x0000 <= ch && ch <= 0xD7FF) || (0xE000 <= ch && ch <= 0xFFFF)) { text.push_back(static_cast(ch)); - } else { // 0x10000 <= ch && ch <= 0x10FFFF + } else { // 0x10000 <= ch && ch <= 0x10FFFF const uint32_t offset = ch - 0x10000; const uint16_t high_offset = (offset >> 10) & 0x3FF; const uint16_t low_offset = offset & 0x3FF; diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index f1ed98147d780..c62fe6e17338b 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -223,8 +223,8 @@ bool KeyboardKeyHandler::KeyboardHook(FlutterWindowsView* view, const bool is_deadchar = (character & 0x80000000) != 0; const char32_t event_character = is_deadchar ? 0 : character; for (const auto& delegate : delegates_) { - delegate->KeyboardHook(key, scancode, action, event_character, extended, was_down, - [sequence_id, this](bool handled) { + delegate->KeyboardHook(key, scancode, action, event_character, extended, + was_down, [sequence_id, this](bool handled) { ResolvePendingEvent(sequence_id, handled); }); } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index ffac3cf0e995c..56ce4491e5faf 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -150,7 +150,8 @@ class TestFlutterWindowsView : public FlutterWindowsView { uint32_t redispatch_char; - void InjectPendingEvents(MockFlutterWindowWin32* win32window, uint32_t redispatch_char) { + void InjectPendingEvents(MockFlutterWindowWin32* win32window, + uint32_t redispatch_char) { std::vector messages; for (const SendInputInfo& input : pending_responds_) { const KEYBDINPUT kbdinput = input.kbdinput; @@ -161,8 +162,9 @@ class TestFlutterWindowsView : public FlutterWindowsView { kbdinput.wScan, kbdinput.dwFlags & KEYEVENTF_EXTENDEDKEY, is_key_up); // TODO(dkwingsmt): Don't check the message results for redispatched // messages for now, because making them work takes non-trivial rework - // to our current structure. https://github.com/flutter/flutter/issues/87843 - // If this is resolved, change them to kWmResultDefault. + // to our current structure. + // https://github.com/flutter/flutter/issues/87843 If this is resolved, + // change them to kWmResultDefault. messages.push_back( Win32Message{message, kbdinput.wVk, lparam, kWmResultDontCheck}); if (redispatch_char != 0 && (kbdinput.dwFlags & KEYEVENTF_KEYUP) == 0) { @@ -171,8 +173,7 @@ class TestFlutterWindowsView : public FlutterWindowsView { } } - win32window->InjectMessageList(messages.size(), - messages.data()); + win32window->InjectMessageList(messages.size(), messages.data()); pending_responds_.clear(); } @@ -404,7 +405,7 @@ constexpr uint64_t kScanCodeDigit1 = 0x02; // constexpr uint64_t kScanCodeNumpad1 = 0x4f; // constexpr uint64_t kScanCodeNumLock = 0x45; constexpr uint64_t kScanCodeControl = 0x1d; -constexpr uint64_t kScanCodeAlt= 0x38; +constexpr uint64_t kScanCodeAlt = 0x38; constexpr uint64_t kScanCodeShiftLeft = 0x2a; // constexpr uint64_t kScanCodeShiftRight = 0x36; constexpr uint64_t kScanCodeBracketLeft = 0x1a; @@ -428,7 +429,7 @@ constexpr bool kNotSynthesized = false; EXPECT_EVENT_EQUALS(_key_call.key_event, __VA_ARGS__); #define EXPECT_CALL_IS_TEXT(_key_call, u16_string) \ - EXPECT_EQ(_key_call.type, kKeyCallOnText); \ + EXPECT_EQ(_key_call.type, kKeyCallOnText); \ EXPECT_EQ(_key_call.text, u16_string); TEST(KeyboardTest, LowerCaseAHandled) { @@ -521,8 +522,9 @@ TEST(KeyboardTest, ShiftLeftKeyA) { kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalShiftLeft, - kLogicalShiftLeft, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, + kPhysicalShiftLeft, kLogicalShiftLeft, "", + kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); @@ -550,9 +552,8 @@ TEST(KeyboardTest, ShiftLeftKeyA) { // Release ShiftLeft tester.SetKeyState(VK_LSHIFT, false, true); tester.InjectMessages( - 1, - WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build( - kWmResultZero)); + 1, WmKeyUpInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalShiftLeft, @@ -593,8 +594,9 @@ TEST(KeyboardTest, CtrlLeftKeyA) { kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalControlLeft, - kLogicalControlLeft, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, + kPhysicalControlLeft, kLogicalControlLeft, "", + kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); @@ -634,13 +636,13 @@ TEST(KeyboardTest, CtrlLeftKeyA) { // Release ControlLeft tester.SetKeyState(VK_LCONTROL, false, true); tester.InjectMessages( - 1, - WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kNotExtended}.Build( - kWmResultZero)); + 1, WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kNotExtended}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalControlLeft, - kLogicalControlLeft, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, + kPhysicalControlLeft, kLogicalControlLeft, "", + kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); @@ -663,8 +665,9 @@ TEST(KeyboardTest, CtrlLeftDigit1) { kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalControlLeft, - kLogicalControlLeft, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, + kPhysicalControlLeft, kLogicalControlLeft, "", + kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); @@ -673,9 +676,8 @@ TEST(KeyboardTest, CtrlLeftDigit1) { // Press 1 tester.InjectMessages( - 1, - WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp}.Build( - kWmResultZero)); + 1, WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp} + .Build(kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalDigit1, @@ -702,13 +704,13 @@ TEST(KeyboardTest, CtrlLeftDigit1) { // Release ControlLeft tester.SetKeyState(VK_LCONTROL, false, true); tester.InjectMessages( - 1, - WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kNotExtended}.Build( - kWmResultZero)); + 1, WmKeyUpInfo{VK_CONTROL, kScanCodeControl, kNotExtended}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalControlLeft, - kLogicalControlLeft, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, + kPhysicalControlLeft, kLogicalControlLeft, "", + kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); @@ -727,8 +729,8 @@ TEST(KeyboardTest, Digit1OnFrenchLayout) { // Press 1 tester.InjectMessages( 2, - WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp}.Build( - kWmResultZero), + WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp} + .Build(kWmResultZero), WmCharInfo{'&', kScanCodeDigit1, kNotExtended, kWasUp}.Build( kWmResultZero)); @@ -773,10 +775,12 @@ TEST(KeyboardTest, AltGrModifiedKey) { kWmResultZero)); EXPECT_EQ(key_calls.size(), 2); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalControlLeft, - kLogicalControlLeft, "", kNotSynthesized); - EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeDown, kPhysicalAltRight, - kLogicalAltRight, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, + kPhysicalControlLeft, kLogicalControlLeft, "", + kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[1], kFlutterKeyEventTypeDown, + kPhysicalAltRight, kLogicalAltRight, "", + kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); @@ -817,8 +821,7 @@ TEST(KeyboardTest, AltGrModifiedKey) { // Release AltGr. Win32 doesn't dispatch ControlLeft up. Instead Flutter will // dispatch one. tester.InjectMessages( - 1, WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kExtended}.Build( - kWmResultZero)); + 1, WmSysKeyUpInfo{VK_MENU, kScanCodeAlt, kExtended}.Build(kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalAltRight, @@ -828,8 +831,9 @@ TEST(KeyboardTest, AltGrModifiedKey) { tester.SetKeyState(VK_LCONTROL, false, false); tester.InjectPendingEvents(); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalControlLeft, - kLogicalControlLeft, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, + kPhysicalControlLeft, kLogicalControlLeft, "", + kNotSynthesized); clear_key_calls(); } @@ -850,23 +854,24 @@ TEST(KeyboardTest, DeadKeyThatCombines) { kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalBracketLeft, - kLogicalBracketRight, "]", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, + kPhysicalBracketLeft, kLogicalBracketRight, "]", + kNotSynthesized); clear_key_calls(); - tester.InjectPendingEvents(0); // No WM_DEADCHAR messages sent here. + tester.InjectPendingEvents(0); // No WM_DEADCHAR messages sent here. EXPECT_EQ(key_calls.size(), 0); clear_key_calls(); // Release ^¨ tester.InjectMessages( - 1, - WmKeyUpInfo{0xDD, kScanCodeBracketLeft, kNotExtended}.Build( - kWmResultZero)); + 1, WmKeyUpInfo{0xDD, kScanCodeBracketLeft, kNotExtended}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalBracketLeft, - kLogicalBracketRight, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, + kPhysicalBracketLeft, kLogicalBracketRight, "", + kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); @@ -886,7 +891,8 @@ TEST(KeyboardTest, DeadKeyThatCombines) { kLogicalKeyE, "ê", kNotSynthesized); clear_key_calls(); - tester.InjectPendingEvents(0xEA); // The redispatched event uses unmodified 'e' + tester.InjectPendingEvents( + 0xEA); // The redispatched event uses unmodified 'e' EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_TEXT(key_calls[0], u"ê"); clear_key_calls(); @@ -922,23 +928,24 @@ TEST(KeyboardTest, DeadKeyThatDoesNotCombine) { kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalBracketLeft, - kLogicalBracketRight, "]", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, + kPhysicalBracketLeft, kLogicalBracketRight, "]", + kNotSynthesized); clear_key_calls(); - tester.InjectPendingEvents(0); // No WM_DEADCHAR messages sent here. + tester.InjectPendingEvents(0); // No WM_DEADCHAR messages sent here. EXPECT_EQ(key_calls.size(), 0); clear_key_calls(); // Release ^¨ tester.InjectMessages( - 1, - WmKeyUpInfo{0xDD, kScanCodeBracketLeft, kNotExtended}.Build( - kWmResultZero)); + 1, WmKeyUpInfo{0xDD, kScanCodeBracketLeft, kNotExtended}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); - EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalBracketLeft, - kLogicalBracketRight, "", kNotSynthesized); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, + kPhysicalBracketLeft, kLogicalBracketRight, "", + kNotSynthesized); clear_key_calls(); tester.InjectPendingEvents(); @@ -948,8 +955,8 @@ TEST(KeyboardTest, DeadKeyThatDoesNotCombine) { // Press 1 tester.InjectMessages( 3, - WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp}.Build( - kWmResultZero), + WmKeyDownInfo{kVirtualDigit1, kScanCodeDigit1, kNotExtended, kWasUp} + .Build(kWmResultZero), WmCharInfo{'^', kScanCodeDigit1, kNotExtended, kWasUp}.Build( kWmResultZero), WmCharInfo{'&', kScanCodeDigit1, kNotExtended, kWasUp}.Build( @@ -1008,8 +1015,8 @@ TEST(KeyboardTest, MultibyteCharacter) { printf("0x%02x ", *st); st++; } - printf(".\n");fflush(stdout); - + printf(".\n"); + fflush(stdout); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyW, @@ -1020,9 +1027,8 @@ TEST(KeyboardTest, MultibyteCharacter) { tester.InjectPendingEvents(0xd800); // Manually inject the redispatched low surrogate. tester.InjectMessages( - 1, - WmCharInfo{0xdf45, kScanCodeKeyW, kNotExtended, kWasUp}.Build( - kWmResultZero)); + 1, WmCharInfo{0xdf45, kScanCodeKeyW, kNotExtended, kWasUp}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_TEXT(key_calls[0], u"𐍅"); @@ -1030,9 +1036,8 @@ TEST(KeyboardTest, MultibyteCharacter) { // Release W tester.InjectMessages( - 1, - WmKeyUpInfo{kVirtualKeyW, kScanCodeKeyW, kNotExtended}.Build( - kWmResultZero)); + 1, WmKeyUpInfo{kVirtualKeyW, kScanCodeKeyW, kNotExtended}.Build( + kWmResultZero)); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeUp, kPhysicalKeyW, diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index 46ca78f133246..93951b3c206e3 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -31,8 +31,8 @@ std::string _print_character(const char* s) { #define _RETURN_IF_NOT_EQUALS(val1, val2) \ if ((val1) != (val2)) { \ return ::testing::AssertionFailure() \ - << "Expected equality of these values:\n " #val1 "\n " \ - << val2 << "\n Actual: \n " << val1; \ + << "Expected equality of these values:\n " #val1 "\n " << val2 \ + << "\n Actual: \n " << val1; \ } ::testing::AssertionResult _EventEquals(const char* expr_event, diff --git a/shell/platform/windows/testing/wm_builders.cc b/shell/platform/windows/testing/wm_builders.cc index ce429560a1064..0c13f87149f46 100644 --- a/shell/platform/windows/testing/wm_builders.cc +++ b/shell/platform/windows/testing/wm_builders.cc @@ -47,8 +47,7 @@ Win32Message WmCharInfo::Build(LRESULT expected_result, HWND hWnd) { Win32Message WmSysKeyUpInfo::Build(LRESULT expected_result, HWND hWnd) { uint32_t lParam = (1 /* repeat_count */ << 0) | (scan_code << 16) | (extended << 24) | (context << 29) | - (1 /* prev_state */ << 30) | - (1 /* transition */ << 31); + (1 /* prev_state */ << 30) | (1 /* transition */ << 31); return Win32Message{ .message = WM_SYSKEYUP, .wParam = key, diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 537875179da4e..88c114fd3d8d9 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -369,10 +369,13 @@ WindowWin32::HandleMessage(UINT const message, 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 + // See + // https://docs.microsoft.com/en-us/windows/win32/learnwin32/accelerator-tables const char32_t event_character = - (message == WM_DEADCHAR || message == WM_SYSDEADCHAR) ? MapVirtualKey(keycode_for_char_message_, MAPVK_VK_TO_CHAR) : - IsPrintable(code_point) ? code_point : 0; + (message == WM_DEADCHAR || message == WM_SYSDEADCHAR) + ? MapVirtualKey(keycode_for_char_message_, MAPVK_VK_TO_CHAR) + : IsPrintable(code_point) ? code_point + : 0; bool handled = OnKey(keycode_for_char_message_, scancode, WM_KEYDOWN, event_character, extended, was_down); keycode_for_char_message_ = 0; From 91d1c8ec18b1c20a83d98e18f4fd731e8748ac53 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 06:06:43 -0700 Subject: [PATCH 19/30] License --- ci/licenses_golden/licenses_flutter | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 9a1fc038bebc0..95918a7ac6216 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1656,6 +1656,7 @@ 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/platform_handler.cc FILE: ../../../flutter/shell/platform/windows/platform_handler.h FILE: ../../../flutter/shell/platform/windows/platform_handler_unittests.cc From f67ed797f296ad87383f1e8d55b9c6a79cb8a3ff Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 16:46:57 -0700 Subject: [PATCH 20/30] Revert key.dart --- lib/ui/key.dart | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/ui/key.dart b/lib/ui/key.dart index ac6b8b7755a90..50e07703fb977 100644 --- a/lib/ui/key.dart +++ b/lib/ui/key.dart @@ -149,17 +149,9 @@ class KeyData { } } - String? _quotedCharCode() { - if (character == null) - return ''; - final Iterable hexChars = character!.codeUnits - .map((int code) => code.toRadixString(16).padLeft(2, '0')); - return '0x${hexChars.join(' ')}'; - } - @override String toString() => 'KeyData(key ${_typeToString(type)}, physical: 0x${physical.toRadixString(16)}, ' - 'logical: ${_logicalToString()}, character: ${_escapeCharacter()}${_quotedCharCode()}${synthesized ? ', synthesized' : ''})'; + 'logical: ${_logicalToString()}, character: ${_escapeCharacter()})'; /// Returns a complete textual description of the information in this object. String toStringFull() { From a80350d8968b65a272b708487f51372f08eb38c0 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 17:45:25 -0700 Subject: [PATCH 21/30] Move CreateKeyEventLparam --- shell/platform/windows/BUILD.gn | 1 + .../windows/flutter_window_win32_unittests.cc | 12 ------- .../platform/windows/testing/test_keyboard.cc | 11 ++++++ .../platform/windows/testing/test_keyboard.h | 23 ++++++++++--- .../testing/test_keyboard_unittests.cc | 34 +++++++++++++++++++ .../windows/window_win32_unittests.cc | 24 +++---------- 6 files changed, 68 insertions(+), 37 deletions(-) create mode 100644 shell/platform/windows/testing/test_keyboard_unittests.cc diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 8354a7d858b0d..96085f1fd5944 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -250,6 +250,7 @@ executable("flutter_windows_unittests") { "testing/mock_window_binding_handler.h", "testing/mock_window_win32.cc", "testing/mock_window_win32.h", + "testing/test_keyboard_unittests.cc", "testing/test_keyboard.cc", "testing/test_keyboard.h", "testing/wm_builders.cc", diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index 2df9f20c4ff27..48e8bbc5c8b96 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -28,18 +28,6 @@ namespace flutter { namespace testing { namespace { -// Creates a valid Windows LPARAM for WM_KEYDOWN and WM_CHAR from parameters -// given. -static LPARAM CreateKeyEventLparam(USHORT scancode, - bool extended, - bool was_down, - USHORT repeat_count = 1, - bool context_code = 0, - bool transition_state = 1) { - return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | - (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | - (LPARAM(scancode) << 16) | LPARAM(repeat_count)); -} // A key event handler that can be spied on while it forwards calls to the real // key event handler. diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index 93951b3c206e3..c42d82342e797 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -54,5 +54,16 @@ ::testing::AssertionResult _EventEquals(const char* expr_event, return ::testing::AssertionSuccess(); } +LPARAM CreateKeyEventLparam(USHORT scancode, + bool extended, + bool was_down, + USHORT repeat_count, + bool context_code, + bool transition_state) { + return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | + (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | + (LPARAM(scancode) << 16) | LPARAM(repeat_count)); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/test_keyboard.h b/shell/platform/windows/testing/test_keyboard.h index 43695271cbf6e..5f2539dccf17e 100644 --- a/shell/platform/windows/testing/test_keyboard.h +++ b/shell/platform/windows/testing/test_keyboard.h @@ -5,7 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_TEST_KEYBOARD_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_TEST_KEYBOARD_H_ -#include +#include #include @@ -16,20 +16,33 @@ namespace flutter { namespace testing { +::testing::AssertionResult _EventEquals(const char* expr_event, + const char* expr_expected, + const FlutterKeyEvent& event, + const FlutterKeyEvent& expected); + // Clone string onto the heap. // // If #string is nullptr, returns nullptr. Otherwise, the returned pointer must // be freed with delete[]. char* clone_string(const char* string); -::testing::AssertionResult _EventEquals(const char* expr_event, - const char* expr_expected, - const FlutterKeyEvent& event, - const FlutterKeyEvent& expected); +// Creates a valid Windows LPARAM for WM_KEYDOWN and WM_CHAR from parameters +// given. +// +// While |CreateKeyEventLparam| is flexible, it's recommended to use dedicated +// functions in wm_builders.h, such as |WmKeyDownInfo|. +LPARAM CreateKeyEventLparam(USHORT scancode, + bool extended, + bool was_down, + USHORT repeat_count = 1, + bool context_code = 0, + bool transition_state = 1); } // namespace testing } // namespace flutter +// Expect the |_target| FlutterKeyEvent has the required properties. #define EXPECT_EVENT_EQUALS(_target, _type, _physical, _logical, _character, \ _synthesized) \ EXPECT_PRED_FORMAT2(_EventEquals, _target, \ diff --git a/shell/platform/windows/testing/test_keyboard_unittests.cc b/shell/platform/windows/testing/test_keyboard_unittests.cc new file mode 100644 index 0000000000000..ed5458c31a2e3 --- /dev/null +++ b/shell/platform/windows/testing/test_keyboard_unittests.cc @@ -0,0 +1,34 @@ +// 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 "flutter/shell/platform/windows/testing/test_keyboard.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(TestKeyboard, CloneString) { + const char* str1 = "123"; + char* cloned_str1 = clone_string(str1); + EXPECT_STREQ(str1, cloned_str1); + EXPECT_NE(str1, cloned_str1); + delete[] cloned_str1; + + EXPECT_EQ(clone_string(nullptr), nullptr); +}; + +TEST(TestKeyboard, CreateKeyEventLparam) { + EXPECT_EQ( + CreateKeyEventLparam(0x1, true, true), + 0xC1010001); + + EXPECT_EQ( + CreateKeyEventLparam(0x05, false, false, 0, 1, 0), + 0x20050000); +}; + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/window_win32_unittests.cc b/shell/platform/windows/window_win32_unittests.cc index 9b2beba8592b1..58f9e027a87e7 100644 --- a/shell/platform/windows/window_win32_unittests.cc +++ b/shell/platform/windows/window_win32_unittests.cc @@ -9,22 +9,6 @@ using testing::_; namespace flutter { namespace testing { -namespace { - -// Creates a valid Windows LPARAM for WM_KEYDOWN and WM_KEYUP from parameters -// given. -static LPARAM CreateKeyEventLparam(USHORT scancode, - bool extended = false, - bool was_down = 1, - USHORT repeat_count = 1, - bool context_code = 0, - bool transition_state = 1) { - return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | - (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | - (LPARAM(scancode) << 16) | LPARAM(repeat_count)); -} - -} // namespace TEST(MockWin32Window, CreateDestroy) { MockWin32Window window; @@ -58,7 +42,7 @@ TEST(MockWin32Window, HorizontalScroll) { TEST(MockWin32Window, KeyDown) { MockWin32Window window; EXPECT_CALL(window, OnKey(_, _, _, _, _, _)).Times(1); - LPARAM lparam = CreateKeyEventLparam(42); + LPARAM lparam = CreateKeyEventLparam(42, false, false); // send a "Shift" key down event. window.InjectWindowMessage(WM_KEYDOWN, 16, lparam); } @@ -66,7 +50,7 @@ TEST(MockWin32Window, KeyDown) { TEST(MockWin32Window, KeyUp) { MockWin32Window window; EXPECT_CALL(window, OnKey(_, _, _, _, _, _)).Times(1); - LPARAM lparam = CreateKeyEventLparam(42); + LPARAM lparam = CreateKeyEventLparam(42, false, true); // send a "Shift" key up event. window.InjectWindowMessage(WM_KEYUP, 16, lparam); } @@ -91,11 +75,11 @@ TEST(MockWin32Window, KeyDownWithCtrl) { keyboard_state[VK_CONTROL] = -1; SetKeyboardState(keyboard_state); - LPARAM lparam = CreateKeyEventLparam(30); + LPARAM lparam = CreateKeyEventLparam(30, false, false); // Expect OnKey, but not OnText, because Control + Key is not followed by // WM_CHAR - EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, true)).Times(1); + EXPECT_CALL(window, OnKey(65, 30, WM_KEYDOWN, 0, false, false)).Times(1); EXPECT_CALL(window, OnText(_)).Times(0); window.InjectWindowMessage(WM_KEYDOWN, 65, lparam); From c2ab9c28fb787cd3005216500a04936dde81dc9e Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 18:16:32 -0700 Subject: [PATCH 22/30] MockEmbedderApiForKeyboard --- shell/platform/windows/keyboard_unittests.cc | 100 ++--------------- .../windows/testing/engine_modifier.h | 5 + .../platform/windows/testing/test_keyboard.cc | 101 ++++++++++++++++++ .../platform/windows/testing/test_keyboard.h | 8 ++ 4 files changed, 122 insertions(+), 92 deletions(-) diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 56ce4491e5faf..2fc01758c2386 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -2,10 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/platform/common/json_message_codec.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/test_utils/key_codes.h" -#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/windows/flutter_windows_engine.h" #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" #include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h" @@ -19,7 +17,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include #include #include @@ -204,15 +201,6 @@ class TestFlutterWindowsView : public FlutterWindowsView { TestKeystate key_state_; }; -// A struct to use as a FlutterPlatformMessageResponseHandle so it can keep the -// callbacks and user data passed to the engine's -// PlatformMessageCreateResponseHandle for use in the SendPlatformMessage -// overridden function. -struct TestResponseHandle { - FlutterDesktopBinaryReply callback; - void* user_data; -}; - typedef enum { kKeyCallOnKey, kKeyCallOnText, @@ -291,14 +279,6 @@ class KeyboardTester { bool KeyboardTester::test_response = false; -std::unique_ptr> keyHandlingResponse(bool handled) { - rapidjson::Document document; - auto& allocator = document.GetAllocator(); - document.SetObject(); - document.AddMember("handled", KeyboardTester::test_response, allocator); - return flutter::JsonMessageCodec::GetInstance().EncodeMessage(document); -} - // Returns an engine instance configured with dummy project path values, and // overridden methods for sending platform messages, so that the engine can // respond as if the framework were connected. @@ -312,32 +292,11 @@ std::unique_ptr GetTestEngine() { EngineModifier modifier(engine.get()); - // This mock handles channel messages. - modifier.embedder_api().SendPlatformMessage = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterPlatformMessage* message) { - if (std::string(message->channel) == std::string("flutter/settings")) { - return kSuccess; - } - if (std::string(message->channel) == std::string("flutter/keyevent")) { - auto response = keyHandlingResponse(true); - const TestResponseHandle* response_handle = - reinterpret_cast( - message->response_handle); - if (response_handle->callback != nullptr) { - response_handle->callback(response->data(), response->size(), - response_handle->user_data); - } - return kSuccess; - } - return kSuccess; - }; - - // This mock handles key events sent through the embedder API, - // and records it in `key_calls`. - modifier.embedder_api().SendKeyEvent = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, void* user_data) { + MockEmbedderApiForKeyboard( + modifier, + [] {return KeyboardTester::test_response;}, + [] (const FlutterKeyEvent* event) { + FlutterKeyEvent clone_event = *event; clone_event.character = event->character == nullptr ? nullptr @@ -346,52 +305,9 @@ std::unique_ptr GetTestEngine() { .type = kKeyCallOnKey, .key_event = clone_event, }); - if (callback != nullptr) { - callback(KeyboardTester::test_response, user_data); - } - return kSuccess; - }; - - // The following mocks enable channel mocking. - modifier.embedder_api().PlatformMessageCreateResponseHandle = - [](auto engine, auto data_callback, auto user_data, auto response_out) { - TestResponseHandle* response_handle = new TestResponseHandle(); - response_handle->user_data = user_data; - response_handle->callback = data_callback; - *response_out = reinterpret_cast( - response_handle); - return kSuccess; - }; - - modifier.embedder_api().PlatformMessageReleaseResponseHandle = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, - FlutterPlatformMessageResponseHandle* response) { - const TestResponseHandle* response_handle = - reinterpret_cast(response); - delete response_handle; - return kSuccess; - }; - - // The following mocks allows RunWithEntrypoint to be run, which creates a - // non-empty FlutterEngine and enables SendKeyEvent. - - modifier.embedder_api().Run = - [](size_t version, const FlutterRendererConfig* config, - const FlutterProjectArgs* args, void* user_data, - FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) { - *engine_out = reinterpret_cast(1); - - return kSuccess; - }; - modifier.embedder_api().UpdateLocales = - [](auto engine, const FlutterLocale** locales, size_t locales_count) { - return kSuccess; - }; - modifier.embedder_api().SendWindowMetricsEvent = - [](auto engine, const FlutterWindowMetricsEvent* event) { - return kSuccess; - }; - modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; }; + return KeyboardTester::test_response; + } + ); engine->RunWithEntrypoint(nullptr); return engine; diff --git a/shell/platform/windows/testing/engine_modifier.h b/shell/platform/windows/testing/engine_modifier.h index 245ad4474d0a5..1b42ed5a723db 100644 --- a/shell/platform/windows/testing/engine_modifier.h +++ b/shell/platform/windows/testing/engine_modifier.h @@ -2,6 +2,9 @@ // 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_TESTING_ENGINE_MODIFIER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_ENGINE_MODIFIER_H_ + #include "flutter/shell/platform/windows/flutter_windows_engine.h" namespace flutter { @@ -46,3 +49,5 @@ class EngineModifier { }; } // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_ENGINE_MODIFIER_H_ diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index c42d82342e797..17452cb23214d 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -2,8 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/shell/platform/common/json_message_codec.h" +#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" +#include #include namespace flutter { @@ -26,6 +29,23 @@ std::string _print_character(const char* s) { } return std::string("\"") + s + "\""; } + +std::unique_ptr> _keyHandlingResponse(bool handled) { + rapidjson::Document document; + auto& allocator = document.GetAllocator(); + document.SetObject(); + document.AddMember("handled", handled, allocator); + return flutter::JsonMessageCodec::GetInstance().EncodeMessage(document); +} + +// A struct to use as a FlutterPlatformMessageResponseHandle so it can keep the +// callbacks and user data passed to the engine's +// PlatformMessageCreateResponseHandle for use in the SendPlatformMessage +// overridden function. +struct TestResponseHandle { + FlutterDesktopBinaryReply callback; + void* user_data; +}; } // namespace #define _RETURN_IF_NOT_EQUALS(val1, val2) \ @@ -65,5 +85,86 @@ LPARAM CreateKeyEventLparam(USHORT scancode, (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } +void MockEmbedderApiForKeyboard(EngineModifier& modifier, + MockKeyEventChannelHandler channel_handler, + MockKeyEventEmbedderHandler embedder_handler) { + // This mock handles channel messages. + modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC( + SendPlatformMessage, + [channel_handler](FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPlatformMessage* message) { + if (std::string(message->channel) == std::string("flutter/settings")) { + return kSuccess; + } + if (std::string(message->channel) == std::string("flutter/keyevent")) { + bool result = channel_handler(); + auto response = _keyHandlingResponse(result); + const TestResponseHandle* response_handle = + reinterpret_cast( + message->response_handle); + if (response_handle->callback != nullptr) { + response_handle->callback(response->data(), response->size(), + response_handle->user_data); + } + return kSuccess; + } + return kSuccess; + }); + + // This mock handles key events sent through the embedder API, + // and records it in `key_calls`. + modifier.embedder_api().SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + [embedder_handler](FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + bool result = embedder_handler(event); + if (callback != nullptr) { + callback(result, user_data); + } + return kSuccess; + }); + + // The following mocks enable channel mocking. + modifier.embedder_api().PlatformMessageCreateResponseHandle = + [](auto engine, auto data_callback, auto user_data, auto response_out) { + TestResponseHandle* response_handle = new TestResponseHandle(); + response_handle->user_data = user_data; + response_handle->callback = data_callback; + *response_out = reinterpret_cast( + response_handle); + return kSuccess; + }; + + modifier.embedder_api().PlatformMessageReleaseResponseHandle = + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, + FlutterPlatformMessageResponseHandle* response) { + const TestResponseHandle* response_handle = + reinterpret_cast(response); + delete response_handle; + return kSuccess; + }; + + // The following mocks allows RunWithEntrypoint to be run, which creates a + // non-empty FlutterEngine and enables SendKeyEvent. + + modifier.embedder_api().Run = + [](size_t version, const FlutterRendererConfig* config, + const FlutterProjectArgs* args, void* user_data, + FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) { + *engine_out = reinterpret_cast(1); + + return kSuccess; + }; + modifier.embedder_api().UpdateLocales = + [](auto engine, const FlutterLocale** locales, size_t locales_count) { + return kSuccess; + }; + modifier.embedder_api().SendWindowMetricsEvent = + [](auto engine, const FlutterWindowMetricsEvent* event) { + return kSuccess; + }; + modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; }; +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/test_keyboard.h b/shell/platform/windows/testing/test_keyboard.h index 5f2539dccf17e..a7b296a35afaf 100644 --- a/shell/platform/windows/testing/test_keyboard.h +++ b/shell/platform/windows/testing/test_keyboard.h @@ -10,6 +10,7 @@ #include #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/testing/engine_modifier.h" #include "gtest/gtest.h" @@ -39,6 +40,13 @@ LPARAM CreateKeyEventLparam(USHORT scancode, bool context_code = 0, bool transition_state = 1); +typedef bool MockKeyEventChannelHandler(); +typedef bool MockKeyEventEmbedderHandler(const FlutterKeyEvent* event); + +void MockEmbedderApiForKeyboard(EngineModifier& modifier, + MockKeyEventChannelHandler channel_handler, + MockKeyEventEmbedderHandler embedder_handler); + } // namespace testing } // namespace flutter From 7674ac7438331bfa019450320d629c578dfb7ff0 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 19:27:37 -0700 Subject: [PATCH 23/30] More MockEmbedderApiForKeyboard --- .../windows/flutter_window_win32_unittests.cc | 55 ++---------- .../windows/flutter_windows_view_unittests.cc | 85 +++---------------- shell/platform/windows/keyboard_unittests.cc | 1 - .../platform/windows/testing/test_keyboard.cc | 51 +++++------ .../platform/windows/testing/test_keyboard.h | 5 +- 5 files changed, 46 insertions(+), 151 deletions(-) diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index 48e8bbc5c8b96..b2724ee55037b 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -12,6 +12,7 @@ #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/mock_window_win32.h" +#include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" @@ -280,15 +281,6 @@ class TestFlutterWindowsView : public FlutterWindowsView { bool is_printable_; }; -// A struct to use as a FlutterPlatformMessageResponseHandle so it can keep the -// callbacks and user data passed to the engine's -// PlatformMessageCreateResponseHandle for use in the SendPlatformMessage -// overridden function. -struct TestResponseHandle { - FlutterDesktopBinaryReply callback; - void* user_data; -}; - // The static value to return as the "handled" value from the framework for key // events. Individual tests set this to change the framework response that the // test engine simulates. @@ -306,46 +298,11 @@ std::unique_ptr GetTestEngine() { auto engine = std::make_unique(project); EngineModifier modifier(engine.get()); - // Force the non-AOT path unless overridden by the test. - modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; }; - - modifier.embedder_api().PlatformMessageCreateResponseHandle = - [](auto engine, auto data_callback, auto user_data, auto response_out) { - TestResponseHandle* response_handle = new TestResponseHandle(); - response_handle->user_data = user_data; - response_handle->callback = data_callback; - *response_out = reinterpret_cast( - response_handle); - return kSuccess; - }; - - modifier.embedder_api().SendPlatformMessage = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterPlatformMessage* message) { - rapidjson::Document document; - auto& allocator = document.GetAllocator(); - document.SetObject(); - document.AddMember("handled", test_response, allocator); - auto encoded = - flutter::JsonMessageCodec::GetInstance().EncodeMessage(document); - const TestResponseHandle* response_handle = - reinterpret_cast( - message->response_handle); - if (response_handle->callback != nullptr) { - response_handle->callback(encoded->data(), encoded->size(), - response_handle->user_data); - } - return kSuccess; - }; - - modifier.embedder_api().PlatformMessageReleaseResponseHandle = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, - FlutterPlatformMessageResponseHandle* response) { - const TestResponseHandle* response_handle = - reinterpret_cast(response); - delete response_handle; - return kSuccess; - }; + MockEmbedderApiForKeyboard( + modifier, + [] {return test_response;}, + [] (const FlutterKeyEvent* event) {return false; } + ); return engine; } diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc index fc57d9bc1f542..331ede8960976 100644 --- a/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/shell/platform/windows/flutter_windows_view_unittests.cc @@ -12,6 +12,7 @@ #include "flutter/shell/platform/windows/flutter_windows_view.h" #include "flutter/shell/platform/windows/testing/engine_modifier.h" #include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" +#include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -59,82 +60,16 @@ std::unique_ptr GetTestEngine() { auto engine = std::make_unique(project); EngineModifier modifier(engine.get()); - - // This mock handles channel messages. This mock handles key events sent - // through the message channel is recorded in `key_event_logs`. - modifier.embedder_api().SendPlatformMessage = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterPlatformMessage* message) { - if (std::string(message->channel) == std::string("flutter/settings")) { - return kSuccess; - } - if (std::string(message->channel) == std::string("flutter/keyevent")) { - key_event_logs.push_back(kKeyEventFromChannel); - auto response = keyHandlingResponse(true); - const TestResponseHandle* response_handle = - reinterpret_cast( - message->response_handle); - if (response_handle->callback != nullptr) { - response_handle->callback(response->data(), response->size(), - response_handle->user_data); - } - return kSuccess; - } - return kSuccess; - }; - - // This mock handles key events sent through the embedder API, - // and records it in `key_event_logs`. - modifier.embedder_api().SendKeyEvent = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, void* user_data) { + MockEmbedderApiForKeyboard( + modifier, + [] { + key_event_logs.push_back(kKeyEventFromChannel); + return test_response; + }, + [](const FlutterKeyEvent* event) { key_event_logs.push_back(kKeyEventFromEmbedder); - if (callback != nullptr) { - callback(test_response, user_data); - } - return kSuccess; - }; - - // The following mocks enable channel mocking. - modifier.embedder_api().PlatformMessageCreateResponseHandle = - [](auto engine, auto data_callback, auto user_data, auto response_out) { - TestResponseHandle* response_handle = new TestResponseHandle(); - response_handle->user_data = user_data; - response_handle->callback = data_callback; - *response_out = reinterpret_cast( - response_handle); - return kSuccess; - }; - - modifier.embedder_api().PlatformMessageReleaseResponseHandle = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, - FlutterPlatformMessageResponseHandle* response) { - const TestResponseHandle* response_handle = - reinterpret_cast(response); - delete response_handle; - return kSuccess; - }; - - // The following mocks allows RunWithEntrypoint to be run, which creates a - // non-empty FlutterEngine and enables SendKeyEvent. - - modifier.embedder_api().Run = - [](size_t version, const FlutterRendererConfig* config, - const FlutterProjectArgs* args, void* user_data, - FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) { - *engine_out = reinterpret_cast(1); - - return kSuccess; - }; - modifier.embedder_api().UpdateLocales = - [](auto engine, const FlutterLocale** locales, size_t locales_count) { - return kSuccess; - }; - modifier.embedder_api().SendWindowMetricsEvent = - [](auto engine, const FlutterWindowMetricsEvent* event) { - return kSuccess; - }; - modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; }; + return test_response; + }); engine->RunWithEntrypoint(nullptr); return engine; diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 2fc01758c2386..af2b6c6eef6f5 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -296,7 +296,6 @@ std::unique_ptr GetTestEngine() { modifier, [] {return KeyboardTester::test_response;}, [] (const FlutterKeyEvent* event) { - FlutterKeyEvent clone_event = *event; clone_event.character = event->character == nullptr ? nullptr diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index 17452cb23214d..d1b7694637d2f 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -85,44 +85,47 @@ LPARAM CreateKeyEventLparam(USHORT scancode, (LPARAM(scancode) << 16) | LPARAM(repeat_count)); } +static MockKeyEventChannelHandler stored_channel_handler; +static MockKeyEventEmbedderHandler stored_embedder_handler; + void MockEmbedderApiForKeyboard(EngineModifier& modifier, MockKeyEventChannelHandler channel_handler, MockKeyEventEmbedderHandler embedder_handler) { + stored_channel_handler = channel_handler; + stored_embedder_handler = embedder_handler; // This mock handles channel messages. - modifier.embedder_api().SendPlatformMessage = MOCK_ENGINE_PROC( - SendPlatformMessage, - [channel_handler](FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterPlatformMessage* message) { - if (std::string(message->channel) == std::string("flutter/settings")) { - return kSuccess; - } - if (std::string(message->channel) == std::string("flutter/keyevent")) { - bool result = channel_handler(); - auto response = _keyHandlingResponse(result); - const TestResponseHandle* response_handle = - reinterpret_cast( - message->response_handle); - if (response_handle->callback != nullptr) { - response_handle->callback(response->data(), response->size(), - response_handle->user_data); - } - return kSuccess; + modifier.embedder_api().SendPlatformMessage = + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPlatformMessage* message) { + if (std::string(message->channel) == std::string("flutter/settings")) { + return kSuccess; + } + if (std::string(message->channel) == std::string("flutter/keyevent")) { + bool result = stored_channel_handler(); + auto response = _keyHandlingResponse(result); + const TestResponseHandle* response_handle = + reinterpret_cast( + message->response_handle); + if (response_handle->callback != nullptr) { + response_handle->callback(response->data(), response->size(), + response_handle->user_data); } return kSuccess; - }); + } + return kSuccess; + }; // This mock handles key events sent through the embedder API, // and records it in `key_calls`. - modifier.embedder_api().SendKeyEvent = MOCK_ENGINE_PROC( - SendKeyEvent, - [embedder_handler](FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterKeyEvent* event, + modifier.embedder_api().SendKeyEvent = + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, const FlutterKeyEvent* event, FlutterKeyEventCallback callback, void* user_data) { - bool result = embedder_handler(event); + bool result = stored_embedder_handler(event); if (callback != nullptr) { callback(result, user_data); } return kSuccess; - }); + }; // The following mocks enable channel mocking. modifier.embedder_api().PlatformMessageCreateResponseHandle = diff --git a/shell/platform/windows/testing/test_keyboard.h b/shell/platform/windows/testing/test_keyboard.h index a7b296a35afaf..658c0c440117f 100644 --- a/shell/platform/windows/testing/test_keyboard.h +++ b/shell/platform/windows/testing/test_keyboard.h @@ -7,6 +7,7 @@ #include +#include #include #include "flutter/shell/platform/embedder/embedder.h" @@ -40,8 +41,8 @@ LPARAM CreateKeyEventLparam(USHORT scancode, bool context_code = 0, bool transition_state = 1); -typedef bool MockKeyEventChannelHandler(); -typedef bool MockKeyEventEmbedderHandler(const FlutterKeyEvent* event); +typedef std::function MockKeyEventChannelHandler; +typedef std::function MockKeyEventEmbedderHandler; void MockEmbedderApiForKeyboard(EngineModifier& modifier, MockKeyEventChannelHandler channel_handler, From 1efa671d9861f807bfb1402598f46f5adc997395 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 19:32:56 -0700 Subject: [PATCH 24/30] Doc --- shell/platform/windows/testing/test_keyboard.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index d1b7694637d2f..d2cd6a443e949 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -88,6 +88,12 @@ LPARAM CreateKeyEventLparam(USHORT scancode, static MockKeyEventChannelHandler stored_channel_handler; static MockKeyEventEmbedderHandler stored_embedder_handler; +// Set EngineModifier, listen to event messages that go through the channel and +// the embedder API, while disabling other methods so that the engine can be +// run headlessly. +// +// The |channel_handler| and |embedder_handler| should return a boolean +// indicating whether the framework decides to handle the event. void MockEmbedderApiForKeyboard(EngineModifier& modifier, MockKeyEventChannelHandler channel_handler, MockKeyEventEmbedderHandler embedder_handler) { From 8a6bf1659233b20b45ea21477a7b2aefd79ed555 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 19:42:58 -0700 Subject: [PATCH 25/30] Move MockMessageQueue --- .../windows/flutter_window_win32_unittests.cc | 1 - shell/platform/windows/keyboard_unittests.cc | 1 - .../windows/testing/mock_window_win32.cc | 38 ------------------ .../windows/testing/mock_window_win32.h | 20 ---------- .../platform/windows/testing/test_keyboard.cc | 39 +++++++++++++++++++ .../platform/windows/testing/test_keyboard.h | 21 ++++++++++ 6 files changed, 60 insertions(+), 60 deletions(-) diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index b2724ee55037b..dc77157e068c6 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -11,7 +11,6 @@ #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/mock_window_win32.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index af2b6c6eef6f5..372d43c4999de 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -11,7 +11,6 @@ #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/mock_window_win32.h" #include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "gmock/gmock.h" diff --git a/shell/platform/windows/testing/mock_window_win32.cc b/shell/platform/windows/testing/mock_window_win32.cc index 371e504aa95c8..f6984ae1db8cb 100644 --- a/shell/platform/windows/testing/mock_window_win32.cc +++ b/shell/platform/windows/testing/mock_window_win32.cc @@ -9,44 +9,6 @@ namespace flutter { namespace testing { -void MockMessageQueue::InjectMessageList(int count, - const Win32Message* messages) { - for (int i = 0; i < count; i += 1) { - _pending_messages.push_back(messages[i]); - } - while (!_pending_messages.empty()) { - Win32Message message = _pending_messages.front(); - _pending_messages.pop_front(); - LRESULT result = Win32SendMessage(message.hWnd, message.message, - message.wParam, message.lParam); - if (message.expected_result != kWmResultDontCheck) { - EXPECT_EQ(result, message.expected_result); - } - } -} - -BOOL MockMessageQueue::Win32PeekMessage(LPMSG lpMsg, - HWND hWnd, - UINT wMsgFilterMin, - UINT wMsgFilterMax, - UINT wRemoveMsg) { - for (auto iter = _pending_messages.begin(); iter != _pending_messages.end(); - ++iter) { - if (iter->message >= wMsgFilterMin && iter->message <= wMsgFilterMax) { - *lpMsg = MSG{ - .message = iter->message, - .wParam = iter->wParam, - .lParam = iter->lParam, - }; - if ((wRemoveMsg & PM_REMOVE) == PM_REMOVE) { - _pending_messages.erase(iter); - } - return TRUE; - } - } - return FALSE; -} - MockWin32Window::MockWin32Window() : WindowWin32(){}; MockWin32Window::~MockWin32Window() = default; diff --git a/shell/platform/windows/testing/mock_window_win32.h b/shell/platform/windows/testing/mock_window_win32.h index 86d42741c9e1e..700ab6c108da2 100644 --- a/shell/platform/windows/testing/mock_window_win32.h +++ b/shell/platform/windows/testing/mock_window_win32.h @@ -16,26 +16,6 @@ namespace flutter { namespace testing { -class MockMessageQueue { - public: - // Simulates a WindowProc message from the OS. - void InjectMessageList(int count, const Win32Message* messages); - - BOOL Win32PeekMessage(LPMSG lpMsg, - HWND hWnd, - UINT wMsgFilterMin, - UINT wMsgFilterMax, - UINT wRemoveMsg); - - protected: - virtual LRESULT Win32SendMessage(HWND hWnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) = 0; - - std::list _pending_messages; -}; - /// Mock for the |WindowWin32| base class. class MockWin32Window : public WindowWin32, public MockMessageQueue { public: diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index d2cd6a443e949..9b65dbc75a1f6 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -175,5 +175,44 @@ void MockEmbedderApiForKeyboard(EngineModifier& modifier, modifier.embedder_api().Shutdown = [](auto engine) { return kSuccess; }; } +void MockMessageQueue::InjectMessageList(int count, + const Win32Message* messages) { + for (int i = 0; i < count; i += 1) { + _pending_messages.push_back(messages[i]); + } + while (!_pending_messages.empty()) { + Win32Message message = _pending_messages.front(); + _pending_messages.pop_front(); + LRESULT result = Win32SendMessage(message.hWnd, message.message, + message.wParam, message.lParam); + if (message.expected_result != kWmResultDontCheck) { + EXPECT_EQ(result, message.expected_result); + } + } +} + +BOOL MockMessageQueue::Win32PeekMessage(LPMSG lpMsg, + HWND hWnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg) { + for (auto iter = _pending_messages.begin(); iter != _pending_messages.end(); + ++iter) { + if (iter->message >= wMsgFilterMin && iter->message <= wMsgFilterMax) { + *lpMsg = MSG{ + .message = iter->message, + .wParam = iter->wParam, + .lParam = iter->lParam, + }; + if ((wRemoveMsg & PM_REMOVE) == PM_REMOVE) { + _pending_messages.erase(iter); + } + return TRUE; + } + } + return FALSE; +} + + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/test_keyboard.h b/shell/platform/windows/testing/test_keyboard.h index 658c0c440117f..47e40281a0f9d 100644 --- a/shell/platform/windows/testing/test_keyboard.h +++ b/shell/platform/windows/testing/test_keyboard.h @@ -12,6 +12,7 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/windows/testing/engine_modifier.h" +#include "flutter/shell/platform/windows/testing/wm_builders.h" #include "gtest/gtest.h" @@ -48,6 +49,26 @@ void MockEmbedderApiForKeyboard(EngineModifier& modifier, MockKeyEventChannelHandler channel_handler, MockKeyEventEmbedderHandler embedder_handler); +class MockMessageQueue { + public: + // Simulates a WindowProc message from the OS. + void InjectMessageList(int count, const Win32Message* messages); + + BOOL Win32PeekMessage(LPMSG lpMsg, + HWND hWnd, + UINT wMsgFilterMin, + UINT wMsgFilterMax, + UINT wRemoveMsg); + + protected: + virtual LRESULT Win32SendMessage(HWND hWnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) = 0; + + std::list _pending_messages; +}; + } // namespace testing } // namespace flutter From d673a0bbb1306e2c6c71e9dc7884784230bc4c7d Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 20:03:45 -0700 Subject: [PATCH 26/30] is_deadchar doesnt seem needed --- shell/platform/windows/flutter_windows_view.h | 5 +++++ shell/platform/windows/keyboard_key_handler.cc | 7 ++----- shell/platform/windows/keyboard_unittests.cc | 7 ------- shell/platform/windows/testing/test_keyboard.h | 10 +++++++++- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index f3432c0575062..4a48499686b2e 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -146,6 +146,11 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, protected: // Called to create the keyboard hook handlers. + // + // The provided |dispatch_event| is where to inject events into the system, + // while |get_key_state| is where to acquire keyboard states. They will be + // the system APIs in production classes, but might be replaced with mock + // functions in unit tests. virtual void RegisterKeyboardHandlers( flutter::BinaryMessenger* messenger, flutter::KeyboardKeyHandler::EventDispatcher dispatch_event, diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index c62fe6e17338b..d5bb4a0d83ecf 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -220,10 +220,8 @@ bool KeyboardKeyHandler::KeyboardHook(FlutterWindowsView* view, } pending_responds_.push_back(std::move(incoming)); - const bool is_deadchar = (character & 0x80000000) != 0; - const char32_t event_character = is_deadchar ? 0 : character; for (const auto& delegate : delegates_) { - delegate->KeyboardHook(key, scancode, action, event_character, extended, + delegate->KeyboardHook(key, scancode, action, character, extended, was_down, [sequence_id, this](bool handled) { ResolvePendingEvent(sequence_id, handled); }); @@ -263,8 +261,7 @@ void KeyboardKeyHandler::ResolvePendingEvent(uint64_t sequence_id, if (event.unreplied == 0) { std::unique_ptr event_ptr = std::move(*iter); pending_responds_.erase(iter); - const bool is_deadchar = (event_ptr->character & 0x80000000) != 0; - const bool should_redispatch = !event_ptr->any_handled && !is_deadchar; + const bool should_redispatch = !event_ptr->any_handled; if (should_redispatch) { RedispatchEvent(std::move(event_ptr)); } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 372d43c4999de..aa7b941cdbf3b 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -924,13 +924,6 @@ TEST(KeyboardTest, MultibyteCharacter) { kWmResultZero)); const char* st = key_calls[0].key_event.character; - printf("CH: "); - while (*st != 0) { - printf("0x%02x ", *st); - st++; - } - printf(".\n"); - fflush(stdout); EXPECT_EQ(key_calls.size(), 1); EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeDown, kPhysicalKeyW, diff --git a/shell/platform/windows/testing/test_keyboard.h b/shell/platform/windows/testing/test_keyboard.h index 47e40281a0f9d..4e7e38231b128 100644 --- a/shell/platform/windows/testing/test_keyboard.h +++ b/shell/platform/windows/testing/test_keyboard.h @@ -49,11 +49,19 @@ void MockEmbedderApiForKeyboard(EngineModifier& modifier, MockKeyEventChannelHandler channel_handler, MockKeyEventEmbedderHandler embedder_handler); +// Simulate a message queue for WM messages. +// +// Subclasses must implement |Win32SendMessage| for how dispatched messages are +// processed. class MockMessageQueue { public: - // Simulates a WindowProc message from the OS. + // Push a list of messages to the message queue, then dispatch + // them with |Win32SendMessage| one by one. void InjectMessageList(int count, const Win32Message* messages); + // Peak the next message in the message queue. + // + // See Win32's |PeekMessage| for documentation. BOOL Win32PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, From 8d84ec0e5bb62bd458742bc1da4b0ff364ba88f7 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 20:27:23 -0700 Subject: [PATCH 27/30] ConvertChar32ToUtf8 --- .../windows/keyboard_key_embedder_handler.cc | 44 +++++++++++++------ .../windows/keyboard_key_embedder_handler.h | 2 +- ...keyboard_key_embedder_handler_unittests.cc | 26 +++++++++++ 3 files changed, 58 insertions(+), 14 deletions(-) diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index 5db8d1a7f0189..2d8349621d650 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -29,6 +29,35 @@ constexpr SHORT kStateMaskPressed = 0x80; const char* empty_character = ""; } // namespace +// Get some bits of the char, from the start'th bit from the right (excluded) +// to the end'th bit from the right (included). +// +// For example, _GetBit(0x1234, 8, 4) => 0x3. +char _GetBit(char32_t ch, size_t start, size_t end) { + return (ch >> end) & ((1 << (start - end)) - 1); +} + +std::string ConvertChar32ToUtf8(char32_t ch) { + std::string result; + assert(0 <= ch && ch <= 0x10FFFF); + if (ch <= 0x007F) { + result.push_back(ch); + } else if (ch <= 0x07FF) { + result.push_back(0b11000000 + _GetBit(ch, 11, 6)); + result.push_back(0b10000000 + _GetBit(ch, 6, 0)); + } else if (ch <= 0xFFFF) { + result.push_back(0b11100000 + _GetBit(ch, 16, 12)); + result.push_back(0b10000000 + _GetBit(ch, 12, 6)); + result.push_back(0b10000000 + _GetBit(ch, 6, 0)); + } else { + result.push_back(0b11110000 + _GetBit(ch, 21, 18)); + result.push_back(0b10000000 + _GetBit(ch, 18, 12)); + result.push_back(0b10000000 + _GetBit(ch, 12, 6)); + result.push_back(0b10000000 + _GetBit(ch, 6, 0)); + } + return result; +} + KeyboardKeyEmbedderHandler::KeyboardKeyEmbedderHandler( SendEvent send_event, GetKeyStateHandler get_key_state) @@ -388,19 +417,8 @@ void KeyboardKeyEmbedderHandler::ConvertUtf32ToUtf8_(char* out, char32_t ch) { out[0] = '\0'; return; } - assert(!(0xD800 <= ch && ch <= 0xDFFF)); - std::wstring text; - // Encode char32 into UTF-16. See https://en.wikipedia.org/wiki/UTF-16. - if ((0x0000 <= ch && ch <= 0xD7FF) || (0xE000 <= ch && ch <= 0xFFFF)) { - text.push_back(static_cast(ch)); - } else { // 0x10000 <= ch && ch <= 0x10FFFF - const uint32_t offset = ch - 0x10000; - const uint16_t high_offset = (offset >> 10) & 0x3FF; - const uint16_t low_offset = offset & 0x3FF; - text.push_back(static_cast(high_offset + 0xD800)); - text.push_back(static_cast(low_offset + 0xDC00)); - } - strcpy_s(out, kCharacterCacheSize, Utf8FromUtf16(text).c_str()); + std::string result = ConvertChar32ToUtf8(ch); + strcpy_s(out, kCharacterCacheSize, result.c_str()); } FlutterKeyEvent KeyboardKeyEmbedderHandler::CreateEmptyEvent() { diff --git a/shell/platform/windows/keyboard_key_embedder_handler.h b/shell/platform/windows/keyboard_key_embedder_handler.h index 04cf500178d9c..7ee88a33e626d 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.h +++ b/shell/platform/windows/keyboard_key_embedder_handler.h @@ -15,7 +15,7 @@ namespace flutter { -namespace {} // namespace +std::string ConvertChar32ToUtf8(char32_t ch); // A delegate of |KeyboardKeyHandler| that handles events by sending // converted |FlutterKeyEvent|s through the embedder API. diff --git a/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc b/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc index c6f405faf6edd..4012f0435ef89 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc @@ -83,6 +83,32 @@ constexpr uint64_t kVirtualKeyA = 0x41; using namespace ::flutter::testing::keycodes; } // namespace +TEST(KeyboardKeyEmbedderHandlerTest, ConvertChar32ToUtf8) { + std::string result; + + result = ConvertChar32ToUtf8(0x0024); + EXPECT_EQ(result.length(), 1); + EXPECT_EQ(result[0], '\x24'); + + result = ConvertChar32ToUtf8(0x00A2); + EXPECT_EQ(result.length(), 2); + EXPECT_EQ(result[0], '\xC2'); + EXPECT_EQ(result[1], '\xA2'); + + result = ConvertChar32ToUtf8(0x0939); + EXPECT_EQ(result.length(), 3); + EXPECT_EQ(result[0], '\xE0'); + EXPECT_EQ(result[1], '\xA4'); + EXPECT_EQ(result[2], '\xB9'); + + result = ConvertChar32ToUtf8(0x10348); + EXPECT_EQ(result.length(), 4); + EXPECT_EQ(result[0], '\xF0'); + EXPECT_EQ(result[1], '\x90'); + EXPECT_EQ(result[2], '\x8D'); + EXPECT_EQ(result[3], '\x88'); +} + // Test the most basic key events. // // Press, hold, and release key A on an US keyboard. From 0a832b019acde7ae9dcf1656062638d205ac340d Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 20:37:28 -0700 Subject: [PATCH 28/30] PeekNextMessageType --- .../platform/windows/keyboard_key_handler.cc | 3 +- .../windows/testing/mock_window_win32.cc | 2 -- .../windows/testing/mock_window_win32.h | 6 ++-- shell/platform/windows/window_win32.cc | 31 +++++++++++-------- shell/platform/windows/window_win32.h | 11 +++++++ 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index d5bb4a0d83ecf..552ebb6e58d22 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -261,8 +261,7 @@ void KeyboardKeyHandler::ResolvePendingEvent(uint64_t sequence_id, if (event.unreplied == 0) { std::unique_ptr event_ptr = std::move(*iter); pending_responds_.erase(iter); - const bool should_redispatch = !event_ptr->any_handled; - if (should_redispatch) { + if (!event_ptr->any_handled) { RedispatchEvent(std::move(event_ptr)); } } diff --git a/shell/platform/windows/testing/mock_window_win32.cc b/shell/platform/windows/testing/mock_window_win32.cc index f6984ae1db8cb..99cc67898fb37 100644 --- a/shell/platform/windows/testing/mock_window_win32.cc +++ b/shell/platform/windows/testing/mock_window_win32.cc @@ -4,8 +4,6 @@ #include "flutter/shell/platform/windows/testing/mock_window_win32.h" -#include "gtest/gtest.h" - namespace flutter { namespace testing { diff --git a/shell/platform/windows/testing/mock_window_win32.h b/shell/platform/windows/testing/mock_window_win32.h index 700ab6c108da2..d3938e7762c59 100644 --- a/shell/platform/windows/testing/mock_window_win32.h +++ b/shell/platform/windows/testing/mock_window_win32.h @@ -6,10 +6,8 @@ #define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WIN32_WINDOW_H_ #include -#include #include "flutter/shell/platform/windows/testing/test_keyboard.h" -#include "flutter/shell/platform/windows/testing/wm_builders.h" #include "flutter/shell/platform/windows/window_win32.h" #include "gmock/gmock.h" @@ -34,8 +32,6 @@ class MockWin32Window : public WindowWin32, public MockMessageQueue { WPARAM const wparam, LPARAM const lparam); - LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); - MOCK_METHOD1(OnDpiScale, void(unsigned int)); MOCK_METHOD2(OnResize, void(unsigned int, unsigned int)); MOCK_METHOD2(OnPointerMove, void(double, double)); @@ -52,6 +48,8 @@ class MockWin32Window : public WindowWin32, public MockMessageQueue { MOCK_METHOD2(OnComposeChange, void(const std::u16string&, int)); protected: + LRESULT Win32DefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + LRESULT Win32SendMessage(HWND hWnd, UINT const message, WPARAM const wparam, diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 88c114fd3d8d9..e57f8a8361b4d 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -191,9 +191,9 @@ static uint16_t ResolveKeyCode(uint16_t original, } static bool IsPrintable(uint32_t c) { - constexpr char32_t min_printable = ' '; - constexpr char32_t del = 0x7F; - return c >= min_printable && c != del; + constexpr char32_t kMinPrintable = ' '; + constexpr char32_t kDelete = 0x7F; + return c >= kMinPrintable && c != kDelete; } LRESULT @@ -430,16 +430,11 @@ WindowWin32::HandleMessage(UINT const message, // to properly produce key down events even though `MapVirtualKey` returns // a valid character. See https://github.com/flutter/flutter/issues/85587. unsigned int character = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR); - bool has_wm_char; - { - MSG next_message; - BOOL has_msg = Win32PeekMessage(&next_message, window_handle_, - WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE); - has_wm_char = has_msg && (next_message.message == WM_DEADCHAR || - next_message.message == WM_SYSDEADCHAR || - next_message.message == WM_CHAR || - next_message.message == WM_SYSCHAR); - } + 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; @@ -491,6 +486,16 @@ 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)); diff --git a/shell/platform/windows/window_win32.h b/shell/platform/windows/window_win32.h index 95d0440224614..5fcedeaf0d622 100644 --- a/shell/platform/windows/window_win32.h +++ b/shell/platform/windows/window_win32.h @@ -188,8 +188,19 @@ 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; + int current_dpi_ = 0; int current_width_ = 0; int current_height_ = 0; From b8ff16ee068fbaea15411dddbda6856596c8aff8 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 10 Aug 2021 20:47:44 -0700 Subject: [PATCH 29/30] Format --- shell/platform/windows/BUILD.gn | 2 +- .../windows/flutter_window_win32_unittests.cc | 6 +-- .../platform/windows/keyboard_key_handler.cc | 4 +- shell/platform/windows/keyboard_unittests.cc | 8 ++-- .../windows/testing/engine_modifier.h | 2 +- .../platform/windows/testing/test_keyboard.cc | 47 +++++++++---------- .../platform/windows/testing/test_keyboard.h | 5 +- .../testing/test_keyboard_unittests.cc | 8 +--- shell/platform/windows/window_win32.cc | 12 ++--- 9 files changed, 43 insertions(+), 51 deletions(-) diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 96085f1fd5944..df8a6c11751fe 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -250,9 +250,9 @@ executable("flutter_windows_unittests") { "testing/mock_window_binding_handler.h", "testing/mock_window_win32.cc", "testing/mock_window_win32.h", - "testing/test_keyboard_unittests.cc", "testing/test_keyboard.cc", "testing/test_keyboard.h", + "testing/test_keyboard_unittests.cc", "testing/wm_builders.cc", "testing/wm_builders.h", "text_input_plugin_unittest.cc", diff --git a/shell/platform/windows/flutter_window_win32_unittests.cc b/shell/platform/windows/flutter_window_win32_unittests.cc index dc77157e068c6..93763e3e8414b 100644 --- a/shell/platform/windows/flutter_window_win32_unittests.cc +++ b/shell/platform/windows/flutter_window_win32_unittests.cc @@ -298,10 +298,8 @@ std::unique_ptr GetTestEngine() { EngineModifier modifier(engine.get()); MockEmbedderApiForKeyboard( - modifier, - [] {return test_response;}, - [] (const FlutterKeyEvent* event) {return false; } - ); + modifier, [] { return test_response; }, + [](const FlutterKeyEvent* event) { return false; }); return engine; } diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index 552ebb6e58d22..0f80687cbba73 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -221,8 +221,8 @@ bool KeyboardKeyHandler::KeyboardHook(FlutterWindowsView* view, pending_responds_.push_back(std::move(incoming)); for (const auto& delegate : delegates_) { - delegate->KeyboardHook(key, scancode, action, character, extended, - was_down, [sequence_id, this](bool handled) { + delegate->KeyboardHook(key, scancode, action, character, extended, was_down, + [sequence_id, this](bool handled) { ResolvePendingEvent(sequence_id, handled); }); } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index aa7b941cdbf3b..7d0cd475059de 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -292,9 +292,8 @@ std::unique_ptr GetTestEngine() { EngineModifier modifier(engine.get()); MockEmbedderApiForKeyboard( - modifier, - [] {return KeyboardTester::test_response;}, - [] (const FlutterKeyEvent* event) { + modifier, [] { return KeyboardTester::test_response; }, + [](const FlutterKeyEvent* event) { FlutterKeyEvent clone_event = *event; clone_event.character = event->character == nullptr ? nullptr @@ -304,8 +303,7 @@ std::unique_ptr GetTestEngine() { .key_event = clone_event, }); return KeyboardTester::test_response; - } - ); + }); engine->RunWithEntrypoint(nullptr); return engine; diff --git a/shell/platform/windows/testing/engine_modifier.h b/shell/platform/windows/testing/engine_modifier.h index 1b42ed5a723db..dee2e6c6c1a96 100644 --- a/shell/platform/windows/testing/engine_modifier.h +++ b/shell/platform/windows/testing/engine_modifier.h @@ -50,4 +50,4 @@ class EngineModifier { } // namespace flutter -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_ENGINE_MODIFIER_H_ +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_ENGINE_MODIFIER_H_ diff --git a/shell/platform/windows/testing/test_keyboard.cc b/shell/platform/windows/testing/test_keyboard.cc index 9b65dbc75a1f6..5cedde95e8ef1 100644 --- a/shell/platform/windows/testing/test_keyboard.cc +++ b/shell/platform/windows/testing/test_keyboard.cc @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/shell/platform/windows/testing/test_keyboard.h" #include "flutter/shell/platform/common/json_message_codec.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" -#include "flutter/shell/platform/windows/testing/test_keyboard.h" #include #include @@ -75,11 +75,11 @@ ::testing::AssertionResult _EventEquals(const char* expr_event, } LPARAM CreateKeyEventLparam(USHORT scancode, - bool extended, - bool was_down, - USHORT repeat_count, - bool context_code, - bool transition_state) { + bool extended, + bool was_down, + USHORT repeat_count, + bool context_code, + bool transition_state) { return ((LPARAM(transition_state) << 31) | (LPARAM(was_down) << 30) | (LPARAM(context_code) << 29) | (LPARAM(extended ? 0x1 : 0x0) << 24) | (LPARAM(scancode) << 16) | LPARAM(repeat_count)); @@ -101,25 +101,25 @@ void MockEmbedderApiForKeyboard(EngineModifier& modifier, stored_embedder_handler = embedder_handler; // This mock handles channel messages. modifier.embedder_api().SendPlatformMessage = - [](FLUTTER_API_SYMBOL(FlutterEngine) engine, - const FlutterPlatformMessage* message) { - if (std::string(message->channel) == std::string("flutter/settings")) { - return kSuccess; - } - if (std::string(message->channel) == std::string("flutter/keyevent")) { - bool result = stored_channel_handler(); - auto response = _keyHandlingResponse(result); - const TestResponseHandle* response_handle = - reinterpret_cast( - message->response_handle); - if (response_handle->callback != nullptr) { - response_handle->callback(response->data(), response->size(), - response_handle->user_data); + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, + const FlutterPlatformMessage* message) { + if (std::string(message->channel) == std::string("flutter/settings")) { + return kSuccess; + } + if (std::string(message->channel) == std::string("flutter/keyevent")) { + bool result = stored_channel_handler(); + auto response = _keyHandlingResponse(result); + const TestResponseHandle* response_handle = + reinterpret_cast( + message->response_handle); + if (response_handle->callback != nullptr) { + response_handle->callback(response->data(), response->size(), + response_handle->user_data); + } + return kSuccess; } return kSuccess; - } - return kSuccess; - }; + }; // This mock handles key events sent through the embedder API, // and records it in `key_calls`. @@ -213,6 +213,5 @@ BOOL MockMessageQueue::Win32PeekMessage(LPMSG lpMsg, return FALSE; } - } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/testing/test_keyboard.h b/shell/platform/windows/testing/test_keyboard.h index 4e7e38231b128..e584f3b22aa2c 100644 --- a/shell/platform/windows/testing/test_keyboard.h +++ b/shell/platform/windows/testing/test_keyboard.h @@ -42,8 +42,9 @@ LPARAM CreateKeyEventLparam(USHORT scancode, bool context_code = 0, bool transition_state = 1); -typedef std::function MockKeyEventChannelHandler; -typedef std::function MockKeyEventEmbedderHandler; +typedef std::function MockKeyEventChannelHandler; +typedef std::function + MockKeyEventEmbedderHandler; void MockEmbedderApiForKeyboard(EngineModifier& modifier, MockKeyEventChannelHandler channel_handler, diff --git a/shell/platform/windows/testing/test_keyboard_unittests.cc b/shell/platform/windows/testing/test_keyboard_unittests.cc index ed5458c31a2e3..58f2f59a00ac6 100644 --- a/shell/platform/windows/testing/test_keyboard_unittests.cc +++ b/shell/platform/windows/testing/test_keyboard_unittests.cc @@ -21,13 +21,9 @@ TEST(TestKeyboard, CloneString) { }; TEST(TestKeyboard, CreateKeyEventLparam) { - EXPECT_EQ( - CreateKeyEventLparam(0x1, true, true), - 0xC1010001); + EXPECT_EQ(CreateKeyEventLparam(0x1, true, true), 0xC1010001); - EXPECT_EQ( - CreateKeyEventLparam(0x05, false, false, 0, 1, 0), - 0x20050000); + EXPECT_EQ(CreateKeyEventLparam(0x05, false, false, 0, 1, 0), 0x20050000); }; } // namespace testing diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index e57f8a8361b4d..89641806d34e4 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -431,10 +431,10 @@ WindowWin32::HandleMessage(UINT const message, // a valid character. See https://github.com/flutter/flutter/issues/85587. unsigned int character = MapVirtualKey(wparam, MAPVK_VK_TO_CHAR); 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); + 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; @@ -488,8 +488,8 @@ void WindowWin32::HandleResize(UINT width, UINT height) { UINT WindowWin32::PeekNextMessageType(UINT wMsgFilterMin, UINT wMsgFilterMax) { MSG next_message; - BOOL has_msg = Win32PeekMessage(&next_message, window_handle_, - wMsgFilterMin, wMsgFilterMax, PM_NOREMOVE); + BOOL has_msg = Win32PeekMessage(&next_message, window_handle_, wMsgFilterMin, + wMsgFilterMax, PM_NOREMOVE); if (!has_msg) { return 0; } From 410947ada91feb122b5c479bd3caff278de539d8 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 11 Aug 2021 13:54:32 -0700 Subject: [PATCH 30/30] Apply suggestions from code review Co-authored-by: Greg Spencer --- shell/platform/windows/window_win32.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/windows/window_win32.cc b/shell/platform/windows/window_win32.cc index 89641806d34e4..d20a166bdd157 100644 --- a/shell/platform/windows/window_win32.cc +++ b/shell/platform/windows/window_win32.cc @@ -360,9 +360,9 @@ WindowWin32::HandleMessage(UINT const message, // 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 appear alone. Filter out high - // surrogates out so that it's the low surrogate message that triggers - // the onKey, asks if the framework handles (which can only be done + // 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;