diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files index 1b3514266fc..d9eeb5fc97d 100644 --- a/engine/src/flutter/ci/licenses_golden/excluded_files +++ b/engine/src/flutter/ci/licenses_golden/excluded_files @@ -421,6 +421,7 @@ ../../../flutter/shell/platform/windows/fixtures ../../../flutter/shell/platform/windows/flutter_project_bundle_unittests.cc ../../../flutter/shell/platform/windows/flutter_window_unittests.cc +../../../flutter/shell/platform/windows/display_monitor_unittests.cc ../../../flutter/shell/platform/windows/flutter_windows_engine_unittests.cc ../../../flutter/shell/platform/windows/flutter_windows_texture_registrar_unittests.cc ../../../flutter/shell/platform/windows/flutter_windows_unittests.cc diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 83ddfc87dec..7c1f30bd3a3 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -54075,6 +54075,8 @@ ORIGIN: ../../../flutter/shell/platform/windows/flutter_project_bundle.h + ../.. ORIGIN: ../../../flutter/shell/platform/windows/flutter_window.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_window.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/display_monitor.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/display_monitor.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_engine.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_engine.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_internal.h + ../../../flutter/LICENSE @@ -57165,6 +57167,8 @@ FILE: ../../../flutter/shell/platform/windows/flutter_project_bundle.h FILE: ../../../flutter/shell/platform/windows/flutter_window.cc FILE: ../../../flutter/shell/platform/windows/flutter_window.h FILE: ../../../flutter/shell/platform/windows/flutter_windows.cc +FILE: ../../../flutter/shell/platform/windows/display_monitor.cc +FILE: ../../../flutter/shell/platform/windows/display_monitor.h FILE: ../../../flutter/shell/platform/windows/flutter_windows_engine.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_engine.h FILE: ../../../flutter/shell/platform/windows/flutter_windows_internal.h diff --git a/engine/src/flutter/shell/common/display_manager.cc b/engine/src/flutter/shell/common/display_manager.cc index e9382028dc6..81f276efa66 100644 --- a/engine/src/flutter/shell/common/display_manager.cc +++ b/engine/src/flutter/shell/common/display_manager.cc @@ -24,7 +24,6 @@ double DisplayManager::GetMainDisplayRefreshRate() const { void DisplayManager::HandleDisplayUpdates( std::vector> displays) { - FML_DCHECK(!displays.empty()); std::scoped_lock lock(displays_mutex_); displays_ = std::move(displays); } diff --git a/engine/src/flutter/shell/platform/windows/BUILD.gn b/engine/src/flutter/shell/platform/windows/BUILD.gn index 7b31e8570d1..036106dfe16 100644 --- a/engine/src/flutter/shell/platform/windows/BUILD.gn +++ b/engine/src/flutter/shell/platform/windows/BUILD.gn @@ -52,6 +52,8 @@ source_set("flutter_windows_source") { "cursor_handler.h", "direct_manipulation.cc", "direct_manipulation.h", + "display_monitor.cc", + "display_monitor.h", "dpi_utils.cc", "dpi_utils.h", "egl/context.cc", @@ -206,6 +208,7 @@ executable("flutter_windows_unittests") { "compositor_software_unittests.cc", "cursor_handler_unittests.cc", "direct_manipulation_unittests.cc", + "display_monitor_unittests.cc", "dpi_utils_unittests.cc", "flutter_project_bundle_unittests.cc", "flutter_window_unittests.cc", diff --git a/engine/src/flutter/shell/platform/windows/display_monitor.cc b/engine/src/flutter/shell/platform/windows/display_monitor.cc new file mode 100644 index 00000000000..f2831448d72 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/display_monitor.cc @@ -0,0 +1,104 @@ +// 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 "display_monitor.h" + +#include + +#include "flutter/shell/platform/windows/dpi_utils.h" +#include "flutter/shell/platform/windows/flutter_windows_engine.h" + +namespace flutter { + +namespace { + +// Data structure to pass to the display enumeration callback. +struct MonitorEnumState { + const DisplayMonitor* display_monitor; + std::vector* displays; +}; + +} // namespace + +DisplayMonitor::DisplayMonitor(FlutterWindowsEngine* engine) + : engine_(engine), win32_(engine->windows_proc_table()) {} + +DisplayMonitor::~DisplayMonitor() {} + +BOOL CALLBACK DisplayMonitor::EnumMonitorCallback(HMONITOR monitor, + HDC hdc, + LPRECT rect, + LPARAM data) { + MonitorEnumState* state = reinterpret_cast(data); + const DisplayMonitor* self = state->display_monitor; + std::vector* displays = state->displays; + + MONITORINFOEXW monitor_info = {}; + monitor_info.cbSize = sizeof(monitor_info); + if (self->win32_->GetMonitorInfoW(monitor, &monitor_info) == 0) { + // Return TRUE to continue enumeration and skip this monitor. + // Returning FALSE would stop the entire enumeration process, + // potentially missing other valid monitors. + return TRUE; + } + + DEVMODEW dev_mode = {}; + dev_mode.dmSize = sizeof(dev_mode); + if (!self->win32_->EnumDisplaySettingsW(monitor_info.szDevice, + ENUM_CURRENT_SETTINGS, &dev_mode)) { + // Return TRUE to continue enumeration and skip this monitor. + // Returning FALSE would stop the entire enumeration process, + // potentially missing other valid monitors. + return TRUE; + } + + UINT dpi = GetDpiForMonitor(monitor); + + FlutterEngineDisplay display = {}; + display.struct_size = sizeof(FlutterEngineDisplay); + display.display_id = reinterpret_cast(monitor); + display.single_display = false; + display.refresh_rate = dev_mode.dmDisplayFrequency; + display.width = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left; + display.height = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top; + display.device_pixel_ratio = + static_cast(dpi) / static_cast(kDefaultDpi); + + displays->push_back(display); + return TRUE; +} + +void DisplayMonitor::UpdateDisplays() { + auto displays = GetDisplays(); + engine_->UpdateDisplay(displays); +} + +bool DisplayMonitor::HandleWindowMessage(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + switch (message) { + case WM_DISPLAYCHANGE: + case WM_DPICHANGED: + UpdateDisplays(); + break; + } + return false; +} + +std::vector DisplayMonitor::GetDisplays() const { + std::vector displays; + MonitorEnumState state = {this, &displays}; + win32_->EnumDisplayMonitors(nullptr, nullptr, EnumMonitorCallback, + reinterpret_cast(&state)); + + if (displays.size() == 1) { + displays[0].single_display = true; + } + + return displays; +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/display_monitor.h b/engine/src/flutter/shell/platform/windows/display_monitor.h new file mode 100644 index 00000000000..ee7e44956bd --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/display_monitor.h @@ -0,0 +1,49 @@ +// 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_DISPLAY_MONITOR_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_DISPLAY_MONITOR_H_ + +#include +#include +#include + +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/windows_proc_table.h" + +namespace flutter { + +class FlutterWindowsEngine; +class DisplayMonitor { + public: + explicit DisplayMonitor(FlutterWindowsEngine* engine); + ~DisplayMonitor(); + + // Updates the display information and notifies the engine + void UpdateDisplays(); + + // Handles Windows messages related to display changes + // Returns true if the message was handled and should not be further processed + bool HandleWindowMessage(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result); + + // Get the display information for all displays + std::vector GetDisplays() const; + + private: + // Called by EnumDisplayMonitors once for each display. + static BOOL CALLBACK EnumMonitorCallback(HMONITOR monitor, + HDC hdc, + LPRECT rect, + LPARAM data); + + FlutterWindowsEngine* engine_; + + std::shared_ptr win32_; +}; +} // namespace flutter +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_DISPLAY_MONITOR_H_ diff --git a/engine/src/flutter/shell/platform/windows/display_monitor_unittests.cc b/engine/src/flutter/shell/platform/windows/display_monitor_unittests.cc new file mode 100644 index 00000000000..6988fff2829 --- /dev/null +++ b/engine/src/flutter/shell/platform/windows/display_monitor_unittests.cc @@ -0,0 +1,123 @@ +// 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/display_monitor.h" + +#include +#include "flutter/shell/platform/windows/testing/flutter_windows_engine_builder.h" +#include "flutter/shell/platform/windows/testing/windows_test.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "shell/platform/windows/testing/mock_windows_proc_table.h" + +// Mock Windows API functions to avoid hardware dependencies +#define MOCK_WINDOWS_API + +namespace flutter { +namespace testing { + +using ::testing::_; +using ::testing::AllOf; +using ::testing::AtLeast; +using ::testing::DoAll; +using ::testing::Field; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::StrEq; + +class DisplayMonitorTest : public WindowsTest {}; + +// Test that the display monitor correctly handles multiple monitors +TEST_F(DisplayMonitorTest, MultipleMonitors) { + auto mock_windows_proc_table = + std::make_shared>(); + + FlutterWindowsEngineBuilder builder(GetContext()); + builder.SetWindowsProcTable(mock_windows_proc_table); + std::unique_ptr engine = builder.Build(); + + HMONITOR mock_monitor1 = reinterpret_cast(123); + HMONITOR mock_monitor2 = reinterpret_cast(456); + + MONITORINFOEXW monitor_info1 = {}; + monitor_info1.cbSize = sizeof(MONITORINFOEXW); + monitor_info1.rcMonitor = {0, 0, 1920, 1080}; + monitor_info1.rcWork = {0, 0, 1920, 1080}; + monitor_info1.dwFlags = MONITORINFOF_PRIMARY; + wcscpy_s(monitor_info1.szDevice, L"\\\\.\\DISPLAY1"); + + MONITORINFOEXW monitor_info2 = {}; + monitor_info2.cbSize = sizeof(MONITORINFOEXW); + monitor_info2.rcMonitor = {1920, 0, 1920 + 2560, 1440}; + monitor_info2.rcWork = {1920, 0, 1920 + 2560, 1440}; + monitor_info2.dwFlags = 0; + wcscpy_s(monitor_info2.szDevice, L"\\\\.\\DISPLAY2"); + + EXPECT_CALL(*mock_windows_proc_table, GetMonitorInfoW(mock_monitor1, _)) + .WillOnce(DoAll(SetArgPointee<1>(monitor_info1), Return(TRUE))); + EXPECT_CALL(*mock_windows_proc_table, GetMonitorInfoW(mock_monitor2, _)) + .WillOnce(DoAll(SetArgPointee<1>(monitor_info2), Return(TRUE))); + + EXPECT_CALL(*mock_windows_proc_table, + EnumDisplayMonitors(nullptr, nullptr, _, _)) + .WillOnce([&](HDC hdc, LPCRECT lprcClip, MONITORENUMPROC lpfnEnum, + LPARAM dwData) { + lpfnEnum(mock_monitor1, nullptr, &monitor_info1.rcMonitor, dwData); + lpfnEnum(mock_monitor2, nullptr, &monitor_info2.rcMonitor, dwData); + return TRUE; + }); + + // Set up GetDpiForMonitor to return different DPI values + EXPECT_CALL(*mock_windows_proc_table, GetDpiForMonitor(mock_monitor1, _)) + .WillRepeatedly(Return(96)); // Default/Standard DPI + EXPECT_CALL(*mock_windows_proc_table, GetDpiForMonitor(mock_monitor2, _)) + .WillRepeatedly(Return(144)); // High DPI + + EXPECT_CALL(*mock_windows_proc_table, EnumDisplaySettings(_, _, _)) + .WillRepeatedly(Return(TRUE)); + + // Create the display monitor with the mock engine + auto display_monitor = std::make_unique(engine.get()); + + display_monitor->UpdateDisplays(); +} + +// Test that the display monitor correctly handles a display change message +TEST_F(DisplayMonitorTest, HandleDisplayChangeMessage) { + // Create a mock Windows proc table + auto mock_windows_proc_table = + std::make_shared>(); + + // Create a mock engine + FlutterWindowsEngineBuilder builder(GetContext()); + builder.SetWindowsProcTable(mock_windows_proc_table); + std::unique_ptr engine = builder.Build(); + + EXPECT_CALL(*mock_windows_proc_table, EnumDisplayMonitors(_, _, _, _)) + .WillRepeatedly(Return(TRUE)); + + // Create the display monitor with the mock engine + auto display_monitor = std::make_unique(engine.get()); + + // Test handling a display change message + HWND dummy_hwnd = reinterpret_cast(1); + LRESULT result = 0; + + // Verify that WM_DISPLAYCHANGE is handled + EXPECT_FALSE(display_monitor->HandleWindowMessage( + dummy_hwnd, WM_DISPLAYCHANGE, 0, 0, &result)); + + // Verify that WM_DPICHANGED is handled + EXPECT_FALSE(display_monitor->HandleWindowMessage(dummy_hwnd, WM_DPICHANGED, + 0, 0, &result)); + + // Verify that other messages are not handled + EXPECT_FALSE(display_monitor->HandleWindowMessage(dummy_hwnd, WM_PAINT, 0, 0, + &result)); +} + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/dpi_utils.cc b/engine/src/flutter/shell/platform/windows/dpi_utils.cc index 81fece664d1..f4e6b93e93d 100644 --- a/engine/src/flutter/shell/platform/windows/dpi_utils.cc +++ b/engine/src/flutter/shell/platform/windows/dpi_utils.cc @@ -10,8 +10,6 @@ namespace flutter { namespace { -constexpr UINT kDefaultDpi = 96; - // This is the MDT_EFFECTIVE_DPI value from MONITOR_DPI_TYPE, an enum declared // in ShellScalingApi.h. Replicating here to avoid importing the library // directly. diff --git a/engine/src/flutter/shell/platform/windows/dpi_utils.h b/engine/src/flutter/shell/platform/windows/dpi_utils.h index 1c14994b612..00f6a29b942 100644 --- a/engine/src/flutter/shell/platform/windows/dpi_utils.h +++ b/engine/src/flutter/shell/platform/windows/dpi_utils.h @@ -9,6 +9,8 @@ namespace flutter { +constexpr UINT kDefaultDpi = 96; + /// Returns the DPI for |hwnd|. Supports all DPI awareness modes, and is /// backward compatible down to Windows Vista. If |hwnd| is nullptr, returns the /// DPI for the primary monitor. If Per-Monitor DPI awareness is not available, 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 7a80bbd889e..f9099c6dc1a 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc @@ -21,6 +21,7 @@ #include "flutter/shell/platform/windows/accessibility_bridge_windows.h" #include "flutter/shell/platform/windows/compositor_opengl.h" #include "flutter/shell/platform/windows/compositor_software.h" +#include "flutter/shell/platform/windows/display_monitor.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" @@ -199,12 +200,22 @@ FlutterWindowsEngine::FlutterWindowsEngine( egl_manager_ = egl::Manager::Create( static_cast(project_->gpu_preference())); window_proc_delegate_manager_ = std::make_unique(); + + display_monitor_ = std::make_unique(this); + window_proc_delegate_manager_->RegisterTopLevelWindowProcDelegate( [](HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar, void* user_data, LRESULT* result) { BASE_DCHECK(user_data); FlutterWindowsEngine* that = static_cast(user_data); + + BASE_DCHECK(that->display_monitor_); + if (that->display_monitor_->HandleWindowMessage(hwnd, msg, wpar, lpar, + result)) { + return true; + } + BASE_DCHECK(that->lifecycle_manager_); bool handled = that->lifecycle_manager_->WindowProc(hwnd, msg, wpar, lpar, result); @@ -486,18 +497,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { return false; } - // Configure device frame rate displayed via devtools. - FlutterEngineDisplay display = {}; - display.struct_size = sizeof(FlutterEngineDisplay); - display.display_id = 0; - display.single_display = true; - display.refresh_rate = - 1.0 / (static_cast(FrameInterval().count()) / 1000000000.0); - - std::vector displays = {display}; - embedder_api_.NotifyDisplayUpdate(engine_, - kFlutterEngineDisplaysUpdateTypeStartup, - displays.data(), displays.size()); + display_monitor_->UpdateDisplays(); SendSystemLocales(); @@ -707,6 +707,15 @@ void FlutterWindowsEngine::AddPluginRegistrarDestructionCallback( plugin_registrar_destruction_callbacks_[callback] = registrar; } +void FlutterWindowsEngine::UpdateDisplay( + const std::vector& displays) { + if (engine_) { + embedder_api_.NotifyDisplayUpdate(engine_, + kFlutterEngineDisplaysUpdateTypeStartup, + displays.data(), displays.size()); + } +} + void FlutterWindowsEngine::SendWindowMetricsEvent( const FlutterWindowMetricsEvent& event) { if (engine_) { @@ -1043,8 +1052,11 @@ void FlutterWindowsEngine::OnQuit(std::optional hwnd, } void FlutterWindowsEngine::OnDwmCompositionChanged() { - std::shared_lock read_lock(views_mutex_); + if (display_monitor_) { + display_monitor_->UpdateDisplays(); + } + std::shared_lock read_lock(views_mutex_); for (auto iterator = views_.begin(); iterator != views_.end(); iterator++) { iterator->second->OnDwmCompositionChanged(); } @@ -1113,4 +1125,17 @@ bool FlutterWindowsEngine::Present(const FlutterPresentViewInfo* info) { return compositor_->Present(view, info->layers, info->layers_count); } +bool FlutterWindowsEngine::HandleDisplayMonitorMessage(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result) { + if (!display_monitor_) { + return false; + } + + return display_monitor_->HandleWindowMessage(hwnd, message, wparam, lparam, + result); +} + } // namespace flutter 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 40a8f873873..d2f266b9f72 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine.h @@ -56,6 +56,7 @@ namespace flutter { constexpr FlutterViewId kImplicitViewId = 0; class FlutterWindowsView; +class DisplayMonitor; // Update the thread priority for the Windows engine. static void WindowsPlatformThreadPrioritySetter( @@ -159,6 +160,11 @@ class FlutterWindowsEngine { return message_dispatcher_.get(); } + DisplayMonitor* display_monitor() { return display_monitor_.get(); } + + // Notifies the engine about a display update. + void UpdateDisplay(const std::vector& displays); + TaskRunner* task_runner() { return task_runner_.get(); } BinaryMessenger* messenger_wrapper() { return messenger_wrapper_.get(); } @@ -418,6 +424,9 @@ class FlutterWindowsEngine { // a view to the engine or after removing a view from the engine. mutable std::shared_mutex views_mutex_; + // The display monitor. + std::unique_ptr display_monitor_; + // Task runner for tasks posted from the engine. std::unique_ptr task_runner_; @@ -510,6 +519,13 @@ class FlutterWindowsEngine { std::unique_ptr platform_view_plugin_; + // Handles display-related window messages. + bool HandleDisplayMonitorMessage(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam, + LRESULT* result); + FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindowsEngine); }; diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc index 5c00bab1764..a6976f993a9 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc @@ -43,8 +43,11 @@ void PumpMessage() { namespace flutter { namespace testing { +using ::testing::_; +using ::testing::DoAll; using ::testing::NiceMock; using ::testing::Return; +using ::testing::SetArgPointee; class FlutterWindowsEngineTest : public WindowsTest {}; @@ -66,6 +69,57 @@ TEST_F(FlutterWindowsEngineTest, RunDoesExpectedInitialization) { builder.AddDartEntrypointArgument("arg1"); builder.AddDartEntrypointArgument("arg2"); + auto windows_proc_table = std::make_shared(); + + HMONITOR mock_monitor = reinterpret_cast(1); + + MONITORINFOEXW monitor_info = {}; + monitor_info.cbSize = sizeof(MONITORINFOEXW); + monitor_info.rcMonitor = {0, 0, 1920, 1080}; + monitor_info.rcWork = {0, 0, 1920, 1080}; + monitor_info.dwFlags = MONITORINFOF_PRIMARY; + wcscpy_s(monitor_info.szDevice, L"\\\\.\\DISPLAY1"); + + EXPECT_CALL(*windows_proc_table, GetMonitorInfoW(mock_monitor, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(monitor_info), Return(TRUE))); + + EXPECT_CALL(*windows_proc_table, EnumDisplayMonitors(nullptr, nullptr, _, _)) + .WillRepeatedly([&](HDC hdc, LPCRECT lprcClip, MONITORENUMPROC lpfnEnum, + LPARAM dwData) { + lpfnEnum(mock_monitor, nullptr, &monitor_info.rcMonitor, dwData); + return TRUE; + }); + + EXPECT_CALL(*windows_proc_table, GetDpiForMonitor(mock_monitor, _)) + .WillRepeatedly(Return(96)); + + // Mock locale information + EXPECT_CALL(*windows_proc_table, GetThreadPreferredUILanguages(_, _, _, _)) + .WillRepeatedly( + [](DWORD flags, PULONG count, PZZWSTR languages, PULONG length) { + // We need to mock the locale information twice because the first + // call is to get the size and the second call is to fill the + // buffer. + if (languages == nullptr) { + // First call is to get the size + *count = 1; // One language + *length = 10; // "fr-FR\0\0" (double null-terminated) + return TRUE; + } else { + // Second call is to fill the buffer + *count = 1; + // Fill with "fr-FR\0\0" (double null-terminated) + wchar_t* lang_buffer = languages; + wcscpy(lang_buffer, L"fr-FR"); + // Move past the first null terminator to add the second + lang_buffer += wcslen(L"fr-FR") + 1; + *lang_buffer = L'\0'; + return TRUE; + } + }); + + builder.SetWindowsProcTable(windows_proc_table); + std::unique_ptr engine = builder.Build(); EngineModifier modifier(engine.get()); @@ -141,23 +195,15 @@ TEST_F(FlutterWindowsEngineTest, RunDoesExpectedInitialization) { // And it should send display info. bool notify_display_update_called = false; - modifier.SetFrameInterval(16600000); // 60 fps. + modifier.embedder_api().NotifyDisplayUpdate = MOCK_ENGINE_PROC( NotifyDisplayUpdate, - ([¬ify_display_update_called, engine_instance = engine.get()]( + ([¬ify_display_update_called]( FLUTTER_API_SYMBOL(FlutterEngine) raw_engine, const FlutterEngineDisplaysUpdateType update_type, const FlutterEngineDisplay* embedder_displays, size_t display_count) { EXPECT_EQ(update_type, kFlutterEngineDisplaysUpdateTypeStartup); - EXPECT_EQ(display_count, 1); - - FlutterEngineDisplay display = embedder_displays[0]; - - EXPECT_EQ(display.display_id, 0); - EXPECT_EQ(display.single_display, true); - EXPECT_EQ(std::floor(display.refresh_rate), 60.0); - notify_display_update_called = true; return kSuccess; })); diff --git a/engine/src/flutter/shell/platform/windows/testing/mock_windows_proc_table.h b/engine/src/flutter/shell/platform/windows/testing/mock_windows_proc_table.h index f7e47b9f603..0a8a9b322a8 100644 --- a/engine/src/flutter/shell/platform/windows/testing/mock_windows_proc_table.h +++ b/engine/src/flutter/shell/platform/windows/testing/mock_windows_proc_table.h @@ -41,6 +41,24 @@ class MockWindowsProcTable : public WindowsProcTable { MOCK_METHOD(HCURSOR, SetCursor, (HCURSOR cursor), (const, override)); + MOCK_METHOD(BOOL, + EnumDisplaySettings, + (LPCWSTR lpszDeviceName, DWORD iModeNum, DEVMODEW* lpDevMode), + (const, override)); + + MOCK_METHOD(BOOL, + GetMonitorInfo, + (HMONITOR hMonitor, LPMONITORINFO lpmi), + (const, override)); + + MOCK_METHOD( + BOOL, + EnumDisplayMonitors, + (HDC hdc, LPCRECT lprcClip, MONITORENUMPROC lpfnEnum, LPARAM dwData), + (const, override)); + + MOCK_METHOD(UINT, GetDpiForMonitor, (HMONITOR, UINT), ()); + private: FML_DISALLOW_COPY_AND_ASSIGN(MockWindowsProcTable); }; diff --git a/engine/src/flutter/shell/platform/windows/windows_proc_table.cc b/engine/src/flutter/shell/platform/windows/windows_proc_table.cc index 2be3c056c03..90f1c19a5e3 100644 --- a/engine/src/flutter/shell/platform/windows/windows_proc_table.cc +++ b/engine/src/flutter/shell/platform/windows/windows_proc_table.cc @@ -120,4 +120,33 @@ BOOL WindowsProcTable::AdjustWindowRectExForDpi(LPRECT lpRect, dwExStyle, dpi); } +int WindowsProcTable::GetSystemMetrics(int nIndex) const { + return ::GetSystemMetrics(nIndex); +} + +BOOL WindowsProcTable::EnumDisplayDevices(LPCWSTR lpDevice, + DWORD iDevNum, + PDISPLAY_DEVICE lpDisplayDevice, + DWORD dwFlags) const { + return ::EnumDisplayDevices(lpDevice, iDevNum, lpDisplayDevice, dwFlags); +} + +BOOL WindowsProcTable::EnumDisplaySettings(LPCWSTR lpszDeviceName, + DWORD iModeNum, + DEVMODEW* lpDevMode) const { + return ::EnumDisplaySettingsW(lpszDeviceName, iModeNum, lpDevMode); +} + +BOOL WindowsProcTable::GetMonitorInfo(HMONITOR hMonitor, + LPMONITORINFO lpmi) const { + return ::GetMonitorInfoW(hMonitor, lpmi); +} + +BOOL WindowsProcTable::EnumDisplayMonitors(HDC hdc, + LPCRECT lprcClip, + MONITORENUMPROC lpfnEnum, + LPARAM dwData) const { + return ::EnumDisplayMonitors(hdc, lprcClip, lpfnEnum, dwData); +} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/windows/windows_proc_table.h b/engine/src/flutter/shell/platform/windows/windows_proc_table.h index 9290e1302d1..d4348023124 100644 --- a/engine/src/flutter/shell/platform/windows/windows_proc_table.h +++ b/engine/src/flutter/shell/platform/windows/windows_proc_table.h @@ -123,6 +123,44 @@ class WindowsProcTable { DWORD dwExStyle, UINT dpi) const; + // Get the system metrics. + // + // See: + // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-getsystemmetrics + virtual int GetSystemMetrics(int nIndex) const; + + // Enumerate display devices. + // + // See: + // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-enumdisplaydevicesw + virtual BOOL EnumDisplayDevices(LPCWSTR lpDevice, + DWORD iDevNum, + PDISPLAY_DEVICE lpDisplayDevice, + DWORD dwFlags) const; + + // Enumerate display settings. + // + // See: + // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-enumdisplaysettingsw + virtual BOOL EnumDisplaySettings(LPCWSTR lpszDeviceName, + DWORD iModeNum, + DEVMODEW* lpDevMode) const; + + // Get monitor info. + // + // See: + // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-getmonitorinfow + virtual BOOL GetMonitorInfo(HMONITOR hMonitor, LPMONITORINFO lpmi) const; + + // Enumerate display monitors. + // + // See: + // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors + virtual BOOL EnumDisplayMonitors(HDC hdc, + LPCRECT lprcClip, + MONITORENUMPROC lpfnEnum, + LPARAM dwData) const; + private: using GetPointerType_ = BOOL __stdcall(UINT32 pointerId, POINTER_INPUT_TYPE* pointerType);