Skip to content

Commit 9d791c9

Browse files
committed
win32: fix multi-monitor DPI switching on modern Windows builds
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 only applied conditionally. Applying it globally on newer systems breaks DPI switching.
1 parent e239d03 commit 9d791c9

File tree

2 files changed

+122
-99
lines changed

2 files changed

+122
-99
lines changed

winit-win32/src/event_loop.rs

Lines changed: 121 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ use crate::ime::ImeContext;
8888
use crate::keyboard::KeyEventBuilder;
8989
use crate::keyboard_layout::LAYOUT_CACHE;
9090
use crate::monitor::{self, MonitorHandle};
91-
use crate::util::wrap_device_id;
91+
use crate::util::{wrap_device_id, WIN10_BUILD_VERSION};
9292
use crate::window::{InitData, Window};
9393
use crate::window_state::{CursorFlags, ImeState, WindowFlags, WindowState};
9494
use crate::{raw_input, util};
@@ -2346,105 +2346,22 @@ unsafe fn public_window_callback_inner(
23462346
}
23472347
}
23482348

2349-
let new_outer_rect: RECT;
2349+
let new_outer_rect: RECT = if WIN10_BUILD_VERSION.is_some_and(|version| version < 22000)
23502350
{
2351-
let suggested_ul =
2352-
(suggested_rect.left + margin_left, suggested_rect.top + margin_top);
2353-
2354-
let mut conservative_rect = RECT {
2355-
left: suggested_ul.0,
2356-
top: suggested_ul.1,
2357-
right: suggested_ul.0 + new_physical_surface_size.width as i32,
2358-
bottom: suggested_ul.1 + new_physical_surface_size.height as i32,
2359-
};
2360-
2361-
conservative_rect = window_flags
2362-
.adjust_rect(window, conservative_rect)
2363-
.unwrap_or(conservative_rect);
2364-
2365-
// If we're dragging the window, offset the window so that the cursor's
2366-
// relative horizontal position in the title bar is preserved.
2367-
if dragging_window {
2368-
let bias = {
2369-
let cursor_pos = {
2370-
let mut pos = unsafe { mem::zeroed() };
2371-
unsafe { GetCursorPos(&mut pos) };
2372-
pos
2373-
};
2374-
let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left)
2375-
as f64
2376-
/ (suggested_rect.right - suggested_rect.left) as f64;
2377-
2378-
(cursor_pos.x
2379-
- (suggested_cursor_horizontal_ratio
2380-
* (conservative_rect.right - conservative_rect.left) as f64)
2381-
as i32)
2382-
- conservative_rect.left
2383-
};
2384-
conservative_rect.left += bias;
2385-
conservative_rect.right += bias;
2386-
}
2387-
2388-
// Check to see if the new window rect is on the monitor with the new DPI factor.
2389-
// If it isn't, offset the window so that it is.
2390-
let new_dpi_monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) };
2391-
let conservative_rect_monitor =
2392-
unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) };
2393-
new_outer_rect = if conservative_rect_monitor == new_dpi_monitor {
2394-
conservative_rect
2395-
} else {
2396-
let get_monitor_rect = |monitor| {
2397-
let mut monitor_info = MONITORINFO {
2398-
cbSize: mem::size_of::<MONITORINFO>() as _,
2399-
..unsafe { mem::zeroed() }
2400-
};
2401-
unsafe { GetMonitorInfoW(monitor, &mut monitor_info) };
2402-
monitor_info.rcMonitor
2403-
};
2404-
let wrong_monitor = conservative_rect_monitor;
2405-
let wrong_monitor_rect = get_monitor_rect(wrong_monitor);
2406-
let new_monitor_rect = get_monitor_rect(new_dpi_monitor);
2407-
2408-
// The direction to nudge the window in to get the window onto the monitor with
2409-
// the new DPI factor. We calculate this by seeing which monitor edges are
2410-
// shared and nudging away from the wrong monitor based on those.
2411-
#[allow(clippy::bool_to_int_with_if)]
2412-
let delta_nudge_to_dpi_monitor = (
2413-
if wrong_monitor_rect.left == new_monitor_rect.right {
2414-
-1
2415-
} else if wrong_monitor_rect.right == new_monitor_rect.left {
2416-
1
2417-
} else {
2418-
0
2419-
},
2420-
if wrong_monitor_rect.bottom == new_monitor_rect.top {
2421-
1
2422-
} else if wrong_monitor_rect.top == new_monitor_rect.bottom {
2423-
-1
2424-
} else {
2425-
0
2426-
},
2427-
);
2428-
2429-
let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left
2430-
+ new_monitor_rect.bottom
2431-
- new_monitor_rect.top;
2432-
for _ in 0..abort_after_iterations {
2433-
conservative_rect.left += delta_nudge_to_dpi_monitor.0;
2434-
conservative_rect.right += delta_nudge_to_dpi_monitor.0;
2435-
conservative_rect.top += delta_nudge_to_dpi_monitor.1;
2436-
conservative_rect.bottom += delta_nudge_to_dpi_monitor.1;
2437-
2438-
if unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) }
2439-
== new_dpi_monitor
2440-
{
2441-
break;
2442-
}
2443-
}
2444-
2445-
conservative_rect
2446-
};
2447-
}
2351+
// Apply Windows 10-specific DPI adjustment workaround
2352+
apply_win10_dpi_adjustment(
2353+
window,
2354+
suggested_rect,
2355+
margin_left,
2356+
margin_top,
2357+
new_physical_surface_size,
2358+
window_flags,
2359+
dragging_window,
2360+
)
2361+
} else {
2362+
// The suggested position is fine w/o adjustment on Windows 11+
2363+
suggested_rect
2364+
};
24482365

24492366
unsafe {
24502367
SetWindowPos(
@@ -2669,3 +2586,108 @@ fn get_pointer_move_kind(
26692586
PointerMoveKind::None
26702587
}
26712588
}
2589+
2590+
/// Apply Windows 10-specific DPI adjustment workaround for window positioning.
2591+
/// This fixes DPI switching issues on older Windows 10 but should not be applied on Windows 11+
2592+
/// where it would break DPI switching.
2593+
fn apply_win10_dpi_adjustment(
2594+
window: HWND,
2595+
suggested_rect: RECT,
2596+
margin_left: i32,
2597+
margin_top: i32,
2598+
new_physical_surface_size: PhysicalSize<u32>,
2599+
window_flags: WindowFlags,
2600+
dragging_window: bool,
2601+
) -> RECT {
2602+
let suggested_ul = (suggested_rect.left + margin_left, suggested_rect.top + margin_top);
2603+
2604+
let mut conservative_rect = RECT {
2605+
left: suggested_ul.0,
2606+
top: suggested_ul.1,
2607+
right: suggested_ul.0 + new_physical_surface_size.width as i32,
2608+
bottom: suggested_ul.1 + new_physical_surface_size.height as i32,
2609+
};
2610+
2611+
conservative_rect =
2612+
window_flags.adjust_rect(window, conservative_rect).unwrap_or(conservative_rect);
2613+
2614+
// If we're dragging the window, offset the window so that the cursor's
2615+
// relative horizontal position in the title bar is preserved.
2616+
if dragging_window {
2617+
let bias = {
2618+
let cursor_pos = {
2619+
let mut pos = unsafe { mem::zeroed() };
2620+
unsafe { GetCursorPos(&mut pos) };
2621+
pos
2622+
};
2623+
let suggested_cursor_horizontal_ratio = (cursor_pos.x - suggested_rect.left) as f64
2624+
/ (suggested_rect.right - suggested_rect.left) as f64;
2625+
2626+
(cursor_pos.x
2627+
- (suggested_cursor_horizontal_ratio
2628+
* (conservative_rect.right - conservative_rect.left) as f64)
2629+
as i32)
2630+
- conservative_rect.left
2631+
};
2632+
conservative_rect.left += bias;
2633+
conservative_rect.right += bias;
2634+
}
2635+
2636+
// Check to see if the new window rect is on the monitor with the new DPI factor.
2637+
// If it isn't, offset the window so that it is.
2638+
let new_dpi_monitor = unsafe { MonitorFromWindow(window, MONITOR_DEFAULTTONULL) };
2639+
let conservative_rect_monitor =
2640+
unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) };
2641+
2642+
if conservative_rect_monitor == new_dpi_monitor {
2643+
return conservative_rect;
2644+
}
2645+
2646+
let get_monitor_rect = |monitor| {
2647+
let mut monitor_info =
2648+
MONITORINFO { cbSize: mem::size_of::<MONITORINFO>() as _, ..unsafe { mem::zeroed() } };
2649+
unsafe { GetMonitorInfoW(monitor, &mut monitor_info) };
2650+
monitor_info.rcMonitor
2651+
};
2652+
let wrong_monitor = conservative_rect_monitor;
2653+
let wrong_monitor_rect = get_monitor_rect(wrong_monitor);
2654+
let new_monitor_rect = get_monitor_rect(new_dpi_monitor);
2655+
2656+
// The direction to nudge the window in to get the window onto the monitor with
2657+
// the new DPI factor. We calculate this by seeing which monitor edges are
2658+
// shared and nudging away from the wrong monitor based on those.
2659+
#[allow(clippy::bool_to_int_with_if)]
2660+
let delta_nudge_to_dpi_monitor = (
2661+
if wrong_monitor_rect.left == new_monitor_rect.right {
2662+
-1
2663+
} else if wrong_monitor_rect.right == new_monitor_rect.left {
2664+
1
2665+
} else {
2666+
0
2667+
},
2668+
if wrong_monitor_rect.bottom == new_monitor_rect.top {
2669+
1
2670+
} else if wrong_monitor_rect.top == new_monitor_rect.bottom {
2671+
-1
2672+
} else {
2673+
0
2674+
},
2675+
);
2676+
2677+
let abort_after_iterations = new_monitor_rect.right - new_monitor_rect.left
2678+
+ new_monitor_rect.bottom
2679+
- new_monitor_rect.top;
2680+
for _ in 0..abort_after_iterations {
2681+
conservative_rect.left += delta_nudge_to_dpi_monitor.0;
2682+
conservative_rect.right += delta_nudge_to_dpi_monitor.0;
2683+
conservative_rect.top += delta_nudge_to_dpi_monitor.1;
2684+
conservative_rect.bottom += delta_nudge_to_dpi_monitor.1;
2685+
2686+
if unsafe { MonitorFromRect(&conservative_rect, MONITOR_DEFAULTTONULL) } == new_dpi_monitor
2687+
{
2688+
break;
2689+
}
2690+
}
2691+
2692+
conservative_rect
2693+
}

winit/src/changelog/unreleased.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,4 @@ changelog entry.
259259
- On Windows, account for mouse wheel lines per scroll setting for `WindowEvent::MouseWheel`.
260260
- On Windows, `Window::theme` will return the correct theme after setting it through `Window::set_theme`.
261261
- On Windows, `Window::set_theme` will change the title bar color immediately now.
262+
- On Windows 11, prevent incorrect shifting when dragging window onto a monitor with different DPI.

0 commit comments

Comments
 (0)