From 784fa0842560b3d1f61e46d5545e4a08e7fa3c58 Mon Sep 17 00:00:00 2001 From: LiJianying Date: Fri, 29 May 2026 22:49:39 +0800 Subject: [PATCH 1/2] fix(windows): implement SetMinimumSize/SetMaximumSize via WM_GETMINMAXINFO The SetMinimumSize and SetMaximumSize methods on Windows were empty placeholder implementations that did nothing. This caused setting window size constraints to have no effect in Flutter apps and other consumers. Fix by: - Storing min/max size constraints in Window::Impl - Registering a WM_GETMINMAXINFO handler via WindowMessageDispatcher that reads the constraints from the Window object and sets ptMinTrackSize/ptMaxTrackSize on the MINMAXINFO structure - This works for both self-created windows and Flutter-hosted windows (via Window(void*) constructor) - Triggering SWP_FRAMECHANGED after setting constraints so the window immediately re-evaluates them - Properly cleaning up the handler in the destructor Closes libnativeapi/nativeapi-flutter#9 --- src/platform/windows/window_windows.cpp | 85 ++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/window_windows.cpp b/src/platform/windows/window_windows.cpp index 998a5b9..de80bff 100644 --- a/src/platform/windows/window_windows.cpp +++ b/src/platform/windows/window_windows.cpp @@ -7,6 +7,7 @@ #include "../../window_registry.h" #include "dpi_utils_windows.h" #include "string_utils_windows.h" +#include "window_message_dispatcher.h" #pragma comment(lib, "dwmapi.lib") @@ -30,6 +31,9 @@ class Window::Impl { WindowId window_id_; TitleBarStyle title_bar_style_; VisualEffect visual_effect_; + Size min_size_{0, 0}; + Size max_size_{0, 0}; + int min_max_handler_id_ = 0; }; // Custom window procedure to handle window messages @@ -184,8 +188,14 @@ Window::Window(void* native_window) { } Window::~Window() { - // Remove window from registry on destruction if (pimpl_ && pimpl_->window_id_ != IdAllocator::kInvalidId) { + // Unregister WM_GETMINMAXINFO handler if registered + if (pimpl_->min_max_handler_id_ != 0 && pimpl_->hwnd_) { + WindowMessageDispatcher::GetInstance().UnregisterHandler( + pimpl_->min_max_handler_id_); + } + + // Remove window from registry on destruction WindowRegistry::GetInstance().Remove(pimpl_->window_id_); // Remove the custom property from HWND if window is still valid @@ -453,25 +463,80 @@ Rectangle Window::GetContentBounds() const { return bounds; } +// Helper function: registers a WM_GETMINMAXINFO handler for the given HWND +// via WindowMessageDispatcher if not already registered. Returns the handler ID. +static int RegisterMinMaxInfoHandler(HWND hwnd, int existing_handler_id) { + if (existing_handler_id != 0) { + return existing_handler_id; + } + if (!hwnd || !IsWindow(hwnd)) { + return 0; + } + auto& dispatcher = WindowMessageDispatcher::GetInstance(); + return dispatcher.RegisterHandler( + hwnd, + [](HWND hwnd, UINT msg, WPARAM wparam, + LPARAM lparam) -> std::optional { + if (msg == WM_GETMINMAXINFO) { + HANDLE prop_handle = GetPropW(hwnd, kWindowIdProperty); + if (prop_handle) { + WindowId window_id = static_cast( + reinterpret_cast(prop_handle)); + if (window_id != IdAllocator::kInvalidId) { + auto window = WindowRegistry::GetInstance().Get(window_id); + if (window) { + auto minSize = window->GetMinimumSize(); + auto maxSize = window->GetMaximumSize(); + MINMAXINFO* mmi = reinterpret_cast(lparam); + if (minSize.width > 0 && minSize.height > 0) { + mmi->ptMinTrackSize.x = static_cast(minSize.width); + mmi->ptMinTrackSize.y = static_cast(minSize.height); + } + if (maxSize.width > 0 && maxSize.height > 0) { + mmi->ptMaxTrackSize.x = static_cast(maxSize.width); + mmi->ptMaxTrackSize.y = static_cast(maxSize.height); + } + return std::make_optional(0); + } + } + } + } + return std::nullopt; + }); +} + void Window::SetMinimumSize(Size size) { - // Windows minimum size would be handled in WM_GETMINMAXINFO message - // This is a placeholder implementation + pimpl_->min_size_ = size; + + if (pimpl_->hwnd_) { + pimpl_->min_max_handler_id_ = + RegisterMinMaxInfoHandler(pimpl_->hwnd_, pimpl_->min_max_handler_id_); + + // Trigger the window to re-evaluate its size constraints + SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); + } } Size Window::GetMinimumSize() const { - // Return default minimum size - return {0, 0}; + return pimpl_->min_size_; } void Window::SetMaximumSize(Size size) { - // Windows maximum size would be handled in WM_GETMINMAXINFO message - // This is a placeholder implementation + pimpl_->max_size_ = size; + + if (pimpl_->hwnd_) { + pimpl_->min_max_handler_id_ = + RegisterMinMaxInfoHandler(pimpl_->hwnd_, pimpl_->min_max_handler_id_); + + // Trigger the window to re-evaluate its size constraints + SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); + } } Size Window::GetMaximumSize() const { - // Return default maximum size (screen size) - return {static_cast(GetSystemMetrics(SM_CXSCREEN)), - static_cast(GetSystemMetrics(SM_CYSCREEN))}; + return pimpl_->max_size_; } void Window::SetResizable(bool is_resizable) { From 92b2ea8eeee92d3feabb53b82671e1f75fe1df60 Mon Sep 17 00:00:00 2001 From: LiJianying Date: Sat, 30 May 2026 10:23:11 +0800 Subject: [PATCH 2/2] Add per-monitor DPI scaling for Windows Use per-monitor DPI scaling across Windows platform code: apply GetScaleFactorForMonitor/Window to convert between physical pixels and logical coordinates for displays, cursor, and window geometry. Updated DisplayManager::GetCursorPosition, Display::GetPosition/GetSize/GetWorkArea/GetScaleFactor, and many Window methods (SetBounds/GetBounds/SetSize/GetSize/SetContentSize/GetContentSize/SetContentBounds/SetPosition/GetPosition and min/max handling) to scale values and round when setting sizes/positions. Added dpi_utils_windows.h declaration and exposed GetScaleFactorForMonitor in dpi_utils_windows.cpp, included dpi header where needed, and added for std::lround. All DPI calls fallback to 1.0 when unavailable; added a comment noting Center uses physical pixels. --- .../windows/display_manager_windows.cpp | 10 +- src/platform/windows/display_windows.cpp | 33 ++++--- src/platform/windows/dpi_utils_windows.cpp | 2 +- src/platform/windows/dpi_utils_windows.h | 3 + src/platform/windows/window_windows.cpp | 94 ++++++++++++++----- 5 files changed, 101 insertions(+), 41 deletions(-) diff --git a/src/platform/windows/display_manager_windows.cpp b/src/platform/windows/display_manager_windows.cpp index 3e3a889..4613c2a 100644 --- a/src/platform/windows/display_manager_windows.cpp +++ b/src/platform/windows/display_manager_windows.cpp @@ -6,6 +6,7 @@ #include "../../display.h" #include "../../display_event.h" #include "../../display_manager.h" +#include "dpi_utils_windows.h" namespace nativeapi { @@ -68,7 +69,14 @@ Display DisplayManager::GetPrimary() { Point DisplayManager::GetCursorPosition() { POINT cursorPos; if (GetCursorPos(&cursorPos)) { - return {static_cast(cursorPos.x), static_cast(cursorPos.y)}; + // Determine which monitor the cursor is on for DPI scaling + HMONITOR hMonitor = + MonitorFromPoint(cursorPos, MONITOR_DEFAULTTONEAREST); + double scale = GetScaleFactorForMonitor(hMonitor); + if (scale <= 0.0) + scale = 1.0; + return {static_cast(cursorPos.x) / scale, + static_cast(cursorPos.y) / scale}; } return {0.0, 0.0}; } diff --git a/src/platform/windows/display_windows.cpp b/src/platform/windows/display_windows.cpp index e09b57e..461408a 100644 --- a/src/platform/windows/display_windows.cpp +++ b/src/platform/windows/display_windows.cpp @@ -1,6 +1,7 @@ #include "../../display.h" #include +#include "dpi_utils_windows.h" #include "string_utils_windows.h" namespace nativeapi { @@ -73,7 +74,11 @@ Point Display::GetPosition() const { return {0.0, 0.0}; MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_); RECT rect = monitorInfo.rcMonitor; - return {static_cast(rect.left), static_cast(rect.top)}; + double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_); + if (scale <= 0.0) + scale = 1.0; + return {static_cast(rect.left) / scale, + static_cast(rect.top) / scale}; } Size Display::GetSize() const { @@ -81,7 +86,11 @@ Size Display::GetSize() const { return {0.0, 0.0}; MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_); RECT rect = monitorInfo.rcMonitor; - return {static_cast(rect.right - rect.left), static_cast(rect.bottom - rect.top)}; + double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_); + if (scale <= 0.0) + scale = 1.0; + return {static_cast(rect.right - rect.left) / scale, + static_cast(rect.bottom - rect.top) / scale}; } Rectangle Display::GetWorkArea() const { @@ -89,22 +98,20 @@ Rectangle Display::GetWorkArea() const { return {0.0, 0.0, 0.0, 0.0}; MONITORINFOEXW monitorInfo = GetMonitorInfoEx(pimpl_->h_monitor_); RECT workRect = monitorInfo.rcWork; - return {static_cast(workRect.left), static_cast(workRect.top), - static_cast(workRect.right - workRect.left), - static_cast(workRect.bottom - workRect.top)}; + double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_); + if (scale <= 0.0) + scale = 1.0; + return {static_cast(workRect.left) / scale, + static_cast(workRect.top) / scale, + static_cast(workRect.right - workRect.left) / scale, + static_cast(workRect.bottom - workRect.top) / scale}; } double Display::GetScaleFactor() const { if (!pimpl_->h_monitor_) return 1.0; - HDC hdc = GetDC(nullptr); - double scaleFactor = 1.0; - if (hdc) { - int dpiX = GetDeviceCaps(hdc, LOGPIXELSX); - scaleFactor = dpiX / 96.0; // 96 DPI is 100% scale - ReleaseDC(nullptr, hdc); - } - return scaleFactor; + double scale = GetScaleFactorForMonitor(pimpl_->h_monitor_); + return (scale > 0.0) ? scale : 1.0; } bool Display::IsPrimary() const { diff --git a/src/platform/windows/dpi_utils_windows.cpp b/src/platform/windows/dpi_utils_windows.cpp index 061f61b..9e9afcd 100644 --- a/src/platform/windows/dpi_utils_windows.cpp +++ b/src/platform/windows/dpi_utils_windows.cpp @@ -3,7 +3,7 @@ namespace nativeapi { // Internal: per-monitor DPI via Shcore when available -static double GetScaleFactorForMonitor(HMONITOR hmonitor) { +double GetScaleFactorForMonitor(HMONITOR hmonitor) { if (!hmonitor) return 1.0; typedef HRESULT(WINAPI * GetDpiForMonitorFunc)(HMONITOR, int, UINT*, UINT*); diff --git a/src/platform/windows/dpi_utils_windows.h b/src/platform/windows/dpi_utils_windows.h index ddeca77..1ebd702 100644 --- a/src/platform/windows/dpi_utils_windows.h +++ b/src/platform/windows/dpi_utils_windows.h @@ -6,4 +6,7 @@ namespace nativeapi { // Returns the DPI scale factor for the given window (1.0 at 96 DPI) double GetScaleFactorForWindow(HWND hwnd); +// Returns the DPI scale factor for the given monitor (1.0 at 96 DPI) +double GetScaleFactorForMonitor(HMONITOR hmonitor); + } // namespace nativeapi diff --git a/src/platform/windows/window_windows.cpp b/src/platform/windows/window_windows.cpp index de80bff..1df6c62 100644 --- a/src/platform/windows/window_windows.cpp +++ b/src/platform/windows/window_windows.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "../../foundation/id_allocator.h" #include "../../window.h" @@ -346,8 +347,14 @@ bool Window::IsFullScreen() const { void Window::SetBounds(Rectangle bounds) { if (pimpl_->hwnd_) { - SetWindowPos(pimpl_->hwnd_, nullptr, static_cast(bounds.x), static_cast(bounds.y), - static_cast(bounds.width), static_cast(bounds.height), SWP_NOZORDER); + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + SetWindowPos(pimpl_->hwnd_, nullptr, + static_cast(std::lround(bounds.x * scale)), + static_cast(std::lround(bounds.y * scale)), + static_cast(std::lround(bounds.width * scale)), + static_cast(std::lround(bounds.height * scale)), SWP_NOZORDER); } } @@ -356,10 +363,13 @@ Rectangle Window::GetBounds() const { if (pimpl_->hwnd_) { RECT rect; GetWindowRect(pimpl_->hwnd_, &rect); - bounds.x = rect.left; - bounds.y = rect.top; - bounds.width = rect.right - rect.left; - bounds.height = rect.bottom - rect.top; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + bounds.x = static_cast(rect.left) / scale; + bounds.y = static_cast(rect.top) / scale; + bounds.width = static_cast(rect.right - rect.left) / scale; + bounds.height = static_cast(rect.bottom - rect.top) / scale; } return bounds; } @@ -368,8 +378,13 @@ void Window::SetSize(Size size, bool animate) { if (pimpl_->hwnd_) { // Windows doesn't have built-in animation for window resizing // Animation would require custom implementation - SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, static_cast(size.width), - static_cast(size.height), SWP_NOMOVE | SWP_NOZORDER); + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, + static_cast(std::lround(size.width * scale)), + static_cast(std::lround(size.height * scale)), + SWP_NOMOVE | SWP_NOZORDER); } } @@ -378,8 +393,11 @@ Size Window::GetSize() const { if (pimpl_->hwnd_) { RECT rect; GetWindowRect(pimpl_->hwnd_, &rect); - size.width = rect.right - rect.left; - size.height = rect.bottom - rect.top; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + size.width = static_cast(rect.right - rect.left) / scale; + size.height = static_cast(rect.bottom - rect.top) / scale; } return size; } @@ -394,8 +412,13 @@ void Window::SetContentSize(Size size) { int borderWidth = (windowRect.right - windowRect.left) - clientRect.right; int borderHeight = (windowRect.bottom - windowRect.top) - clientRect.bottom; - SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, static_cast(size.width) + borderWidth, - static_cast(size.height) + borderHeight, SWP_NOMOVE | SWP_NOZORDER); + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + SetWindowPos(pimpl_->hwnd_, nullptr, 0, 0, + static_cast(std::lround(size.width * scale)) + borderWidth, + static_cast(std::lround(size.height * scale)) + borderHeight, + SWP_NOMOVE | SWP_NOZORDER); } } @@ -404,8 +427,11 @@ Size Window::GetContentSize() const { if (pimpl_->hwnd_) { RECT rect; GetClientRect(pimpl_->hwnd_, &rect); - size.width = rect.right; - size.height = rect.bottom; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + size.width = static_cast(rect.right) / scale; + size.height = static_cast(rect.bottom) / scale; } return size; } @@ -428,11 +454,15 @@ void Window::SetContentBounds(Rectangle bounds) { int offsetX = clientTopLeft.x - windowRect.left; int offsetY = clientTopLeft.y - windowRect.top; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + // Calculate window position so that client area is at bounds position - int windowX = static_cast(bounds.x) - offsetX; - int windowY = static_cast(bounds.y) - offsetY; - int windowWidth = static_cast(bounds.width) + borderWidth; - int windowHeight = static_cast(bounds.height) + borderHeight; + int windowX = static_cast(std::lround(bounds.x * scale)) - offsetX; + int windowY = static_cast(std::lround(bounds.y * scale)) - offsetY; + int windowWidth = static_cast(std::lround(bounds.width * scale)) + borderWidth; + int windowHeight = static_cast(std::lround(bounds.height * scale)) + borderHeight; SetWindowPos(pimpl_->hwnd_, nullptr, windowX, windowY, windowWidth, windowHeight, SWP_NOZORDER); } @@ -488,13 +518,16 @@ static int RegisterMinMaxInfoHandler(HWND hwnd, int existing_handler_id) { auto minSize = window->GetMinimumSize(); auto maxSize = window->GetMaximumSize(); MINMAXINFO* mmi = reinterpret_cast(lparam); + double scale_mm = GetScaleFactorForWindow(hwnd); + if (scale_mm <= 0.0) + scale_mm = 1.0; if (minSize.width > 0 && minSize.height > 0) { - mmi->ptMinTrackSize.x = static_cast(minSize.width); - mmi->ptMinTrackSize.y = static_cast(minSize.height); + mmi->ptMinTrackSize.x = static_cast(std::lround(minSize.width * scale_mm)); + mmi->ptMinTrackSize.y = static_cast(std::lround(minSize.height * scale_mm)); } if (maxSize.width > 0 && maxSize.height > 0) { - mmi->ptMaxTrackSize.x = static_cast(maxSize.width); - mmi->ptMaxTrackSize.y = static_cast(maxSize.height); + mmi->ptMaxTrackSize.x = static_cast(std::lround(maxSize.width * scale_mm)); + mmi->ptMaxTrackSize.y = static_cast(std::lround(maxSize.height * scale_mm)); } return std::make_optional(0); } @@ -668,8 +701,13 @@ bool Window::IsAlwaysOnTop() const { void Window::SetPosition(Point point) { if (pimpl_->hwnd_) { - SetWindowPos(pimpl_->hwnd_, nullptr, static_cast(point.x), static_cast(point.y), 0, 0, - SWP_NOSIZE | SWP_NOZORDER); + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + SetWindowPos(pimpl_->hwnd_, nullptr, + static_cast(std::lround(point.x * scale)), + static_cast(std::lround(point.y * scale)), + 0, 0, SWP_NOSIZE | SWP_NOZORDER); } } @@ -678,8 +716,11 @@ Point Window::GetPosition() const { if (pimpl_->hwnd_) { RECT rect; GetWindowRect(pimpl_->hwnd_, &rect); - point.x = rect.left; - point.y = rect.top; + double scale = GetScaleFactorForWindow(pimpl_->hwnd_); + if (scale <= 0.0) + scale = 1.0; + point.x = static_cast(rect.left) / scale; + point.y = static_cast(rect.top) / scale; } return point; } @@ -700,6 +741,7 @@ void Window::Center() { GetMonitorInfo(monitor, &mi); // Calculate the center position on the monitor's work area + // All values here are in physical pixels (GetWindowRect and rcWork), so no DPI scaling needed int centerX = mi.rcWork.left + (mi.rcWork.right - mi.rcWork.left - windowWidth) / 2; int centerY = mi.rcWork.top + (mi.rcWork.bottom - mi.rcWork.top - windowHeight) / 2;