From bb04632eeb16dd0c7644aeb862ae4a3c8ff9011f Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 19 Aug 2020 06:49:39 -0700 Subject: [PATCH] [windows] Allow delegation of top-level WindowProc (flutter/engine#20613) Adds APIs for runners to delegate WindowProc handlers into the Flutter engine, and for plugins to register as possible delegates. This allows for plugins to alter top-level window behavior in ways that can only be done from the WindowProc, such as resize control. This functionality remains entirely on the native side, so is synchronous. Part of https://github.com/flutter/flutter/issues/53168 --- .../ci/licenses_golden/licenses_flutter | 3 + .../flutter/shell/platform/windows/BUILD.gn | 3 + .../client_wrapper/flutter_view_controller.cc | 11 ++ .../include/flutter/flutter_view_controller.h | 14 ++ .../flutter/plugin_registrar_windows.h | 78 ++++++++ .../plugin_registrar_windows_unittests.cc | 129 +++++++++++++- .../testing/stub_flutter_windows_api.cc | 34 ++++ .../testing/stub_flutter_windows_api.h | 19 ++ .../shell/platform/windows/flutter_windows.cc | 34 +++- .../windows/flutter_windows_engine.cc | 6 +- .../platform/windows/flutter_windows_engine.h | 10 ++ .../platform/windows/public/flutter_windows.h | 43 ++++- .../windows/win32_dpi_utils_unittests.cc | 4 + .../windows/win32_flutter_window_unittests.cc | 4 + .../win32_window_proc_delegate_manager.cc | 42 +++++ .../win32_window_proc_delegate_manager.h | 58 ++++++ ..._window_proc_delegate_manager_unittests.cc | 168 ++++++++++++++++++ .../windows/win32_window_unittests.cc | 4 + .../shell/platform/windows/window_state.h | 4 +- 19 files changed, 660 insertions(+), 8 deletions(-) create mode 100644 engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager.cc create mode 100644 engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager.h create mode 100644 engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager_unittests.cc diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 5389f652c87..99124c9effd 100755 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -1376,6 +1376,9 @@ FILE: ../../../flutter/shell/platform/windows/win32_task_runner.cc FILE: ../../../flutter/shell/platform/windows/win32_task_runner.h FILE: ../../../flutter/shell/platform/windows/win32_window.cc FILE: ../../../flutter/shell/platform/windows/win32_window.h +FILE: ../../../flutter/shell/platform/windows/win32_window_proc_delegate_manager.cc +FILE: ../../../flutter/shell/platform/windows/win32_window_proc_delegate_manager.h +FILE: ../../../flutter/shell/platform/windows/win32_window_proc_delegate_manager_unittests.cc FILE: ../../../flutter/shell/platform/windows/win32_window_unittests.cc FILE: ../../../flutter/shell/platform/windows/window_binding_handler.h FILE: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h diff --git a/engine/src/flutter/shell/platform/windows/BUILD.gn b/engine/src/flutter/shell/platform/windows/BUILD.gn index ab8e703f73b..9ecb0d2f5e8 100644 --- a/engine/src/flutter/shell/platform/windows/BUILD.gn +++ b/engine/src/flutter/shell/platform/windows/BUILD.gn @@ -68,6 +68,8 @@ source_set("flutter_windows_source") { "win32_task_runner.h", "win32_window.cc", "win32_window.h", + "win32_window_proc_delegate_manager.cc", + "win32_window_proc_delegate_manager.h", "window_binding_handler.h", "window_binding_handler_delegate.h", "window_state.h", @@ -127,6 +129,7 @@ executable("flutter_windows_unittests") { "testing/win32_window_test.h", "win32_dpi_utils_unittests.cc", "win32_flutter_window_unittests.cc", + "win32_window_proc_delegate_manager_unittests.cc", "win32_window_unittests.cc", ] diff --git a/engine/src/flutter/shell/platform/windows/client_wrapper/flutter_view_controller.cc b/engine/src/flutter/shell/platform/windows/client_wrapper/flutter_view_controller.cc index 8fb81476b0a..e32c4e59ea6 100644 --- a/engine/src/flutter/shell/platform/windows/client_wrapper/flutter_view_controller.cc +++ b/engine/src/flutter/shell/platform/windows/client_wrapper/flutter_view_controller.cc @@ -29,6 +29,17 @@ FlutterViewController::~FlutterViewController() { } } +std::optional FlutterViewController::HandleTopLevelWindowProc( + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + LRESULT result; + bool handled = FlutterDesktopViewControllerHandleTopLevelWindowProc( + controller_, hwnd, message, wparam, lparam, &result); + return handled ? result : std::optional(std::nullopt); +} + std::chrono::nanoseconds FlutterViewController::ProcessMessages() { return engine_->ProcessMessages(); } diff --git a/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h b/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h index f6e3e8b5ae4..32a1f2f8fd3 100644 --- a/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h +++ b/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/flutter_view_controller.h @@ -5,8 +5,12 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_CLIENT_WRAPPER_INCLUDE_FLUTTER_FLUTTER_VIEW_CONTROLLER_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_CLIENT_WRAPPER_INCLUDE_FLUTTER_FLUTTER_VIEW_CONTROLLER_H_ +#include #include +#include +#include + #include "dart_project.h" #include "flutter_engine.h" #include "flutter_view.h" @@ -42,6 +46,16 @@ class FlutterViewController : public PluginRegistry { // Returns the view managed by this controller. FlutterView* view() { return view_.get(); } + // Allows the Flutter engine and any interested plugins an opportunity to + // handle the given message. + // + // If a result is returned, then the message was handled in such a way that + // further handling should not be done. + std::optional HandleTopLevelWindowProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam); + // DEPRECATED. Call engine()->ProcessMessages() instead. std::chrono::nanoseconds ProcessMessages(); diff --git a/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h b/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h index 9a13a54501c..16bab4891ef 100644 --- a/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h +++ b/engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/plugin_registrar_windows.h @@ -5,15 +5,24 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_CLIENT_WRAPPER_INCLUDE_FLUTTER_PLUGIN_REGISTRAR_WINDOWS_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_CLIENT_WRAPPER_INCLUDE_FLUTTER_PLUGIN_REGISTRAR_WINDOWS_H_ +#include #include #include +#include #include "flutter_view.h" #include "plugin_registrar.h" namespace flutter { +// A delegate callback for WindowProc delegation. +// +// Implementations should return a value only if they have handled the message +// and want to stop all further handling. +using WindowProcDelegate = std::function(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)>; + // An extension to PluginRegistrar providing access to Windows-specific // functionality. class PluginRegistrarWindows : public PluginRegistrar { @@ -35,9 +44,78 @@ class PluginRegistrarWindows : public PluginRegistrar { FlutterView* GetView() { return view_.get(); } + // Registers |delegate| to recieve WindowProc callbacks for the top-level + // window containing this Flutter instance. Returns an ID that can be used to + // unregister the handler. + // + // Delegates are not guaranteed to be called: + // - The application may choose not to delegate WindowProc calls. + // - If multiple plugins are registered, the first one that returns a value + // from the delegate message will "win", and others will not be called. + // The order of delegate calls is not defined. + // + // Delegates should be implemented as narrowly as possible, only returning + // a value in cases where it's important that other delegates not run, to + // minimize the chances of conflicts between plugins. + int RegisterTopLevelWindowProcDelegate(WindowProcDelegate delegate) { + if (window_proc_delegates_.empty()) { + FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate( + registrar(), PluginRegistrarWindows::OnTopLevelWindowProc, this); + } + int delegate_id = next_window_proc_delegate_id_++; + window_proc_delegates_.emplace(delegate_id, std::move(delegate)); + return delegate_id; + } + + // Unregisters a previously registered delegate. + void UnregisterTopLevelWindowProcDelegate(int proc_id) { + window_proc_delegates_.erase(proc_id); + if (window_proc_delegates_.empty()) { + FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate( + registrar(), PluginRegistrarWindows::OnTopLevelWindowProc); + } + } + private: + // A FlutterDesktopWindowProcCallback implementation that forwards back to + // a PluginRegistarWindows instance provided as |user_data|. + static bool OnTopLevelWindowProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + void* user_data, + LRESULT* result) { + const auto* registrar = static_cast(user_data); + std::optional optional_result = registrar->CallTopLevelWindowProcDelegates( + hwnd, message, wparam, lparam); + if (optional_result) { + *result = *optional_result; + } + return optional_result.has_value(); + } + + std::optional CallTopLevelWindowProcDelegates(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) const { + std::optional result; + for (const auto& pair : window_proc_delegates_) { + result = pair.second(hwnd, message, wparam, lparam); + // Stop as soon as any delegate indicates that it has handled the message. + if (result) { + break; + } + } + return result; + } + // The associated FlutterView, if any. std::unique_ptr view_; + + // The next ID to return from RegisterWindowProcDelegate. + int next_window_proc_delegate_id_ = 1; + + std::map window_proc_delegates_; }; } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc b/engine/src/flutter/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc index 07eaff0c1e2..63049a5eb82 100644 --- a/engine/src/flutter/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/client_wrapper/plugin_registrar_windows_unittests.cc @@ -14,7 +14,34 @@ namespace flutter { namespace { // Stub implementation to validate calls to the API. -class TestWindowsApi : public testing::StubFlutterWindowsApi {}; +class TestWindowsApi : public testing::StubFlutterWindowsApi { + public: + void PluginRegistrarRegisterTopLevelWindowProcDelegate( + FlutterDesktopWindowProcCallback delegate, + void* user_data) override { + ++registered_delegate_count_; + last_registered_delegate_ = delegate; + last_registered_user_data_ = user_data; + } + + void PluginRegistrarUnregisterTopLevelWindowProcDelegate( + FlutterDesktopWindowProcCallback delegate) override { + --registered_delegate_count_; + } + + int registered_delegate_count() { return registered_delegate_count_; } + + FlutterDesktopWindowProcCallback last_registered_delegate() { + return last_registered_delegate_; + } + + void* last_registered_user_data() { return last_registered_user_data_; } + + private: + int registered_delegate_count_ = 0; + FlutterDesktopWindowProcCallback last_registered_delegate_ = nullptr; + void* last_registered_user_data_ = nullptr; +}; } // namespace @@ -27,4 +54,104 @@ TEST(PluginRegistrarWindowsTest, GetView) { EXPECT_NE(registrar.GetView(), nullptr); } +TEST(PluginRegistrarWindowsTest, RegisterUnregister) { + testing::ScopedStubFlutterWindowsApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + PluginRegistrarWindows registrar( + reinterpret_cast(1)); + + WindowProcDelegate delegate = [](HWND hwnd, UINT message, WPARAM wparam, + LPARAM lparam) { + return std::optional(); + }; + int id_a = registrar.RegisterTopLevelWindowProcDelegate(delegate); + EXPECT_EQ(test_api->registered_delegate_count(), 1); + int id_b = registrar.RegisterTopLevelWindowProcDelegate(delegate); + // All the C++-level delegates are driven by a since C callback, so the + // registration count should stay the same. + EXPECT_EQ(test_api->registered_delegate_count(), 1); + + // Unregistering one of the two delegates shouldn't cause the underlying C + // callback to be unregistered. + registrar.UnregisterTopLevelWindowProcDelegate(id_a); + EXPECT_EQ(test_api->registered_delegate_count(), 1); + // Unregistering both should unregister it. + registrar.UnregisterTopLevelWindowProcDelegate(id_b); + EXPECT_EQ(test_api->registered_delegate_count(), 0); + + EXPECT_NE(id_a, id_b); +} + +TEST(PluginRegistrarWindowsTest, CallsRegisteredDelegates) { + testing::ScopedStubFlutterWindowsApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + PluginRegistrarWindows registrar( + reinterpret_cast(1)); + + HWND dummy_hwnd; + bool called_a = false; + WindowProcDelegate delegate_a = [&called_a, &dummy_hwnd]( + HWND hwnd, UINT message, WPARAM wparam, + LPARAM lparam) { + called_a = true; + EXPECT_EQ(hwnd, dummy_hwnd); + EXPECT_EQ(message, 2); + EXPECT_EQ(wparam, 3); + EXPECT_EQ(lparam, 4); + return std::optional(); + }; + bool called_b = false; + WindowProcDelegate delegate_b = [&called_b](HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + called_b = true; + return std::optional(); + }; + int id_a = registrar.RegisterTopLevelWindowProcDelegate(delegate_a); + int id_b = registrar.RegisterTopLevelWindowProcDelegate(delegate_b); + + LRESULT result = 0; + bool handled = test_api->last_registered_delegate()( + dummy_hwnd, 2, 3, 4, test_api->last_registered_user_data(), &result); + EXPECT_TRUE(called_a); + EXPECT_TRUE(called_b); + EXPECT_FALSE(handled); +} + +TEST(PluginRegistrarWindowsTest, StopsOnceHandled) { + testing::ScopedStubFlutterWindowsApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + PluginRegistrarWindows registrar( + reinterpret_cast(1)); + + bool called_a = false; + WindowProcDelegate delegate_a = [&called_a](HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + called_a = true; + return std::optional(7); + }; + bool called_b = false; + WindowProcDelegate delegate_b = [&called_b](HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + called_b = true; + return std::optional(7); + }; + int id_a = registrar.RegisterTopLevelWindowProcDelegate(delegate_a); + int id_b = registrar.RegisterTopLevelWindowProcDelegate(delegate_b); + + HWND dummy_hwnd; + LRESULT result = 0; + bool handled = test_api->last_registered_delegate()( + dummy_hwnd, 2, 3, 4, test_api->last_registered_user_data(), &result); + // Only one of the delegates should have been called, since each claims to + // have fully handled the message. + EXPECT_TRUE(called_a || called_b); + EXPECT_NE(called_a, called_b); + // The return value should propagate through. + EXPECT_TRUE(handled); + EXPECT_EQ(result, 7); +} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.cc b/engine/src/flutter/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.cc index de8b54902a6..20613734f90 100644 --- a/engine/src/flutter/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.cc +++ b/engine/src/flutter/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.cc @@ -64,6 +64,20 @@ FlutterDesktopViewRef FlutterDesktopViewControllerGetView( return reinterpret_cast(1); } +bool FlutterDesktopViewControllerHandleTopLevelWindowProc( + FlutterDesktopViewControllerRef controller, + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + if (s_stub_implementation) { + return s_stub_implementation->ViewControllerHandleTopLevelWindowProc( + hwnd, message, wparam, lparam, result); + } + return false; +} + FlutterDesktopEngineRef FlutterDesktopEngineCreate( const FlutterDesktopEngineProperties& engine_properties) { if (s_stub_implementation) { @@ -119,3 +133,23 @@ FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView( // The stub ignores this, so just return an arbitrary non-zero value. return reinterpret_cast(1); } + +void FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate( + FlutterDesktopPluginRegistrarRef registrar, + FlutterDesktopWindowProcCallback delegate, + void* user_data) { + if (s_stub_implementation) { + return s_stub_implementation + ->PluginRegistrarRegisterTopLevelWindowProcDelegate(delegate, + user_data); + } +} + +void FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate( + FlutterDesktopPluginRegistrarRef registrar, + FlutterDesktopWindowProcCallback delegate) { + if (s_stub_implementation) { + return s_stub_implementation + ->PluginRegistrarUnregisterTopLevelWindowProcDelegate(delegate); + } +} diff --git a/engine/src/flutter/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.h b/engine/src/flutter/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.h index 310e07f4e27..532abff1967 100644 --- a/engine/src/flutter/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.h +++ b/engine/src/flutter/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.h @@ -38,6 +38,15 @@ class StubFlutterWindowsApi { // Called for FlutterDesktopViewControllerDestroy. virtual void ViewControllerDestroy() {} + // Called for FlutterDesktopViewControllerHandleTopLevelWindowProc. + virtual bool ViewControllerHandleTopLevelWindowProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + return false; + } + // Called for FlutterDesktopEngineCreate. virtual FlutterDesktopEngineRef EngineCreate( const FlutterDesktopEngineProperties& engine_properties) { @@ -55,6 +64,16 @@ class StubFlutterWindowsApi { // Called for FlutterDesktopViewGetHWND. virtual HWND ViewGetHWND() { return reinterpret_cast(1); } + + // Called for FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate. + virtual void PluginRegistrarRegisterTopLevelWindowProcDelegate( + FlutterDesktopWindowProcCallback delegate, + void* user_data) {} + + // Called for + // FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate. + virtual void PluginRegistrarUnregisterTopLevelWindowProcDelegate( + FlutterDesktopWindowProcCallback delegate) {} }; // A test helper that owns a stub implementation, making it the test stub for diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows.cc b/engine/src/flutter/shell/platform/windows/flutter_windows.cc index 57b641e3fc8..f35a4c163ea 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows.cc @@ -83,6 +83,23 @@ FlutterDesktopViewRef FlutterDesktopViewControllerGetView( return controller->view_wrapper.get(); } +bool FlutterDesktopViewControllerHandleTopLevelWindowProc( + FlutterDesktopViewControllerRef controller, + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + std::optional delegate_result = + controller->view->GetEngine() + ->window_proc_delegate_manager() + ->OnTopLevelWindowProc(hwnd, message, wparam, lparam); + if (delegate_result) { + *result = *delegate_result; + } + return delegate_result.has_value(); +} + FlutterDesktopEngineRef FlutterDesktopEngineCreate( const FlutterDesktopEngineProperties& engine_properties) { flutter::FlutterProjectBundle project(engine_properties); @@ -133,6 +150,21 @@ FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView( return registrar->view.get(); } +void FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate( + FlutterDesktopPluginRegistrarRef registrar, + FlutterDesktopWindowProcCallback delegate, + void* user_data) { + registrar->engine->window_proc_delegate_manager() + ->RegisterTopLevelWindowProcDelegate(delegate, user_data); +} + +void FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate( + FlutterDesktopPluginRegistrarRef registrar, + FlutterDesktopWindowProcCallback delegate) { + registrar->engine->window_proc_delegate_manager() + ->UnregisterTopLevelWindowProcDelegate(delegate); +} + UINT FlutterDesktopGetDpiForHWND(HWND hwnd) { return flutter::GetDpiForHWND(hwnd); } @@ -156,7 +188,7 @@ void FlutterDesktopResyncOutputStreams() { FlutterDesktopMessengerRef FlutterDesktopRegistrarGetMessenger( FlutterDesktopPluginRegistrarRef registrar) { - return registrar->messenger; + return registrar->engine->messenger(); } void FlutterDesktopRegistrarSetDestructionHandler( diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc index 58c45e067a8..5090eb4c355 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc @@ -111,8 +111,11 @@ FlutterWindowsEngine::FlutterWindowsEngine(const FlutterProjectBundle& project) messenger_->dispatcher = message_dispatcher_.get(); plugin_registrar_ = std::make_unique(); - plugin_registrar_->messenger = messenger_.get(); + plugin_registrar_->engine = this; plugin_registrar_->view = std::make_unique(); + + window_proc_delegate_manager_ = + std::make_unique(); } FlutterWindowsEngine::~FlutterWindowsEngine() { @@ -190,7 +193,6 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { return false; } - plugin_registrar_->messenger->engine = engine_; SendSystemSettings(); return true; diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h index 520ee42fae8..7381eb162e6 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h @@ -5,13 +5,16 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOWS_ENGINE_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_FLUTTER_WINDOWS_ENGINE_H_ +#include #include +#include #include #include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h" #include "flutter/shell/platform/windows/flutter_project_bundle.h" #include "flutter/shell/platform/windows/public/flutter_windows.h" #include "flutter/shell/platform/windows/win32_task_runner.h" +#include "flutter/shell/platform/windows/win32_window_proc_delegate_manager.h" #include "flutter/shell/platform/windows/window_state.h" namespace flutter { @@ -64,6 +67,10 @@ class FlutterWindowsEngine { Win32TaskRunner* task_runner() { return task_runner_.get(); } + Win32WindowProcDelegateManager* window_proc_delegate_manager() { + return window_proc_delegate_manager_.get(); + } + // Callback passed to Flutter engine for notifying window of platform // messages. void HandlePlatformMessage(const FlutterPlatformMessage*); @@ -97,6 +104,9 @@ class FlutterWindowsEngine { // The plugin registrar handle given to API clients. std::unique_ptr plugin_registrar_; + + // The manager for WindowProc delegate registration and callbacks. + std::unique_ptr window_proc_delegate_manager_; }; } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/public/flutter_windows.h b/engine/src/flutter/shell/platform/windows/public/flutter_windows.h index 7cd0b89f2fb..56eefb281ff 100644 --- a/engine/src/flutter/shell/platform/windows/public/flutter_windows.h +++ b/engine/src/flutter/shell/platform/windows/public/flutter_windows.h @@ -93,6 +93,19 @@ FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopViewControllerGetEngine( FLUTTER_EXPORT FlutterDesktopViewRef FlutterDesktopViewControllerGetView(FlutterDesktopViewControllerRef controller); +// Allows the Flutter engine and any interested plugins an opportunity to +// handle the given message. +// +// If the WindowProc was handled and further handling should stop, this returns +// true and |result| will be populated. |result| is not set if returning false. +FLUTTER_EXPORT bool FlutterDesktopViewControllerHandleTopLevelWindowProc( + FlutterDesktopViewControllerRef controller, + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result); + // ========== Engine ========== // Creates a Flutter engine with the given properties. @@ -146,12 +159,38 @@ FlutterDesktopEngineGetMessenger(FlutterDesktopEngineRef engine); FLUTTER_EXPORT HWND FlutterDesktopViewGetHWND(FlutterDesktopViewRef view); // ========== Plugin Registrar (extensions) ========== +// These are Windows-specific extensions to flutter_plugin_registrar.h -// Returns the view associated with this registrar's engine instance -// This is a Windows-specific extension to flutter_plugin_registrar.h. +// Function pointer type for top level WindowProc delegate registration. +// +// The user data will be whatever was passed to +// FlutterDesktopRegisterTopLevelWindowProcHandler. +// +// Implementations should populate |result| and return true if the WindowProc +// was handled and further handling should stop. |result| is ignored if the +// function returns false. +typedef bool (*FlutterDesktopWindowProcCallback)(HWND /* hwnd */, + UINT /* uMsg */, + WPARAM /*wParam*/, + LPARAM /* lParam*/, + void* /* user data */, + LRESULT* result); + +// Returns the view associated with this registrar's engine instance. FLUTTER_EXPORT FlutterDesktopViewRef FlutterDesktopPluginRegistrarGetView( FlutterDesktopPluginRegistrarRef registrar); +FLUTTER_EXPORT void +FlutterDesktopPluginRegistrarRegisterTopLevelWindowProcDelegate( + FlutterDesktopPluginRegistrarRef registrar, + FlutterDesktopWindowProcCallback delegate, + void* user_data); + +FLUTTER_EXPORT void +FlutterDesktopPluginRegistrarUnregisterTopLevelWindowProcDelegate( + FlutterDesktopPluginRegistrarRef registrar, + FlutterDesktopWindowProcCallback delegate); + // ========== Freestanding Utilities ========== // Gets the DPI for a given |hwnd|, depending on the supported APIs per diff --git a/engine/src/flutter/shell/platform/windows/win32_dpi_utils_unittests.cc b/engine/src/flutter/shell/platform/windows/win32_dpi_utils_unittests.cc index 90a580bb953..c580a9d5580 100644 --- a/engine/src/flutter/shell/platform/windows/win32_dpi_utils_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/win32_dpi_utils_unittests.cc @@ -1,3 +1,7 @@ +// 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/win32_dpi_utils.h" diff --git a/engine/src/flutter/shell/platform/windows/win32_flutter_window_unittests.cc b/engine/src/flutter/shell/platform/windows/win32_flutter_window_unittests.cc index dece5a280f8..ff3a5d81f8c 100644 --- a/engine/src/flutter/shell/platform/windows/win32_flutter_window_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/win32_flutter_window_unittests.cc @@ -1,3 +1,7 @@ +// 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/win32_flutter_window_test.h" #include "gtest/gtest.h" diff --git a/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager.cc b/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager.cc new file mode 100644 index 00000000000..3d70feb2556 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager.cc @@ -0,0 +1,42 @@ +// 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/win32_window_proc_delegate_manager.h" + +#include "flutter/shell/platform/embedder/embedder.h" + +namespace flutter { + +Win32WindowProcDelegateManager::Win32WindowProcDelegateManager() = default; +Win32WindowProcDelegateManager::~Win32WindowProcDelegateManager() = default; + +void Win32WindowProcDelegateManager::RegisterTopLevelWindowProcDelegate( + FlutterDesktopWindowProcCallback delegate, + void* user_data) { + top_level_window_proc_handlers_[delegate] = user_data; +} + +void Win32WindowProcDelegateManager::UnregisterTopLevelWindowProcDelegate( + FlutterDesktopWindowProcCallback delegate) { + top_level_window_proc_handlers_.erase(delegate); +} + +std::optional Win32WindowProcDelegateManager::OnTopLevelWindowProc( + HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + std::optional result; + for (const auto& [handler, user_data] : top_level_window_proc_handlers_) { + LPARAM handler_result; + // Stop as soon as any delegate indicates that it has handled the message. + if (handler(hwnd, message, wparam, lparam, user_data, &handler_result)) { + result = handler_result; + break; + } + } + return result; +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager.h b/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager.h new file mode 100644 index 00000000000..a87aad23967 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager.h @@ -0,0 +1,58 @@ +// 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_WIN32_WINDOW_PROC_DELEGATE_MANAGER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_WIN32_WINDOW_PROC_DELEGATE_MANAGER_H_ + +#include + +#include +#include + +#include "flutter/shell/platform/windows/public/flutter_windows.h" + +namespace flutter { + +// Handles registration, unregistration, and dispatching for WindowProc +// delegation. +class Win32WindowProcDelegateManager { + public: + explicit Win32WindowProcDelegateManager(); + ~Win32WindowProcDelegateManager(); + + // Prevent copying. + Win32WindowProcDelegateManager(Win32WindowProcDelegateManager const&) = + delete; + Win32WindowProcDelegateManager& operator=( + Win32WindowProcDelegateManager const&) = delete; + + // Adds |delegate| as a delegate to be called for |OnTopLevelWindowProc|. + // + // Multiple calls with the same |delegate| will replace the previous + // registration, even if |user_data| is different. + void RegisterTopLevelWindowProcDelegate( + FlutterDesktopWindowProcCallback delegate, + void* user_data); + + // Unregisters |delegate| as a delate for |OnTopLevelWindowProc|. + void UnregisterTopLevelWindowProcDelegate( + FlutterDesktopWindowProcCallback delegate); + + // Calls any registered WindowProc delegates. + // + // If a result is returned, then the message was handled in such a way that + // further handling should not be done. + std::optional OnTopLevelWindowProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam); + + private: + std::map + top_level_window_proc_handlers_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_WIN32_WINDOW_PROC_DELEGATE_MANAGER_H_ diff --git a/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager_unittests.cc b/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager_unittests.cc new file mode 100644 index 00000000000..27e5c77ae0a --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/win32_window_proc_delegate_manager_unittests.cc @@ -0,0 +1,168 @@ +// 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/win32_window_proc_delegate_manager.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +namespace { + +using TestWindowProcDelegate = std::function(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)>; + +// A FlutterDesktopWindowProcCallback that forwards to a std::function provided +// as user_data. +bool TestWindowProcCallback(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + void* user_data, + LRESULT* result) { + TestWindowProcDelegate& delegate = + *static_cast(user_data); + auto delegate_result = delegate(hwnd, message, wparam, lparam); + if (delegate_result) { + *result = *delegate_result; + } + return delegate_result.has_value(); +} + +// Same as the above, but with a different address, to test multiple +// registration. +bool TestWindowProcCallback2(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + void* user_data, + LRESULT* result) { + return TestWindowProcCallback(hwnd, message, wparam, lparam, user_data, + result); +} + +} // namespace + +TEST(Win32WindowProcDelegateManagerTest, CallsCorrectly) { + Win32WindowProcDelegateManager manager; + HWND dummy_hwnd; + + bool called = false; + TestWindowProcDelegate delegate = [&called, &dummy_hwnd]( + HWND hwnd, UINT message, WPARAM wparam, + LPARAM lparam) { + called = true; + EXPECT_EQ(hwnd, dummy_hwnd); + EXPECT_EQ(message, 2); + EXPECT_EQ(wparam, 3); + EXPECT_EQ(lparam, 4); + return std::optional(); + }; + manager.RegisterTopLevelWindowProcDelegate(TestWindowProcCallback, &delegate); + auto result = manager.OnTopLevelWindowProc(dummy_hwnd, 2, 3, 4); + + EXPECT_TRUE(called); + EXPECT_FALSE(result); +} + +TEST(Win32WindowProcDelegateManagerTest, ReplacementRegister) { + Win32WindowProcDelegateManager manager; + + bool called_a = false; + TestWindowProcDelegate delegate_a = + [&called_a](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + called_a = true; + return std::optional(); + }; + bool called_b = false; + TestWindowProcDelegate delegate_b = + [&called_b](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + called_b = true; + return std::optional(); + }; + manager.RegisterTopLevelWindowProcDelegate(TestWindowProcCallback, + &delegate_a); + // The function pointer is the same, so this should replace, not add. + manager.RegisterTopLevelWindowProcDelegate(TestWindowProcCallback, + &delegate_b); + manager.OnTopLevelWindowProc(nullptr, 0, 0, 0); + + EXPECT_FALSE(called_a); + EXPECT_TRUE(called_b); +} + +TEST(Win32WindowProcDelegateManagerTest, RegisterMultiple) { + Win32WindowProcDelegateManager manager; + + bool called_a = false; + TestWindowProcDelegate delegate_a = + [&called_a](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + called_a = true; + return std::optional(); + }; + bool called_b = false; + TestWindowProcDelegate delegate_b = + [&called_b](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + called_b = true; + return std::optional(); + }; + manager.RegisterTopLevelWindowProcDelegate(TestWindowProcCallback, + &delegate_a); + // Function pointer is different, so both should be called. + manager.RegisterTopLevelWindowProcDelegate(TestWindowProcCallback2, + &delegate_b); + manager.OnTopLevelWindowProc(nullptr, 0, 0, 0); + + EXPECT_TRUE(called_a); + EXPECT_TRUE(called_b); +} + +TEST(Win32WindowProcDelegateManagerTest, ConflictingDelegates) { + Win32WindowProcDelegateManager manager; + + bool called_a = false; + TestWindowProcDelegate delegate_a = + [&called_a](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + called_a = true; + return std::optional(1); + }; + bool called_b = false; + TestWindowProcDelegate delegate_b = + [&called_b](HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + called_b = true; + return std::optional(1); + }; + manager.RegisterTopLevelWindowProcDelegate(TestWindowProcCallback, + &delegate_a); + manager.RegisterTopLevelWindowProcDelegate(TestWindowProcCallback2, + &delegate_b); + auto result = manager.OnTopLevelWindowProc(nullptr, 0, 0, 0); + + EXPECT_TRUE(result); + // Exactly one of the handlers should be called since each will claim to have + // handled the message. Which one is unspecified, since the calling order is + // unspecified. + EXPECT_TRUE(called_a || called_b); + EXPECT_NE(called_a, called_b); +} + +TEST(Win32WindowProcDelegateManagerTest, Unregister) { + Win32WindowProcDelegateManager manager; + + bool called = false; + TestWindowProcDelegate delegate = [&called](HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam) { + called = true; + return std::optional(); + }; + manager.RegisterTopLevelWindowProcDelegate(TestWindowProcCallback, &delegate); + manager.UnregisterTopLevelWindowProcDelegate(TestWindowProcCallback); + auto result = manager.OnTopLevelWindowProc(nullptr, 0, 0, 0); + + EXPECT_FALSE(result); + EXPECT_FALSE(called); +} + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/win32_window_unittests.cc b/engine/src/flutter/shell/platform/windows/win32_window_unittests.cc index 0adc0b03883..fea9faa4547 100644 --- a/engine/src/flutter/shell/platform/windows/win32_window_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/win32_window_unittests.cc @@ -1,3 +1,7 @@ +// 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/win32_window_test.h" #include "gtest/gtest.h" diff --git a/engine/src/flutter/shell/platform/windows/window_state.h b/engine/src/flutter/shell/platform/windows/window_state.h index 08fb5385050..c26a26680f0 100644 --- a/engine/src/flutter/shell/platform/windows/window_state.h +++ b/engine/src/flutter/shell/platform/windows/window_state.h @@ -34,8 +34,8 @@ struct FlutterDesktopView { // State associated with the plugin registrar. struct FlutterDesktopPluginRegistrar { - // The plugin messenger handle given to API clients. - FlutterDesktopMessenger* messenger; + // The engine that owns this state object. + flutter::FlutterWindowsEngine* engine = nullptr; // The handle for the view associated with this registrar. std::unique_ptr view;