From 488c036a05d418e13bbdcdc349e2db2f6b7f58e2 Mon Sep 17 00:00:00 2001 From: moooozi <92719420+moooozi@users.noreply.github.com> Date: Sun, 24 Aug 2025 05:57:31 +0200 Subject: [PATCH] win32: disable DPI re-adjustments on Windows 11 For earlier Windows 10 builds (pre-22000), a workaround was necessary to fix dragging window onto a monitor with different DPI. This commit makes the old DPI workaround to only apply conditionally on affected Windows versions. Fixes #4041. --- winit-win32/src/dark_mode.rs | 34 +---- winit-win32/src/event_loop.rs | 220 ++++++++++++++++-------------- winit-win32/src/util.rs | 31 ++++- winit/src/changelog/unreleased.md | 1 + 4 files changed, 154 insertions(+), 132 deletions(-) diff --git a/winit-win32/src/dark_mode.rs b/winit-win32/src/dark_mode.rs index 7c3e5e08..eb1991f6 100644 --- a/winit-win32/src/dark_mode.rs +++ b/winit-win32/src/dark_mode.rs @@ -5,9 +5,8 @@ use std::{ffi::c_void, ptr}; use windows_sys::core::{PCSTR, PCWSTR}; use windows_sys::w; -use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, NTSTATUS, S_OK, WPARAM}; +use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, S_OK, WPARAM}; use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; -use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW; use windows_sys::Win32::UI::Accessibility::{HCF_HIGHCONTRASTON, HIGHCONTRASTA}; use windows_sys::Win32::UI::Controls::SetWindowTheme; use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow; @@ -17,39 +16,10 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ use winit_core::window::Theme; use super::util; - -static WIN10_BUILD_VERSION: LazyLock> = LazyLock::new(|| { - type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS; - let handle = get_function!("ntdll.dll", RtlGetVersion); - - if let Some(rtl_get_version) = handle { - unsafe { - let mut vi = OSVERSIONINFOW { - dwOSVersionInfoSize: 0, - dwMajorVersion: 0, - dwMinorVersion: 0, - dwBuildNumber: 0, - dwPlatformId: 0, - szCSDVersion: [0; 128], - }; - - let status = (rtl_get_version)(&mut vi); - - if status >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { - Some(vi.dwBuildNumber) - } else { - None - } - } - } else { - None - } -}); - static DARK_MODE_SUPPORTED: LazyLock = LazyLock::new(|| { // We won't try to do anything for windows versions < 17763 // (Windows 10 October 2018 update) - match *WIN10_BUILD_VERSION { + match *util::WIN10_BUILD_VERSION { Some(v) => v >= 17763, None => false, } diff --git a/winit-win32/src/event_loop.rs b/winit-win32/src/event_loop.rs index e0807187..974170a5 100644 --- a/winit-win32/src/event_loop.rs +++ b/winit-win32/src/event_loop.rs @@ -88,7 +88,7 @@ use crate::ime::ImeContext; use crate::keyboard::KeyEventBuilder; use crate::keyboard_layout::LAYOUT_CACHE; use crate::monitor::{self, MonitorHandle}; -use crate::util::wrap_device_id; +use crate::util::{wrap_device_id, WIN10_BUILD_VERSION}; use crate::window::{InitData, Window}; use crate::window_state::{CursorFlags, ImeState, WindowFlags, WindowState}; use crate::{raw_input, util}; @@ -2346,105 +2346,22 @@ unsafe fn public_window_callback_inner( } } - let new_outer_rect: RECT; + let new_outer_rect: RECT = if WIN10_BUILD_VERSION.is_some_and(|version| version < 22000) { - let suggested_ul = - (suggested_rect.left + margin_left, suggested_rect.top + margin_top); - - let mut conservative_rect = RECT { - left: suggested_ul.0, - top: suggested_ul.1, - right: suggested_ul.0 + new_physical_surface_size.width as i32, - bottom: suggested_ul.1 + new_physical_surface_size.height as i32, - }; - - conservative_rect = window_flags - .adjust_rect(window, conservative_rect) - .unwrap_or(conservative_rect); - - // If we're dragging the window, offset the window so that the cursor's - // relative horizontal position in the title bar is preserved. - if dragging_window { - let bias = { - let cursor_pos = { - let mut pos = unsafe { mem::zeroed() }; - unsafe { GetCursorPos(&mut pos) }; - pos - }; - let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) - as f64 - / (suggested_rect.right - suggested_rect.left) as f64; - - (cursor_pos.x - - (suggested_cursor_horizontal_ratio - * (conservative_rect.right - conservative_rect.left) as f64) - as i32) - - conservative_rect.left - }; - conservative_rect.left += bias; - conservative_rect.right += bias; - } - - // Check to see if the new window rect is on the monitor with the new DPI factor. - // If it isn't, offset the window so that it is. - let new_dpi_monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) }; - let conservative_rect_monitor = - unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) }; - new_outer_rect = if conservative_rect_monitor == new_dpi_monitor { - conservative_rect - } else { - let get_monitor_rect = |monitor| { - let mut monitor_info = MONITORINFO { - cbSize: mem::size_of::() as _, - ..unsafe { mem::zeroed() } - }; - unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }; - monitor_info.rcMonitor - }; - let wrong_monitor = conservative_rect_monitor; - let wrong_monitor_rect = get_monitor_rect(wrong_monitor); - let new_monitor_rect = get_monitor_rect(new_dpi_monitor); - - // The direction to nudge the window in to get the window onto the monitor with - // the new DPI factor. We calculate this by seeing which monitor edges are - // shared and nudging away from the wrong monitor based on those. - #[allow(clippy::bool_to_int_with_if)] - let delta_nudge_to_dpi_monitor = ( - if wrong_monitor_rect.left == new_monitor_rect.right { - -1 - } else if wrong_monitor_rect.right == new_monitor_rect.left { - 1 - } else { - 0 - }, - if wrong_monitor_rect.bottom == new_monitor_rect.top { - 1 - } else if wrong_monitor_rect.top == new_monitor_rect.bottom { - -1 - } else { - 0 - }, - ); - - let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left - + new_monitor_rect.bottom - - new_monitor_rect.top; - for _ in 0..abort_after_iterations { - conservative_rect.left += delta_nudge_to_dpi_monitor.0; - conservative_rect.right += delta_nudge_to_dpi_monitor.0; - conservative_rect.top += delta_nudge_to_dpi_monitor.1; - conservative_rect.bottom += delta_nudge_to_dpi_monitor.1; - - if unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) } - == new_dpi_monitor - { - break; - } - } - - conservative_rect - }; - } + // Apply Windows 10-specific DPI adjustment workaround + apply_win10_dpi_adjustment( + window, + suggested_rect, + margin_left, + margin_top, + new_physical_surface_size, + window_flags, + dragging_window, + ) + } else { + // The suggested position is fine w/o adjustment on Windows 11+ + suggested_rect + }; unsafe { SetWindowPos( @@ -2669,3 +2586,108 @@ fn get_pointer_move_kind( PointerMoveKind::None } } + +/// Apply Windows 10-specific DPI adjustment workaround for window positioning. +/// This fixes DPI switching issues on older Windows 10 but should not be applied on Windows 11+ +/// where it would break DPI switching. +fn apply_win10_dpi_adjustment( + window: HWND, + suggested_rect: RECT, + margin_left: i32, + margin_top: i32, + new_physical_surface_size: PhysicalSize, + window_flags: WindowFlags, + dragging_window: bool, +) -> RECT { + let suggested_ul = (suggested_rect.left + margin_left, suggested_rect.top + margin_top); + + let mut conservative_rect = RECT { + left: suggested_ul.0, + top: suggested_ul.1, + right: suggested_ul.0 + new_physical_surface_size.width as i32, + bottom: suggested_ul.1 + new_physical_surface_size.height as i32, + }; + + conservative_rect = + window_flags.adjust_rect(window, conservative_rect).unwrap_or(conservative_rect); + + // If we're dragging the window, offset the window so that the cursor's + // relative horizontal position in the title bar is preserved. + if dragging_window { + let bias = { + let cursor_pos = { + let mut pos = unsafe { mem::zeroed() }; + unsafe { GetCursorPos(&mut pos) }; + pos + }; + let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) as f64 + / (suggested_rect.right - suggested_rect.left) as f64; + + (cursor_pos.x + - (suggested_cursor_horizontal_ratio + * (conservative_rect.right - conservative_rect.left) as f64) + as i32) + - conservative_rect.left + }; + conservative_rect.left += bias; + conservative_rect.right += bias; + } + + // Check to see if the new window rect is on the monitor with the new DPI factor. + // If it isn't, offset the window so that it is. + let new_dpi_monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) }; + let conservative_rect_monitor = + unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) }; + + if conservative_rect_monitor == new_dpi_monitor { + return conservative_rect; + } + + let get_monitor_rect = |monitor| { + let mut monitor_info = + MONITORINFO { cbSize: mem::size_of::() as _, ..unsafe { mem::zeroed() } }; + unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }; + monitor_info.rcMonitor + }; + let wrong_monitor = conservative_rect_monitor; + let wrong_monitor_rect = get_monitor_rect(wrong_monitor); + let new_monitor_rect = get_monitor_rect(new_dpi_monitor); + + // The direction to nudge the window in to get the window onto the monitor with + // the new DPI factor. We calculate this by seeing which monitor edges are + // shared and nudging away from the wrong monitor based on those. + #[allow(clippy::bool_to_int_with_if)] + let delta_nudge_to_dpi_monitor = ( + if wrong_monitor_rect.left == new_monitor_rect.right { + -1 + } else if wrong_monitor_rect.right == new_monitor_rect.left { + 1 + } else { + 0 + }, + if wrong_monitor_rect.bottom == new_monitor_rect.top { + 1 + } else if wrong_monitor_rect.top == new_monitor_rect.bottom { + -1 + } else { + 0 + }, + ); + + let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left + + new_monitor_rect.bottom + - new_monitor_rect.top; + for _ in 0..abort_after_iterations { + conservative_rect.left += delta_nudge_to_dpi_monitor.0; + conservative_rect.right += delta_nudge_to_dpi_monitor.0; + conservative_rect.top += delta_nudge_to_dpi_monitor.1; + conservative_rect.bottom += delta_nudge_to_dpi_monitor.1; + + if unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) } == new_dpi_monitor + { + break; + } + } + + conservative_rect +} diff --git a/winit-win32/src/util.rs b/winit-win32/src/util.rs index feaa1a97..4d1ee935 100644 --- a/winit-win32/src/util.rs +++ b/winit-win32/src/util.rs @@ -7,9 +7,10 @@ use std::sync::LazyLock; use std::{io, mem, ptr}; use windows_sys::core::{HRESULT, PCWSTR}; -use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, POINT, RECT}; +use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, NTSTATUS, POINT, RECT}; use windows_sys::Win32::Graphics::Gdi::{ClientToScreen, HMONITOR}; use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; +use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW; use windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER; use windows_sys::Win32::UI::HiDpi::{ DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS, @@ -254,6 +255,34 @@ pub type GetPointerDeviceRects = unsafe extern "system" fn( pub type GetPointerTouchInfo = unsafe extern "system" fn(pointer_id: u32, touch_info: *mut POINTER_TOUCH_INFO) -> BOOL; +pub(crate) static WIN10_BUILD_VERSION: LazyLock> = LazyLock::new(|| { + type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS; + let handle = get_function!("ntdll.dll", RtlGetVersion); + + if let Some(rtl_get_version) = handle { + unsafe { + let mut vi = OSVERSIONINFOW { + dwOSVersionInfoSize: 0, + dwMajorVersion: 0, + dwMinorVersion: 0, + dwBuildNumber: 0, + dwPlatformId: 0, + szCSDVersion: [0; 128], + }; + + let status = (rtl_get_version)(&mut vi); + + if status >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 { + Some(vi.dwBuildNumber) + } else { + None + } + } + } else { + None + } +}); + pub(crate) static GET_DPI_FOR_WINDOW: LazyLock> = LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow)); pub(crate) static ADJUST_WINDOW_RECT_EX_FOR_DPI: LazyLock> = diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 0d8f3240..6c7cbbac 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -259,3 +259,4 @@ changelog entry. - On Windows, account for mouse wheel lines per scroll setting for `WindowEvent::MouseWheel`. - On Windows, `Window::theme` will return the correct theme after setting it through `Window::set_theme`. - On Windows, `Window::set_theme` will change the title bar color immediately now. +- On Windows 11, prevent incorrect shifting when dragging window onto a monitor with different DPI.