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;