diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index f0aab9220ea43..0c98489ac30ad 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -17,6 +17,7 @@ #include "flutter/shell/platform/common/path_utils.h" #include "flutter/shell/platform/windows/accessibility_bridge_windows.h" #include "flutter/shell/platform/windows/flutter_windows_view.h" +#include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" #include "flutter/shell/platform/windows/system_utils.h" #include "flutter/shell/platform/windows/task_runner.h" #include "flutter/third_party/accessibility/ax/ax_node.h" @@ -206,6 +207,8 @@ FlutterWindowsEngine::FlutterWindowsEngine( // Set up internal channels. // TODO: Replace this with an embedder.h API. See // https://github.com/flutter/flutter/issues/71099 + internal_plugin_registrar_ = + std::make_unique(plugin_registrar_.get()); cursor_handler_ = std::make_unique(messenger_wrapper_.get(), this); platform_handler_ = @@ -322,7 +325,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { }; args.on_pre_engine_restart_callback = [](void* user_data) { auto host = static_cast(user_data); - host->view()->OnPreEngineRestart(); + host->OnPreEngineRestart(); }; args.update_semantics_callback = [](const FlutterSemanticsUpdate* update, void* user_data) { @@ -401,6 +404,7 @@ bool FlutterWindowsEngine::Stop() { void FlutterWindowsEngine::SetView(FlutterWindowsView* view) { view_ = view; + InitializeKeyboard(); } void FlutterWindowsEngine::OnVsync(intptr_t baton) { @@ -561,6 +565,47 @@ void FlutterWindowsEngine::SendSystemLocales() { flutter_locale_list.size()); } +void FlutterWindowsEngine::InitializeKeyboard() { + if (view_ == nullptr) { + FML_LOG(ERROR) << "Cannot initialize keyboard on Windows headless mode."; + } + + auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = GetKeyState; + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan = + [](UINT virtual_key, bool extended) { + return MapVirtualKey(virtual_key, + extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC); + }; + keyboard_key_handler_ = std::move(CreateKeyboardKeyHandler( + internal_plugin_messenger, get_key_state, map_vk_to_scan)); + text_input_plugin_ = + std::move(CreateTextInputPlugin(internal_plugin_messenger)); +} + +std::unique_ptr +FlutterWindowsEngine::CreateKeyboardKeyHandler( + BinaryMessenger* messenger, + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) { + auto keyboard_key_handler = std::make_unique(); + keyboard_key_handler->AddDelegate( + std::make_unique( + [this](const FlutterKeyEvent& event, FlutterKeyEventCallback callback, + void* user_data) { + return SendKeyEvent(event, callback, user_data); + }, + get_key_state, map_vk_to_scan)); + keyboard_key_handler->AddDelegate( + std::make_unique(messenger)); + return keyboard_key_handler; +} + +std::unique_ptr FlutterWindowsEngine::CreateTextInputPlugin( + BinaryMessenger* messenger) { + return std::make_unique(messenger, view_); +} + bool FlutterWindowsEngine::RegisterExternalTexture(int64_t texture_id) { return (embedder_api_.RegisterExternalTexture(engine_, texture_id) == kSuccess); @@ -625,6 +670,13 @@ FlutterWindowsEngine::CreateAccessibilityBridge(FlutterWindowsEngine* engine, return std::make_shared(engine, view); } +void FlutterWindowsEngine::OnPreEngineRestart() { + // Reset the keyboard's state on hot restart. + if (view_) { + InitializeKeyboard(); + } +} + gfx::NativeViewAccessible FlutterWindowsEngine::GetNativeAccessibleFromId( AccessibilityNodeId id) { if (!accessibility_bridge_) { diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index e461d086a0db8..9d12e93123c3d 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -25,10 +25,13 @@ #include "flutter/shell/platform/windows/flutter_desktop_messenger.h" #include "flutter/shell/platform/windows/flutter_project_bundle.h" #include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h" +#include "flutter/shell/platform/windows/keyboard_handler_base.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/settings_plugin.h" #include "flutter/shell/platform/windows/task_runner.h" +#include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/shell/platform/windows/window_proc_delegate_manager.h" #include "flutter/shell/platform/windows/window_state.h" #include "flutter/shell/platform/windows/windows_registry.h" @@ -163,6 +166,11 @@ class FlutterWindowsEngine { FlutterKeyEventCallback callback, void* user_data); + KeyboardHandlerBase* keyboard_key_handler() { + return keyboard_key_handler_.get(); + } + TextInputPlugin* text_input_plugin() { return text_input_plugin_.get(); } + // Sends the given message to the engine, calling |reply| with |user_data| // when a response is received from the engine if they are non-null. bool SendPlatformMessage(const char* channel, @@ -249,6 +257,22 @@ class FlutterWindowsEngine { void UpdateAccessibilityFeatures(FlutterAccessibilityFeature flags); protected: + // Creates the keyboard key handler. + // + // Exposing this method allows unit tests to override in order to + // capture information. + virtual std::unique_ptr CreateKeyboardKeyHandler( + BinaryMessenger* messenger, + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan); + + // Creates the text input plugin. + // + // Exposing this method allows unit tests to override in order to + // capture information. + virtual std::unique_ptr CreateTextInputPlugin( + BinaryMessenger* messenger); + // Creates an accessibility bridge with the provided parameters. // // By default this method calls AccessibilityBridge's constructor. Exposing @@ -257,6 +281,12 @@ class FlutterWindowsEngine { FlutterWindowsEngine* engine, FlutterWindowsView* view); + // Invoked by the engine right before the engine is restarted. + // + // This should reset necessary states to as if the engine has just been + // created. This is typically caused by a hot restart (Shift-R in CLI.) + void OnPreEngineRestart(); + private: // Allows swapping out embedder_api_ calls in tests. friend class EngineModifier; @@ -267,6 +297,12 @@ class FlutterWindowsEngine { // system changes. void SendSystemLocales(); + // Create the keyboard & text input sub-systems. + // + // This requires that a view is attached to the engine. + // Calling this method again resets the keyboard state. + void InitializeKeyboard(); + void HandleAccessibilityMessage(FlutterDesktopMessengerRef messenger, const FlutterDesktopMessage* message); @@ -309,12 +345,21 @@ class FlutterWindowsEngine { // May be nullptr if ANGLE failed to initialize. std::unique_ptr surface_manager_; + // The plugin registrar managing internal plugins. + std::unique_ptr internal_plugin_registrar_; + // Handler for cursor events. std::unique_ptr cursor_handler_; // Handler for the flutter/platform channel. std::unique_ptr platform_handler_; + // Handlers for keyboard events from Windows. + std::unique_ptr keyboard_key_handler_; + + // Handlers for text events from Windows. + std::unique_ptr text_input_plugin_; + // The settings plugin. std::unique_ptr settings_plugin_; diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 50c0ca0bedb56..4b2a392be0603 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -9,7 +9,6 @@ #include "flutter/fml/platform/win/wstring_conversion.h" #include "flutter/shell/platform/common/accessibility_bridge.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/text_input_plugin.h" #include "flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h" @@ -59,42 +58,12 @@ void FlutterWindowsView::SetEngine( engine_->SetView(this); - internal_plugin_registrar_ = - std::make_unique(engine_->GetRegistrar()); - - // Set up the system channel handlers. - auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); - InitializeKeyboard(); - PhysicalWindowBounds bounds = binding_handler_->GetPhysicalWindowBounds(); SendWindowMetrics(bounds.width, bounds.height, binding_handler_->GetDpiScale()); } -std::unique_ptr -FlutterWindowsView::CreateKeyboardKeyHandler( - BinaryMessenger* messenger, - KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, - KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) { - auto keyboard_key_handler = std::make_unique(); - keyboard_key_handler->AddDelegate( - std::make_unique( - [this](const FlutterKeyEvent& event, FlutterKeyEventCallback callback, - void* user_data) { - return engine_->SendKeyEvent(event, callback, user_data); - }, - get_key_state, map_vk_to_scan)); - keyboard_key_handler->AddDelegate( - std::make_unique(messenger)); - return keyboard_key_handler; -} - -std::unique_ptr FlutterWindowsView::CreateTextInputPlugin( - BinaryMessenger* messenger) { - return std::make_unique(messenger, this); -} - uint32_t FlutterWindowsView::GetFrameBufferId(size_t width, size_t height) { // Called on an engine-controlled (non-platform) thread. std::unique_lock lock(resize_mutex_); @@ -129,10 +98,6 @@ void FlutterWindowsView::ForceRedraw() { } } -void FlutterWindowsView::OnPreEngineRestart() { - InitializeKeyboard(); -} - void FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) { // Called on the platform thread. std::unique_lock lock(resize_mutex_); @@ -176,7 +141,7 @@ void FlutterWindowsView::OnPointerMove(double x, FlutterPointerDeviceKind device_kind, int32_t device_id, int modifiers_state) { - keyboard_key_handler_->SyncModifiersIfNeeded(modifiers_state); + engine_->keyboard_key_handler()->SyncModifiersIfNeeded(modifiers_state); SendPointerMove(x, y, GetOrCreatePointerState(device_kind, device_id)); } @@ -293,20 +258,6 @@ void FlutterWindowsView::OnResetImeComposing() { binding_handler_->OnResetImeComposing(); } -void FlutterWindowsView::InitializeKeyboard() { - auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); - KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = GetKeyState; - KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan = - [](UINT virtual_key, bool extended) { - return MapVirtualKey(virtual_key, - extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC); - }; - keyboard_key_handler_ = std::move(CreateKeyboardKeyHandler( - internal_plugin_messenger, get_key_state, map_vk_to_scan)); - text_input_plugin_ = - std::move(CreateTextInputPlugin(internal_plugin_messenger)); -} - // Sends new size information to FlutterEngine. void FlutterWindowsView::SendWindowMetrics(size_t width, size_t height, @@ -454,7 +405,7 @@ void FlutterWindowsView::SendPointerPanZoomEnd(int32_t device_id) { } void FlutterWindowsView::SendText(const std::u16string& text) { - text_input_plugin_->TextHook(text); + engine_->text_input_plugin()->TextHook(text); } void FlutterWindowsView::SendKey(int key, @@ -464,32 +415,32 @@ void FlutterWindowsView::SendKey(int key, bool extended, bool was_down, KeyEventCallback callback) { - keyboard_key_handler_->KeyboardHook( + engine_->keyboard_key_handler()->KeyboardHook( key, scancode, action, character, extended, was_down, [=, callback = std::move(callback)](bool handled) { if (!handled) { - text_input_plugin_->KeyboardHook(key, scancode, action, character, - extended, was_down); + engine_->text_input_plugin()->KeyboardHook( + key, scancode, action, character, extended, was_down); } callback(handled); }); } void FlutterWindowsView::SendComposeBegin() { - text_input_plugin_->ComposeBeginHook(); + engine_->text_input_plugin()->ComposeBeginHook(); } void FlutterWindowsView::SendComposeCommit() { - text_input_plugin_->ComposeCommitHook(); + engine_->text_input_plugin()->ComposeCommitHook(); } void FlutterWindowsView::SendComposeEnd() { - text_input_plugin_->ComposeEndHook(); + engine_->text_input_plugin()->ComposeEndHook(); } void FlutterWindowsView::SendComposeChange(const std::u16string& text, int cursor_pos) { - text_input_plugin_->ComposeChangeHook(text, cursor_pos); + engine_->text_input_plugin()->ComposeChangeHook(text, cursor_pos); } void FlutterWindowsView::SendScroll(double x, diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index acaa9ad106e0d..9eed22ce8fa55 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -17,10 +17,7 @@ #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/windows/angle_surface_manager.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_embedder_handler.h" #include "flutter/shell/platform/windows/public/flutter_windows.h" -#include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/shell/platform/windows/text_input_plugin_delegate.h" #include "flutter/shell/platform/windows/window_binding_handler.h" #include "flutter/shell/platform/windows/window_binding_handler_delegate.h" @@ -102,12 +99,6 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // Sets the cursor directly from a cursor handle. void SetFlutterCursor(HCURSOR cursor); - // Invoked by the engine right before the engine is restarted. - // - // This should reset necessary states to as if the view has just been - // created. This is typically caused by a hot restart (Shift-R in CLI.) - void OnPreEngineRestart(); - // |WindowBindingHandlerDelegate| void OnWindowSizeChanged(size_t width, size_t height) override; @@ -209,21 +200,6 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, virtual ui::AXFragmentRootDelegateWin* GetAxFragmentRootDelegate() override; protected: - // Called to create keyboard key handler. - // - // 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 std::unique_ptr CreateKeyboardKeyHandler( - BinaryMessenger* messenger, - KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, - KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan); - - // Called to create text input plugin. - virtual std::unique_ptr CreateTextInputPlugin( - BinaryMessenger* messenger); - virtual void NotifyWinEventWrapper(ui::AXPlatformNodeWin* node, ax::mojom::Event event); @@ -269,11 +245,6 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, kDone, }; - // Initialize states related to keyboard. - // - // This is called when the view is first created, or restarted. - void InitializeKeyboard(); - // Sends a window metrics update to the Flutter engine using current window // dimensions in physical void SendWindowMetrics(size_t width, size_t height, double dpiscale) const; @@ -379,15 +350,6 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate, // Keeps track of pointer states in relation to the window. std::unordered_map> pointer_states_; - // The plugin registrar managing internal plugins. - std::unique_ptr internal_plugin_registrar_; - - // Handlers for keyboard events from Windows. - std::unique_ptr keyboard_key_handler_; - - // Handlers for text events from Windows. - std::unique_ptr text_input_plugin_; - // Currently configured WindowBindingHandler for view. std::unique_ptr binding_handler_; diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 62f7b21bb8d45..abf5cdc1e16b0 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -316,76 +316,6 @@ class MockKeyboardManagerDelegate : public KeyboardManager::WindowDelegate, std::list redispatched_messages_; }; -// A FlutterWindowsView that overrides the RegisterKeyboardHandlers function -// to register the keyboard hook handlers that can be spied upon. -class TestFlutterWindowsView : public FlutterWindowsView { - public: - typedef std::function U16StringHandler; - - TestFlutterWindowsView( - U16StringHandler on_text, - KeyboardKeyEmbedderHandler::GetKeyStateHandler get_keyboard_state, - KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) - // The WindowBindingHandler is used for window size and such, and doesn't - // affect keyboard. - : FlutterWindowsView( - std::make_unique<::testing::NiceMock>()), - get_keyboard_state_(std::move(get_keyboard_state)), - map_vk_to_scan_(std::move(map_vk_to_scan)), - on_text_(std::move(on_text)) {} - - void OnText(const std::u16string& text) override { on_text_(text); } - - void HandleMessage(const char* channel, - const char* method, - const char* args) { - rapidjson::Document args_doc; - args_doc.Parse(args); - FML_DCHECK(!args_doc.HasParseError()); - - rapidjson::Document message_doc(rapidjson::kObjectType); - auto& allocator = message_doc.GetAllocator(); - message_doc.AddMember("method", rapidjson::Value(method, allocator), - allocator); - message_doc.AddMember("args", args_doc, allocator); - - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - message_doc.Accept(writer); - - std::unique_ptr> data = - JsonMessageCodec::GetInstance().EncodeMessage(message_doc); - FlutterPlatformMessageResponseHandle response_handle; - const FlutterPlatformMessage message = { - sizeof(FlutterPlatformMessage), // struct_size - channel, // channel - data->data(), // message - data->size(), // message_size - &response_handle, // response_handle - }; - GetEngine()->HandlePlatformMessage(&message); - } - - protected: - std::unique_ptr CreateKeyboardKeyHandler( - BinaryMessenger* messenger, - KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, - KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) - override { - return FlutterWindowsView::CreateKeyboardKeyHandler( - messenger, - [this](int virtual_key) { return get_keyboard_state_(virtual_key); }, - [this](int virtual_key, bool extended) { - return map_vk_to_scan_(virtual_key, extended); - }); - } - - private: - U16StringHandler on_text_; - KeyboardKeyEmbedderHandler::GetKeyStateHandler get_keyboard_state_; - KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan_; -}; - typedef struct { enum { kKeyCallOnKey, @@ -411,6 +341,20 @@ void clear_key_calls() { key_calls.clear(); } +// A FlutterWindowsView that spies on text. +class TestFlutterWindowsView : public FlutterWindowsView { + public: + TestFlutterWindowsView(std::unique_ptr window) + : FlutterWindowsView(std::move(window)) {} + + void OnText(const std::u16string& text) override { + key_calls.push_back(KeyCall{ + .type = KeyCall::kKeyCallOnText, + .text = text, + }); + } +}; + class KeyboardTester { public: using ResponseHandler = @@ -419,36 +363,14 @@ class KeyboardTester { explicit KeyboardTester(WindowsTestContext& context) : callback_handler_(RespondValue(false)), map_virtual_key_layout_(LayoutDefault) { + std::unique_ptr engine = GetTestEngine(context); + + engine_ = engine.get(); view_ = std::make_unique( - [](const std::u16string& text) { - key_calls.push_back(KeyCall{ - .type = KeyCall::kKeyCallOnText, - .text = text, - }); - }, - [this](int virtual_key) -> SHORT { - // `window_` is not initialized yet when this callback is first - // called. - return window_ ? window_->GetKeyState(virtual_key) : 0; - }, - [this](UINT virtual_key, bool extended) -> SHORT { - return map_virtual_key_layout_( - virtual_key, extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC); - }); - view_->SetEngine(GetTestEngine( - context, [&callback_handler = callback_handler_]( - const FlutterKeyEvent* event, - MockKeyResponseController::ResponseCallback callback) { - FlutterKeyEvent clone_event = *event; - clone_event.character = event->character == nullptr - ? nullptr - : clone_string(event->character); - key_calls.push_back(KeyCall{ - .type = KeyCall::kKeyCallOnKey, - .key_event = clone_event, - }); - callback_handler(event, callback); - })); + // The WindowBindingHandler is used for window size and such, and + // doesn't affect keyboard. + std::make_unique<::testing::NiceMock>()); + view_->SetEngine(std::move(engine)); window_ = std::make_unique( view_.get(), [this](UINT virtual_key) -> SHORT { return map_virtual_key_layout_(virtual_key, MAPVK_VK_TO_CHAR); @@ -458,6 +380,9 @@ class KeyboardTester { TestFlutterWindowsView& GetView() { return *view_; } MockKeyboardManagerDelegate& GetWindow() { return *window_; } + // Reset the keyboard by invoking the engine restart handler. + void ResetKeyboard() { EngineModifier{engine_}.Restart(); } + // Set all events to be handled (true) or unhandled (false). void Responding(bool response) { callback_handler_ = RespondValue(response); } @@ -481,6 +406,37 @@ class KeyboardTester { window_->InjectKeyboardChanges(changes); } + // Simulates receiving a platform message from the framework. + void InjectPlatformMessage(const char* channel, + const char* method, + const char* args) { + rapidjson::Document args_doc; + args_doc.Parse(args); + FML_DCHECK(!args_doc.HasParseError()); + + rapidjson::Document message_doc(rapidjson::kObjectType); + auto& allocator = message_doc.GetAllocator(); + message_doc.AddMember("method", rapidjson::Value(method, allocator), + allocator); + message_doc.AddMember("args", args_doc, allocator); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + message_doc.Accept(writer); + + std::unique_ptr> data = + JsonMessageCodec::GetInstance().EncodeMessage(message_doc); + FlutterPlatformMessageResponseHandle response_handle; + const FlutterPlatformMessage message = { + sizeof(FlutterPlatformMessage), // struct_size + channel, // channel + data->data(), // message + data->size(), // message_size + &response_handle, // response_handle + }; + view_->GetEngine()->HandlePlatformMessage(&message); + } + // Get the number of redispatched messages since the last clear, then clear // the counter. size_t RedispatchedMessageCountAndClear() { @@ -491,6 +447,7 @@ class KeyboardTester { } private: + FlutterWindowsEngine* engine_; std::unique_ptr view_; std::unique_ptr window_; MockKeyResponseController::EmbedderCallbackHandler callback_handler_; @@ -500,10 +457,20 @@ class KeyboardTester { // overridden methods for sending platform messages, so that the engine can // respond as if the framework were connected. std::unique_ptr GetTestEngine( - WindowsTestContext& context, - MockKeyResponseController::EmbedderCallbackHandler - embedder_callback_handler) { + WindowsTestContext& context) { FlutterWindowsEngineBuilder builder{context}; + + builder.SetCreateKeyboardHandlerCallbacks( + [this](int virtual_key) -> SHORT { + // `window_` is not initialized yet when this callback is first + // called. + return window_ ? window_->GetKeyState(virtual_key) : 0; + }, + [this](UINT virtual_key, bool extended) -> SHORT { + return map_virtual_key_layout_( + virtual_key, extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC); + }); + auto engine = builder.Build(); EngineModifier modifier(engine.get()); @@ -511,7 +478,19 @@ class KeyboardTester { auto key_response_controller = std::make_shared(); key_response_controller->SetEmbedderResponse( - std::move(embedder_callback_handler)); + [&callback_handler = callback_handler_]( + const FlutterKeyEvent* event, + MockKeyResponseController::ResponseCallback callback) { + FlutterKeyEvent clone_event = *event; + clone_event.character = event->character == nullptr + ? nullptr + : clone_string(event->character); + key_calls.push_back(KeyCall{ + .type = KeyCall::kKeyCallOnKey, + .key_event = clone_event, + }); + callback_handler(event, callback); + }); key_response_controller->SetTextInputResponse( [](std::unique_ptr document) { rapidjson::StringBuffer buffer; @@ -522,10 +501,10 @@ class KeyboardTester { .text_method_call = buffer.GetString(), }); }); - MockEmbedderApiForKeyboard(modifier, key_response_controller); engine->Run(); + return engine; } @@ -994,8 +973,8 @@ TEST_F(KeyboardTest, RestartClearsKeyboardState) { WmCharInfo{'a', kScanCodeKeyA, kNotExtended, kWasUp}.Build( kWmResultZero)}); - // Send the "hot restart" signal. This should reset the keyboard's state. - tester.GetView().OnPreEngineRestart(); + // Reset the keyboard's state. + tester.ResetKeyboard(); // Hold A. Notice the message declares the key is already down, however, the // the keyboard does not send a repeat event as its state was reset. @@ -2179,7 +2158,7 @@ TEST_F(KeyboardTest, TextInputSubmit) { // US Keyboard layout - tester.GetView().HandleMessage( + tester.InjectPlatformMessage( "flutter/textinput", "TextInput.setClient", R"|([108, {"inputAction": "TextInputAction.none"}])|"); diff --git a/shell/platform/windows/testing/engine_modifier.h b/shell/platform/windows/testing/engine_modifier.h index 111b1190149aa..7ff46cbe370a7 100644 --- a/shell/platform/windows/testing/engine_modifier.h +++ b/shell/platform/windows/testing/engine_modifier.h @@ -60,6 +60,10 @@ class EngineModifier { // engine unless overwritten again. void ReleaseSurfaceManager() { engine_->surface_manager_.release(); } + // Run the FlutterWindowsEngine's handler that runs right before an engine + // restart. This resets the keyboard's state if it exists. + void Restart() { engine_->OnPreEngineRestart(); } + private: FlutterWindowsEngine* engine_; }; diff --git a/shell/platform/windows/testing/flutter_windows_engine_builder.cc b/shell/platform/windows/testing/flutter_windows_engine_builder.cc index 0aa2f1139ada4..5898c5dcc5385 100644 --- a/shell/platform/windows/testing/flutter_windows_engine_builder.cc +++ b/shell/platform/windows/testing/flutter_windows_engine_builder.cc @@ -7,6 +7,42 @@ namespace flutter { namespace testing { +class TestFlutterWindowsEngine : public FlutterWindowsEngine { + public: + TestFlutterWindowsEngine( + const FlutterProjectBundle& project, + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) + : FlutterWindowsEngine(project), + get_key_state_(std::move(get_key_state)), + map_vk_to_scan_(std::move(map_vk_to_scan)) {} + + // Prevent copying. + TestFlutterWindowsEngine(TestFlutterWindowsEngine const&) = delete; + TestFlutterWindowsEngine& operator=(TestFlutterWindowsEngine const&) = delete; + + protected: + std::unique_ptr CreateKeyboardKeyHandler( + BinaryMessenger* messenger, + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) { + if (get_key_state_) { + get_key_state = get_key_state_; + } + + if (map_vk_to_scan_) { + map_vk_to_scan = map_vk_to_scan_; + } + + return FlutterWindowsEngine::CreateKeyboardKeyHandler( + messenger, get_key_state, map_vk_to_scan); + } + + private: + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state_; + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan_; +}; + FlutterWindowsEngineBuilder::FlutterWindowsEngineBuilder( WindowsTestContext& context) : context_(context) { @@ -26,6 +62,13 @@ void FlutterWindowsEngineBuilder::AddDartEntrypointArgument(std::string arg) { dart_entrypoint_arguments_.emplace_back(std::move(arg)); } +void FlutterWindowsEngineBuilder::SetCreateKeyboardHandlerCallbacks( + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan) { + get_key_state_ = std::move(get_key_state); + map_vk_to_scan_ = std::move(map_vk_to_scan); +} + std::unique_ptr FlutterWindowsEngineBuilder::Build() { std::vector dart_args; dart_args.reserve(dart_entrypoint_arguments_.size()); @@ -44,7 +87,8 @@ std::unique_ptr FlutterWindowsEngineBuilder::Build() { FlutterProjectBundle project(properties_); - return std::make_unique(project); + return std::make_unique(project, get_key_state_, + map_vk_to_scan_); } } // namespace testing diff --git a/shell/platform/windows/testing/flutter_windows_engine_builder.h b/shell/platform/windows/testing/flutter_windows_engine_builder.h index cc41f03feed37..014da1476533d 100644 --- a/shell/platform/windows/testing/flutter_windows_engine_builder.h +++ b/shell/platform/windows/testing/flutter_windows_engine_builder.h @@ -8,6 +8,7 @@ #include #include "flutter/shell/platform/windows/flutter_windows_engine.h" +#include "flutter/shell/platform/windows/keyboard_key_embedder_handler.h" #include "flutter/shell/platform/windows/public/flutter_windows.h" #include "flutter/shell/platform/windows/testing/windows_test_context.h" @@ -23,6 +24,10 @@ class FlutterWindowsEngineBuilder { void AddDartEntrypointArgument(std::string arg); + void SetCreateKeyboardHandlerCallbacks( + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state, + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan); + std::unique_ptr Build(); // Prevent copying. @@ -35,6 +40,8 @@ class FlutterWindowsEngineBuilder { FlutterDesktopEngineProperties properties_ = {}; std::string dart_entrypoint_; std::vector dart_entrypoint_arguments_; + KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state_; + KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan_; }; } // namespace testing diff --git a/shell/platform/windows/text_input_plugin.cc b/shell/platform/windows/text_input_plugin.cc index 6031c4cd41b70..394ab3feaecdf 100644 --- a/shell/platform/windows/text_input_plugin.cc +++ b/shell/platform/windows/text_input_plugin.cc @@ -5,6 +5,7 @@ #include "flutter/shell/platform/windows/text_input_plugin.h" #include "flutter/fml/string_conversion.h" #include "flutter/shell/platform/common/text_editing_delta.h" +#include "flutter/shell/platform/windows/text_input_plugin_delegate.h" #include diff --git a/shell/platform/windows/text_input_plugin.h b/shell/platform/windows/text_input_plugin.h index e9c36c3966eb2..17a2a3d7e8fec 100644 --- a/shell/platform/windows/text_input_plugin.h +++ b/shell/platform/windows/text_input_plugin.h @@ -16,10 +16,11 @@ #include "flutter/shell/platform/common/text_editing_delta.h" #include "flutter/shell/platform/common/text_input_model.h" #include "flutter/shell/platform/windows/keyboard_handler_base.h" -#include "flutter/shell/platform/windows/text_input_plugin_delegate.h" namespace flutter { +class TextInputPluginDelegate; + // Implements a text input plugin. // // Specifically handles window events within windows. diff --git a/shell/platform/windows/text_input_plugin_unittest.cc b/shell/platform/windows/text_input_plugin_unittest.cc index 1bb5a2aa2eb82..9c254b9a58201 100644 --- a/shell/platform/windows/text_input_plugin_unittest.cc +++ b/shell/platform/windows/text_input_plugin_unittest.cc @@ -10,6 +10,7 @@ #include "flutter/shell/platform/common/json_message_codec.h" #include "flutter/shell/platform/common/json_method_codec.h" #include "flutter/shell/platform/windows/testing/test_binary_messenger.h" +#include "flutter/shell/platform/windows/text_input_plugin_delegate.h" #include "gmock/gmock.h" #include "gtest/gtest.h"