On Windows, make AdjustRect calls DPI-aware when possible (#1015)

* Use AdjustWidowRectExForDPI when available

* Prioritize presevering logical size when handling WM_DPICHANGED

* Format

* Add changelog entry
This commit is contained in:
Osspial 2020-01-04 01:29:40 -05:00
parent f379d069b9
commit 6ffd78767f
7 changed files with 143 additions and 107 deletions

View file

@ -144,6 +144,9 @@ and `WindowEvent::HoveredFile`.
- On macOS, change `WindowExtMacOS::request_user_attention()` to take an `enum` instead of a `bool`.
# 0.20.0 Alpha 1 (2019-06-21)
- On Windows, fix window rectangle not getting set correctly on high-DPI systems.
# 0.20.0 Alpha 1
- Changes below are considered **breaking**.
- Change all occurrences of `EventsLoop` to `EventLoop`.

View file

@ -5,7 +5,7 @@ fn main() {
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{CursorIcon, Fullscreen, WindowBuilder},

View file

@ -333,7 +333,15 @@ impl<'a> WindowEvent<'a> {
HoveredFileCancelled => Some(HoveredFileCancelled),
ReceivedCharacter(c) => Some(ReceivedCharacter(c)),
Focused(focused) => Some(Focused(focused)),
KeyboardInput { device_id, input, is_synthetic } => Some(KeyboardInput { device_id, input, is_synthetic }),
KeyboardInput {
device_id,
input,
is_synthetic,
} => Some(KeyboardInput {
device_id,
input,
is_synthetic,
}),
CursorMoved {
device_id,
position,

View file

@ -2,54 +2,30 @@
use std::sync::Once;
use crate::platform_impl::platform::util::{
ENABLE_NON_CLIENT_DPI_SCALING, GET_DPI_FOR_MONITOR, GET_DPI_FOR_WINDOW, SET_PROCESS_DPI_AWARE,
SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT,
};
use winapi::{
shared::{
minwindef::{BOOL, FALSE, UINT},
minwindef::FALSE,
windef::{DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, HMONITOR, HWND},
winerror::S_OK,
},
um::{
shellscalingapi::{
MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
PROCESS_PER_MONITOR_DPI_AWARE,
},
shellscalingapi::{MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE},
wingdi::{GetDeviceCaps, LOGPIXELSX},
winnt::HRESULT,
winuser::{self, MONITOR_DEFAULTTONEAREST},
},
};
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _;
type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL;
type SetProcessDpiAwareness = unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT;
type SetProcessDpiAwarenessContext =
unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL;
type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> UINT;
type GetDpiForMonitor = unsafe extern "system" fn(
hmonitor: HMONITOR,
dpi_type: MONITOR_DPI_TYPE,
dpi_x: *mut UINT,
dpi_y: *mut UINT,
) -> HRESULT;
type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
lazy_static! {
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> =
get_function!("user32.dll", GetDpiForWindow);
static ref GET_DPI_FOR_MONITOR: Option<GetDpiForMonitor> =
get_function!("shcore.dll", GetDpiForMonitor);
static ref ENABLE_NON_CLIENT_DPI_SCALING: Option<EnableNonClientDpiScaling> =
get_function!("user32.dll", EnableNonClientDpiScaling);
}
pub fn become_dpi_aware() {
static ENABLE_DPI_AWARENESS: Once = Once::new();
ENABLE_DPI_AWARENESS.call_once(|| {
unsafe {
if let Some(SetProcessDpiAwarenessContext) =
get_function!("user32.dll", SetProcessDpiAwarenessContext)
{
if let Some(SetProcessDpiAwarenessContext) = *SET_PROCESS_DPI_AWARENESS_CONTEXT {
// We are on Windows 10 Anniversary Update (1607) or later.
if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
== FALSE
@ -58,13 +34,10 @@ pub fn become_dpi_aware() {
// V1 if we can't set V2.
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
}
} else if let Some(SetProcessDpiAwareness) =
get_function!("shcore.dll", SetProcessDpiAwareness)
{
} else if let Some(SetProcessDpiAwareness) = *SET_PROCESS_DPI_AWARENESS {
// We are on Windows 8.1 or later.
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
} else if let Some(SetProcessDPIAware) = get_function!("user32.dll", SetProcessDPIAware)
{
} else if let Some(SetProcessDPIAware) = *SET_PROCESS_DPI_AWARE {
// We are on Vista or later.
SetProcessDPIAware();
}

View file

@ -36,7 +36,7 @@ use winapi::{
},
um::{
commctrl, libloaderapi, ole2, processthreadsapi, winbase,
winnt::{HANDLE, LPCSTR, SHORT},
winnt::{HANDLE, LPCSTR, SHORT},
winuser,
},
};
@ -48,15 +48,12 @@ use crate::{
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
platform_impl::platform::{
dark_mode::try_dark_mode,
dpi::{
become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling,
},
dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling},
drop_handler::FileDropHandler,
event::{
self, handle_extended_keys, process_key_params, vkey_to_winit_vkey, ModifiersStateSide,
},
monitor, raw_input, util,
window::adjust_size,
window_state::{CursorFlags, WindowFlags, WindowState},
wrap_device_id, WindowId, DEVICE_ID,
},
@ -1394,11 +1391,9 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
let window_state = subclass_input.window_state.lock();
if window_state.min_size.is_some() || window_state.max_size.is_some() {
let style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD;
let ex_style = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD;
if let Some(min_size) = window_state.min_size {
let min_size = min_size.to_physical(window_state.dpi_factor);
let (width, height) = adjust_size(min_size, style, ex_style);
let (width, height): (u32, u32) = util::adjust_size(window, min_size).into();
(*mmi).ptMinTrackSize = POINT {
x: width as i32,
y: height as i32,
@ -1406,7 +1401,7 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
}
if let Some(max_size) = window_state.max_size {
let max_size = max_size.to_physical(window_state.dpi_factor);
let (width, height) = adjust_size(max_size, style, ex_style);
let (width, height): (u32, u32) = util::adjust_size(window, max_size).into();
(*mmi).ptMaxTrackSize = POINT {
x: width as i32,
y: height as i32,
@ -1428,10 +1423,11 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx
let new_dpi_x = u32::from(LOWORD(wparam as DWORD));
let new_dpi_factor = dpi_to_scale_factor(new_dpi_x);
let old_dpi_factor: f64;
let allow_resize = {
let mut window_state = subclass_input.window_state.lock();
let old_dpi_factor = window_state.dpi_factor;
old_dpi_factor = window_state.dpi_factor;
window_state.dpi_factor = new_dpi_factor;
new_dpi_factor != old_dpi_factor && window_state.fullscreen.is_none()
@ -1468,10 +1464,26 @@ unsafe extern "system" fn public_window_callback<T: 'static>(
margins_vertical = (margin_bottom + margin_top) as u32;
}
let physical_inner_rect = PhysicalSize::new(
(rect.right - rect.left) as u32 - margins_horizontal,
(rect.bottom - rect.top) as u32 - margins_vertical,
);
// Use the rect suggested by Windows
// let physical_inner_rect = PhysicalSize::new(
// (rect.right - rect.left) as u32 - margins_horizontal,
// (rect.bottom - rect.top) as u32 - margins_vertical,
// );
// We calculate our own rect because the default suggested rect doesn't do a great job
// of preserving the window's logical size.
let physical_inner_rect = {
let mut current_rect = mem::zeroed();
winuser::GetClientRect(window, &mut current_rect);
let client_rect = PhysicalSize::new(
(current_rect.right - current_rect.left) as u32,
(current_rect.bottom - current_rect.top) as u32,
);
client_rect
.to_logical(old_dpi_factor)
.to_physical(new_dpi_factor)
};
// `allow_resize` prevents us from re-applying DPI adjustment to the restored size after
// exiting fullscreen (the restored size is already DPI adjusted).

View file

@ -6,51 +6,22 @@ use std::{
sync::atomic::{AtomicBool, Ordering},
};
use crate::window::CursorIcon;
use crate::{dpi::PhysicalSize, window::CursorIcon};
use winapi::{
ctypes::wchar_t,
shared::{
minwindef::{BOOL, DWORD},
windef::{HWND, RECT},
minwindef::{BOOL, DWORD, UINT},
windef::{DPI_AWARENESS_CONTEXT, HMONITOR, HWND, LPRECT, RECT},
},
um::{
libloaderapi::{GetProcAddress, LoadLibraryA},
shellscalingapi::{MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS},
winbase::lstrlenW,
winnt::LPCSTR,
winnt::{HRESULT, LONG, LPCSTR},
winuser,
},
};
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
crate::platform_impl::platform::util::get_function_impl(
concat!($lib, '\0'),
concat!(stringify!($func), '\0'),
)
.map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) })
};
}
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where
T: Copy + PartialEq + BitAnd<T, Output = T>,
@ -105,6 +76,18 @@ pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> {
}
}
pub fn adjust_size(hwnd: HWND, size: PhysicalSize) -> PhysicalSize {
let (width, height): (u32, u32) = size.into();
let rect = RECT {
left: 0,
right: width as LONG,
top: 0,
bottom: height as LONG,
};
let rect = adjust_window_rect(hwnd, rect).unwrap_or(rect);
PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _)
}
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
unsafe {
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);
@ -124,7 +107,14 @@ pub fn adjust_window_rect_with_styles(
*r = rect;
let b_menu = !winuser::GetMenu(hwnd).is_null() as BOOL;
winuser::AdjustWindowRectEx(r, style as _, b_menu, style_ex as _)
if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) =
(*GET_DPI_FOR_WINDOW, *ADJUST_WINDOW_RECT_EX_FOR_DPI)
{
let dpi = get_dpi_for_window(hwnd);
adjust_window_rect_ex_for_dpi(r, style as _, b_menu, style_ex as _, dpi)
} else {
winuser::AdjustWindowRectEx(r, style as _, b_menu, style_ex as _)
}
})
}
}
@ -206,3 +196,71 @@ impl CursorIcon {
}
}
}
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
pub(super) fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
crate::platform_impl::platform::util::get_function_impl(
concat!($lib, '\0'),
concat!(stringify!($func), '\0'),
)
.map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) })
};
}
pub type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL;
pub type SetProcessDpiAwareness =
unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT;
pub type SetProcessDpiAwarenessContext =
unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL;
pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> UINT;
pub type GetDpiForMonitor = unsafe extern "system" fn(
hmonitor: HMONITOR,
dpi_type: MONITOR_DPI_TYPE,
dpi_x: *mut UINT,
dpi_y: *mut UINT,
) -> HRESULT;
pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
pub type AdjustWindowRectExForDpi = unsafe extern "system" fn(
rect: LPRECT,
dwStyle: DWORD,
bMenu: BOOL,
dwExStyle: DWORD,
dpi: UINT,
) -> BOOL;
lazy_static! {
pub static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> =
get_function!("user32.dll", GetDpiForWindow);
pub static ref ADJUST_WINDOW_RECT_EX_FOR_DPI: Option<AdjustWindowRectExForDpi> =
get_function!("user32.dll", AdjustWindowRectExForDpi);
pub static ref GET_DPI_FOR_MONITOR: Option<GetDpiForMonitor> =
get_function!("shcore.dll", GetDpiForMonitor);
pub static ref ENABLE_NON_CLIENT_DPI_SCALING: Option<EnableNonClientDpiScaling> =
get_function!("user32.dll", EnableNonClientDpiScaling);
pub static ref SET_PROCESS_DPI_AWARENESS_CONTEXT: Option<SetProcessDpiAwarenessContext> =
get_function!("user32.dll", SetProcessDpiAwarenessContext);
pub static ref SET_PROCESS_DPI_AWARENESS: Option<SetProcessDpiAwareness> =
get_function!("shcore.dll", SetProcessDpiAwareness);
pub static ref SET_PROCESS_DPI_AWARE: Option<SetProcessDPIAware> =
get_function!("user32.dll", SetProcessDPIAware);
}

View file

@ -14,7 +14,7 @@ use std::{
use winapi::{
ctypes::c_int,
shared::{
minwindef::{DWORD, HINSTANCE, UINT},
minwindef::{HINSTANCE, UINT},
windef::{HWND, POINT, RECT},
},
um::{
@ -37,9 +37,7 @@ use crate::{
dark_mode::try_dark_mode,
dpi::{dpi_to_scale_factor, hwnd_dpi},
drop_handler::FileDropHandler,
event_loop::{
self, EventLoopWindowTarget, DESTROY_MSG_ID,
},
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID},
icon::{self, IconType, WinIcon},
monitor, util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
@ -669,22 +667,6 @@ pub struct WindowWrapper(HWND);
unsafe impl Sync for WindowWrapper {}
unsafe impl Send for WindowWrapper {}
pub unsafe fn adjust_size(
physical_size: PhysicalSize,
style: DWORD,
ex_style: DWORD,
) -> (LONG, LONG) {
let (width, height): (u32, u32) = physical_size.into();
let mut rect = RECT {
left: 0,
right: width as LONG,
top: 0,
bottom: height as LONG,
};
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
(rect.right - rect.left, rect.bottom - rect.top)
}
unsafe fn init<T: 'static>(
mut attributes: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,