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.
This commit is contained in:
parent
a4af50ec13
commit
488c036a05
4 changed files with 154 additions and 132 deletions
|
|
@ -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<Option<u32>> = 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<bool> = 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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::<MONITORINFO>() 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<u32>,
|
||||
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::<MONITORINFO>() 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Option<u32>> = 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<Option<GetDpiForWindow>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||
pub(crate) static ADJUST_WINDOW_RECT_EX_FOR_DPI: LazyLock<Option<AdjustWindowRectExForDpi>> =
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue