From d4a84fb084db13761937d71ccf658135185c8301 Mon Sep 17 00:00:00 2001 From: kryptan Date: Fri, 13 Oct 2017 14:31:15 +0300 Subject: [PATCH 1/3] Implement high-DPI support on Windows --- Cargo.toml | 10 ++- src/lib.rs | 2 + src/platform/windows/dpi.rs | 128 ++++++++++++++++++++++++++++ src/platform/windows/events_loop.rs | 23 +++++ src/platform/windows/mod.rs | 1 + src/platform/windows/monitor.rs | 11 ++- src/platform/windows/window.rs | 7 +- 7 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 src/platform/windows/dpi.rs diff --git a/Cargo.toml b/Cargo.toml index d74816cf0c..fd591d2f91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,13 @@ repository = "https://github.com/tomaka/winit" documentation = "https://docs.rs/winit" categories = ["gui"] +[features] +default = [] + +# On Windows winit will attempt to enable process-wide DPI awareness. This may be +# undesireable in some circumstances and can be disabled by enabling this feature. +windows-dont-enable-dpi-awareness = [] + [dependencies] lazy_static = "0.2.2" libc = "0.2" @@ -29,7 +36,8 @@ core-graphics = "0.10" [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.2" shell32-sys = "0.1" -user32-sys = "~0.1.2" +user32-sys = "0.2.0" +gdi32-sys = "0.2.0" kernel32-sys = "0.2" dwmapi-sys = "0.1" diff --git a/src/lib.rs b/src/lib.rs index d13a7e6497..a88e77594e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -95,6 +95,8 @@ extern crate shell32; #[cfg(target_os = "windows")] extern crate user32; #[cfg(target_os = "windows")] +extern crate gdi32; +#[cfg(target_os = "windows")] extern crate dwmapi; #[cfg(any(target_os = "macos", target_os = "ios"))] #[macro_use] diff --git a/src/platform/windows/dpi.rs b/src/platform/windows/dpi.rs new file mode 100644 index 0000000000..586837bb48 --- /dev/null +++ b/src/platform/windows/dpi.rs @@ -0,0 +1,128 @@ +#![allow(non_camel_case_types, non_snake_case)] +use std::mem; +use std::os::raw::c_void; +use winapi; +use user32; +use gdi32; +use kernel32; + +// Helper function to dynamically load function pointer. +// `library` and `function` must be zero-terminated. +fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> { + unsafe { + // Library names we will use are ASCII so we can use the A version to avoid string conversion. + let module = kernel32::LoadLibraryA(library.as_ptr() as winapi::LPCSTR); + if module.is_null() { + return None; + } + + let function_ptr = kernel32::GetProcAddress(module, function.as_ptr() as winapi::LPCSTR); + if function_ptr.is_null() { + return None; + } + + Some(function_ptr) + } +} + +macro_rules! get_function { + ($lib:expr, $func:ident) => { + get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')).map(|f| unsafe { mem::transmute::<*const _, $func>(f) }) + } +} + +type GetDpiForWindow = unsafe extern "system" fn (hwnd: winapi::HWND) -> winapi::UINT; +type GetDpiForMonitor = unsafe extern "system" fn (hmonitor: winapi::HMONITOR, dpi_type: winapi::MONITOR_DPI_TYPE, dpi_x: *mut winapi::UINT, dpi_y: *mut winapi::UINT) -> winapi::HRESULT; + +lazy_static! { + static ref GET_DPI_FOR_WINDOW: Option = get_function!("user32.dll", GetDpiForWindow); + static ref GET_DPI_FOR_MONITOR: Option = get_function!("shcore.dll", GetDpiForMonitor); +} + +#[cfg(feature = "windows-dont-enable-dpi-awareness")] +pub fn become_dpi_aware() {} + +#[cfg(not(feature = "windows-dont-enable-dpi-awareness"))] +pub fn become_dpi_aware() { + use std::sync::{Once, ONCE_INIT}; + + type DPI_AWARENESS_CONTEXT = isize; + + const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE: DPI_AWARENESS_CONTEXT = -3; + + type SetProcessDPIAware = unsafe extern "system" fn () -> winapi::BOOL; + type SetProcessDpiAwareness = unsafe extern "system" fn (value: winapi::PROCESS_DPI_AWARENESS) -> winapi::HRESULT; + type SetProcessDpiAwarenessContext = unsafe extern "system" fn (value: DPI_AWARENESS_CONTEXT) -> winapi::BOOL; + + static ENABLE_DPI_AWARENESS: Once = ONCE_INIT; + ENABLE_DPI_AWARENESS.call_once(|| { + unsafe { + if let Some(SetProcessDpiAwarenessContext) = get_function!("user32.dll", SetProcessDpiAwarenessContext) { + // We are on Windows 10 Anniversary Update (1607) or later. + + // Note that there is also newer DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 which will also enable scaling + // of the window title, but if we use it then glViewort will not work correctly. Until this issue is + // investigated we are using older DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE. + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + } else if let Some(SetProcessDpiAwareness) = get_function!("shcore.dll", SetProcessDpiAwareness) { + // We are on Windows 8.1 or later. + SetProcessDpiAwareness(winapi::Process_Per_Monitor_DPI_Aware); + } else if let Some(SetProcessDPIAware) = get_function!("user32.dll", SetProcessDPIAware) { + // We are on Vista or later. + SetProcessDPIAware(); + } + } + }); +} + +pub unsafe fn get_monitor_dpi(hmonitor: winapi::HMONITOR) -> Option { + if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { + // We are on Windows 8.1 or later. + let mut dpi_x = 0; + let mut dpi_y = 0; + if GetDpiForMonitor(hmonitor, winapi::MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == winapi::S_OK { + // MSDN says that "the values of *dpiX and *dpiY are identical. You only need to record one of the values + // to determine the DPI and respond appropriately". + // + // https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx + return Some(dpi_x as u32) + } + } + + None +} + +pub unsafe fn get_window_dpi(hwnd: winapi::HWND, hdc: winapi::HDC) -> u32 { + if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW { + // We are on Windows 10 Anniversary Update (1607) or later. + match GetDpiForWindow(hwnd) { + 0 => 96, // 0 is returned if hwnd is invalid + dpi => dpi as u32, + } + } else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { + // We are on Windows 8.1 or later. + let monitor = user32::MonitorFromWindow(hwnd, winapi::MONITOR_DEFAULTTONEAREST); + if monitor.is_null() { + return 96; + } + + let mut dpi_x = 0; + let mut dpi_y = 0; + if GetDpiForMonitor(monitor, winapi::MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == winapi::S_OK { + dpi_x as u32 + } else { + 96 + } + } else { + // We are on Vista or later. + if user32::IsProcessDPIAware() != winapi::FALSE { + // If the process is DPI aware then scaling is not performed by OS and must be performed by the application. + // Therefore we return real DPI value. + gdi32::GetDeviceCaps(hdc, winapi::wingdi::LOGPIXELSX) as u32 + } else { + // If the process is DPI unaware then scaling is performed by OS and we must return 96 to prevent the application + // from scaling itself which would lead to double scaling. + 96 + } + } +} \ No newline at end of file diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs index 7213bcdce1..58a8ac78e3 100644 --- a/src/platform/windows/events_loop.rs +++ b/src/platform/windows/events_loop.rs @@ -769,6 +769,29 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, 0 }, + // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change DPI, + // therefore all applications are closed while DPI is changing. + winapi::WM_DPICHANGED => { + use events::WindowEvent::HiDPIFactorChanged; + + // This message actually provides two DPI values - x and y. However MSDN says that "you only need to + // use either the X-axis or the Y-axis value when scaling your application since they are the same". + // + // https://msdn.microsoft.com/en-us/library/windows/desktop/dn312083(v=vs.85).aspx + let dpi_x = winapi::LOWORD(wparam as u32); + + // Resize window to the size suggested by Windows. + let rect = *mem::transmute::(lparam); + user32::SetWindowPos(window, ptr::null_mut(), rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, winapi::SWP_NOZORDER | winapi::SWP_NOACTIVATE); + + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: HiDPIFactorChanged(dpi_x as f32/96.0) // 96 is the standard DPI value used when there is no scaling + }); + + 0 + }, + _ => { user32::DefWindowProcW(window, msg, wparam, lparam) } diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e0b4fdb818..741ee28145 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -31,3 +31,4 @@ mod event; mod events_loop; mod monitor; mod window; +mod dpi; diff --git a/src/platform/windows/monitor.rs b/src/platform/windows/monitor.rs index fb17be8006..17c1345eb2 100644 --- a/src/platform/windows/monitor.rs +++ b/src/platform/windows/monitor.rs @@ -5,6 +5,7 @@ use std::collections::VecDeque; use std::{mem, ptr}; use super::EventsLoop; +use super::dpi; /// Win32 implementation of the main `MonitorId` object. #[derive(Clone)] @@ -62,6 +63,8 @@ unsafe extern "system" fn monitor_enum_proc(hmonitor: winapi::HMONITOR, _: winap return winapi::TRUE; } + let dpi = dpi::get_monitor_dpi(hmonitor).unwrap_or(96); + (*monitors).push_back(MonitorId { adapter_name: monitor_info.szDevice, hmonitor: HMonitor(hmonitor), @@ -69,7 +72,7 @@ unsafe extern "system" fn monitor_enum_proc(hmonitor: winapi::HMONITOR, _: winap primary: monitor_info.dwFlags & winapi::MONITORINFOF_PRIMARY != 0, position, dimensions, - hidpi_factor: 1.0, + hidpi_factor: dpi as f32/96.0, }); // TRUE means continue enumeration. @@ -79,6 +82,9 @@ unsafe extern "system" fn monitor_enum_proc(hmonitor: winapi::HMONITOR, _: winap impl EventsLoop { pub fn get_available_monitors(&self) -> VecDeque { unsafe { + // We need to enable DPI awareness to get correct resolution and DPI of each monitor. + dpi::become_dpi_aware(); + let mut result: VecDeque = VecDeque::new(); user32::EnumDisplayMonitors(ptr::null_mut(), ptr::null_mut(), Some(monitor_enum_proc), &mut result as *mut _ as winapi::LPARAM); result @@ -86,6 +92,9 @@ impl EventsLoop { } pub fn get_primary_monitor(&self) -> MonitorId { + // We need to enable DPI awareness to get correct resolution and DPI of the primary monitor. + dpi::become_dpi_aware(); + // we simply get all available monitors and return the one with the `MONITORINFOF_PRIMARY` flag // TODO: it is possible to query the win32 API for the primary monitor, this should be done // instead diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index d3d7592124..28e80f054d 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -11,6 +11,7 @@ use std::sync::Mutex; use std::sync::mpsc::channel; use platform::platform::events_loop; +use platform::platform::dpi; use platform::platform::EventsLoop; use platform::platform::PlatformSpecificWindowBuilderAttributes; use platform::platform::MonitorId; @@ -251,7 +252,9 @@ impl Window { #[inline] pub fn hidpi_factor(&self) -> f32 { - 1.0 + unsafe { + dpi::get_window_dpi(self.window.0, self.window.1) as f32/96.0 + } } pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { @@ -320,6 +323,8 @@ impl Drop for WindowWrapper { unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, inserter: events_loop::Inserter) -> Result { + dpi::become_dpi_aware(); + let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter()) .collect::>(); From 54f7bca7d3cf4b83c4c9d62dd97e7d68774bc179 Mon Sep 17 00:00:00 2001 From: kryptan Date: Thu, 2 Nov 2017 14:28:53 +0300 Subject: [PATCH 2/3] Add EventsLoopExt::new_no_dpi_aware() method instead of using cargo feature --- CHANGELOG.md | 1 + Cargo.toml | 7 ------- src/os/windows.rs | 20 ++++++++++++++++++ src/platform/windows/dpi.rs | 32 ++++++++++++++--------------- src/platform/windows/events_loop.rs | 11 ++++++++-- src/platform/windows/monitor.rs | 4 ++-- src/platform/windows/window.rs | 3 ++- 7 files changed, 49 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f731e5ce..0aa15a27a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - On Windows added `MonitorIdExt::hmonitor` method - Impl `Clone` for `EventsLoopProxy` - `EventsLoop::get_primary_monitor()` on X11 will fallback to any available monitor if no primary is found +- On Windows added `EventsLoopExt::new_no_dpi_aware` method # Version 0.8.3 (2017-10-11) diff --git a/Cargo.toml b/Cargo.toml index fd591d2f91..bc89c09888 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,13 +10,6 @@ repository = "https://github.com/tomaka/winit" documentation = "https://docs.rs/winit" categories = ["gui"] -[features] -default = [] - -# On Windows winit will attempt to enable process-wide DPI awareness. This may be -# undesireable in some circumstances and can be disabled by enabling this feature. -windows-dont-enable-dpi-awareness = [] - [dependencies] lazy_static = "0.2.2" libc = "0.2" diff --git a/src/os/windows.rs b/src/os/windows.rs index a7d36260b4..593023504d 100644 --- a/src/os/windows.rs +++ b/src/os/windows.rs @@ -2,11 +2,31 @@ use std::os::raw::c_void; use libc; +use EventsLoop; use MonitorId; use Window; use WindowBuilder; +use platform::EventsLoop as WindowsEventsLoop; use winapi; +/// Additional methods on `EventsLoop` that are specific to Windows. +pub trait EventsLoopExt { + /// By default, winit on Windows will attempt to enable process-wide DPI awareness. This may be + /// undesirable in some circumstances and can be avoided by creating events loop using this function. + fn new_no_dpi_aware() -> Self + where Self: Sized; +} + +impl EventsLoopExt for EventsLoop { + #[inline] + fn new_no_dpi_aware() -> Self { + EventsLoop { + events_loop: WindowsEventsLoop::with_dpi_awareness(false), + _marker: ::std::marker::PhantomData, + } + } +} + /// Additional methods on `Window` that are specific to Windows. pub trait WindowExt { /// Returns the native handle that is used by this window. diff --git a/src/platform/windows/dpi.rs b/src/platform/windows/dpi.rs index 586837bb48..c5dbc3d19a 100644 --- a/src/platform/windows/dpi.rs +++ b/src/platform/windows/dpi.rs @@ -1,11 +1,22 @@ #![allow(non_camel_case_types, non_snake_case)] use std::mem; use std::os::raw::c_void; +use std::sync::{Once, ONCE_INIT}; use winapi; use user32; use gdi32; use kernel32; +type DPI_AWARENESS_CONTEXT = isize; + +const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE: DPI_AWARENESS_CONTEXT = -3; + +type SetProcessDPIAware = unsafe extern "system" fn () -> winapi::BOOL; +type SetProcessDpiAwareness = unsafe extern "system" fn (value: winapi::PROCESS_DPI_AWARENESS) -> winapi::HRESULT; +type SetProcessDpiAwarenessContext = unsafe extern "system" fn (value: DPI_AWARENESS_CONTEXT) -> winapi::BOOL; +type GetDpiForWindow = unsafe extern "system" fn (hwnd: winapi::HWND) -> winapi::UINT; +type GetDpiForMonitor = unsafe extern "system" fn (hmonitor: winapi::HMONITOR, dpi_type: winapi::MONITOR_DPI_TYPE, dpi_x: *mut winapi::UINT, dpi_y: *mut winapi::UINT) -> winapi::HRESULT; + // Helper function to dynamically load function pointer. // `library` and `function` must be zero-terminated. fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> { @@ -31,28 +42,15 @@ macro_rules! get_function { } } -type GetDpiForWindow = unsafe extern "system" fn (hwnd: winapi::HWND) -> winapi::UINT; -type GetDpiForMonitor = unsafe extern "system" fn (hmonitor: winapi::HMONITOR, dpi_type: winapi::MONITOR_DPI_TYPE, dpi_x: *mut winapi::UINT, dpi_y: *mut winapi::UINT) -> winapi::HRESULT; - lazy_static! { static ref GET_DPI_FOR_WINDOW: Option = get_function!("user32.dll", GetDpiForWindow); static ref GET_DPI_FOR_MONITOR: Option = get_function!("shcore.dll", GetDpiForMonitor); } -#[cfg(feature = "windows-dont-enable-dpi-awareness")] -pub fn become_dpi_aware() {} - -#[cfg(not(feature = "windows-dont-enable-dpi-awareness"))] -pub fn become_dpi_aware() { - use std::sync::{Once, ONCE_INIT}; - - type DPI_AWARENESS_CONTEXT = isize; - - const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE: DPI_AWARENESS_CONTEXT = -3; - - type SetProcessDPIAware = unsafe extern "system" fn () -> winapi::BOOL; - type SetProcessDpiAwareness = unsafe extern "system" fn (value: winapi::PROCESS_DPI_AWARENESS) -> winapi::HRESULT; - type SetProcessDpiAwarenessContext = unsafe extern "system" fn (value: DPI_AWARENESS_CONTEXT) -> winapi::BOOL; +pub fn become_dpi_aware(enable: bool) { + if !enable { + return; + } static ENABLE_DPI_AWARENESS: Once = ONCE_INIT; ENABLE_DPI_AWARENESS.call_once(|| { diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs index 58a8ac78e3..d12fd7a647 100644 --- a/src/platform/windows/events_loop.rs +++ b/src/platform/windows/events_loop.rs @@ -82,11 +82,17 @@ pub struct EventsLoop { // Variable that contains the block state of the win32 event loop thread during a WM_SIZE event. // The mutex's value is `true` when it's blocked, and should be set to false when it's done // blocking. That's done by the parent thread when it receives a Resized event. - win32_block_loop: Arc<(Mutex, Condvar)> + win32_block_loop: Arc<(Mutex, Condvar)>, + // Whether to enable process-global DPI awareness. + pub(super) dpi_aware: bool, } impl EventsLoop { pub fn new() -> EventsLoop { + Self::with_dpi_awareness(true) + } + + pub fn with_dpi_awareness(dpi_aware: bool) -> EventsLoop { // The main events transfer channel. let (tx, rx) = mpsc::channel(); let win32_block_loop = Arc::new((Mutex::new(false), Condvar::new())); @@ -149,7 +155,8 @@ impl EventsLoop { EventsLoop { thread_id: unsafe { kernel32::GetThreadId(thread.as_raw_handle()) }, receiver: rx, - win32_block_loop + win32_block_loop, + dpi_aware } } diff --git a/src/platform/windows/monitor.rs b/src/platform/windows/monitor.rs index 17c1345eb2..2e097dd023 100644 --- a/src/platform/windows/monitor.rs +++ b/src/platform/windows/monitor.rs @@ -83,7 +83,7 @@ impl EventsLoop { pub fn get_available_monitors(&self) -> VecDeque { unsafe { // We need to enable DPI awareness to get correct resolution and DPI of each monitor. - dpi::become_dpi_aware(); + dpi::become_dpi_aware(self.dpi_aware); let mut result: VecDeque = VecDeque::new(); user32::EnumDisplayMonitors(ptr::null_mut(), ptr::null_mut(), Some(monitor_enum_proc), &mut result as *mut _ as winapi::LPARAM); @@ -93,7 +93,7 @@ impl EventsLoop { pub fn get_primary_monitor(&self) -> MonitorId { // We need to enable DPI awareness to get correct resolution and DPI of the primary monitor. - dpi::become_dpi_aware(); + dpi::become_dpi_aware(self.dpi_aware); // we simply get all available monitors and return the one with the `MONITORINFOF_PRIMARY` flag // TODO: it is possible to query the win32 API for the primary monitor, this should be done diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index 28e80f054d..f6766ceddb 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -49,6 +49,8 @@ impl Window { let (tx, rx) = channel(); + dpi::become_dpi_aware(events_loop.dpi_aware); + events_loop.execute_in_thread(move |inserter| { // We dispatch an `init` function because of code style. let win = unsafe { init(w_attr.take().unwrap(), pl_attr.take().unwrap(), inserter) }; @@ -323,7 +325,6 @@ impl Drop for WindowWrapper { unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, inserter: events_loop::Inserter) -> Result { - dpi::become_dpi_aware(); let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter()) .collect::>(); From 7cbe16bf36b020461820b4cb2450497191804c45 Mon Sep 17 00:00:00 2001 From: kryptan Date: Fri, 3 Nov 2017 13:32:36 +0300 Subject: [PATCH 3/3] Use DPI awareness V2 if available. Also add call to EnableNonClientDpiScaling to enable non-client scaling even if V2 is not available --- src/platform/windows/dpi.rs | 18 +++++++++++++----- src/platform/windows/events_loop.rs | 6 ++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/dpi.rs b/src/platform/windows/dpi.rs index c5dbc3d19a..7e3adcc216 100644 --- a/src/platform/windows/dpi.rs +++ b/src/platform/windows/dpi.rs @@ -10,12 +10,14 @@ use kernel32; type DPI_AWARENESS_CONTEXT = isize; const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE: DPI_AWARENESS_CONTEXT = -3; +const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4; type SetProcessDPIAware = unsafe extern "system" fn () -> winapi::BOOL; type SetProcessDpiAwareness = unsafe extern "system" fn (value: winapi::PROCESS_DPI_AWARENESS) -> winapi::HRESULT; type SetProcessDpiAwarenessContext = unsafe extern "system" fn (value: DPI_AWARENESS_CONTEXT) -> winapi::BOOL; type GetDpiForWindow = unsafe extern "system" fn (hwnd: winapi::HWND) -> winapi::UINT; type GetDpiForMonitor = unsafe extern "system" fn (hmonitor: winapi::HMONITOR, dpi_type: winapi::MONITOR_DPI_TYPE, dpi_x: *mut winapi::UINT, dpi_y: *mut winapi::UINT) -> winapi::HRESULT; +type EnableNonClientDpiScaling = unsafe extern "system" fn (hwnd: winapi::HWND) -> winapi::BOOL; // Helper function to dynamically load function pointer. // `library` and `function` must be zero-terminated. @@ -45,6 +47,7 @@ macro_rules! get_function { lazy_static! { static ref GET_DPI_FOR_WINDOW: Option = get_function!("user32.dll", GetDpiForWindow); static ref GET_DPI_FOR_MONITOR: Option = get_function!("shcore.dll", GetDpiForMonitor); + static ref ENABLE_NON_CLIENT_DPI_SCALING: Option = get_function!("user32.dll", EnableNonClientDpiScaling); } pub fn become_dpi_aware(enable: bool) { @@ -57,11 +60,10 @@ pub fn become_dpi_aware(enable: bool) { unsafe { if let Some(SetProcessDpiAwarenessContext) = get_function!("user32.dll", SetProcessDpiAwarenessContext) { // We are on Windows 10 Anniversary Update (1607) or later. - - // Note that there is also newer DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 which will also enable scaling - // of the window title, but if we use it then glViewort will not work correctly. Until this issue is - // investigated we are using older DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE. - SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) == winapi::FALSE { + // V2 only works with Windows 10 Creators Update (1703). Try using the older V1 if we can't set V2. + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + } } else if let Some(SetProcessDpiAwareness) = get_function!("shcore.dll", SetProcessDpiAwareness) { // We are on Windows 8.1 or later. SetProcessDpiAwareness(winapi::Process_Per_Monitor_DPI_Aware); @@ -73,6 +75,12 @@ pub fn become_dpi_aware(enable: bool) { }); } +pub unsafe fn enable_non_client_dpi_scaling(hwnd: winapi::HWND) { + if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING { + EnableNonClientDpiScaling(hwnd); + } +} + pub unsafe fn get_monitor_dpi(hmonitor: winapi::HMONITOR) -> Option { if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR { // We are on Windows 8.1 or later. diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs index d12fd7a647..f9dcae2a01 100644 --- a/src/platform/windows/events_loop.rs +++ b/src/platform/windows/events_loop.rs @@ -32,6 +32,7 @@ use user32; use winapi; use platform::platform::event; +use platform::platform::dpi; use platform::platform::Cursor; use platform::platform::WindowId; use platform::platform::DEVICE_ID; @@ -322,6 +323,11 @@ pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT, -> winapi::LRESULT { match msg { + winapi::WM_NCCREATE => { + dpi::enable_non_client_dpi_scaling(window); + user32::DefWindowProcW(window, msg, wparam, lparam) + }, + winapi::WM_CLOSE => { use events::WindowEvent::Closed; send_event(Event::WindowEvent {