From 966fd91c82f16bfad4ebb0ca6fcf85ad640d2ada Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 6 Sep 2026 17:27:42 +0200 Subject: [PATCH 01/17] Refactor Library type --- src/platform/win/gl.rs | 9 ++-- src/platform/win/window.rs | 6 +-- src/platform/win/window_state.rs | 12 +++-- src/wrappers/win32/dpi.rs | 2 +- src/wrappers/win32/library.rs | 62 +++++++++++++++++++++---- src/wrappers/win32/user32.rs | 71 ++++++++++------------------- src/wrappers/win32/window/handle.rs | 5 +- 7 files changed, 94 insertions(+), 73 deletions(-) diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index da8e716a..e4a8f998 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -10,19 +10,19 @@ use crate::wrappers::win32::window::{ with_dummy_window, HWnd, OwnDeviceContext, PixelFormat, PixelFormatAttribs, WglContext, WglExtra, }; -use crate::wrappers::win32::LibraryModule; +use crate::wrappers::win32::{ExtendedUser32, LibraryModule, RawLibrary}; pub type GlContext = Rc; pub struct GlContextInner { hdc: OwnDeviceContext, wgl_ctx: WglContext, - gl_library: LibraryModule, + gl_library: RawLibrary, } impl GlContextInner { pub fn create(window: HWnd, config: GlConfig) -> Result { - let gl_library = unsafe { LibraryModule::load(s!("opengl32.dll"))? }; + let gl_library = unsafe { RawLibrary::load(c"opengl32.dll")? }; // Create temporary window and context to load function pointers let extra = with_dummy_window(|hwnd_tmp| { @@ -69,8 +69,7 @@ impl GlContextInner { return addr as *const c_void; } - let symbol_ptr = PCSTR::from_raw(symbol_ptr); - if let Some(addr) = unsafe { self.gl_library.get_proc_address(symbol_ptr) } { + if let Some(addr) = unsafe { self.gl_library.get(symbol) } { return addr; } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index d808362e..2f9ab996 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -22,8 +22,8 @@ use crate::window::WindowInitializer; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; use crate::wrappers::win32::{ - ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessContext, ExtendedUser32, Rect, - WindowStyle, + ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessContext, ExtendedUser32, + LibraryModule, Rect, WindowStyle, }; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSize}; @@ -695,7 +695,7 @@ unsafe fn wnd_proc_inner( impl WindowHandle { pub fn create_window(init: WindowInitializer) -> Result { - let extended_user_32 = ExtendedUser32::load()?; + let extended_user_32 = unsafe { LibraryModule::load()? }; let shared_state = WindowSharedState::new(extended_user_32, &init.settings); diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 070e8ac4..33f71d21 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -5,7 +5,7 @@ use crate::utils::SizingStrategy; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; -use crate::wrappers::win32::{Dpi, ExtendedUser32}; +use crate::wrappers::win32::{Dpi, ExtendedUser32, LibraryModule}; use crate::WindowSettings; use crate::{MouseCursor, WindowSize}; use raw_window_handle::{DisplayHandle, Win32WindowHandle}; @@ -23,7 +23,7 @@ pub(crate) struct WindowState { pub mouse_was_outside_window: Cell, pub cursor_icon: Cell, - pub user32: ExtendedUser32, + pub user32: LibraryModule, pub shared: Rc, #[cfg(feature = "opengl")] @@ -31,7 +31,9 @@ pub(crate) struct WindowState { } impl WindowState { - pub fn new(hwnd: HWnd, user32: ExtendedUser32, shared: Rc) -> Self { + pub fn new( + hwnd: HWnd, user32: LibraryModule, shared: Rc, + ) -> Self { Self { hwnd, keyboard_state: RefCell::new(KeyboardState::new()), @@ -131,12 +133,12 @@ pub struct WindowSharedState { pub resize_host_originated: Cell, pub destroy_host_originated: Cell, - pub user32: ExtendedUser32, + pub user32: LibraryModule, pub sizing_strategy: SizingStrategy, } impl WindowSharedState { - pub fn new(user32: ExtendedUser32, settings: &WindowSettings) -> Rc { + pub fn new(user32: LibraryModule, settings: &WindowSettings) -> Rc { Self { parented: (settings.parent.is_some() || settings.wait_for_parent).into(), is_alive: true.into(), diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index 8c687ee8..f235e324 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -26,7 +26,7 @@ pub struct DpiAwarenessContext<'a> { } impl<'a> DpiAwarenessContext<'a> { - pub fn new(user32: &'a ExtendedUser32) -> Result { + pub fn new(user32: &'a LibraryModule) -> Result { let Some(set_thread_dpi_awareness_context) = user32.set_thread_dpi_awareness_context else { return Ok(Self { previous: null_mut(), user32 }); }; diff --git a/src/wrappers/win32/library.rs b/src/wrappers/win32/library.rs index 707ada59..d0e680ae 100644 --- a/src/wrappers/win32/library.rs +++ b/src/wrappers/win32/library.rs @@ -1,27 +1,69 @@ -use std::ffi::c_void; +use std::ffi::{c_void, CStr}; +use std::ops::Deref; use std::ptr::NonNull; -use windows_core::{Error, PCSTR}; +use windows_core::Error; use windows_sys::Win32::Foundation::FreeLibrary; use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; -pub struct LibraryModule(NonNull); +pub trait Module: Clone + Sized { + const MODULE_NAME: &'static CStr; + fn load(library: &RawLibrary) -> Self; +} + +pub struct LibraryModule { + _library: RawLibrary, + module: M, +} + +impl LibraryModule { + pub unsafe fn load() -> Result { + let library = RawLibrary::load(M::MODULE_NAME)?; + Ok(Self { module: M::load(&library), _library: library }) + } +} + +impl Clone for LibraryModule { + fn clone(&self) -> Self { + let library = unsafe { RawLibrary::load(M::MODULE_NAME) }; + + // PANIC: This should not be able to happen, since we already loaded it once and it's still loaded in Clone + let library = match library { + Ok(library) => library, + Err(e) => unreachable!("Failed to load module: {}", e), + }; + + Self { _library: library, module: self.module.clone() } + } +} + +impl Deref for LibraryModule { + type Target = M; + fn deref(&self) -> &M { + &self.module + } +} + +pub struct RawLibrary(NonNull); -impl LibraryModule { - pub unsafe fn load(module_name: PCSTR) -> Result { - let library = unsafe { LoadLibraryA(module_name.as_ptr()) }; +impl RawLibrary { + pub unsafe fn load(module_name: &CStr) -> Result { + let library = unsafe { LoadLibraryA(module_name.as_ptr().cast()) }; let Some(library) = NonNull::new(library) else { return Err(Error::from_thread()) }; Ok(Self(library)) } - pub unsafe fn get_proc_address(&self, name: PCSTR) -> Option<*const c_void> { - let addr = unsafe { GetProcAddress(self.0.as_ptr(), name.as_ptr()) }; + /// # Safety + /// + /// T *must* be a function pointer type, and must match the given `name`. + pub unsafe fn get(&self, name: &CStr) -> Option { + let addr = unsafe { GetProcAddress(self.0.as_ptr(), name.as_ptr().cast()) }?; - addr.map(|f| f as _) + Some(core::mem::transmute_copy(&addr)) } } -impl Drop for LibraryModule { +impl Drop for RawLibrary { fn drop(&mut self) { unsafe { FreeLibrary(self.0.as_ptr()) }; } diff --git a/src/wrappers/win32/user32.rs b/src/wrappers/win32/user32.rs index a1a3385f..721f3c2e 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -1,5 +1,5 @@ -use crate::wrappers::win32::LibraryModule; -use std::ffi::c_void; +use crate::wrappers::win32::{LibraryModule, Module, RawLibrary}; +use std::ffi::{c_void, CStr}; use std::mem::transmute; use windows_core::{s, Error}; use windows_sys::core::BOOL; @@ -31,56 +31,33 @@ type AdjustWindowRectExForDpi = unsafe extern "system" fn( dpi: u32, ) -> BOOL; -impl ExtendedUser32 { - pub fn load() -> Result { - let library = unsafe { LibraryModule::load(s!("user32.dll"))? }; +type IsValidDpiAwarenessContext = unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL; - unsafe { - Ok(Self { - set_thread_dpi_awareness_context: library - .get_proc_address(s!("SetThreadDpiAwarenessContext")) - .map(|p| transmute::<*const c_void, SetThreadDpiAwarenessContext>(p)), - adjust_window_rect_ex_for_dpi: library - .get_proc_address(s!("AdjustWindowRectExForDpi")) - .map(|p| transmute::<*const c_void, AdjustWindowRectExForDpi>(p)), - get_dpi_for_window: library - .get_proc_address(s!("GetDpiForWindow")) - .map(|p| transmute::<*const c_void, GetDpiForWindow>(p)), - set_process_dpi_awareness_context: library - .get_proc_address(s!("SetProcessDpiAwarenessContext")) - .map(|p| transmute::<*const c_void, SetProcessDpiAwarenessContext>(p)), - _library: library, - }) - } - } - - pub fn set_process_dpi_awareness_context(&self) -> Result<(), Error> { - let Some(func) = self.set_process_dpi_awareness_context else { return Ok(()) }; - - let result = unsafe { func(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }; - - if result == 0 { - return Err(Error::from_thread()); - } +// Checks the above typedefs match the function definitions from windows_sys +const _: () = { + let _: GetDpiForWindow = GetDpiForWindow; + let _: AdjustWindowRectExForDpi = AdjustWindowRectExForDpi; + let _: SetThreadDpiAwarenessContext = SetThreadDpiAwarenessContext; + let _: IsValidDpiAwarenessContext = IsValidDpiAwarenessContext; +}; - Ok(()) - } +#[derive(Copy, Clone)] +pub struct ExtendedUser32 { + pub set_thread_dpi_awareness_context: Option, + pub adjust_window_rect_ex_for_dpi: Option, + pub get_dpi_for_window: Option, } -impl Clone for ExtendedUser32 { - fn clone(&self) -> Self { - let library = unsafe { LibraryModule::load(s!("user32.dll")) }; - - // PANIC: This should not be able to happen, since we already loaded it once and it's still loaded in Clone - let Ok(library) = library else { unreachable!() }; +impl Module for ExtendedUser32 { + const MODULE_NAME: &'static CStr = c"user32.dll"; - Self { - _library: library, - - set_thread_dpi_awareness_context: self.set_thread_dpi_awareness_context, - adjust_window_rect_ex_for_dpi: self.adjust_window_rect_ex_for_dpi, - get_dpi_for_window: self.get_dpi_for_window, - set_process_dpi_awareness_context: self.set_process_dpi_awareness_context, + fn load(library: &RawLibrary) -> Self { + unsafe { + Self { + set_thread_dpi_awareness_context: library.get(c"SetThreadDpiAwarenessContext"), + adjust_window_rect_ex_for_dpi: library.get(c"AdjustWindowRectExForDpi"), + get_dpi_for_window: library.get(c"GetDpiForWindow"), + } } } } diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index 4b56ce6c..ebe392f9 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -2,7 +2,7 @@ use crate::wrappers::win32::dpi::{Dpi, DpiAwarenessContext}; use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::user32::ExtendedUser32; -use crate::wrappers::win32::Rect; +use crate::wrappers::win32::{LibraryModule, Rect}; use std::ffi::c_void; use std::num::NonZeroUsize; use std::ptr::{null_mut, NonNull}; @@ -151,7 +151,8 @@ impl HWnd { } pub fn resize_and_activate( - &self, client_size: PhysicalSize, window_dpi: Option, user32: &ExtendedUser32, + &self, client_size: PhysicalSize, window_dpi: Option, + user32: &LibraryModule, ) -> Result<()> { let dpi_ctx = DpiAwarenessContext::new(user32)?; let style = self.get_style()?; From 370e24f4d08522c5ccedcbafbf8fd8f69363c4a1 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:58:39 +0200 Subject: [PATCH 02/17] wip --- src/wrappers/win32/user32.rs | 85 ++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/src/wrappers/win32/user32.rs b/src/wrappers/win32/user32.rs index 721f3c2e..6402a747 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -1,51 +1,65 @@ -use crate::wrappers::win32::{LibraryModule, Module, RawLibrary}; -use std::ffi::{c_void, CStr}; -use std::mem::transmute; -use windows_core::{s, Error}; +use crate::wrappers::win32::{Module, RawLibrary}; +use std::ffi::CStr; use windows_sys::core::BOOL; -use windows_sys::Win32::Foundation::{HWND, RECT}; +use windows_sys::Win32::Foundation::{HANDLE, HWND, RECT}; use windows_sys::Win32::UI::HiDpi::{ DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, }; use windows_sys::Win32::UI::WindowsAndMessaging::{WINDOW_EX_STYLE, WINDOW_STYLE}; -pub struct ExtendedUser32 { - _library: LibraryModule, - pub set_thread_dpi_awareness_context: Option, - pub adjust_window_rect_ex_for_dpi: Option, - pub get_dpi_for_window: Option, - pub set_process_dpi_awareness_context: Option, -} - +type AdjustWindowRectExForDpi = + unsafe extern "system" fn(*mut RECT, WINDOW_STYLE, BOOL, WINDOW_EX_STYLE, u32) -> BOOL; +type AreDpiAwarenessContextsEqual = + unsafe extern "system" fn(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT) -> BOOL; +type EnableNonClientDpiScaling = unsafe extern "system" fn(HWND) -> BOOL; +type GetAwarenessFromDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> BOOL; +type GetDpiAwarenessContextForProcess = unsafe extern "system" fn(HANDLE) -> DPI_AWARENESS_CONTEXT; +type GetDpiForSystem = unsafe extern "system" fn() -> u32; +type GetDpiForWindow = unsafe extern "system" fn(HWND) -> u32; +type GetDpiFromDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> u32; +type GetSystemDpiForProcess = unsafe extern "system" fn(HANDLE) -> u32; +type GetWindowDpiAwarenessContext = unsafe extern "system" fn(HWND) -> DPI_AWARENESS_CONTEXT; +type GetWindowDpiHostingBehavior = unsafe extern "system" fn(HWND) -> DPI_HOSTING_BEHAVIOR; +type IsValidDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> BOOL; +type SetProcessDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> BOOL; type SetThreadDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> DPI_AWARENESS_CONTEXT; type SetProcessDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> BOOL; -type GetDpiForWindow = unsafe extern "system" fn(HWND) -> u32; - -type AdjustWindowRectExForDpi = unsafe extern "system" fn( - lprect: *mut RECT, - dwstyle: WINDOW_STYLE, - bmenu: BOOL, - dwexstyle: WINDOW_EX_STYLE, - dpi: u32, -) -> BOOL; - -type IsValidDpiAwarenessContext = unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL; - // Checks the above typedefs match the function definitions from windows_sys const _: () = { - let _: GetDpiForWindow = GetDpiForWindow; let _: AdjustWindowRectExForDpi = AdjustWindowRectExForDpi; - let _: SetThreadDpiAwarenessContext = SetThreadDpiAwarenessContext; + let _: AreDpiAwarenessContextsEqual = AreDpiAwarenessContextsEqual; + let _: EnableNonClientDpiScaling = EnableNonClientDpiScaling; + let _: GetAwarenessFromDpiAwarenessContext = GetAwarenessFromDpiAwarenessContext; + let _: GetDpiAwarenessContextForProcess = GetDpiAwarenessContextForProcess; + let _: GetDpiForSystem = GetDpiForSystem; + let _: GetDpiForWindow = GetDpiForWindow; + let _: GetDpiFromDpiAwarenessContext = GetDpiFromDpiAwarenessContext; + let _: GetSystemDpiForProcess = GetSystemDpiForProcess; + let _: GetWindowDpiAwarenessContext = GetWindowDpiAwarenessContext; + let _: GetWindowDpiHostingBehavior = GetWindowDpiHostingBehavior; let _: IsValidDpiAwarenessContext = IsValidDpiAwarenessContext; + let _: SetProcessDpiAwarenessContext = SetProcessDpiAwarenessContext; + let _: SetThreadDpiAwarenessContext = SetThreadDpiAwarenessContext; }; #[derive(Copy, Clone)] pub struct ExtendedUser32 { - pub set_thread_dpi_awareness_context: Option, pub adjust_window_rect_ex_for_dpi: Option, + pub are_dpi_awareness_contexts_equal: Option, + pub enable_non_client_dpi_scaling: Option, + pub get_awareness_from_dpi_awareness_context: Option, + pub get_dpi_awareness_context_for_process: Option, + pub get_dpi_for_system: Option, pub get_dpi_for_window: Option, + pub get_dpi_from_dpi_awareness_context: Option, + pub get_system_dpi_for_process: Option, + pub get_window_dpi_awareness_context: Option, + pub get_window_dpi_hosting_behavior: Option, + pub is_valid_dpi_awareness_context: Option, + pub set_process_dpi_awareness_context: Option, + pub set_thread_dpi_awareness_context: Option, } impl Module for ExtendedUser32 { @@ -54,9 +68,22 @@ impl Module for ExtendedUser32 { fn load(library: &RawLibrary) -> Self { unsafe { Self { - set_thread_dpi_awareness_context: library.get(c"SetThreadDpiAwarenessContext"), adjust_window_rect_ex_for_dpi: library.get(c"AdjustWindowRectExForDpi"), + are_dpi_awareness_contexts_equal: library.get(c"AreDpiAwarenessContextsEqual"), + enable_non_client_dpi_scaling: library.get(c"EnableNonClientDpiScaling"), + get_awareness_from_dpi_awareness_context: library + .get(c"GetAwarenessFromDpiAwarenessContext"), + get_dpi_awareness_context_for_process: library + .get(c"GetDpiAwarenessContextForProcess"), get_dpi_for_window: library.get(c"GetDpiForWindow"), + get_dpi_for_system: library.get(c"GetDpiForSystem"), + get_dpi_from_dpi_awareness_context: library.get(c"GetDpiFromDpiAwarenessContext"), + get_system_dpi_for_process: library.get(c"GetSystemDpiForProcess"), + get_window_dpi_awareness_context: library.get(c"GetWindowDpiAwarenessContext"), + get_window_dpi_hosting_behavior: library.get(c"GetWindowDpiHostingBehavior"), + is_valid_dpi_awareness_context: library.get(c"IsValidDpiAwarenessContext"), + set_process_dpi_awareness_context: library.get(c"SetProcessDpiAwarenessContext"), + set_thread_dpi_awareness_context: library.get(c"SetThreadDpiAwarenessContext"), } } } From 20d169f7d0d063ea3ccf1dc25d2893c07b2fbb12 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:35:32 +0200 Subject: [PATCH 03/17] wip --- src/platform/win/mod.rs | 4 +-- src/wrappers/win32.rs | 2 ++ src/wrappers/win32/dpi.rs | 50 ++++++++++++++++++++++++++++++++++++ src/wrappers/win32/shcore.rs | 35 +++++++++++++++++++++++++ src/wrappers/win32/user32.rs | 6 ++--- 5 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 src/wrappers/win32/shcore.rs diff --git a/src/platform/win/mod.rs b/src/platform/win/mod.rs index 0a92c809..9ec62af6 100644 --- a/src/platform/win/mod.rs +++ b/src/platform/win/mod.rs @@ -7,7 +7,7 @@ mod window_state; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; -use crate::wrappers::win32::ExtendedUser32; +use crate::wrappers::win32::{ExtendedUser32, LibraryModule}; pub use error::{PlatformError, Result}; use raw_window_handle::{ DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, Win32WindowHandle, @@ -99,7 +99,7 @@ impl Display for ParentWindowHandleError { #[inline] pub fn assume_standalone_in_process() { - let user32 = match ExtendedUser32::load() { + let user32 = match unsafe { LibraryModule::::load() } { Ok(user32) => user32, Err(e) => { crate::warn!("Failed to load user32.dll: {}", e); diff --git a/src/wrappers/win32.rs b/src/wrappers/win32.rs index 4f2c5b01..3b62f2ca 100644 --- a/src/wrappers/win32.rs +++ b/src/wrappers/win32.rs @@ -3,6 +3,7 @@ mod dpi; pub mod h_instance; mod library; mod rect; +mod shcore; mod style; mod user32; pub mod uuid; @@ -11,6 +12,7 @@ pub mod window; pub use dpi::*; pub use library::*; pub use rect::Rect; +pub use shcore::*; pub use style::*; pub use user32::*; diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index f235e324..473559d6 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -20,6 +20,56 @@ impl Default for Dpi { } } +/// Legacy (replaced by ProcessDpiContextAwareness), process-wide +#[repr(i32)] +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum ProcessDpiAwareness { + Unaware = PROCESS_DPI_UNAWARE, + SystemDpiAware = PROCESS_SYSTEM_DPI_AWARE, + PerMonitorDpiAware = PROCESS_PER_MONITOR_DPI_AWARE, +} + +impl ProcessDpiAwareness { + fn from_raw(raw: PROCESS_DPI_AWARENESS) -> Option { + match raw { + PROCESS_DPI_UNAWARE => Some(Self::SystemDpiAware), + PROCESS_SYSTEM_DPI_AWARE => Some(Self::PerMonitorDpiAware), + PROCESS_PER_MONITOR_DPI_AWARE => Some(Self::Unaware), + _ => { + crate::warn!("Unknown PROCESS_DPI_AWARENESS value: {}", raw); + None + } + } + } + + pub fn get(lib: &ExtendedShCore) -> Option { + let mut value = -1; + let result = HRESULT(unsafe { lib.get_process_dpi_awareness?(null_mut(), &mut value) }); + + if result.is_err() { + crate::warn!("GetProcessDpiAwareness failed: {}", result.message()); + return None; + } + + if value < 0 { + crate::warn!("GetProcessDpiAwareness did not return a value"); + return None; + } + + Self::from_raw(value) + } + + pub fn set(&self, lib: &ExtendedShCore) -> Result<()> { + let Some(set) = lib.set_process_dpi_awareness else { return Ok(()) }; + + HRESULT(unsafe { set(*self as _) }).ok() + } +} + +pub struct DpiAwareness { + value: DPI_AWARENESS, +} + pub struct DpiAwarenessContext<'a> { previous: DPI_AWARENESS_CONTEXT, user32: &'a ExtendedUser32, diff --git a/src/wrappers/win32/shcore.rs b/src/wrappers/win32/shcore.rs new file mode 100644 index 00000000..5abb4e2e --- /dev/null +++ b/src/wrappers/win32/shcore.rs @@ -0,0 +1,35 @@ +use crate::wrappers::win32::{Module, RawLibrary}; +use std::ffi::CStr; +use windows_sys::core::{BOOL, HRESULT}; +use windows_sys::Win32::Foundation::{HANDLE, HWND, RECT}; +use windows_sys::Win32::UI::HiDpi::*; + +type GetProcessDpiAwareness = + unsafe extern "system" fn(HANDLE, *mut PROCESS_DPI_AWARENESS) -> HRESULT; + +type SetProcessDpiAwareness = unsafe extern "system" fn(PROCESS_DPI_AWARENESS) -> HRESULT; + +// Checks the above typedefs match the function definitions from windows_sys +const _: () = { + let _: GetProcessDpiAwareness = GetProcessDpiAwareness; + let _: SetProcessDpiAwareness = SetProcessDpiAwareness; +}; + +#[derive(Copy, Clone)] +pub struct ExtendedShCore { + pub get_process_dpi_awareness: Option, + pub set_process_dpi_awareness: Option, +} + +impl Module for ExtendedShCore { + const MODULE_NAME: &'static CStr = c"api-ms-win-shcore-scaling-l1-1-1.dll"; + + fn load(library: &RawLibrary) -> Self { + unsafe { + Self { + get_process_dpi_awareness: library.get(c"GetProcessDpiAwareness"), + set_process_dpi_awareness: library.get(c"SetProcessDpiAwareness"), + } + } + } +} diff --git a/src/wrappers/win32/user32.rs b/src/wrappers/win32/user32.rs index 6402a747..a1e157df 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -2,9 +2,7 @@ use crate::wrappers::win32::{Module, RawLibrary}; use std::ffi::CStr; use windows_sys::core::BOOL; use windows_sys::Win32::Foundation::{HANDLE, HWND, RECT}; -use windows_sys::Win32::UI::HiDpi::{ - DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, -}; +use windows_sys::Win32::UI::HiDpi::*; use windows_sys::Win32::UI::WindowsAndMessaging::{WINDOW_EX_STYLE, WINDOW_STYLE}; type AdjustWindowRectExForDpi = @@ -17,6 +15,7 @@ type GetDpiAwarenessContextForProcess = unsafe extern "system" fn(HANDLE) -> DPI type GetDpiForSystem = unsafe extern "system" fn() -> u32; type GetDpiForWindow = unsafe extern "system" fn(HWND) -> u32; type GetDpiFromDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> u32; +type GetProcessDpiAwarenessContext = unsafe extern "system" fn(HANDLE) -> u32; type GetSystemDpiForProcess = unsafe extern "system" fn(HANDLE) -> u32; type GetWindowDpiAwarenessContext = unsafe extern "system" fn(HWND) -> DPI_AWARENESS_CONTEXT; type GetWindowDpiHostingBehavior = unsafe extern "system" fn(HWND) -> DPI_HOSTING_BEHAVIOR; @@ -24,7 +23,6 @@ type IsValidDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEX type SetProcessDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> BOOL; type SetThreadDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> DPI_AWARENESS_CONTEXT; -type SetProcessDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> BOOL; // Checks the above typedefs match the function definitions from windows_sys const _: () = { From b86944689a97de2eaf1b24075aacee7479c53fe2 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 7 Sep 2026 15:13:13 +0200 Subject: [PATCH 04/17] wip --- src/platform/win/dpi.rs | 23 +++++ src/platform/win/mod.rs | 2 +- src/platform/win/window.rs | 55 ++++++----- src/platform/win/window_state.rs | 19 +++- src/wrappers/win32/dpi.rs | 140 ++++++++++++++++++++++------ src/wrappers/win32/shcore.rs | 6 ++ src/wrappers/win32/user32.rs | 2 +- src/wrappers/win32/window.rs | 4 +- src/wrappers/win32/window/handle.rs | 8 +- 9 files changed, 193 insertions(+), 66 deletions(-) create mode 100644 src/platform/win/dpi.rs diff --git a/src/platform/win/dpi.rs b/src/platform/win/dpi.rs new file mode 100644 index 00000000..e979bbf6 --- /dev/null +++ b/src/platform/win/dpi.rs @@ -0,0 +1,23 @@ +use crate::wrappers::win32::window::HWnd; +use crate::wrappers::win32::ExtendedUser32; + +pub enum StrategyType { + Assume96Dpi, +} + +#[derive(Copy, Clone, Default)] +pub struct DpiScalingStrategy {} + +impl DpiScalingStrategy { + pub fn get(user32: &ExtendedUser32, parent: Option) -> Self { + if let Some(parent) = parent { + todo!() + } else { + todo!() + } + } + + pub fn assume_96_dpi(&self) -> bool { + todo!() + } +} diff --git a/src/platform/win/mod.rs b/src/platform/win/mod.rs index 9ec62af6..e19516e5 100644 --- a/src/platform/win/mod.rs +++ b/src/platform/win/mod.rs @@ -1,3 +1,4 @@ +mod dpi; mod drop_target; mod error; mod hook; @@ -97,7 +98,6 @@ impl Display for ParentWindowHandleError { } } -#[inline] pub fn assume_standalone_in_process() { let user32 = match unsafe { LibraryModule::::load() } { Ok(user32) => user32, diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 2f9ab996..279061c6 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -16,13 +16,14 @@ use super::drop_target::DropTarget; use super::*; use crate::handler::WindowHandlerBuilder; use crate::host::Host; +use crate::platform::win::dpi::DpiScalingStrategy; use crate::platform::win::window_state::{WindowSharedState, WindowState}; use crate::platform::PlatformError; use crate::window::WindowInitializer; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; use crate::wrappers::win32::{ - ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessContext, ExtendedUser32, + ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessGuard, ExtendedUser32, LibraryModule, Rect, WindowStyle, }; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSize}; @@ -85,7 +86,8 @@ impl WindowHandle { }; let _guard = self.state.originate_host_resize(); - hwnd.resize_and_activate(new_size, self.state.current_dpi.get(), &self.state.user32)?; + let dpi_ctx = DpiAwarenessGuard::new(&self.state.user32)?; + hwnd.resize_and_activate(new_size, self.state.current_dpi.get(), &dpi_ctx)?; if self.state.current_size.get() == new_size { Ok(()) @@ -118,8 +120,9 @@ impl WindowHandle { } let _guard = self.state.originate_host_resize(); + let dpi_ctx = DpiAwarenessGuard::new(&self.state.user32)?; - hwnd.resize_and_activate(new_size, None, &self.state.user32)?; + hwnd.resize_and_activate(new_size, None, &dpi_ctx)?; if self.state.current_size.get() == new_size { Ok(()) @@ -216,9 +219,12 @@ pub struct BaseviewWindow { impl BaseviewWindow { pub fn create(shared_state: Rc, init: WindowInitializer) -> Result { - let dpi_ctx = DpiAwarenessContext::new(&shared_state.user32)?; - let style = WindowStyle::from_settings(&init.settings); + let parent = init.settings.parent.map(|p| p.inner.handle); + + shared_state.init_parent(parent); + + let dpi_ctx = DpiAwarenessGuard::new(&shared_state.user32)?; let window_size = shared_state.current_size.get(); @@ -249,7 +255,6 @@ impl BaseviewWindow { } }; - let parent = init.settings.parent.map(|p| p.inner.handle); let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, None)?; let title = HSTRING::from(init.settings.title); let window = create_window(&title, style, rect.size(), parent, &dpi_ctx, initializer)?; @@ -309,28 +314,30 @@ impl Drop for BaseviewWindow { impl WindowImpl for BaseviewWindow { fn after_create(&self, window: HWnd) -> core::result::Result<(), PlatformError> { - let hwnd = window.as_raw(); let window_state = &self.window_state; - self._keyboard_hook.set(Some(hook::init_keyboard_hook(hwnd))); + self._keyboard_hook.set(Some(hook::init_keyboard_hook(window.as_raw()))); - // Now we can get the actual dpi of the window. - let dpi = window.get_dpi(&self.window_state.user32)?; + if !window_state.shared.dpi_scaling_strategy.get().assume_96_dpi() { + // Now we can get the actual dpi of the window. + let dpi = window.get_dpi(&self.window_state.user32)?; - if let Some(dpi) = dpi { - if Some(dpi) != window_state.shared.current_dpi.get() { - window_state.shared.current_dpi.set(Some(dpi)); + if let Some(dpi) = dpi { + if Some(dpi) != window_state.shared.current_dpi.get() { + window_state.shared.current_dpi.set(Some(dpi)); - // We cannot create a window in "logical" pixels, and we can't DPI-scale to physical pixels because we - // have no way to know where the window will end up. - // So, at window creation, we assume a DPI=96, and if it ends up wrong, we resize the window - // to the actual logical size the user desired. - let new_size = self.initial_size.to_physical(dpi.scale_factor()); + // We cannot create a window in "logical" pixels, and we can't DPI-scale to physical pixels because we + // have no way to know where the window will end up. + // So, at window creation, we assume a DPI=96, and if it ends up wrong, we resize the window + // to the actual logical size the user desired. + let new_size = self.initial_size.to_physical(dpi.scale_factor()); - // Preemptively update so a synchronous WM_SIZE from SetWindowPos below - // doesn't also emit Resized. - window_state.shared.current_size.set(new_size); - window.resize_and_activate(new_size, Some(dpi), &window_state.user32)?; + // Preemptively update so a synchronous WM_SIZE from SetWindowPos below + // doesn't also emit Resized. + window_state.shared.current_size.set(new_size); + let guard = DpiAwarenessGuard::new(&window_state.shared.user32)?; + window.resize_and_activate(new_size, Some(dpi), &guard)?; + } } } @@ -587,7 +594,7 @@ unsafe fn wnd_proc_inner( let suggested_nc_rect = Rect((lparam as *const RECT).read()); let dpi = Dpi((wparam & 0xFFFF) as u16 as u32); - let dpi_ctx = DpiAwarenessContext::new(&window_state.user32).unwrap(); + let dpi_ctx = DpiAwarenessGuard::new(&window_state.user32).unwrap(); let style = window.get_style().unwrap(); let suggested_rect = dpi_ctx.nc_area_to_client_area(suggested_nc_rect, style, Some(dpi)).unwrap(); @@ -661,7 +668,7 @@ unsafe fn wnd_proc_inner( let info = lparam as *mut MINMAXINFO; - let ctx = DpiAwarenessContext::new(&window_state.user32).unwrap(); + let ctx = DpiAwarenessGuard::new(&window_state.user32).unwrap(); let style = window.get_style().unwrap(); let dpi = window_state.shared.current_dpi.get(); diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 33f71d21..d87b817a 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -1,11 +1,12 @@ use crate::dpi::{PhysicalSize, Size}; +use crate::platform::win::dpi::DpiScalingStrategy; use crate::platform::win::keyboard::KeyboardState; use crate::platform::PlatformHandle; use crate::utils::SizingStrategy; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; -use crate::wrappers::win32::{Dpi, ExtendedUser32, LibraryModule}; +use crate::wrappers::win32::{Dpi, DpiAwarenessGuard, ExtendedUser32, LibraryModule}; use crate::WindowSettings; use crate::{MouseCursor, WindowSize}; use raw_window_handle::{DisplayHandle, Win32WindowHandle}; @@ -88,7 +89,9 @@ impl WindowState { let dpi = self.shared.current_dpi.get(); let new_size = size.to_physical(self.shared.scale_factor()); - self.hwnd.resize_and_activate(new_size, dpi, &self.user32)?; + let ctx = DpiAwarenessGuard::new(&self.user32)?; + + self.hwnd.resize_and_activate(new_size, dpi, &ctx)?; Ok(()) } @@ -132,6 +135,7 @@ pub struct WindowSharedState { pub fallback_scale_factor: Cell>, pub resize_host_originated: Cell, pub destroy_host_originated: Cell, + pub dpi_scaling_strategy: Cell, pub user32: LibraryModule, pub sizing_strategy: SizingStrategy, @@ -149,10 +153,21 @@ impl WindowSharedState { destroy_host_originated: false.into(), sizing_strategy: SizingStrategy::from_settings(settings), user32, + dpi_scaling_strategy: DpiScalingStrategy::default().into(), } .into() } + pub fn init_parent(&self, parent: Option) { + let strategy = DpiScalingStrategy::get(&self.user32, parent); + + if strategy.assume_96_dpi() { + self.current_dpi.set(Some(Dpi::default())); + } + + self.dpi_scaling_strategy.set(strategy); + } + pub fn size(&self) -> WindowSize { WindowSize::from_physical(self.current_size.get(), self.scale_factor()) } diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index 473559d6..5acc0b99 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -1,7 +1,9 @@ use super::*; use crate::wrappers::win32::user32::ExtendedUser32; +use std::ffi::c_void; +use std::ptr::NonNull; use windows_core::{Error, Result}; -use windows_sys::Win32::Foundation::RECT; +use windows_sys::Win32::Foundation::{RECT, TRUE}; use windows_sys::Win32::UI::HiDpi::*; use windows_sys::Win32::UI::WindowsAndMessaging::{AdjustWindowRectEx, USER_DEFAULT_SCREEN_DPI}; @@ -12,6 +14,17 @@ impl Dpi { pub fn scale_factor(&self) -> f64 { self.0 as f64 / USER_DEFAULT_SCREEN_DPI as f64 } + + /// Windows 10, version 1607 + pub fn get_system(user32: &ExtendedUser32) -> Option { + if let Some(get_dpi_for_system) = user32.get_dpi_for_system { + Some(Self(unsafe { get_dpi_for_system() })) + } else if let Some(get_system_dpi_for_process) = user32.get_system_dpi_for_process { + Some(Self(unsafe { get_system_dpi_for_process(null_mut()) })) + } else { + None + } + } } impl Default for Dpi { @@ -20,7 +33,7 @@ impl Default for Dpi { } } -/// Legacy (replaced by ProcessDpiContextAwareness), process-wide +/// Win8 Legacy (replaced by DpiAwarenessContext in Win10), process-wide #[repr(i32)] #[derive(Copy, Clone, Eq, PartialEq)] pub enum ProcessDpiAwareness { @@ -66,50 +79,116 @@ impl ProcessDpiAwareness { } } +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum DpiAwarenessContextType { + Unaware, + SystemDpiAware, + PerMonitorDpiAware, + PerMonitorDpiAwareV2, +} + +#[derive(Copy, Clone)] +pub struct DpiAwarenessContext { + inner: NonNull, +} + +impl DpiAwarenessContext { + /// Windows 10, version 1607 + pub fn is_valid(&self, user32: &ExtendedUser32) -> Option { + Some(unsafe { user32.is_valid_dpi_awareness_context?(self.inner.as_ptr()) } == TRUE) + } + + /// Windows 10, version 1607 + pub fn set_thread(&self, user32: &ExtendedUser32) -> Option> { + let previous = unsafe { + user32.set_thread_dpi_awareness_context?(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + }; + + let Some(inner) = NonNull::new(previous) else { return Some(Err(Error::from_thread())) }; + + Some(Ok(Self { inner })) + } + + /// Windows 10, version 1803 + pub fn dpi(&self, user32: &ExtendedUser32) -> Option { + todo!() + } + + /// Windows 10, version 1607 + pub fn equals( + &self, other: impl Into, user32: &ExtendedUser32, + ) -> Option { + Some( + unsafe { + user32.are_dpi_awareness_contexts_equal?( + self.inner.as_ptr(), + other.into().inner.as_ptr(), + ) + } == TRUE, + ) + } +} + +impl From for DpiAwarenessContext { + fn from(value: DpiAwarenessContextType) -> Self { + let inner = match value { + DpiAwarenessContextType::Unaware => DPI_AWARENESS_CONTEXT_UNAWARE, + DpiAwarenessContextType::SystemDpiAware => DPI_AWARENESS_CONTEXT_SYSTEM_AWARE, + DpiAwarenessContextType::PerMonitorDpiAware => DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, + DpiAwarenessContextType::PerMonitorDpiAwareV2 => { + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + } + }; + + let Some(inner) = NonNull::new(inner) else { unreachable!() }; + + Self { inner } + } +} + pub struct DpiAwareness { value: DPI_AWARENESS, } -pub struct DpiAwarenessContext<'a> { - previous: DPI_AWARENESS_CONTEXT, - user32: &'a ExtendedUser32, +pub struct DpiAwarenessGuard<'a> { + inner: Option<(DpiAwarenessContext, &'a ExtendedUser32)>, } -impl<'a> DpiAwarenessContext<'a> { - pub fn new(user32: &'a LibraryModule) -> Result { +impl<'a> DpiAwarenessGuard<'a> { + pub fn new(user32: &'a ExtendedUser32) -> Result { let Some(set_thread_dpi_awareness_context) = user32.set_thread_dpi_awareness_context else { - return Ok(Self { previous: null_mut(), user32 }); + return Ok(Self { inner: None }); }; let previous = unsafe { set_thread_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }; - if previous.is_null() { - return Err(Error::from_thread()); - } + let Some(previous) = NonNull::new(previous) else { return Err(Error::from_thread()) }; - Ok(DpiAwarenessContext { previous, user32 }) + Ok(DpiAwarenessGuard { inner: Some((DpiAwarenessContext { inner: previous }, user32)) }) } pub fn client_area_to_nc_area( &self, mut rect: Rect, style: WindowStyle, dpi: Option, ) -> Result { - let (Some(adjust_window_rect_ex_for_dpi), Some(dpi)) = - (self.user32.adjust_window_rect_ex_for_dpi, dpi) - else { - let result = unsafe { AdjustWindowRectEx(&mut rect.0, style.style, 0, style.style_ex) }; - - if result == 0 { - return Err(Error::from_thread()); + let result = if let ( + Some(( + _, + ExtendedUser32 { + adjust_window_rect_ex_for_dpi: Some(adjust_window_rect_ex_for_dpi), + .. + }, + )), + Some(dpi), + ) = (self.inner, dpi) + { + // adjust_window_rect_ex_for_dpi takes the current DPI awareness context in consideration. + // Therefore, this method taking &self enforces that the DPI aware context is correct. + unsafe { + adjust_window_rect_ex_for_dpi(&mut rect.0, style.style, 0, style.style_ex, dpi.0) } - - return Ok(rect); - }; - - // adjust_window_rect_ex_for_dpi takes the current DPI awareness context in consideration. - // Therefore, this method taking &self enforces that the DPI aware context is correct. - let result = unsafe { - adjust_window_rect_ex_for_dpi(&mut rect.0, style.style, 0, style.style_ex, dpi.0) + } else { + unsafe { AdjustWindowRectEx(&mut rect.0, style.style, 0, style.style_ex) } }; if result == 0 { @@ -133,11 +212,10 @@ impl<'a> DpiAwarenessContext<'a> { } } -impl Drop for DpiAwarenessContext<'_> { +impl Drop for DpiAwarenessGuard<'_> { fn drop(&mut self) { - if let Some(set_thread_dpi_awareness_context) = self.user32.set_thread_dpi_awareness_context - { - let _ = unsafe { set_thread_dpi_awareness_context(self.previous) }; + if let Some(inner) = self.inner { + let _ = inner.0.set_thread(inner.1); } } } diff --git a/src/wrappers/win32/shcore.rs b/src/wrappers/win32/shcore.rs index 5abb4e2e..c342d398 100644 --- a/src/wrappers/win32/shcore.rs +++ b/src/wrappers/win32/shcore.rs @@ -21,6 +21,12 @@ pub struct ExtendedShCore { pub set_process_dpi_awareness: Option, } +impl ExtendedShCore { + pub fn can_handle_process_dpi_awareness(&self) -> bool { + self.get_process_dpi_awareness.is_some() && self.set_process_dpi_awareness.is_some() + } +} + impl Module for ExtendedShCore { const MODULE_NAME: &'static CStr = c"api-ms-win-shcore-scaling-l1-1-1.dll"; diff --git a/src/wrappers/win32/user32.rs b/src/wrappers/win32/user32.rs index a1e157df..629ad5e7 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -7,7 +7,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{WINDOW_EX_STYLE, WINDOW_STYLE} type AdjustWindowRectExForDpi = unsafe extern "system" fn(*mut RECT, WINDOW_STYLE, BOOL, WINDOW_EX_STYLE, u32) -> BOOL; -type AreDpiAwarenessContextsEqual = +pub type AreDpiAwarenessContextsEqual = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT) -> BOOL; type EnableNonClientDpiScaling = unsafe extern "system" fn(HWND) -> BOOL; type GetAwarenessFromDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> BOOL; diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 856af271..4401c075 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -21,7 +21,7 @@ use std::rc::Rc; use window_class::RegisteredClass; use windows_core::{Error, Result, HSTRING}; -use crate::wrappers::win32::dpi::DpiAwarenessContext; +use crate::wrappers::win32::dpi::DpiAwarenessGuard; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::style::WindowStyle; use windows_sys::Win32::Foundation::{LPARAM, LRESULT, WPARAM}; @@ -63,7 +63,7 @@ pub trait WindowImpl: 'static { /// [`WindowImpl::after_create`] instead. pub fn create_window( title: &HSTRING, style: WindowStyle, nc_size: PhysicalSize, parent: Option, - _dpi_ctx: &DpiAwarenessContext, initializer: impl FnOnce(HWnd) -> W + 'static, + _dpi_ctx: &DpiAwarenessGuard, initializer: impl FnOnce(HWnd) -> W + 'static, ) -> Result { let instance = HInstance::get_from_dll(); let window_class = RegisteredClass::register_new(instance, Some(wnd_proc::))?; diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index ebe392f9..6b37eab4 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -1,8 +1,8 @@ use crate::dpi::{PhysicalPosition, PhysicalSize}; -use crate::wrappers::win32::dpi::{Dpi, DpiAwarenessContext}; +use crate::wrappers::win32::dpi::{Dpi, DpiAwarenessGuard}; use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::user32::ExtendedUser32; -use crate::wrappers::win32::{LibraryModule, Rect}; +use crate::wrappers::win32::Rect; use std::ffi::c_void; use std::num::NonZeroUsize; use std::ptr::{null_mut, NonNull}; @@ -151,10 +151,8 @@ impl HWnd { } pub fn resize_and_activate( - &self, client_size: PhysicalSize, window_dpi: Option, - user32: &LibraryModule, + &self, client_size: PhysicalSize, window_dpi: Option, dpi_ctx: &DpiAwarenessGuard, ) -> Result<()> { - let dpi_ctx = DpiAwarenessContext::new(user32)?; let style = self.get_style()?; let rect = Rect::from(client_size); From d0a093dfd8d3df841e470359c4b144945e7cfc1f Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:26:05 +0200 Subject: [PATCH 05/17] wip --- src/platform/win/dpi.rs | 103 ++++++++++++++++++++++++++-- src/platform/win/mod.rs | 14 +--- src/platform/win/window.rs | 30 +++++--- src/platform/win/window_state.rs | 8 ++- src/wrappers/win32/dpi.rs | 54 ++++++++++++--- src/wrappers/win32/library.rs | 9 ++- src/wrappers/win32/shcore.rs | 2 +- src/wrappers/win32/user32.rs | 2 +- src/wrappers/win32/window/handle.rs | 37 +++++++++- 9 files changed, 215 insertions(+), 44 deletions(-) diff --git a/src/platform/win/dpi.rs b/src/platform/win/dpi.rs index e979bbf6..494fe975 100644 --- a/src/platform/win/dpi.rs +++ b/src/platform/win/dpi.rs @@ -1,23 +1,116 @@ use crate::wrappers::win32::window::HWnd; -use crate::wrappers::win32::ExtendedUser32; +use crate::wrappers::win32::{ + DpiAwarenessContext, DpiAwarenessContextType, ExtendedShCore, ExtendedUser32, LibraryModule, + ProcessDpiAwareness, +}; +use crate::WindowSettings; -pub enum StrategyType { +pub(crate) enum StrategyType { Assume96Dpi, } #[derive(Copy, Clone, Default)] -pub struct DpiScalingStrategy {} +pub(crate) struct DpiScalingStrategy { + assume_96_dpi: bool, + thread_dpi_awareness_context_type: Option, +} impl DpiScalingStrategy { - pub fn get(user32: &ExtendedUser32, parent: Option) -> Self { + pub fn get(user32: &ExtendedUser32, parent: Option, settings: &WindowSettings) -> Self { + // If we have an OpenGL context, then we must follow the process' DPI awareness setting. + // + // We hope it's the same as the one for the (potential) parent window, or that it's at least + // compatible. But if not, then having a DPI Awareness reset due to mismatched DPI Awareness + // is still better than half of the window being unusuable. + #[cfg(feature = "opengl")] + if settings.gl_config.is_some() { + return Self::get_from_process(); + } + if let Some(parent) = parent { + let parent_context = parent.get_dpi_awareness_context(user32); + + if parent.supports_mixed_dpi_hosting_behavior(user32) { + todo!() + } + + // Check if parent supports mixed context + // If not, use parent DPI awareness + + // If it does, choose whatever suits us best! todo!() } else { - todo!() + // No parent, we can choose whatever suits us best! + + Self::get_best_supported() } } + // If we have an OpenGL context, we *must* follow the process + // DPI awareness, otherwise some OpenGL Drivers (namely AMD and NVIDIA) will crap out. + // See: https://github.com/RustAudio/baseview/issues/321 + // https://forum.juce.com/t/scaling-issues-with-retina-and-opengl-on-windows-in-ableton-live/41573 + // https://forums.developer.nvidia.com/t/high-dpi-scaling-bug-with-nvidia-drivers-and-opengl-on-windows-10/79615 + #[cfg(feature = "opengl")] + fn get_from_process() -> Self { + todo!() + } + + fn get_best_supported() -> Self { + todo!() + } + + fn get_from_parent() -> Self { + todo!() + } + pub fn assume_96_dpi(&self) -> bool { todo!() } + + pub fn thread_dpi_awareness_context_type(&self) -> Option { + todo!() + } +} + +pub(crate) fn set_process_dpi_awareness() { + if !set_process_dpi_awareness_context() { + // Win8.1 fallback + set_process_dpi_awareness_legacy() + } +} + +fn set_process_dpi_awareness_context() -> bool { + let user32 = match LibraryModule::::load() { + Ok(user32) => user32, + Err(e) => { + crate::warn!("Failed to load user32.dll: {}", e); + return false; + } + }; + + let Some(supported) = DpiAwarenessContextType::best_supported(&user32) else { return false }; + + match DpiAwarenessContext::from(supported).set_process(&user32) { + None => false, + Some(Ok(())) => true, + Some(Err(e)) => { + crate::warn!("Failed to set Process DPI Awareness Context: {}", e); + false + } + } +} + +fn set_process_dpi_awareness_legacy() { + let shcore = match LibraryModule::::load() { + Ok(user32) => user32, + Err(e) => { + crate::warn!("Failed to load api-ms-win-shcore-scaling-l1-1-1.dll: {}", e); + return; + } + }; + + if let Err(e) = ProcessDpiAwareness::PerMonitorDpiAware.set(&shcore) { + crate::warn!("Failed to set Process DPI Awareness: {}", e); + } } diff --git a/src/platform/win/mod.rs b/src/platform/win/mod.rs index e19516e5..ef733ce8 100644 --- a/src/platform/win/mod.rs +++ b/src/platform/win/mod.rs @@ -8,7 +8,7 @@ mod window_state; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; -use crate::wrappers::win32::{ExtendedUser32, LibraryModule}; +pub(crate) use dpi::DpiScalingStrategy; pub use error::{PlatformError, Result}; use raw_window_handle::{ DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, Win32WindowHandle, @@ -99,15 +99,5 @@ impl Display for ParentWindowHandleError { } pub fn assume_standalone_in_process() { - let user32 = match unsafe { LibraryModule::::load() } { - Ok(user32) => user32, - Err(e) => { - crate::warn!("Failed to load user32.dll: {}", e); - return; - } - }; - - if let Err(e) = user32.set_process_dpi_awareness_context() { - crate::warn!("Failed to set process dpi_awareness_context: {}", e); - } + dpi::set_process_dpi_awareness(); } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 279061c6..6fe0d597 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -86,7 +86,8 @@ impl WindowHandle { }; let _guard = self.state.originate_host_resize(); - let dpi_ctx = DpiAwarenessGuard::new(&self.state.user32)?; + let dpi_ctx = + DpiAwarenessGuard::new(&self.state.user32, self.state.dpi_scaling_strategy.get())?; hwnd.resize_and_activate(new_size, self.state.current_dpi.get(), &dpi_ctx)?; if self.state.current_size.get() == new_size { @@ -120,7 +121,8 @@ impl WindowHandle { } let _guard = self.state.originate_host_resize(); - let dpi_ctx = DpiAwarenessGuard::new(&self.state.user32)?; + let dpi_ctx = + DpiAwarenessGuard::new(&self.state.user32, self.state.dpi_scaling_strategy.get())?; hwnd.resize_and_activate(new_size, None, &dpi_ctx)?; @@ -219,12 +221,13 @@ pub struct BaseviewWindow { impl BaseviewWindow { pub fn create(shared_state: Rc, init: WindowInitializer) -> Result { + shared_state.init(&init); + let style = WindowStyle::from_settings(&init.settings); let parent = init.settings.parent.map(|p| p.inner.handle); - shared_state.init_parent(parent); - - let dpi_ctx = DpiAwarenessGuard::new(&shared_state.user32)?; + let dpi_ctx = + DpiAwarenessGuard::new(&shared_state.user32, shared_state.dpi_scaling_strategy.get())?; let window_size = shared_state.current_size.get(); @@ -335,7 +338,10 @@ impl WindowImpl for BaseviewWindow { // Preemptively update so a synchronous WM_SIZE from SetWindowPos below // doesn't also emit Resized. window_state.shared.current_size.set(new_size); - let guard = DpiAwarenessGuard::new(&window_state.shared.user32)?; + let guard = DpiAwarenessGuard::new( + &window_state.shared.user32, + self.shared_state.dpi_scaling_strategy.get(), + )?; window.resize_and_activate(new_size, Some(dpi), &guard)?; } } @@ -594,7 +600,11 @@ unsafe fn wnd_proc_inner( let suggested_nc_rect = Rect((lparam as *const RECT).read()); let dpi = Dpi((wparam & 0xFFFF) as u16 as u32); - let dpi_ctx = DpiAwarenessGuard::new(&window_state.user32).unwrap(); + let dpi_ctx = DpiAwarenessGuard::new( + &window_state.user32, + window_state.shared.dpi_scaling_strategy.get(), + ) + .unwrap(); let style = window.get_style().unwrap(); let suggested_rect = dpi_ctx.nc_area_to_client_area(suggested_nc_rect, style, Some(dpi)).unwrap(); @@ -668,7 +678,11 @@ unsafe fn wnd_proc_inner( let info = lparam as *mut MINMAXINFO; - let ctx = DpiAwarenessGuard::new(&window_state.user32).unwrap(); + let ctx = DpiAwarenessGuard::new( + &window_state.user32, + window_state.shared.dpi_scaling_strategy.get(), + ) + .unwrap(); let style = window.get_style().unwrap(); let dpi = window_state.shared.current_dpi.get(); diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index d87b817a..5112df96 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -3,6 +3,7 @@ use crate::platform::win::dpi::DpiScalingStrategy; use crate::platform::win::keyboard::KeyboardState; use crate::platform::PlatformHandle; use crate::utils::SizingStrategy; +use crate::window::WindowInitializer; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; @@ -89,7 +90,7 @@ impl WindowState { let dpi = self.shared.current_dpi.get(); let new_size = size.to_physical(self.shared.scale_factor()); - let ctx = DpiAwarenessGuard::new(&self.user32)?; + let ctx = DpiAwarenessGuard::new(&self.user32, self.shared.dpi_scaling_strategy.get())?; self.hwnd.resize_and_activate(new_size, dpi, &ctx)?; Ok(()) @@ -158,8 +159,9 @@ impl WindowSharedState { .into() } - pub fn init_parent(&self, parent: Option) { - let strategy = DpiScalingStrategy::get(&self.user32, parent); + pub fn init(&self, init: &WindowInitializer) { + let parent = init.settings.parent.as_ref().map(|p| p.inner.handle); + let strategy = DpiScalingStrategy::get(&self.user32, parent, &init.settings); if strategy.assume_96_dpi() { self.current_dpi.set(Some(Dpi::default())); diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index 5acc0b99..b8e4dad1 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -1,9 +1,10 @@ use super::*; +use crate::platform::DpiScalingStrategy; use crate::wrappers::win32::user32::ExtendedUser32; use std::ffi::c_void; use std::ptr::NonNull; use windows_core::{Error, Result}; -use windows_sys::Win32::Foundation::{RECT, TRUE}; +use windows_sys::Win32::Foundation::{FALSE, RECT, TRUE}; use windows_sys::Win32::UI::HiDpi::*; use windows_sys::Win32::UI::WindowsAndMessaging::{AdjustWindowRectEx, USER_DEFAULT_SCREEN_DPI}; @@ -16,6 +17,7 @@ impl Dpi { } /// Windows 10, version 1607 + #[allow(clippy::manual_map, reason = "This is more readable")] pub fn get_system(user32: &ExtendedUser32) -> Option { if let Some(get_dpi_for_system) = user32.get_dpi_for_system { Some(Self(unsafe { get_dpi_for_system() })) @@ -79,20 +81,43 @@ impl ProcessDpiAwareness { } } +/// Windows 10, version 1607 #[derive(Copy, Clone, Eq, PartialEq)] pub enum DpiAwarenessContextType { Unaware, SystemDpiAware, PerMonitorDpiAware, + /// Windows 10, version 1703 PerMonitorDpiAwareV2, } +impl DpiAwarenessContextType { + /// Windows 10, version 1607 + pub fn best_supported(user32: &ExtendedUser32) -> Option { + use DpiAwarenessContextType::*; + + let ordered = [PerMonitorDpiAwareV2, PerMonitorDpiAware, SystemDpiAware, Unaware]; + + for awareness_type in ordered { + if DpiAwarenessContext::from(awareness_type).is_valid(user32)? { + return Some(awareness_type); + } + } + + None + } +} + #[derive(Copy, Clone)] pub struct DpiAwarenessContext { inner: NonNull, } impl DpiAwarenessContext { + pub fn from_raw(raw: NonNull) -> Self { + Self { inner: raw } + } + /// Windows 10, version 1607 pub fn is_valid(&self, user32: &ExtendedUser32) -> Option { Some(unsafe { user32.is_valid_dpi_awareness_context?(self.inner.as_ptr()) } == TRUE) @@ -109,6 +134,18 @@ impl DpiAwarenessContext { Some(Ok(Self { inner })) } + pub fn set_process(&self, user32: &ExtendedUser32) -> Option> { + let result = unsafe { + user32.set_process_dpi_awareness_context?(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + }; + + if result == FALSE { + return Some(Err(Error::from_thread())); + } + + Some(Ok(())) + } + /// Windows 10, version 1803 pub fn dpi(&self, user32: &ExtendedUser32) -> Option { todo!() @@ -155,17 +192,16 @@ pub struct DpiAwarenessGuard<'a> { } impl<'a> DpiAwarenessGuard<'a> { - pub fn new(user32: &'a ExtendedUser32) -> Result { - let Some(set_thread_dpi_awareness_context) = user32.set_thread_dpi_awareness_context else { + pub fn new(user32: &'a ExtendedUser32, strategy: DpiScalingStrategy) -> Result { + let Some(new_context) = strategy.thread_dpi_awareness_context_type() else { return Ok(Self { inner: None }); }; - let previous = - unsafe { set_thread_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }; - - let Some(previous) = NonNull::new(previous) else { return Err(Error::from_thread()) }; - - Ok(DpiAwarenessGuard { inner: Some((DpiAwarenessContext { inner: previous }, user32)) }) + match DpiAwarenessContext::from(new_context).set_thread(user32) { + None => Ok(Self { inner: None }), + Some(Err(e)) => Err(e), + Some(Ok(previous)) => Ok(Self { inner: Some((previous, user32)) }), + } } pub fn client_area_to_nc_area( diff --git a/src/wrappers/win32/library.rs b/src/wrappers/win32/library.rs index d0e680ae..12e50af0 100644 --- a/src/wrappers/win32/library.rs +++ b/src/wrappers/win32/library.rs @@ -5,7 +5,10 @@ use windows_core::Error; use windows_sys::Win32::Foundation::FreeLibrary; use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA}; -pub trait Module: Clone + Sized { +/// # Safety +/// +/// Implementations must ensure that the module with name `MODULE_NAME` is safe to load. +pub unsafe trait Module: Clone + Sized { const MODULE_NAME: &'static CStr; fn load(library: &RawLibrary) -> Self; } @@ -16,8 +19,8 @@ pub struct LibraryModule { } impl LibraryModule { - pub unsafe fn load() -> Result { - let library = RawLibrary::load(M::MODULE_NAME)?; + pub fn load() -> Result { + let library = unsafe { RawLibrary::load(M::MODULE_NAME)? }; Ok(Self { module: M::load(&library), _library: library }) } } diff --git a/src/wrappers/win32/shcore.rs b/src/wrappers/win32/shcore.rs index c342d398..00874265 100644 --- a/src/wrappers/win32/shcore.rs +++ b/src/wrappers/win32/shcore.rs @@ -27,7 +27,7 @@ impl ExtendedShCore { } } -impl Module for ExtendedShCore { +unsafe impl Module for ExtendedShCore { const MODULE_NAME: &'static CStr = c"api-ms-win-shcore-scaling-l1-1-1.dll"; fn load(library: &RawLibrary) -> Self { diff --git a/src/wrappers/win32/user32.rs b/src/wrappers/win32/user32.rs index 629ad5e7..4dcf8ba7 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -60,7 +60,7 @@ pub struct ExtendedUser32 { pub set_thread_dpi_awareness_context: Option, } -impl Module for ExtendedUser32 { +unsafe impl Module for ExtendedUser32 { const MODULE_NAME: &'static CStr = c"user32.dll"; fn load(library: &RawLibrary) -> Self { diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index 6b37eab4..f5fcd142 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -2,15 +2,16 @@ use crate::wrappers::win32::dpi::{Dpi, DpiAwarenessGuard}; use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::user32::ExtendedUser32; -use crate::wrappers::win32::Rect; +use crate::wrappers::win32::{DpiAwarenessContext, Rect}; use std::ffi::c_void; use std::num::NonZeroUsize; use std::ptr::{null_mut, NonNull}; use windows::Win32::System::Ole::IDropTarget; use windows_core::{Error, Interface, InterfaceRef, Result, HRESULT}; -use windows_sys::Win32::Foundation::{SetLastError, HWND, POINT, S_OK}; +use windows_sys::Win32::Foundation::{SetLastError, HWND, POINT, S_OK, TRUE}; use windows_sys::Win32::Graphics::Gdi::ScreenToClient; use windows_sys::Win32::System::Ole::{RegisterDragDrop, RevokeDragDrop}; +use windows_sys::Win32::UI::HiDpi::DPI_HOSTING_BEHAVIOR_MIXED; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ GetFocus, ReleaseCapture, SetCapture, SetFocus, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT, }; @@ -276,4 +277,36 @@ impl HWnd { pub fn get_own_dc(&self) -> Result { super::OwnDeviceContext::from_window(*self) } + + pub fn supports_mixed_dpi_hosting_behavior(&self, user32: &ExtendedUser32) -> bool { + let Some(get_window_dpi_hosting_behavior) = user32.get_window_dpi_hosting_behavior else { + return false; + }; + + let result = unsafe { get_window_dpi_hosting_behavior(self.as_raw()) }; + + result == DPI_HOSTING_BEHAVIOR_MIXED + } + + pub fn get_dpi_awareness_context( + &self, user32: &ExtendedUser32, + ) -> Result> { + let Some(get_window_dpi_awareness_context) = user32.get_window_dpi_awareness_context else { + return Ok(None); + }; + + let result = unsafe { get_window_dpi_awareness_context(self.as_raw()) }; + + let Some(raw) = NonNull::new(result) else { + return Err(Error::from_thread()); + }; + + let ctx = DpiAwarenessContext::from_raw(raw); + + if ctx.is_valid(user32) == Some(false) { + return Ok(None); + } + + Ok(Some(ctx)) + } } From 97a8de012ab704340dc9d2b004ce8c839127c4d3 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:05:21 +0200 Subject: [PATCH 06/17] wip --- src/platform/win/dpi.rs | 137 ++++++++++++++++++++-------- src/platform/win/window.rs | 4 +- src/platform/win/window_state.rs | 4 +- src/tracing.rs | 9 +- src/wrappers/win32/dpi.rs | 50 +++++++--- src/wrappers/win32/library.rs | 18 ++++ src/wrappers/win32/window/handle.rs | 15 ++- 7 files changed, 168 insertions(+), 69 deletions(-) diff --git a/src/platform/win/dpi.rs b/src/platform/win/dpi.rs index 494fe975..98f2ba7a 100644 --- a/src/platform/win/dpi.rs +++ b/src/platform/win/dpi.rs @@ -1,75 +1,134 @@ use crate::wrappers::win32::window::HWnd; use crate::wrappers::win32::{ - DpiAwarenessContext, DpiAwarenessContextType, ExtendedShCore, ExtendedUser32, LibraryModule, - ProcessDpiAwareness, + DpiAwarenessContext, DpiAwarenessContextType, ExtendedShCore, ExtendedUser32, + LazyLibraryModule, LibraryModule, ProcessDpiAwareness, }; use crate::WindowSettings; - -pub(crate) enum StrategyType { - Assume96Dpi, -} +use std::cell::LazyCell; +use std::ops::Deref; #[derive(Copy, Clone, Default)] pub(crate) struct DpiScalingStrategy { - assume_96_dpi: bool, - thread_dpi_awareness_context_type: Option, + pub assume_96_dpi: bool, + pub thread_dpi_awareness_context: Option, } impl DpiScalingStrategy { - pub fn get(user32: &ExtendedUser32, parent: Option, settings: &WindowSettings) -> Self { + pub fn get( + user32: Option<&ExtendedUser32>, parent: Option, settings: &WindowSettings, + ) -> Self { + let shcore = LibraryModule::::lazy(); + // If we have an OpenGL context, then we must follow the process' DPI awareness setting. + // Otherwise, some OpenGL Drivers (namely AMD and NVIDIA) will crap out. // // We hope it's the same as the one for the (potential) parent window, or that it's at least // compatible. But if not, then having a DPI Awareness reset due to mismatched DPI Awareness - // is still better than half of the window being unusuable. + // is still better than half of the window being unusable. + // + // See: https://github.com/RustAudio/baseview/issues/321 + // https://forum.juce.com/t/scaling-issues-with-retina-and-opengl-on-windows-in-ableton-live/41573 + // https://forums.developer.nvidia.com/t/high-dpi-scaling-bug-with-nvidia-drivers-and-opengl-on-windows-10/79615 #[cfg(feature = "opengl")] if settings.gl_config.is_some() { - return Self::get_from_process(); + return Self::get_from_process(user32, &shcore); } - if let Some(parent) = parent { - let parent_context = parent.get_dpi_awareness_context(user32); + let Some(user32_lib) = user32 else { + return Self::get_from_process_legacy(&shcore); + }; - if parent.supports_mixed_dpi_hosting_behavior(user32) { - todo!() + if let Some(parent) = parent { + let Some(parent_dpi_ctx) = parent.get_dpi_awareness_context(user32_lib) else { + return Self::get_from_process(user32, &shcore); + }; + + if parent.supports_mixed_dpi_hosting_behavior(user32_lib) { + Self::get_best_matching_with_dpi_parent_awareness_context( + parent_dpi_ctx, + user32_lib, + &shcore, + ) + } else { + Self::get_from_specific_dpi_awareness_context(parent_dpi_ctx, user32_lib) } - - // Check if parent supports mixed context - // If not, use parent DPI awareness - - // If it does, choose whatever suits us best! - todo!() } else { // No parent, we can choose whatever suits us best! - - Self::get_best_supported() + Self::get_best_supported(user32_lib, &shcore) } } - // If we have an OpenGL context, we *must* follow the process - // DPI awareness, otherwise some OpenGL Drivers (namely AMD and NVIDIA) will crap out. - // See: https://github.com/RustAudio/baseview/issues/321 - // https://forum.juce.com/t/scaling-issues-with-retina-and-opengl-on-windows-in-ableton-live/41573 - // https://forums.developer.nvidia.com/t/high-dpi-scaling-bug-with-nvidia-drivers-and-opengl-on-windows-10/79615 - #[cfg(feature = "opengl")] - fn get_from_process() -> Self { - todo!() + fn get_from_process( + user32: Option<&ExtendedUser32>, shcore: &LazyLibraryModule, + ) -> Self { + let Some(user32) = user32 else { + return Self::get_from_process_legacy(shcore); + }; + + let Some(dpi_awareness_context) = DpiAwarenessContext::get_from_process(user32) else { + return Self::get_from_process_legacy(shcore); + }; + + Self::get_from_specific_dpi_awareness_context(dpi_awareness_context, user32) } - fn get_best_supported() -> Self { - todo!() + fn get_best_matching_with_dpi_parent_awareness_context( + parent_dpi_awareness_context: DpiAwarenessContext, user32: &ExtendedUser32, + shcore: &LazyLibraryModule, + ) -> Self { + use DpiAwarenessContextType::*; + + let dpi_awareness_type = parent_dpi_awareness_context.get_type(user32); + + // These are documented to not be compatible with per-monitor awareness types, so we'll fall back to System-aware + // See: https://learn.microsoft.com/en-us/windows/win32/api/windef/ne-windef-dpi_hosting_behavior#remarks + if matches!(dpi_awareness_type, Some(Unaware | UnawareGDIScaled | SystemDpiAware)) { + return Self { + assume_96_dpi: false, + thread_dpi_awareness_context: Some(SystemDpiAware.into()), + }; + } + + Self::get_best_supported(user32, shcore) } - fn get_from_parent() -> Self { - todo!() + fn get_from_specific_dpi_awareness_context( + dpi_awareness_context: DpiAwarenessContext, user32: &ExtendedUser32, + ) -> Self { + use DpiAwarenessContextType::*; + + let dpi_awareness_type = dpi_awareness_context.get_type(user32); + + // If type is unknown, assume it's better than System-Aware, and we can at least fetch the actual DPI. + let assume_96_dpi = matches!(dpi_awareness_type, Some(Unaware | UnawareGDIScaled)); + + Self { assume_96_dpi, thread_dpi_awareness_context: Some(dpi_awareness_context) } } - pub fn assume_96_dpi(&self) -> bool { - todo!() + fn get_from_process_legacy(shcore: &LazyLibraryModule) -> Self { + use ProcessDpiAwareness::*; + + let Some(shcore) = LazyCell::deref(shcore) else { return Self::completely_unaware() }; + + let awareness = ProcessDpiAwareness::get(shcore); + + let assume_96_dpi = matches!(awareness, None | Some(Unaware)); + + Self { assume_96_dpi, thread_dpi_awareness_context: None } } - pub fn thread_dpi_awareness_context_type(&self) -> Option { - todo!() + fn completely_unaware() -> Self { + Self { assume_96_dpi: true, thread_dpi_awareness_context: None } + } + + fn get_best_supported( + user32: &ExtendedUser32, shcore: &LazyLibraryModule, + ) -> Self { + let Some(best_supported) = DpiAwarenessContextType::best_supported(user32) else { + return Self::get_from_process_legacy(shcore); + }; + + Self::get_from_specific_dpi_awareness_context(best_supported.into(), user32) } } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 6fe0d597..458e0ace 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -321,7 +321,7 @@ impl WindowImpl for BaseviewWindow { self._keyboard_hook.set(Some(hook::init_keyboard_hook(window.as_raw()))); - if !window_state.shared.dpi_scaling_strategy.get().assume_96_dpi() { + if !window_state.shared.dpi_scaling_strategy.get().assume_96_dpi { // Now we can get the actual dpi of the window. let dpi = window.get_dpi(&self.window_state.user32)?; @@ -716,7 +716,7 @@ unsafe fn wnd_proc_inner( impl WindowHandle { pub fn create_window(init: WindowInitializer) -> Result { - let extended_user_32 = unsafe { LibraryModule::load()? }; + let extended_user_32 = LibraryModule::load()?; let shared_state = WindowSharedState::new(extended_user_32, &init.settings); diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 5112df96..f40f8eaf 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -161,9 +161,9 @@ impl WindowSharedState { pub fn init(&self, init: &WindowInitializer) { let parent = init.settings.parent.as_ref().map(|p| p.inner.handle); - let strategy = DpiScalingStrategy::get(&self.user32, parent, &init.settings); + let strategy = DpiScalingStrategy::get(Some(&self.user32), parent, &init.settings); - if strategy.assume_96_dpi() { + if strategy.assume_96_dpi { self.current_dpi.set(Some(Dpi::default())); } diff --git a/src/tracing.rs b/src/tracing.rs index 4540129a..59d0d521 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -1,11 +1,11 @@ #![allow(unused, reason = "Some platform may not use all macros")] #[cfg(feature = "tracing")] -pub use tracing::{error, warn}; +pub use tracing::{debug, error, warn}; #[cfg(not(feature = "tracing"))] mod tracing_impl { - macro_rules! __warn { + macro_rules! __void { ($($f:tt)*) => { { let _ = ($($f)*); @@ -13,8 +13,9 @@ mod tracing_impl { }; } - pub(crate) use __warn as warn; - pub(crate) use __warn as error; + pub(crate) use __void as debug; + pub(crate) use __void as error; + pub(crate) use __void as warn; } #[cfg(not(feature = "tracing"))] diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index b8e4dad1..5999c2f4 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -1,6 +1,7 @@ use super::*; use crate::platform::DpiScalingStrategy; use crate::wrappers::win32::user32::ExtendedUser32; +use crate::wrappers::win32::DpiAwarenessContextType::*; use std::ffi::c_void; use std::ptr::NonNull; use windows_core::{Error, Result}; @@ -21,6 +22,7 @@ impl Dpi { pub fn get_system(user32: &ExtendedUser32) -> Option { if let Some(get_dpi_for_system) = user32.get_dpi_for_system { Some(Self(unsafe { get_dpi_for_system() })) + // This is unlikely to be present if the above isn't, but it's worth a try } else if let Some(get_system_dpi_for_process) = user32.get_system_dpi_for_process { Some(Self(unsafe { get_system_dpi_for_process(null_mut()) })) } else { @@ -85,6 +87,7 @@ impl ProcessDpiAwareness { #[derive(Copy, Clone, Eq, PartialEq)] pub enum DpiAwarenessContextType { Unaware, + UnawareGDIScaled, SystemDpiAware, PerMonitorDpiAware, /// Windows 10, version 1703 @@ -92,13 +95,13 @@ pub enum DpiAwarenessContextType { } impl DpiAwarenessContextType { + // Sorted by order of goodness + const ALL: [Self; 5] = + [PerMonitorDpiAwareV2, PerMonitorDpiAware, SystemDpiAware, Unaware, UnawareGDIScaled]; + /// Windows 10, version 1607 pub fn best_supported(user32: &ExtendedUser32) -> Option { - use DpiAwarenessContextType::*; - - let ordered = [PerMonitorDpiAwareV2, PerMonitorDpiAware, SystemDpiAware, Unaware]; - - for awareness_type in ordered { + for awareness_type in Self::ALL { if DpiAwarenessContext::from(awareness_type).is_valid(user32)? { return Some(awareness_type); } @@ -146,6 +149,12 @@ impl DpiAwarenessContext { Some(Ok(())) } + pub fn get_from_process(user32: &ExtendedUser32) -> Option { + let context = unsafe { user32.get_dpi_awareness_context_for_process?(null_mut()) }; + + NonNull::new(context).map(Self::from_raw) + } + /// Windows 10, version 1803 pub fn dpi(&self, user32: &ExtendedUser32) -> Option { todo!() @@ -164,17 +173,32 @@ impl DpiAwarenessContext { } == TRUE, ) } + + /// Returns None if type is unknown + /// + /// Windows 10, version 1607 + pub(crate) fn get_type(&self, user32: &ExtendedUser32) -> Option { + for dpi_type in DpiAwarenessContextType::ALL { + let context = DpiAwarenessContext::from(dpi_type); + if context.is_valid(user32)? && self.equals(context, &user32)? { + return Some(dpi_type); + } + } + + None + } } impl From for DpiAwarenessContext { fn from(value: DpiAwarenessContextType) -> Self { + use DpiAwarenessContextType::*; + let inner = match value { - DpiAwarenessContextType::Unaware => DPI_AWARENESS_CONTEXT_UNAWARE, - DpiAwarenessContextType::SystemDpiAware => DPI_AWARENESS_CONTEXT_SYSTEM_AWARE, - DpiAwarenessContextType::PerMonitorDpiAware => DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, - DpiAwarenessContextType::PerMonitorDpiAwareV2 => { - DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 - } + Unaware => DPI_AWARENESS_CONTEXT_UNAWARE, + UnawareGDIScaled => DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, + SystemDpiAware => DPI_AWARENESS_CONTEXT_SYSTEM_AWARE, + PerMonitorDpiAware => DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, + PerMonitorDpiAwareV2 => DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, }; let Some(inner) = NonNull::new(inner) else { unreachable!() }; @@ -193,11 +217,11 @@ pub struct DpiAwarenessGuard<'a> { impl<'a> DpiAwarenessGuard<'a> { pub fn new(user32: &'a ExtendedUser32, strategy: DpiScalingStrategy) -> Result { - let Some(new_context) = strategy.thread_dpi_awareness_context_type() else { + let Some(new_context) = strategy.thread_dpi_awareness_context else { return Ok(Self { inner: None }); }; - match DpiAwarenessContext::from(new_context).set_thread(user32) { + match new_context.set_thread(user32) { None => Ok(Self { inner: None }), Some(Err(e)) => Err(e), Some(Ok(previous)) => Ok(Self { inner: Some((previous, user32)) }), diff --git a/src/wrappers/win32/library.rs b/src/wrappers/win32/library.rs index 12e50af0..da052595 100644 --- a/src/wrappers/win32/library.rs +++ b/src/wrappers/win32/library.rs @@ -1,3 +1,4 @@ +use std::cell::LazyCell; use std::ffi::{c_void, CStr}; use std::ops::Deref; use std::ptr::NonNull; @@ -18,11 +19,28 @@ pub struct LibraryModule { module: M, } +pub type LazyLibraryModule = LazyCell>>; + impl LibraryModule { pub fn load() -> Result { let library = unsafe { RawLibrary::load(M::MODULE_NAME)? }; Ok(Self { module: M::load(&library), _library: library }) } + + pub fn lazy() -> LazyLibraryModule { + LazyCell::new(|| match Self::load() { + Ok(module) => Some(module), + Err(err) => { + crate::warn!( + "Error loading module '{}': {}", + M::MODULE_NAME.to_string_lossy(), + err + ); + + None + } + }) + } } impl Clone for LibraryModule { diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index f5fcd142..0cb7df17 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -290,23 +290,20 @@ impl HWnd { pub fn get_dpi_awareness_context( &self, user32: &ExtendedUser32, - ) -> Result> { - let Some(get_window_dpi_awareness_context) = user32.get_window_dpi_awareness_context else { - return Ok(None); - }; - - let result = unsafe { get_window_dpi_awareness_context(self.as_raw()) }; + ) -> Option { + let result = unsafe { user32.get_window_dpi_awareness_context?(self.as_raw()) }; let Some(raw) = NonNull::new(result) else { - return Err(Error::from_thread()); + crate::warn!("Failed to get DpiAwarenessContext from window: {}", Error::from_thread()); + return None; }; let ctx = DpiAwarenessContext::from_raw(raw); if ctx.is_valid(user32) == Some(false) { - return Ok(None); + return None; } - Ok(Some(ctx)) + Some(ctx) } } From 91e7dd34af6a4791bf3b001f8a0fd758c4b3a89b Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:24:55 +0200 Subject: [PATCH 07/17] Add lots of debugging --- src/platform/win/dpi.rs | 14 ++++++++++++++ src/tracing.rs | 22 +++++++++++++++++++++- src/wrappers/win32/dpi.rs | 4 ++-- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/platform/win/dpi.rs b/src/platform/win/dpi.rs index 98f2ba7a..9d3c9af4 100644 --- a/src/platform/win/dpi.rs +++ b/src/platform/win/dpi.rs @@ -17,6 +17,7 @@ impl DpiScalingStrategy { pub fn get( user32: Option<&ExtendedUser32>, parent: Option, settings: &WindowSettings, ) -> Self { + let _span = crate::debug_span!("DpiScalingStrategy"); let shcore = LibraryModule::::lazy(); // If we have an OpenGL context, then we must follow the process' DPI awareness setting. @@ -31,15 +32,18 @@ impl DpiScalingStrategy { // https://forums.developer.nvidia.com/t/high-dpi-scaling-bug-with-nvidia-drivers-and-opengl-on-windows-10/79615 #[cfg(feature = "opengl")] if settings.gl_config.is_some() { + crate::debug!("OpenGL context requested: bypassing DPI awareness checking, using process DPI awareness instead."); return Self::get_from_process(user32, &shcore); } let Some(user32_lib) = user32 else { + crate::debug!("user32.dll is unavailable: falling back to legacy Windows 8 APIs for DPI awareness detection."); return Self::get_from_process_legacy(&shcore); }; if let Some(parent) = parent { let Some(parent_dpi_ctx) = parent.get_dpi_awareness_context(user32_lib) else { + crate::debug!("Could not get DPI Awareness Context from parent, falling back to process DPI Awareness."); return Self::get_from_process(user32, &shcore); }; @@ -79,10 +83,15 @@ impl DpiScalingStrategy { use DpiAwarenessContextType::*; let dpi_awareness_type = parent_dpi_awareness_context.get_type(user32); + crate::debug!( + "Parent DPI hosting behavior is mixed, parent DPI Awareness Context type is {:?}.", + dpi_awareness_type + ); // These are documented to not be compatible with per-monitor awareness types, so we'll fall back to System-aware // See: https://learn.microsoft.com/en-us/windows/win32/api/windef/ne-windef-dpi_hosting_behavior#remarks if matches!(dpi_awareness_type, Some(Unaware | UnawareGDIScaled | SystemDpiAware)) { + crate::debug!("Parent has DPI Awareness Context with Per-Monitor DPI awareness, falling back to System DPI Awareness."); return Self { assume_96_dpi: false, thread_dpi_awareness_context: Some(SystemDpiAware.into()), @@ -99,6 +108,8 @@ impl DpiScalingStrategy { let dpi_awareness_type = dpi_awareness_context.get_type(user32); + crate::debug!("Using DPI Awareness Context of type {:?}.", dpi_awareness_type); + // If type is unknown, assume it's better than System-Aware, and we can at least fetch the actual DPI. let assume_96_dpi = matches!(dpi_awareness_type, Some(Unaware | UnawareGDIScaled)); @@ -112,6 +123,8 @@ impl DpiScalingStrategy { let awareness = ProcessDpiAwareness::get(shcore); + crate::debug!("Using legacy Process DPI Awareness: {:?}", awareness); + let assume_96_dpi = matches!(awareness, None | Some(Unaware)); Self { assume_96_dpi, thread_dpi_awareness_context: None } @@ -125,6 +138,7 @@ impl DpiScalingStrategy { user32: &ExtendedUser32, shcore: &LazyLibraryModule, ) -> Self { let Some(best_supported) = DpiAwarenessContextType::best_supported(user32) else { + crate::debug!("No DPI Awareness Context types are available. Falling back to legacy Windows 8 APIs."); return Self::get_from_process_legacy(shcore); }; diff --git a/src/tracing.rs b/src/tracing.rs index 59d0d521..bea25d3a 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -1,7 +1,7 @@ #![allow(unused, reason = "Some platform may not use all macros")] #[cfg(feature = "tracing")] -pub use tracing::{debug, error, warn}; +pub use tracing::{debug, debug_span, error, span, warn}; #[cfg(not(feature = "tracing"))] mod tracing_impl { @@ -16,6 +16,26 @@ mod tracing_impl { pub(crate) use __void as debug; pub(crate) use __void as error; pub(crate) use __void as warn; + + pub struct Span; + pub struct SpanGuard; + impl Span { + pub fn entered(&self) -> SpanGuard { + SpanGuard; + } + } + + macro_rules! __span { + ($($f:tt)*) => { + { + let _ = ($($f)*); + Span + } + }; + } + + pub(crate) use __span as span; + pub(crate) use __span as debug_span; } #[cfg(not(feature = "tracing"))] diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index 5999c2f4..73104dbe 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -39,7 +39,7 @@ impl Default for Dpi { /// Win8 Legacy (replaced by DpiAwarenessContext in Win10), process-wide #[repr(i32)] -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum ProcessDpiAwareness { Unaware = PROCESS_DPI_UNAWARE, SystemDpiAware = PROCESS_SYSTEM_DPI_AWARE, @@ -84,7 +84,7 @@ impl ProcessDpiAwareness { } /// Windows 10, version 1607 -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum DpiAwarenessContextType { Unaware, UnawareGDIScaled, From 2c6cbcf2600642eb496307b68c2b057de47158ae Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 01:12:20 +0200 Subject: [PATCH 08/17] wip --- examples/plugin_clack/Cargo.toml | 2 ++ examples/plugin_clack/src/gui.rs | 5 +++++ examples/render_femtovg/Cargo.toml | 1 + examples/render_femtovg/src/main.rs | 6 +++++- examples/render_wgpu/Cargo.toml | 5 +++-- examples/render_wgpu/src/main.rs | 5 ++++- src/platform/win/gl.rs | 3 +-- src/platform/win/window.rs | 3 +-- src/tracing.rs | 4 ++-- 9 files changed, 24 insertions(+), 10 deletions(-) diff --git a/examples/plugin_clack/Cargo.toml b/examples/plugin_clack/Cargo.toml index 67729152..4ebf67c5 100644 --- a/examples/plugin_clack/Cargo.toml +++ b/examples/plugin_clack/Cargo.toml @@ -12,3 +12,5 @@ clack-extensions = { version = "0.1.1", features = ["gui", "state", "clack-plugi baseview = { path = "../..", features = ["opengl"] } softbuffer = "0.4.8" +tracing-subscriber = { workspace = true } +tracing = "0.1.44" \ No newline at end of file diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 74fefa5e..b0cd5895 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -10,6 +10,7 @@ use clack_extensions::gui::{ }; use clack_plugin::plugin::PluginError; use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle}; +use tracing::Level; pub struct ExamplePluginGui { pub handle: Window, @@ -29,6 +30,10 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { + tracing_subscriber::fmt::fmt() + .with_max_level(Level::DEBUG) + .init(); + let options = WindowSettings::new() .wait_for_parent() .with_size(PhysicalSize::new(400, 200)) diff --git a/examples/render_femtovg/Cargo.toml b/examples/render_femtovg/Cargo.toml index f45a5bc7..7f187f75 100644 --- a/examples/render_femtovg/Cargo.toml +++ b/examples/render_femtovg/Cargo.toml @@ -8,3 +8,4 @@ publish = false baseview = { path = "../..", features = ["opengl", "tracing"] } femtovg = "0.26" tracing-subscriber = { workspace = true } +tracing = "0.1.44" diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index 2a012c6f..39df534e 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -7,6 +7,7 @@ use baseview::{ use femtovg::renderer::OpenGl; use femtovg::{Canvas, Color}; use std::cell::{Cell, RefCell}; +use tracing::Level; struct FemtovgExample { window_context: WindowContext, @@ -116,8 +117,11 @@ impl WindowHandler for FemtovgExample { } fn main() -> Result<(), baseview::Error> { + tracing_subscriber::fmt::fmt() + .with_max_level(Level::DEBUG) + .init(); + unsafe { baseview::assume_standalone_in_process() }; - tracing_subscriber::fmt::init(); let window_open_options = WindowSettings::new() .with_title("Femtovg on Baseview") diff --git a/examples/render_wgpu/Cargo.toml b/examples/render_wgpu/Cargo.toml index e17e600f..5fddcf35 100644 --- a/examples/render_wgpu/Cargo.toml +++ b/examples/render_wgpu/Cargo.toml @@ -5,8 +5,9 @@ edition = "2021" publish = false [dependencies] -baseview = { path = "../..", features = ["opengl"] } +baseview = { path = "../..", features = ["opengl", "tracing"] } wgpu = "30.0.1" -env_logger = "0.11.11" log = "0.4.34" pollster = "1.0.1" +tracing-subscriber = { workspace = true } +tracing = "0.1.44" \ No newline at end of file diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index 5b8f971a..74cc088d 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -6,6 +6,7 @@ use baseview::{ use log::LevelFilter; use std::cell::RefCell; +use tracing::Level; struct WgpuExample { window_context: WindowContext, @@ -210,7 +211,9 @@ impl WindowHandler for WgpuExample { } fn main() -> Result<(), baseview::Error> { - env_logger::builder().filter_level(LevelFilter::Debug).init(); + tracing_subscriber::fmt::fmt() + .with_max_level(Level::DEBUG) + .init(); unsafe { baseview::assume_standalone_in_process() }; diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index e4a8f998..4d81f3a7 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -1,7 +1,6 @@ use std::ffi::{c_void, CStr}; use std::num::NonZeroI32; use std::rc::Rc; -use windows_core::{s, PCSTR}; use windows_sys::Win32::Graphics::OpenGL::wglGetProcAddress; use crate::gl::*; @@ -10,7 +9,7 @@ use crate::wrappers::win32::window::{ with_dummy_window, HWnd, OwnDeviceContext, PixelFormat, PixelFormatAttribs, WglContext, WglExtra, }; -use crate::wrappers::win32::{ExtendedUser32, LibraryModule, RawLibrary}; +use crate::wrappers::win32::{RawLibrary}; pub type GlContext = Rc; diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 458e0ace..b003e991 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -16,14 +16,13 @@ use super::drop_target::DropTarget; use super::*; use crate::handler::WindowHandlerBuilder; use crate::host::Host; -use crate::platform::win::dpi::DpiScalingStrategy; use crate::platform::win::window_state::{WindowSharedState, WindowState}; use crate::platform::PlatformError; use crate::window::WindowInitializer; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; use crate::wrappers::win32::{ - ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessGuard, ExtendedUser32, + ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessGuard, LibraryModule, Rect, WindowStyle, }; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSize}; diff --git a/src/tracing.rs b/src/tracing.rs index bea25d3a..4384ec2d 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -21,7 +21,7 @@ mod tracing_impl { pub struct SpanGuard; impl Span { pub fn entered(&self) -> SpanGuard { - SpanGuard; + SpanGuard } } @@ -29,7 +29,7 @@ mod tracing_impl { ($($f:tt)*) => { { let _ = ($($f)*); - Span + crate::Span } }; } From c555e7840299664c05512e20fe8d0c6865891669 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:55:19 +0200 Subject: [PATCH 09/17] wip --- examples/render_femtovg/src/main.rs | 1 + src/platform/win/dpi.rs | 20 ++++++++- src/platform/win/window.rs | 63 ++++++++++++++++------------- src/wrappers/win32/dpi.rs | 36 ++++++++++++----- src/wrappers/win32/shcore.rs | 13 +++--- src/wrappers/win32/window/handle.rs | 47 +++++++++++++++------ 6 files changed, 121 insertions(+), 59 deletions(-) diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index 39df534e..cc09e97e 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -122,6 +122,7 @@ fn main() -> Result<(), baseview::Error> { .init(); unsafe { baseview::assume_standalone_in_process() }; + tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).init(); let window_open_options = WindowSettings::new() .with_title("Femtovg on Baseview") diff --git a/src/platform/win/dpi.rs b/src/platform/win/dpi.rs index 9d3c9af4..29b84d8e 100644 --- a/src/platform/win/dpi.rs +++ b/src/platform/win/dpi.rs @@ -1,6 +1,6 @@ use crate::wrappers::win32::window::HWnd; use crate::wrappers::win32::{ - DpiAwarenessContext, DpiAwarenessContextType, ExtendedShCore, ExtendedUser32, + Dpi, DpiAwarenessContext, DpiAwarenessContextType, ExtendedShCore, ExtendedUser32, LazyLibraryModule, LibraryModule, ProcessDpiAwareness, }; use crate::WindowSettings; @@ -146,6 +146,24 @@ impl DpiScalingStrategy { } } +impl DpiScalingStrategy { + pub fn get_dpi_for_window(&self, own_window: HWnd, user32: &ExtendedUser32) -> Option { + if self.assume_96_dpi { + return Some(Dpi::default()); + } + + if let Some(dpi) = own_window.get_dpi(user32) { + return Some(dpi); + } + + if let Some(dpi) = own_window.get_dpi_awareness_context(user32).and_then(|d| d.dpi(user32)) + { + return Some(dpi); + } + todo!() + } +} + pub(crate) fn set_process_dpi_awareness() { if !set_process_dpi_awareness_context() { // Win8.1 fallback diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index b003e991..22325ea4 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -7,7 +7,7 @@ use windows_sys::Win32::{ use crate::dpi::{PhysicalPosition, PhysicalSize, Size}; use crate::{warn, EventStatus, HandlerError, WindowHandler}; use std::cell::{Cell, OnceCell}; -use std::num::NonZeroUsize; +use std::num::{NonZeroU32, NonZeroUsize}; use windows_sys::Win32::Foundation::POINT; pub(crate) const BV_WINDOW_MUST_CLOSE: u32 = WM_USER + 1; @@ -22,8 +22,8 @@ use crate::window::WindowInitializer; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; use crate::wrappers::win32::{ - ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessGuard, - LibraryModule, Rect, WindowStyle, + ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessGuard, LibraryModule, Rect, + WindowStyle, }; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSize}; @@ -320,29 +320,33 @@ impl WindowImpl for BaseviewWindow { self._keyboard_hook.set(Some(hook::init_keyboard_hook(window.as_raw()))); - if !window_state.shared.dpi_scaling_strategy.get().assume_96_dpi { - // Now we can get the actual dpi of the window. - let dpi = window.get_dpi(&self.window_state.user32)?; - - if let Some(dpi) = dpi { - if Some(dpi) != window_state.shared.current_dpi.get() { - window_state.shared.current_dpi.set(Some(dpi)); - - // We cannot create a window in "logical" pixels, and we can't DPI-scale to physical pixels because we - // have no way to know where the window will end up. - // So, at window creation, we assume a DPI=96, and if it ends up wrong, we resize the window - // to the actual logical size the user desired. - let new_size = self.initial_size.to_physical(dpi.scale_factor()); - - // Preemptively update so a synchronous WM_SIZE from SetWindowPos below - // doesn't also emit Resized. - window_state.shared.current_size.set(new_size); - let guard = DpiAwarenessGuard::new( - &window_state.shared.user32, - self.shared_state.dpi_scaling_strategy.get(), - )?; - window.resize_and_activate(new_size, Some(dpi), &guard)?; - } + // Now we can get the actual dpi of the window. + let dpi = window_state + .shared + .dpi_scaling_strategy + .get() + .get_dpi_for_window(window, &self.shared_state.user32); + + let dpi = window.get_dpi(&self.window_state.user32); + + if let Some(dpi) = dpi { + if Some(dpi) != window_state.shared.current_dpi.get() { + window_state.shared.current_dpi.set(Some(dpi)); + + // We cannot create a window in "logical" pixels, and we can't DPI-scale to physical pixels because we + // have no way to know where the window will end up. + // So, at window creation, we assume a DPI=96, and if it ends up wrong, we resize the window + // to the actual logical size the user desired. + let new_size = self.initial_size.to_physical(dpi.scale_factor()); + + // Preemptively update so a synchronous WM_SIZE from SetWindowPos below + // doesn't also emit Resized. + window_state.shared.current_size.set(new_size); + let guard = DpiAwarenessGuard::new( + &window_state.shared.user32, + self.shared_state.dpi_scaling_strategy.get(), + )?; + window.resize_and_activate(new_size, Some(dpi), &guard)?; } } @@ -597,7 +601,10 @@ unsafe fn wnd_proc_inner( } WM_DPICHANGED => { let suggested_nc_rect = Rect((lparam as *const RECT).read()); - let dpi = Dpi((wparam & 0xFFFF) as u16 as u32); + let Some(dpi) = NonZeroU32::new((wparam & 0xFFFF) as u16 as u32) else { + return Some(-1); + }; + let dpi = Dpi(dpi); let dpi_ctx = DpiAwarenessGuard::new( &window_state.user32, @@ -613,7 +620,7 @@ unsafe fn wnd_proc_inner( let changed = window_state.shared.current_size.get() != new_size || window_state.shared.current_dpi.get() != Some(dpi); - window_state.shared.current_dpi.replace(Some(dpi)); + window_state.shared.current_dpi.set(Some(dpi)); let previous_size = window_state.shared.current_size.replace(new_size); // Windows makes us resize the window manually. This however will not send a WM_SIZE event, diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index 73104dbe..7af6fa8b 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -3,6 +3,7 @@ use crate::platform::DpiScalingStrategy; use crate::wrappers::win32::user32::ExtendedUser32; use crate::wrappers::win32::DpiAwarenessContextType::*; use std::ffi::c_void; +use std::num::NonZeroU32; use std::ptr::NonNull; use windows_core::{Error, Result}; use windows_sys::Win32::Foundation::{FALSE, RECT, TRUE}; @@ -10,30 +11,35 @@ use windows_sys::Win32::UI::HiDpi::*; use windows_sys::Win32::UI::WindowsAndMessaging::{AdjustWindowRectEx, USER_DEFAULT_SCREEN_DPI}; #[derive(Copy, Clone, Eq, PartialEq)] -pub struct Dpi(pub u32); +pub struct Dpi(pub NonZeroU32); impl Dpi { pub fn scale_factor(&self) -> f64 { - self.0 as f64 / USER_DEFAULT_SCREEN_DPI as f64 + self.0.get() as f64 / USER_DEFAULT_SCREEN_DPI as f64 } /// Windows 10, version 1607 #[allow(clippy::manual_map, reason = "This is more readable")] pub fn get_system(user32: &ExtendedUser32) -> Option { if let Some(get_dpi_for_system) = user32.get_dpi_for_system { - Some(Self(unsafe { get_dpi_for_system() })) + Some(Self(NonZeroU32::new(unsafe { get_dpi_for_system() })?)) // This is unlikely to be present if the above isn't, but it's worth a try } else if let Some(get_system_dpi_for_process) = user32.get_system_dpi_for_process { - Some(Self(unsafe { get_system_dpi_for_process(null_mut()) })) + Some(Self(NonZeroU32::new(unsafe { get_system_dpi_for_process(null_mut()) })?)) } else { None } } + + pub const USER_DEFAULT: Self = Self(match NonZeroU32::new(USER_DEFAULT_SCREEN_DPI) { + None => unreachable!(), + Some(dpi) => dpi, + }); } impl Default for Dpi { fn default() -> Self { - Self(USER_DEFAULT_SCREEN_DPI) + Self::USER_DEFAULT } } @@ -157,7 +163,9 @@ impl DpiAwarenessContext { /// Windows 10, version 1803 pub fn dpi(&self, user32: &ExtendedUser32) -> Option { - todo!() + let result = unsafe { user32.get_dpi_from_dpi_awareness_context?(self.inner.as_ptr()) }; + + Some(Dpi(NonZeroU32::new(result)?)) } /// Windows 10, version 1607 @@ -207,10 +215,6 @@ impl From for DpiAwarenessContext { } } -pub struct DpiAwareness { - value: DPI_AWARENESS, -} - pub struct DpiAwarenessGuard<'a> { inner: Option<(DpiAwarenessContext, &'a ExtendedUser32)>, } @@ -228,6 +232,10 @@ impl<'a> DpiAwarenessGuard<'a> { } } + pub fn context(&self) -> Option { + self.inner.map(|i| i.0) + } + pub fn client_area_to_nc_area( &self, mut rect: Rect, style: WindowStyle, dpi: Option, ) -> Result { @@ -245,7 +253,13 @@ impl<'a> DpiAwarenessGuard<'a> { // adjust_window_rect_ex_for_dpi takes the current DPI awareness context in consideration. // Therefore, this method taking &self enforces that the DPI aware context is correct. unsafe { - adjust_window_rect_ex_for_dpi(&mut rect.0, style.style, 0, style.style_ex, dpi.0) + adjust_window_rect_ex_for_dpi( + &mut rect.0, + style.style, + 0, + style.style_ex, + dpi.0.get(), + ) } } else { unsafe { AdjustWindowRectEx(&mut rect.0, style.style, 0, style.style_ex) } diff --git a/src/wrappers/win32/shcore.rs b/src/wrappers/win32/shcore.rs index 00874265..06d3d8b7 100644 --- a/src/wrappers/win32/shcore.rs +++ b/src/wrappers/win32/shcore.rs @@ -2,6 +2,7 @@ use crate::wrappers::win32::{Module, RawLibrary}; use std::ffi::CStr; use windows_sys::core::{BOOL, HRESULT}; use windows_sys::Win32::Foundation::{HANDLE, HWND, RECT}; +use windows_sys::Win32::Graphics::Gdi::HMONITOR; use windows_sys::Win32::UI::HiDpi::*; type GetProcessDpiAwareness = @@ -9,22 +10,21 @@ type GetProcessDpiAwareness = type SetProcessDpiAwareness = unsafe extern "system" fn(PROCESS_DPI_AWARENESS) -> HRESULT; +type GetDpiForMonitor = + unsafe extern "system" fn(HMONITOR, MONITOR_DPI_TYPE, *mut u32, *mut u32) -> HRESULT; + // Checks the above typedefs match the function definitions from windows_sys const _: () = { let _: GetProcessDpiAwareness = GetProcessDpiAwareness; let _: SetProcessDpiAwareness = SetProcessDpiAwareness; + let _: GetDpiForMonitor = GetDpiForMonitor; }; #[derive(Copy, Clone)] pub struct ExtendedShCore { pub get_process_dpi_awareness: Option, pub set_process_dpi_awareness: Option, -} - -impl ExtendedShCore { - pub fn can_handle_process_dpi_awareness(&self) -> bool { - self.get_process_dpi_awareness.is_some() && self.set_process_dpi_awareness.is_some() - } + pub get_dpi_for_monitor: Option, } unsafe impl Module for ExtendedShCore { @@ -35,6 +35,7 @@ unsafe impl Module for ExtendedShCore { Self { get_process_dpi_awareness: library.get(c"GetProcessDpiAwareness"), set_process_dpi_awareness: library.get(c"SetProcessDpiAwareness"), + get_dpi_for_monitor: library.get(c"GetDpiForMonitor"), } } } diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index 0cb7df17..de2d2743 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -2,16 +2,18 @@ use crate::wrappers::win32::dpi::{Dpi, DpiAwarenessGuard}; use crate::wrappers::win32::style::WindowStyle; use crate::wrappers::win32::user32::ExtendedUser32; -use crate::wrappers::win32::{DpiAwarenessContext, Rect}; +use crate::wrappers::win32::{DpiAwarenessContext, ExtendedShCore, Rect}; use std::ffi::c_void; -use std::num::NonZeroUsize; +use std::num::{NonZeroU32, NonZeroUsize}; use std::ptr::{null_mut, NonNull}; use windows::Win32::System::Ole::IDropTarget; use windows_core::{Error, Interface, InterfaceRef, Result, HRESULT}; -use windows_sys::Win32::Foundation::{SetLastError, HWND, POINT, S_OK, TRUE}; -use windows_sys::Win32::Graphics::Gdi::ScreenToClient; +use windows_sys::Win32::Foundation::{SetLastError, HWND, POINT, S_OK}; +use windows_sys::Win32::Graphics::Gdi::{ + MonitorFromWindow, ScreenToClient, MONITOR_DEFAULTTOPRIMARY, +}; use windows_sys::Win32::System::Ole::{RegisterDragDrop, RevokeDragDrop}; -use windows_sys::Win32::UI::HiDpi::DPI_HOSTING_BEHAVIOR_MIXED; +use windows_sys::Win32::UI::HiDpi::{DPI_HOSTING_BEHAVIOR_MIXED, MDT_DEFAULT}; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ GetFocus, ReleaseCapture, SetCapture, SetFocus, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT, }; @@ -99,16 +101,35 @@ impl HWnd { Ok(()) } - pub fn get_dpi(&self, extended_user32: &ExtendedUser32) -> Result> { - let Some(get_dpi_for_window) = extended_user32.get_dpi_for_window else { - return Ok(None); - }; - + pub fn get_dpi(&self, extended_user32: &ExtendedUser32) -> Option { // SAFETY: This type guarantees the HWND is safe to use. - match unsafe { get_dpi_for_window(self.as_raw()) } { - 0 => Err(Error::from_thread()), - dpi => Ok(Some(Dpi(dpi))), + match NonZeroU32::new(unsafe { extended_user32.get_dpi_for_window?(self.as_raw()) }) { + None => { + crate::warn!("Could not get DPI for window: {}", Error::from_thread()); + None + } + Some(dpi) => Some(Dpi(dpi)), + } + } + + pub fn get_dpi_from_monitor(&self, shcore: &ExtendedShCore) -> Option { + let get_dpi_for_monitor = shcore.get_dpi_for_monitor?; + + let monitor = unsafe { MonitorFromWindow(self.as_raw(), MONITOR_DEFAULTTOPRIMARY) }; + if monitor.is_null() { + return None; } + + let mut x = 0; + let mut _y = 0; + + let result = HRESULT(unsafe { get_dpi_for_monitor(monitor, MDT_DEFAULT, &mut x, &mut _y) }); + if result.is_err() { + crate::warn!("GetDpiForMonitor failed: {}", result.message()); + return None; + } + + Some(Dpi(NonZeroU32::new(x)?)) } pub fn register_drag_drop(&self, drop_target: InterfaceRef) -> Result<()> { From 9353816c6b96fe2e8d51d392aa43c55beec239fb Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:30:23 +0200 Subject: [PATCH 10/17] wip --- src/platform/win/dpi.rs | 32 +++++++++---- src/platform/win/window.rs | 10 +++- src/wrappers/win32/dpi.rs | 71 ++++++++++++++++++++++------- src/wrappers/win32/shcore.rs | 4 +- src/wrappers/win32/user32.rs | 6 --- src/wrappers/win32/window.rs | 4 ++ src/wrappers/win32/window/data.rs | 10 ++++ src/wrappers/win32/window/handle.rs | 14 +++++- src/wrappers/win32/window/proc.rs | 53 ++++++++++++++------- 9 files changed, 150 insertions(+), 54 deletions(-) diff --git a/src/platform/win/dpi.rs b/src/platform/win/dpi.rs index 29b84d8e..9b6f35f6 100644 --- a/src/platform/win/dpi.rs +++ b/src/platform/win/dpi.rs @@ -1,7 +1,7 @@ use crate::wrappers::win32::window::HWnd; use crate::wrappers::win32::{ - Dpi, DpiAwarenessContext, DpiAwarenessContextType, ExtendedShCore, ExtendedUser32, - LazyLibraryModule, LibraryModule, ProcessDpiAwareness, + Dpi, DpiAwarenessContext, DpiAwarenessContextType, DpiAwarenessGuard, ExtendedShCore, + ExtendedUser32, LazyLibraryModule, LibraryModule, ProcessDpiAwareness, }; use crate::WindowSettings; use std::cell::LazyCell; @@ -152,15 +152,27 @@ impl DpiScalingStrategy { return Some(Dpi::default()); } - if let Some(dpi) = own_window.get_dpi(user32) { - return Some(dpi); - } + DpiAwarenessGuard::with_guard_optional(user32, *self, || { + if let Some(dpi) = own_window.get_dpi(user32) { + return Some(dpi); + } - if let Some(dpi) = own_window.get_dpi_awareness_context(user32).and_then(|d| d.dpi(user32)) - { - return Some(dpi); - } - todo!() + if let Some(dpi) = + own_window.get_dpi_awareness_context(user32).and_then(|d| d.dpi(user32)) + { + return Some(dpi); + } + + let shcore = LibraryModule::::lazy(); + + if let Some(dpi) = + shcore.as_ref().and_then(|shcore| own_window.get_dpi_from_monitor(shcore)) + { + return Some(dpi); + } + + Dpi::get_system(user32) + }) } } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 22325ea4..867902e0 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -315,6 +315,14 @@ impl Drop for BaseviewWindow { } impl WindowImpl for BaseviewWindow { + fn non_client_create(&self, window: HWnd) -> std::result::Result<(), PlatformError> { + if self.shared_state.dpi_scaling_strategy.get().assume_96_dpi { + window.enable_non_client_dpi_scaling(&self.shared_state.user32); + } + + Ok(()) + } + fn after_create(&self, window: HWnd) -> core::result::Result<(), PlatformError> { let window_state = &self.window_state; @@ -327,8 +335,6 @@ impl WindowImpl for BaseviewWindow { .get() .get_dpi_for_window(window, &self.shared_state.user32); - let dpi = window.get_dpi(&self.window_state.user32); - if let Some(dpi) = dpi { if Some(dpi) != window_state.shared.current_dpi.get() { window_state.shared.current_dpi.set(Some(dpi)); diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index 7af6fa8b..ab529977 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -7,6 +7,7 @@ use std::num::NonZeroU32; use std::ptr::NonNull; use windows_core::{Error, Result}; use windows_sys::Win32::Foundation::{FALSE, RECT, TRUE}; +use windows_sys::Win32::Graphics::Gdi::{GetDC, GetDeviceCaps, ReleaseDC, LOGPIXELSX}; use windows_sys::Win32::UI::HiDpi::*; use windows_sys::Win32::UI::WindowsAndMessaging::{AdjustWindowRectEx, USER_DEFAULT_SCREEN_DPI}; @@ -18,8 +19,7 @@ impl Dpi { self.0.get() as f64 / USER_DEFAULT_SCREEN_DPI as f64 } - /// Windows 10, version 1607 - #[allow(clippy::manual_map, reason = "This is more readable")] + /// Windows 10, version 1607. pub fn get_system(user32: &ExtendedUser32) -> Option { if let Some(get_dpi_for_system) = user32.get_dpi_for_system { Some(Self(NonZeroU32::new(unsafe { get_dpi_for_system() })?)) @@ -27,10 +27,35 @@ impl Dpi { } else if let Some(get_system_dpi_for_process) = user32.get_system_dpi_for_process { Some(Self(NonZeroU32::new(unsafe { get_system_dpi_for_process(null_mut()) })?)) } else { - None + Self::get_from_device_caps() } } + fn get_from_device_caps() -> Option { + struct DisplayDC(NonNull); + + impl DisplayDC { + fn get() -> Option { + let result = unsafe { GetDC(null_mut()) }; + NonNull::new(result).map(Self) + } + + fn dpi(&self) -> Option { + let result = unsafe { GetDeviceCaps(self.0.as_ptr(), LOGPIXELSX as _) }; + let result: u32 = result.try_into().ok()?; + NonZeroU32::new(result).map(Dpi) + } + } + + impl Drop for DisplayDC { + fn drop(&mut self) { + let _ = unsafe { ReleaseDC(null_mut(), self.0.as_ptr()) }; + } + } + + DisplayDC::get()?.dpi() + } + pub const USER_DEFAULT: Self = Self(match NonZeroU32::new(USER_DEFAULT_SCREEN_DPI) { None => unreachable!(), Some(dpi) => dpi, @@ -43,7 +68,7 @@ impl Default for Dpi { } } -/// Win8 Legacy (replaced by DpiAwarenessContext in Win10), process-wide +/// Win8 Legacy (replaced by DpiAwarenessContext in Win10), process-wide. #[repr(i32)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum ProcessDpiAwareness { @@ -89,14 +114,14 @@ impl ProcessDpiAwareness { } } -/// Windows 10, version 1607 +/// Windows 10, version 1607. #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum DpiAwarenessContextType { Unaware, UnawareGDIScaled, SystemDpiAware, PerMonitorDpiAware, - /// Windows 10, version 1703 + /// Windows 10, version 1703. PerMonitorDpiAwareV2, } @@ -105,7 +130,7 @@ impl DpiAwarenessContextType { const ALL: [Self; 5] = [PerMonitorDpiAwareV2, PerMonitorDpiAware, SystemDpiAware, Unaware, UnawareGDIScaled]; - /// Windows 10, version 1607 + /// Windows 10, version 1607. pub fn best_supported(user32: &ExtendedUser32) -> Option { for awareness_type in Self::ALL { if DpiAwarenessContext::from(awareness_type).is_valid(user32)? { @@ -127,12 +152,12 @@ impl DpiAwarenessContext { Self { inner: raw } } - /// Windows 10, version 1607 + /// Windows 10, version 1607. pub fn is_valid(&self, user32: &ExtendedUser32) -> Option { Some(unsafe { user32.is_valid_dpi_awareness_context?(self.inner.as_ptr()) } == TRUE) } - /// Windows 10, version 1607 + /// Windows 10, version 1607. pub fn set_thread(&self, user32: &ExtendedUser32) -> Option> { let previous = unsafe { user32.set_thread_dpi_awareness_context?(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) @@ -161,14 +186,14 @@ impl DpiAwarenessContext { NonNull::new(context).map(Self::from_raw) } - /// Windows 10, version 1803 + /// Windows 10, version 1803. pub fn dpi(&self, user32: &ExtendedUser32) -> Option { let result = unsafe { user32.get_dpi_from_dpi_awareness_context?(self.inner.as_ptr()) }; Some(Dpi(NonZeroU32::new(result)?)) } - /// Windows 10, version 1607 + /// Windows 10, version 1607. pub fn equals( &self, other: impl Into, user32: &ExtendedUser32, ) -> Option { @@ -182,13 +207,13 @@ impl DpiAwarenessContext { ) } - /// Returns None if type is unknown + /// Returns None if type is unknown. /// - /// Windows 10, version 1607 + /// Windows 10, version 1607. pub(crate) fn get_type(&self, user32: &ExtendedUser32) -> Option { for dpi_type in DpiAwarenessContextType::ALL { let context = DpiAwarenessContext::from(dpi_type); - if context.is_valid(user32)? && self.equals(context, &user32)? { + if context.is_valid(user32)? && self.equals(context, user32)? { return Some(dpi_type); } } @@ -232,8 +257,22 @@ impl<'a> DpiAwarenessGuard<'a> { } } - pub fn context(&self) -> Option { - self.inner.map(|i| i.0) + pub fn with_guard_optional( + user32: &'a ExtendedUser32, strategy: DpiScalingStrategy, handler: impl FnOnce() -> T, + ) -> T { + let guard = match DpiAwarenessGuard::new(user32, strategy) { + Ok(guard) => Some(guard), + Err(e) => { + crate::warn!("Could not set up thread DPIAwarenessContext: {}", e); + None + } + }; + + let result = handler(); + + drop(guard); + + result } pub fn client_area_to_nc_area( diff --git a/src/wrappers/win32/shcore.rs b/src/wrappers/win32/shcore.rs index 06d3d8b7..d372b5ca 100644 --- a/src/wrappers/win32/shcore.rs +++ b/src/wrappers/win32/shcore.rs @@ -1,7 +1,7 @@ use crate::wrappers::win32::{Module, RawLibrary}; use std::ffi::CStr; -use windows_sys::core::{BOOL, HRESULT}; -use windows_sys::Win32::Foundation::{HANDLE, HWND, RECT}; +use windows_sys::core::HRESULT; +use windows_sys::Win32::Foundation::HANDLE; use windows_sys::Win32::Graphics::Gdi::HMONITOR; use windows_sys::Win32::UI::HiDpi::*; diff --git a/src/wrappers/win32/user32.rs b/src/wrappers/win32/user32.rs index 4dcf8ba7..26aae965 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -10,12 +10,10 @@ type AdjustWindowRectExForDpi = pub type AreDpiAwarenessContextsEqual = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT) -> BOOL; type EnableNonClientDpiScaling = unsafe extern "system" fn(HWND) -> BOOL; -type GetAwarenessFromDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> BOOL; type GetDpiAwarenessContextForProcess = unsafe extern "system" fn(HANDLE) -> DPI_AWARENESS_CONTEXT; type GetDpiForSystem = unsafe extern "system" fn() -> u32; type GetDpiForWindow = unsafe extern "system" fn(HWND) -> u32; type GetDpiFromDpiAwarenessContext = unsafe extern "system" fn(DPI_AWARENESS_CONTEXT) -> u32; -type GetProcessDpiAwarenessContext = unsafe extern "system" fn(HANDLE) -> u32; type GetSystemDpiForProcess = unsafe extern "system" fn(HANDLE) -> u32; type GetWindowDpiAwarenessContext = unsafe extern "system" fn(HWND) -> DPI_AWARENESS_CONTEXT; type GetWindowDpiHostingBehavior = unsafe extern "system" fn(HWND) -> DPI_HOSTING_BEHAVIOR; @@ -29,7 +27,6 @@ const _: () = { let _: AdjustWindowRectExForDpi = AdjustWindowRectExForDpi; let _: AreDpiAwarenessContextsEqual = AreDpiAwarenessContextsEqual; let _: EnableNonClientDpiScaling = EnableNonClientDpiScaling; - let _: GetAwarenessFromDpiAwarenessContext = GetAwarenessFromDpiAwarenessContext; let _: GetDpiAwarenessContextForProcess = GetDpiAwarenessContextForProcess; let _: GetDpiForSystem = GetDpiForSystem; let _: GetDpiForWindow = GetDpiForWindow; @@ -47,7 +44,6 @@ pub struct ExtendedUser32 { pub adjust_window_rect_ex_for_dpi: Option, pub are_dpi_awareness_contexts_equal: Option, pub enable_non_client_dpi_scaling: Option, - pub get_awareness_from_dpi_awareness_context: Option, pub get_dpi_awareness_context_for_process: Option, pub get_dpi_for_system: Option, pub get_dpi_for_window: Option, @@ -69,8 +65,6 @@ unsafe impl Module for ExtendedUser32 { adjust_window_rect_ex_for_dpi: library.get(c"AdjustWindowRectExForDpi"), are_dpi_awareness_contexts_equal: library.get(c"AreDpiAwarenessContextsEqual"), enable_non_client_dpi_scaling: library.get(c"EnableNonClientDpiScaling"), - get_awareness_from_dpi_awareness_context: library - .get(c"GetAwarenessFromDpiAwarenessContext"), get_dpi_awareness_context_for_process: library .get(c"GetDpiAwarenessContextForProcess"), get_dpi_for_window: library.get(c"GetDpiForWindow"), diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 4401c075..b5713549 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -28,6 +28,10 @@ use windows_sys::Win32::Foundation::{LPARAM, LRESULT, WPARAM}; use windows_sys::Win32::UI::WindowsAndMessaging::CreateWindowExW; pub trait WindowImpl: 'static { + fn non_client_create( + &self, window: HWnd, + ) -> core::result::Result<(), crate::platform::PlatformError>; + /// Called during the processing of the WM_CREATE message, but after this type was properly /// initialized. /// diff --git a/src/wrappers/win32/window/data.rs b/src/wrappers/win32/window/data.rs index 6c133d72..769be955 100644 --- a/src/wrappers/win32/window/data.rs +++ b/src/wrappers/win32/window/data.rs @@ -47,6 +47,16 @@ impl WindowData { unreachable!("WindowData is already initialized"); } + if let Some(inner) = self.inner_impl.get() { + inner.non_client_create(window)?; + } + + Ok(()) + } + + pub fn on_create( + &self, window: HWnd, + ) -> core::result::Result<(), crate::platform::PlatformError> { if let Some(inner) = self.inner_impl.get() { inner.after_create(window)?; } diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index de2d2743..1f463d83 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -8,7 +8,7 @@ use std::num::{NonZeroU32, NonZeroUsize}; use std::ptr::{null_mut, NonNull}; use windows::Win32::System::Ole::IDropTarget; use windows_core::{Error, Interface, InterfaceRef, Result, HRESULT}; -use windows_sys::Win32::Foundation::{SetLastError, HWND, POINT, S_OK}; +use windows_sys::Win32::Foundation::{SetLastError, FALSE, HWND, POINT, S_OK}; use windows_sys::Win32::Graphics::Gdi::{ MonitorFromWindow, ScreenToClient, MONITOR_DEFAULTTOPRIMARY, }; @@ -327,4 +327,16 @@ impl HWnd { Some(ctx) } + + pub fn enable_non_client_dpi_scaling(&self, user32: &ExtendedUser32) { + let Some(enable) = user32.enable_non_client_dpi_scaling else { return }; + let result = unsafe { enable(self.as_raw()) }; + + if result == FALSE { + crate::warn!( + "Could not enable non-client DPI scaling for window: {}", + Error::from_thread() + ); + } + } } diff --git a/src/wrappers/win32/window/proc.rs b/src/wrappers/win32/window/proc.rs index a2bdda61..681aef9e 100644 --- a/src/wrappers/win32/window/proc.rs +++ b/src/wrappers/win32/window/proc.rs @@ -1,4 +1,5 @@ use super::*; +use crate::platform::PlatformError; use std::ptr::NonNull; use std::rc::Rc; use windows_sys::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM}; @@ -15,7 +16,7 @@ pub unsafe extern "system" fn wnd_proc( let window = unsafe { HWnd::from_raw(window) }; match message_code { - WM_CREATE => { + WM_NCCREATE => { let create = unsafe { &*(l_param as *const CREATESTRUCTW) }; let inner_ptr = create.lpCreateParams as *mut WindowData; @@ -42,24 +43,18 @@ pub unsafe extern "system" fn wnd_proc( inner.initialize(window) }; - match result { - // If successful, all good. - // Ownership of the inner state has been passed to the window via the userdata ptr. - Ok(()) => 0, - - // If initializer failed, abort. - Err(e) => { - // First, revoke ownership from the window, we don't want it to be used by any subsequent messages. - let _ = window.set_userdata_ptr(core::ptr::null::()); + handle_error_as_fatal(result, window, inner_ptr) + } + WM_CREATE => { + let create = unsafe { &*(l_param as *const CREATESTRUCTW) }; + let inner_ptr = NonNull::new(create.lpCreateParams as *mut WindowData); - // Try to recover and free the received pointer data. But if this also fails, better to leak - // it than risk crashing - drop(Rc::from_raw(inner_ptr.as_ptr())); + let Some(inner_ptr) = window.get_userdata_ptr::>().or(inner_ptr) else { + return handle_default(); + }; - crate::error!("Window initializer failed while trying to create window: {}", e); - -1 - } - } + let result = WindowData::handle(inner_ptr, |inner| inner.on_create(window)); + handle_error_as_fatal(result, window, inner_ptr) } WM_DESTROY => { let Some(state_ptr) = window.get_userdata_ptr::>() else { @@ -93,3 +88,27 @@ pub unsafe extern "system" fn wnd_proc( } } } + +unsafe fn handle_error_as_fatal( + result: core::result::Result<(), PlatformError>, window: HWnd, + inner_ptr: NonNull>, +) -> LRESULT { + match result { + // If successful, all good. + // Ownership of the inner state has been passed to the window via the userdata ptr. + Ok(()) => 0, + + // If initializer failed, abort. + Err(e) => { + // First, revoke ownership from the window, we don't want it to be used by any subsequent messages. + let _ = window.set_userdata_ptr(core::ptr::null::()); + + // Try to recover and free the received pointer data. But if this also fails, better to leak + // it than risk crashing + drop(Rc::from_raw(inner_ptr.as_ptr())); + + crate::error!("Window initializer failed while trying to create window: {}", e); + -1 + } + } +} From 769980f3f2539338748ca1c3b2a8ad473f471031 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:35:08 +0200 Subject: [PATCH 11/17] fixes --- examples/plugin_clack/src/gui.rs | 4 +--- examples/render_femtovg/src/main.rs | 4 +--- examples/render_wgpu/src/main.rs | 4 +--- src/platform/win/dpi.rs | 3 ++- src/platform/win/gl.rs | 2 +- src/platform/win/window_state.rs | 7 ++++++- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index b0cd5895..41cf287a 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -30,9 +30,7 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { - tracing_subscriber::fmt::fmt() - .with_max_level(Level::DEBUG) - .init(); + tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).init(); let options = WindowSettings::new() .wait_for_parent() diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index cc09e97e..b38de289 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -117,9 +117,7 @@ impl WindowHandler for FemtovgExample { } fn main() -> Result<(), baseview::Error> { - tracing_subscriber::fmt::fmt() - .with_max_level(Level::DEBUG) - .init(); + tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).init(); unsafe { baseview::assume_standalone_in_process() }; tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).init(); diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index 74cc088d..34f7dfc6 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -211,9 +211,7 @@ impl WindowHandler for WgpuExample { } fn main() -> Result<(), baseview::Error> { - tracing_subscriber::fmt::fmt() - .with_max_level(Level::DEBUG) - .init(); + tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).init(); unsafe { baseview::assume_standalone_in_process() }; diff --git a/src/platform/win/dpi.rs b/src/platform/win/dpi.rs index 9b6f35f6..2df4c3c5 100644 --- a/src/platform/win/dpi.rs +++ b/src/platform/win/dpi.rs @@ -15,7 +15,8 @@ pub(crate) struct DpiScalingStrategy { impl DpiScalingStrategy { pub fn get( - user32: Option<&ExtendedUser32>, parent: Option, settings: &WindowSettings, + user32: Option<&ExtendedUser32>, parent: Option, + #[cfg(feature = "opengl")] settings: &WindowSettings, ) -> Self { let _span = crate::debug_span!("DpiScalingStrategy"); let shcore = LibraryModule::::lazy(); diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index 4d81f3a7..aac73f6d 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -9,7 +9,7 @@ use crate::wrappers::win32::window::{ with_dummy_window, HWnd, OwnDeviceContext, PixelFormat, PixelFormatAttribs, WglContext, WglExtra, }; -use crate::wrappers::win32::{RawLibrary}; +use crate::wrappers::win32::RawLibrary; pub type GlContext = Rc; diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index f40f8eaf..58bd85d2 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -161,7 +161,12 @@ impl WindowSharedState { pub fn init(&self, init: &WindowInitializer) { let parent = init.settings.parent.as_ref().map(|p| p.inner.handle); - let strategy = DpiScalingStrategy::get(Some(&self.user32), parent, &init.settings); + let strategy = DpiScalingStrategy::get( + Some(&self.user32), + parent, + #[cfg(feature = "opengl")] + &init.settings, + ); if strategy.assume_96_dpi { self.current_dpi.set(Some(Dpi::default())); From 7d8668bf5fe3dd7f1471727183fcfb884f48d721 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:40:32 +0200 Subject: [PATCH 12/17] fixes --- src/platform/win/dpi.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/platform/win/dpi.rs b/src/platform/win/dpi.rs index 2df4c3c5..082214a2 100644 --- a/src/platform/win/dpi.rs +++ b/src/platform/win/dpi.rs @@ -3,7 +3,6 @@ use crate::wrappers::win32::{ Dpi, DpiAwarenessContext, DpiAwarenessContextType, DpiAwarenessGuard, ExtendedShCore, ExtendedUser32, LazyLibraryModule, LibraryModule, ProcessDpiAwareness, }; -use crate::WindowSettings; use std::cell::LazyCell; use std::ops::Deref; @@ -16,7 +15,7 @@ pub(crate) struct DpiScalingStrategy { impl DpiScalingStrategy { pub fn get( user32: Option<&ExtendedUser32>, parent: Option, - #[cfg(feature = "opengl")] settings: &WindowSettings, + #[cfg(feature = "opengl")] settings: &crate::WindowSettings, ) -> Self { let _span = crate::debug_span!("DpiScalingStrategy"); let shcore = LibraryModule::::lazy(); From f528aa8ba6f364c89dfcab8b34350610b372f2df Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 19:01:01 +0200 Subject: [PATCH 13/17] fixes --- examples/render_wgpu/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index 34f7dfc6..41f45df3 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -4,7 +4,6 @@ use baseview::{ WindowSize, }; -use log::LevelFilter; use std::cell::RefCell; use tracing::Level; From 16066780db6924348c9da23f91fdb1d220cc755a Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 8 Sep 2026 01:23:41 +0200 Subject: [PATCH 14/17] fix (... omg) --- examples/render_femtovg/src/main.rs | 1 - src/wrappers/win32/window.rs | 4 ++-- src/wrappers/win32/window/proc.rs | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index b38de289..98d27e90 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -120,7 +120,6 @@ fn main() -> Result<(), baseview::Error> { tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).init(); unsafe { baseview::assume_standalone_in_process() }; - tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).init(); let window_open_options = WindowSettings::new() .with_title("Femtovg on Baseview") diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index b5713549..a0b8f236 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -87,11 +87,11 @@ pub fn create_window( parent.map(|p| p.as_raw()).unwrap_or(null_mut()), null_mut(), instance.as_raw(), - Rc::into_raw(data).cast(), + Rc::into_raw(Rc::clone(&data)).cast(), ) }; - let Some(hwnd) = NonNull::new(hwnd) else { return Err(Error::from_thread()) }; + let Some(hwnd) = NonNull::new(hwnd) else { return Err(dbg!(Error::from_thread())) }; // SAFETY: This Hwnd is valid since it came from CreateWindowExW let hwnd = unsafe { HWnd::from_raw(hwnd) }; diff --git a/src/wrappers/win32/window/proc.rs b/src/wrappers/win32/window/proc.rs index 681aef9e..70265f5e 100644 --- a/src/wrappers/win32/window/proc.rs +++ b/src/wrappers/win32/window/proc.rs @@ -43,7 +43,7 @@ pub unsafe extern "system" fn wnd_proc( inner.initialize(window) }; - handle_error_as_fatal(result, window, inner_ptr) + (handle_error_as_fatal(result, window, inner_ptr) == 0) as _ } WM_CREATE => { let create = unsafe { &*(l_param as *const CREATESTRUCTW) }; From d40cab59ab1ce76885088bacc2224687dce2de2f Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Wed, 9 Sep 2026 16:51:39 +0200 Subject: [PATCH 15/17] fix --- src/wrappers/win32/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index a0b8f236..826bc26a 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -91,7 +91,7 @@ pub fn create_window( ) }; - let Some(hwnd) = NonNull::new(hwnd) else { return Err(dbg!(Error::from_thread())) }; + let Some(hwnd) = NonNull::new(hwnd) else { return Err(Error::from_thread()) }; // SAFETY: This Hwnd is valid since it came from CreateWindowExW let hwnd = unsafe { HWnd::from_raw(hwnd) }; From 7d7a86134626ef852db5fc82566884cbc659fc58 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:16:00 +0200 Subject: [PATCH 16/17] Add extra logging --- examples/plugin_clack/Cargo.toml | 4 ++-- examples/plugin_clack/src/gui.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/plugin_clack/Cargo.toml b/examples/plugin_clack/Cargo.toml index 4ebf67c5..8c8932e4 100644 --- a/examples/plugin_clack/Cargo.toml +++ b/examples/plugin_clack/Cargo.toml @@ -9,8 +9,8 @@ crate-type = ["cdylib"] [dependencies] clack-plugin = "0.1.1" clack-extensions = { version = "0.1.1", features = ["gui", "state", "clack-plugin", "raw-window-handle_06"] } -baseview = { path = "../..", features = ["opengl"] } +baseview = { path = "../..", features = ["opengl", "tracing"] } softbuffer = "0.4.8" tracing-subscriber = { workspace = true } -tracing = "0.1.44" \ No newline at end of file +tracing = "0.1.44" diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 41cf287a..d229d34b 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -30,7 +30,7 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { - tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).init(); + tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).with_ansi(false).init(); let options = WindowSettings::new() .wait_for_parent() From 1d4adfbd157020b69d8fb38fa90feee293de593e Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Wed, 9 Sep 2026 20:18:11 +0200 Subject: [PATCH 17/17] Add new femtovg example, with extra logging --- Cargo.toml | 2 +- examples/plugin_clack/Cargo.toml | 2 +- examples/plugin_clack/src/gui.rs | 7 +- examples/plugin_clack_femtovg/Cargo.toml | 17 ++ examples/plugin_clack_femtovg/src/audio.rs | 31 +++ examples/plugin_clack_femtovg/src/gui.rs | 194 ++++++++++++++++++ examples/plugin_clack_femtovg/src/lib.rs | 77 +++++++ .../src/window_handler.rs | 115 +++++++++++ 8 files changed, 438 insertions(+), 7 deletions(-) create mode 100644 examples/plugin_clack_femtovg/Cargo.toml create mode 100644 examples/plugin_clack_femtovg/src/audio.rs create mode 100644 examples/plugin_clack_femtovg/src/gui.rs create mode 100644 examples/plugin_clack_femtovg/src/lib.rs create mode 100644 examples/plugin_clack_femtovg/src/window_handler.rs diff --git a/Cargo.toml b/Cargo.toml index 83db5db1..af0379a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [ ] } [workspace] -members = ["examples/cursors", "examples/open_parented", "examples/open_window", "examples/plugin_clack", "examples/render_femtovg", "examples/render_wgpu"] +members = ["examples/cursors", "examples/open_parented", "examples/open_window", "examples/plugin_clack", "examples/render_femtovg", "examples/render_wgpu", "examples/plugin_clack_femtovg"] [lints.clippy] missing-safety-doc = "allow" diff --git a/examples/plugin_clack/Cargo.toml b/examples/plugin_clack/Cargo.toml index 8c8932e4..37331613 100644 --- a/examples/plugin_clack/Cargo.toml +++ b/examples/plugin_clack/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] [dependencies] clack-plugin = "0.1.1" clack-extensions = { version = "0.1.1", features = ["gui", "state", "clack-plugin", "raw-window-handle_06"] } -baseview = { path = "../..", features = ["opengl", "tracing"] } +baseview = { path = "../..", features = ["tracing"] } softbuffer = "0.4.8" tracing-subscriber = { workspace = true } diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index d229d34b..4e4ca276 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,7 +1,6 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::*; -use baseview::gl::GlConfig; use baseview::host::{Host, HostCallbacks, HostMainThreadCaller}; use baseview::{HandlerError, Window, WindowSettings, WindowSize}; use clack_extensions::gui::{ @@ -32,10 +31,8 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).with_ansi(false).init(); - let options = WindowSettings::new() - .wait_for_parent() - .with_size(PhysicalSize::new(400, 200)) - .with_gl_config(GlConfig::default()); + let options = + WindowSettings::new().wait_for_parent().with_size(PhysicalSize::new(400, 200)); let mut host = Host::new().with_main_thread(unsafe { MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() } diff --git a/examples/plugin_clack_femtovg/Cargo.toml b/examples/plugin_clack_femtovg/Cargo.toml new file mode 100644 index 00000000..04a212a0 --- /dev/null +++ b/examples/plugin_clack_femtovg/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "plugin_clack_femtovg" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +clack-plugin = "0.1.1" +clack-extensions = { version = "0.1.1", features = ["gui", "state", "clack-plugin", "raw-window-handle_06"] } +baseview = { path = "../..", features = ["opengl", "tracing"] } +femtovg = "0.26" +raw-window-handle = { version = "0.6.2", features = ["std"] } + +tracing-subscriber = { workspace = true } +tracing = "0.1.44" diff --git a/examples/plugin_clack_femtovg/src/audio.rs b/examples/plugin_clack_femtovg/src/audio.rs new file mode 100644 index 00000000..b3e34ee4 --- /dev/null +++ b/examples/plugin_clack_femtovg/src/audio.rs @@ -0,0 +1,31 @@ +use crate::ExamplePluginMainThread; +use clack_plugin::prelude::*; + +pub struct ExamplePluginAudioProcessor; + +impl<'a> PluginAudioProcessor<'a, (), ExamplePluginMainThread<'a>> for ExamplePluginAudioProcessor { + fn activate( + _host: HostAudioProcessorHandle<'a>, _main_thread: &mut ExamplePluginMainThread, + _shared: &'a (), _audio_config: PluginAudioConfiguration, + ) -> Result { + Ok(Self) + } + + fn process( + &mut self, _process: Process, mut audio: Audio, _events: Events, + ) -> Result { + for mut port in audio.port_pairs() { + let channels = port.channels()?.into_f32().expect("Expected f32 channels"); + + for channel_pair in channels { + match channel_pair { + ChannelPair::OutputOnly(o) => o.fill(0.0), + ChannelPair::InputOutput(i, o) => o.copy_from_slice(i), + _ => {} + } + } + } + + Ok(ProcessStatus::Continue) + } +} diff --git a/examples/plugin_clack_femtovg/src/gui.rs b/examples/plugin_clack_femtovg/src/gui.rs new file mode 100644 index 00000000..e42f0c30 --- /dev/null +++ b/examples/plugin_clack_femtovg/src/gui.rs @@ -0,0 +1,194 @@ +use crate::window_handler::FemtovgExample; +use crate::ExamplePluginMainThread; +use baseview::dpi::*; +use baseview::gl::GlConfig; +use baseview::host::{Host, HostCallbacks, HostMainThreadCaller}; +use baseview::{HandlerError, Window, WindowSettings, WindowSize}; +use clack_extensions::gui::{ + AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui, + PluginGuiImpl, Window as ClapWindow, +}; +use clack_plugin::plugin::PluginError; +use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle}; +use tracing::Level; + +pub struct ExamplePluginGui { + pub handle: Window, +} + +impl PluginGuiImpl for ExamplePluginMainThread<'_> { + fn is_api_supported(&mut self, configuration: GuiConfiguration) -> bool { + !configuration.is_floating + && Some(configuration.api_type) == GuiApiType::default_for_current_platform() + } + + fn get_preferred_api(&mut self) -> Option> { + Some(GuiConfiguration { + api_type: GuiApiType::default_for_current_platform()?, + is_floating: false, + }) + } + + fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { + tracing_subscriber::fmt::fmt().with_max_level(Level::DEBUG).with_ansi(false).init(); + + let options = WindowSettings::new() + .wait_for_parent() + .with_size(PhysicalSize::new(400, 200)) + .with_gl_config(GlConfig::default()); + + let mut host = Host::new().with_main_thread(unsafe { + MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() } + }); + + if let Some(gui) = self.host_gui { + host = host.with_callbacks(unsafe { + HostGuiCallbacks { ext: gui, host: self.host.with_arbitrary_lifetime() } + }); + } + + let window = Window::create_with_host(options, FemtovgExample::new, host)?; + + self.gui = Some(ExamplePluginGui { handle: window }); + Ok(()) + } + + fn destroy(&mut self) { + let Some(gui) = self.gui.take() else { return }; + + gui.handle.close() + } + + fn set_scale(&mut self, scale: f64) -> Result<(), PluginError> { + let Some(gui) = &self.gui else { + return Err(PluginError::Message("set_scale called without a GUI active")); + }; + gui.handle.suggest_fallback_scale_factor(scale)?; + + Ok(()) + } + + fn get_size(&mut self) -> Option { + let Some(gui) = &self.gui else { + eprintln!("get_size called without a GUI active"); + return None; + }; + + let size = gui.handle.size().to_native_size(); + + Some(GuiSize { width: size.width, height: size.height }) + } + + fn can_resize(&mut self) -> bool { + let Some(gui) = &self.gui else { return false }; + + gui.handle.is_resizable() + } + + fn get_resize_hints(&mut self) -> Option { + let can_resize = self.can_resize(); + + Some(GuiResizeHints { + strategy: AspectRatioStrategy::Disregard, // Not supported + + can_resize_vertically: can_resize, + can_resize_horizontally: can_resize, + }) + } + + fn adjust_size(&mut self, mut size: GuiSize) -> Option { + let Some(gui) = &self.gui else { return None }; + let scale_factor = gui.handle.size().scale_factor; + + if let Some(max_size) = gui.handle.max_size() { + let max_size = NativeSize::from_size(max_size, scale_factor); + size.width = size.width.min(max_size.width); + size.height = size.height.min(max_size.height); + } + + if let Some(min_size) = gui.handle.min_size() { + let min_size = NativeSize::from_size(min_size, scale_factor); + size.width = size.width.max(min_size.width); + size.height = size.height.max(min_size.height); + } + + Some(size) + } + + fn set_size(&mut self, size: GuiSize) -> Result<(), PluginError> { + let Some(gui) = &self.gui else { + return Err(PluginError::Message("set_size called without a GUI active")); + }; + + gui.handle.resize(NativeSize { width: size.width, height: size.height })?; + + Ok(()) + } + + fn set_parent(&mut self, window: ClapWindow) -> Result<(), PluginError> { + let Some(gui) = &self.gui else { + return Err(PluginError::Message("set_parent called without a GUI active")); + }; + + // SAFETY: The CLAP spec ensures the parent window handle is valid for at least this call + let parent = unsafe { window.borrow_handle_unchecked()? }; + + gui.handle.set_parent(&parent)?; + gui.handle.show()?; + + Ok(()) + } + + fn set_transient(&mut self, _window: ClapWindow) -> Result<(), PluginError> { + unimplemented!() // Not supported yet + } + + fn suggest_title(&mut self, _title: &str) { + // Not supported yet + } + + fn show(&mut self) -> Result<(), PluginError> { + let Some(gui) = &self.gui else { + return Err(PluginError::Message("show called without a GUI active")); + }; + gui.handle.show()?; + + Ok(()) + } + + fn hide(&mut self) -> Result<(), PluginError> { + let Some(gui) = &self.gui else { + return Err(PluginError::Message("hide called without a GUI active")); + }; + gui.handle.show()?; + + Ok(()) + } +} + +struct MainThreadHandler { + host: HostSharedHandle<'static>, +} + +impl HostMainThreadCaller for MainThreadHandler { + fn call_main_thread(&mut self) { + self.host.request_callback(); + } +} + +struct HostGuiCallbacks { + ext: HostGui, + host: HostMainThreadHandle<'static>, +} + +impl HostCallbacks for HostGuiCallbacks { + fn request_resize(&mut self, new_size: WindowSize) -> Result<(), HandlerError> { + let new_size = new_size.to_native_size(); + self.ext.request_resize(&self.host, new_size.width, new_size.height)?; + Ok(()) + } + + fn destroyed(&mut self) { + self.ext.closed(&self.host, true); + } +} diff --git a/examples/plugin_clack_femtovg/src/lib.rs b/examples/plugin_clack_femtovg/src/lib.rs new file mode 100644 index 00000000..a81ef789 --- /dev/null +++ b/examples/plugin_clack_femtovg/src/lib.rs @@ -0,0 +1,77 @@ +use crate::audio::ExamplePluginAudioProcessor; +use crate::gui::ExamplePluginGui; +use clack_extensions::gui::{HostGui, PluginGui}; +use clack_extensions::state::{PluginState, PluginStateImpl}; +use clack_plugin::prelude::*; +use clack_plugin::stream::{InputStream, OutputStream}; + +mod audio; +mod gui; +mod window_handler; + +/// The type that represents our plugin in Clack. +/// +/// This is what implements the [`Plugin`] trait, where all the other subtypes are attached. +pub struct ExamplePlugin; + +impl Plugin for ExamplePlugin { + type AudioProcessor<'a> = ExamplePluginAudioProcessor; + type Shared<'a> = (); + type MainThread<'a> = ExamplePluginMainThread<'a>; + + fn declare_extensions(builder: &mut PluginExtensions, _shared: Option<&()>) { + builder.register::().register::(); + } +} + +impl DefaultPluginFactory for ExamplePlugin { + fn get_descriptor() -> PluginDescriptor { + use clack_plugin::plugin::features::*; + + PluginDescriptor::new( + "org.rust-audio.clack.gain-baseview-femtovg", + "Clack Gain Baseview Femtovg Example", + ) + .with_features([AUDIO_EFFECT, STEREO]) + } + + fn new_shared(_host: HostSharedHandle<'_>) -> Result, PluginError> { + Ok(()) + } + + fn new_main_thread<'a>( + host: HostMainThreadHandle<'a>, _shared: &'a Self::Shared<'a>, + ) -> Result, PluginError> { + Ok(Self::MainThread { gui: None, host_gui: host.get_extension(), host }) + } +} + +/// The data that belongs to the main thread of our plugin. +pub struct ExamplePluginMainThread<'a> { + /// The host handle + host: HostMainThreadHandle<'a>, + // The host GUI extension handle + host_gui: Option, + /// The plugin's GUI state and context + gui: Option, +} + +impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread<'a> { + fn on_main_thread(&mut self) { + if let Some(gui) = self.gui.as_mut() { + gui.handle.host_main_thread_callback(); + } + } +} + +impl PluginStateImpl for ExamplePluginMainThread<'_> { + fn save(&mut self, _output: &mut OutputStream) -> Result<(), PluginError> { + Ok(()) + } + + fn load(&mut self, _input: &mut InputStream) -> Result<(), PluginError> { + Ok(()) + } +} + +clack_export_entry!(SinglePluginEntry); diff --git a/examples/plugin_clack_femtovg/src/window_handler.rs b/examples/plugin_clack_femtovg/src/window_handler.rs new file mode 100644 index 00000000..80d53e48 --- /dev/null +++ b/examples/plugin_clack_femtovg/src/window_handler.rs @@ -0,0 +1,115 @@ +use baseview::dpi::PhysicalPosition; +use baseview::gl::GlContext; +use baseview::{ + Event, EventStatus, HandlerError, MouseEvent, WindowContext, WindowHandler, WindowSize, +}; +use femtovg::renderer::OpenGl; +use femtovg::{Canvas, Color}; +use std::cell::{Cell, RefCell}; + +pub struct FemtovgExample { + window_context: WindowContext, + gl_context: GlContext, + canvas: RefCell>, + current_mouse_position: Cell>, + damaged: Cell, +} + +impl WindowHandler for FemtovgExample { + fn on_frame(&self) -> Result<(), HandlerError> { + if !self.damaged.get() { + return Ok(()); + } + + let context = &self.gl_context; + unsafe { context.make_current()? }; + + let mut canvas = self.canvas.borrow_mut(); + + let screen_height = canvas.height(); + let screen_width = canvas.width(); + + // Clear + canvas.clear_rect(0, 0, screen_width, screen_height, Color::rgb(0xAA, 0xAA, 0xAA)); + + // Make big blue rectangle + canvas.clear_rect( + (screen_width as f32 * 0.1).floor() as u32, + (screen_height as f32 * 0.1).floor() as u32, + (screen_width as f32 * 0.8).floor() as u32, + (screen_height as f32 * 0.8).floor() as u32, + Color::rgbf(0., 0.3, 0.9), + ); + + let mouse_position = self.current_mouse_position.get().cast::(); + + // Make smol orange rectangle + canvas.clear_rect( + (mouse_position.x - 15).clamp(0, screen_width as i32 - 30) as u32, + (mouse_position.y - 15).clamp(0, screen_height as i32 - 30) as u32, + 30, + 30, + Color::rgbf(0.9, 0.3, 0.), + ); + + // Tell renderer to execute all drawing commands + canvas.flush(); + context.swap_buffers()?; + unsafe { context.make_not_current()? }; + self.damaged.set(false); + + Ok(()) + } + + fn resized(&self, new_size: WindowSize) -> Result<(), HandlerError> { + let size = new_size.physical; + self.canvas.borrow_mut().set_size(size.width, size.height, new_size.scale_factor as f32); + self.damaged.set(true); + + Ok(()) + } + + fn on_event(&self, event: Event) -> EventStatus { + match event { + Event::Mouse( + MouseEvent::CursorMoved { position, .. } + | MouseEvent::DragEntered { position, .. } + | MouseEvent::DragMoved { position, .. } + | MouseEvent::DragDropped { position, .. }, + ) => { + self.current_mouse_position.set(position); + if position.y > 400. && !self.window_context.has_focus() { + let _ = self.window_context.focus(); + } + self.damaged.set(true); + } + _ => {} + }; + + EventStatus::Captured + } +} + +impl FemtovgExample { + pub fn new(window_context: WindowContext) -> Result { + let Some(gl_context) = window_context.gl_context() else { unreachable!() }; + unsafe { gl_context.make_current()? }; + + let renderer = + unsafe { OpenGl::new_from_function_cstr(|s| gl_context.get_proc_address(s)) }?; + + let mut canvas = Canvas::new(renderer)?; + let size = window_context.size(); + + canvas.set_size(size.physical.width, size.physical.height, size.scale_factor as f32); + + unsafe { gl_context.make_not_current()? }; + Ok(Self { + gl_context, + window_context, + canvas: canvas.into(), + damaged: true.into(), + current_mouse_position: Cell::new(PhysicalPosition::default()), + }) + } +}