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 67729152..37331613 100644 --- a/examples/plugin_clack/Cargo.toml +++ b/examples/plugin_clack/Cargo.toml @@ -9,6 +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 = ["tracing"] } softbuffer = "0.4.8" +tracing-subscriber = { workspace = true } +tracing = "0.1.44" diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 74fefa5e..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::{ @@ -10,6 +9,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,10 +29,10 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { - let options = WindowSettings::new() - .wait_for_parent() - .with_size(PhysicalSize::new(400, 200)) - .with_gl_config(GlConfig::default()); + 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)); 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()), + }) + } +} 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..98d27e90 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,9 @@ 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..41f45df3 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -4,8 +4,8 @@ use baseview::{ WindowSize, }; -use log::LevelFilter; use std::cell::RefCell; +use tracing::Level; struct WgpuExample { window_context: WindowContext, @@ -210,7 +210,7 @@ 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/dpi.rs b/src/platform/win/dpi.rs new file mode 100644 index 00000000..082214a2 --- /dev/null +++ b/src/platform/win/dpi.rs @@ -0,0 +1,219 @@ +use crate::wrappers::win32::window::HWnd; +use crate::wrappers::win32::{ + Dpi, DpiAwarenessContext, DpiAwarenessContextType, DpiAwarenessGuard, ExtendedShCore, + ExtendedUser32, LazyLibraryModule, LibraryModule, ProcessDpiAwareness, +}; +use std::cell::LazyCell; +use std::ops::Deref; + +#[derive(Copy, Clone, Default)] +pub(crate) struct DpiScalingStrategy { + pub assume_96_dpi: bool, + pub thread_dpi_awareness_context: Option, +} + +impl DpiScalingStrategy { + pub fn get( + user32: Option<&ExtendedUser32>, parent: Option, + #[cfg(feature = "opengl")] settings: &crate::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. + // 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 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() { + 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); + }; + + 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) + } + } else { + // No parent, we can choose whatever suits us best! + Self::get_best_supported(user32_lib, &shcore) + } + } + + 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_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); + 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()), + }; + } + + Self::get_best_supported(user32, shcore) + } + + 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); + + 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)); + + Self { assume_96_dpi, thread_dpi_awareness_context: Some(dpi_awareness_context) } + } + + 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); + + 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 } + } + + 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 { + crate::debug!("No DPI Awareness Context types are available. Falling back to legacy Windows 8 APIs."); + return Self::get_from_process_legacy(shcore); + }; + + Self::get_from_specific_dpi_awareness_context(best_supported.into(), user32) + } +} + +impl DpiScalingStrategy { + pub fn get_dpi_for_window(&self, own_window: HWnd, user32: &ExtendedUser32) -> Option { + if self.assume_96_dpi { + return Some(Dpi::default()); + } + + 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); + } + + 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) + }) + } +} + +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/gl.rs b/src/platform/win/gl.rs index da8e716a..aac73f6d 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,19 +9,19 @@ use crate::wrappers::win32::window::{ with_dummy_window, HWnd, OwnDeviceContext, PixelFormat, PixelFormatAttribs, WglContext, WglExtra, }; -use crate::wrappers::win32::LibraryModule; +use crate::wrappers::win32::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 +68,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/mod.rs b/src/platform/win/mod.rs index 0a92c809..ef733ce8 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; @@ -7,7 +8,7 @@ mod window_state; use crate::wrappers::win32::h_instance::HInstance; use crate::wrappers::win32::window::HWnd; -use crate::wrappers::win32::ExtendedUser32; +pub(crate) use dpi::DpiScalingStrategy; pub use error::{PlatformError, Result}; use raw_window_handle::{ DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, Win32WindowHandle, @@ -97,17 +98,6 @@ impl Display for ParentWindowHandleError { } } -#[inline] pub fn assume_standalone_in_process() { - let user32 = match ExtendedUser32::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 d808362e..867902e0 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,7 +22,7 @@ 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, + ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessGuard, LibraryModule, Rect, WindowStyle, }; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSize}; @@ -85,7 +85,9 @@ 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, 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 { Ok(()) @@ -118,8 +120,10 @@ impl WindowHandle { } let _guard = self.state.originate_host_resize(); + let dpi_ctx = + DpiAwarenessGuard::new(&self.state.user32, self.state.dpi_scaling_strategy.get())?; - 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 +220,13 @@ pub struct BaseviewWindow { impl BaseviewWindow { pub fn create(shared_state: Rc, init: WindowInitializer) -> Result { - let dpi_ctx = DpiAwarenessContext::new(&shared_state.user32)?; + shared_state.init(&init); let style = WindowStyle::from_settings(&init.settings); + let parent = init.settings.parent.map(|p| p.inner.handle); + + let dpi_ctx = + DpiAwarenessGuard::new(&shared_state.user32, shared_state.dpi_scaling_strategy.get())?; let window_size = shared_state.current_size.get(); @@ -249,7 +257,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)?; @@ -308,14 +315,25 @@ 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 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)?; + let dpi = window_state + .shared + .dpi_scaling_strategy + .get() + .get_dpi_for_window(window, &self.shared_state.user32); if let Some(dpi) = dpi { if Some(dpi) != window_state.shared.current_dpi.get() { @@ -330,7 +348,11 @@ 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); - window.resize_and_activate(new_size, Some(dpi), &window_state.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)?; } } @@ -585,9 +607,16 @@ 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 = DpiAwarenessContext::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(); @@ -597,7 +626,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, @@ -661,7 +690,11 @@ 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, + window_state.shared.dpi_scaling_strategy.get(), + ) + .unwrap(); let style = window.get_style().unwrap(); let dpi = window_state.shared.current_dpi.get(); @@ -695,7 +728,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 = 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..58bd85d2 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -1,11 +1,13 @@ 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::window::WindowInitializer; 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, DpiAwarenessGuard, ExtendedUser32, LibraryModule}; use crate::WindowSettings; use crate::{MouseCursor, WindowSize}; use raw_window_handle::{DisplayHandle, Win32WindowHandle}; @@ -23,7 +25,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 +33,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()), @@ -86,7 +90,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.shared.dpi_scaling_strategy.get())?; + + self.hwnd.resize_and_activate(new_size, dpi, &ctx)?; Ok(()) } @@ -130,13 +136,14 @@ 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: 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(), @@ -147,10 +154,27 @@ impl WindowSharedState { destroy_host_originated: false.into(), sizing_strategy: SizingStrategy::from_settings(settings), user32, + dpi_scaling_strategy: DpiScalingStrategy::default().into(), } .into() } + 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, + #[cfg(feature = "opengl")] + &init.settings, + ); + + 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/tracing.rs b/src/tracing.rs index 4540129a..4384ec2d 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, debug_span, error, span, warn}; #[cfg(not(feature = "tracing"))] mod tracing_impl { - macro_rules! __warn { + macro_rules! __void { ($($f:tt)*) => { { let _ = ($($f)*); @@ -13,8 +13,29 @@ 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; + + pub struct Span; + pub struct SpanGuard; + impl Span { + pub fn entered(&self) -> SpanGuard { + SpanGuard + } + } + + macro_rules! __span { + ($($f:tt)*) => { + { + let _ = ($($f)*); + crate::Span + } + }; + } + + pub(crate) use __span as span; + pub(crate) use __span as debug_span; } #[cfg(not(feature = "tracing"))] 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 8c687ee8..ab529977 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -1,65 +1,307 @@ use super::*; +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::RECT; +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}; #[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. + 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() })?)) + // 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(NonZeroU32::new(unsafe { get_system_dpi_for_process(null_mut()) })?)) + } else { + 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, + }); } impl Default for Dpi { fn default() -> Self { - Self(USER_DEFAULT_SCREEN_DPI) + Self::USER_DEFAULT } } -pub struct DpiAwarenessContext<'a> { - previous: DPI_AWARENESS_CONTEXT, - user32: &'a ExtendedUser32, +/// Win8 Legacy (replaced by DpiAwarenessContext in Win10), process-wide. +#[repr(i32)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ProcessDpiAwareness { + Unaware = PROCESS_DPI_UNAWARE, + SystemDpiAware = PROCESS_SYSTEM_DPI_AWARE, + PerMonitorDpiAware = PROCESS_PER_MONITOR_DPI_AWARE, } -impl<'a> DpiAwarenessContext<'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 }); - }; +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 + } + } + } - let previous = - unsafe { set_thread_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) }; + pub fn get(lib: &ExtendedShCore) -> Option { + let mut value = -1; + let result = HRESULT(unsafe { lib.get_process_dpi_awareness?(null_mut(), &mut value) }); - if previous.is_null() { - return Err(Error::from_thread()); + if result.is_err() { + crate::warn!("GetProcessDpiAwareness failed: {}", result.message()); + return None; } - Ok(DpiAwarenessContext { previous, user32 }) + if value < 0 { + crate::warn!("GetProcessDpiAwareness did not return a value"); + return None; + } + + Self::from_raw(value) } - 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) }; + pub fn set(&self, lib: &ExtendedShCore) -> Result<()> { + let Some(set) = lib.set_process_dpi_awareness else { return Ok(()) }; + + HRESULT(unsafe { set(*self as _) }).ok() + } +} + +/// Windows 10, version 1607. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum DpiAwarenessContextType { + Unaware, + UnawareGDIScaled, + SystemDpiAware, + PerMonitorDpiAware, + /// Windows 10, version 1703. + PerMonitorDpiAwareV2, +} + +impl DpiAwarenessContextType { + // Sorted by order of goodness + const ALL: [Self; 5] = + [PerMonitorDpiAwareV2, PerMonitorDpiAware, SystemDpiAware, Unaware, UnawareGDIScaled]; - if result == 0 { - return Err(Error::from_thread()); + /// 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)? { + return Some(awareness_type); } + } + + None + } +} - return Ok(rect); +#[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) + } + + /// 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) }; - // 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 Some(inner) = NonNull::new(previous) else { return Some(Err(Error::from_thread())) }; + + Some(Ok(Self { inner })) + } + + pub fn set_process(&self, user32: &ExtendedUser32) -> Option> { let result = unsafe { - adjust_window_rect_ex_for_dpi(&mut rect.0, style.style, 0, style.style_ex, dpi.0) + user32.set_process_dpi_awareness_context?(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + }; + + if result == FALSE { + return Some(Err(Error::from_thread())); + } + + 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 { + let result = unsafe { user32.get_dpi_from_dpi_awareness_context?(self.inner.as_ptr()) }; + + Some(Dpi(NonZeroU32::new(result)?)) + } + + /// 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, + ) + } + + /// 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 { + 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!() }; + + Self { inner } + } +} + +pub struct DpiAwarenessGuard<'a> { + inner: Option<(DpiAwarenessContext, &'a ExtendedUser32)>, +} + +impl<'a> DpiAwarenessGuard<'a> { + pub fn new(user32: &'a ExtendedUser32, strategy: DpiScalingStrategy) -> Result { + let Some(new_context) = strategy.thread_dpi_awareness_context else { + return Ok(Self { inner: None }); + }; + + 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)) }), + } + } + + 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( + &self, mut rect: Rect, style: WindowStyle, dpi: Option, + ) -> Result { + 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.get(), + ) + } + } else { + unsafe { AdjustWindowRectEx(&mut rect.0, style.style, 0, style.style_ex) } }; if result == 0 { @@ -83,11 +325,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/library.rs b/src/wrappers/win32/library.rs index 707ada59..da052595 100644 --- a/src/wrappers/win32/library.rs +++ b/src/wrappers/win32/library.rs @@ -1,27 +1,90 @@ -use std::ffi::c_void; +use std::cell::LazyCell; +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); +/// # 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; +} + +pub struct LibraryModule { + _library: RawLibrary, + 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 { + 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/shcore.rs b/src/wrappers/win32/shcore.rs new file mode 100644 index 00000000..d372b5ca --- /dev/null +++ b/src/wrappers/win32/shcore.rs @@ -0,0 +1,42 @@ +use crate::wrappers::win32::{Module, RawLibrary}; +use std::ffi::CStr; +use windows_sys::core::HRESULT; +use windows_sys::Win32::Foundation::HANDLE; +use windows_sys::Win32::Graphics::Gdi::HMONITOR; +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; + +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, + pub get_dpi_for_monitor: Option, +} + +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 { + unsafe { + 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/user32.rs b/src/wrappers/win32/user32.rs index a1a3385f..26aae965 100644 --- a/src/wrappers/win32/user32.rs +++ b/src/wrappers/win32/user32.rs @@ -1,86 +1,82 @@ -use crate::wrappers::win32::LibraryModule; -use std::ffi::c_void; -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::UI::HiDpi::{ - DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, -}; +use windows_sys::Win32::Foundation::{HANDLE, HWND, RECT}; +use windows_sys::Win32::UI::HiDpi::*; 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; +pub type AreDpiAwarenessContextsEqual = + unsafe extern "system" fn(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT) -> BOOL; +type EnableNonClientDpiScaling = unsafe extern "system" fn(HWND) -> 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; + +// Checks the above typedefs match the function definitions from windows_sys +const _: () = { + let _: AdjustWindowRectExForDpi = AdjustWindowRectExForDpi; + let _: AreDpiAwarenessContextsEqual = AreDpiAwarenessContextsEqual; + let _: EnableNonClientDpiScaling = EnableNonClientDpiScaling; + 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 { - _library: LibraryModule, - 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_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, } -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; - -impl ExtendedUser32 { - pub fn load() -> Result { - let library = unsafe { LibraryModule::load(s!("user32.dll"))? }; +unsafe impl Module for ExtendedUser32 { + const MODULE_NAME: &'static CStr = c"user32.dll"; + fn load(library: &RawLibrary) -> Self { 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()); - } - - Ok(()) - } -} - -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!() }; - - 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, + Self { + 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_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"), + } } } } diff --git a/src/wrappers/win32/window.rs b/src/wrappers/win32/window.rs index 856af271..826bc26a 100644 --- a/src/wrappers/win32/window.rs +++ b/src/wrappers/win32/window.rs @@ -21,13 +21,17 @@ 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}; 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. /// @@ -63,7 +67,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::))?; @@ -83,7 +87,7 @@ 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(), ) }; 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 4b56ce6c..1f463d83 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -1,16 +1,19 @@ 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::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}; -use windows_sys::Win32::Graphics::Gdi::ScreenToClient; +use windows_sys::Win32::Foundation::{SetLastError, FALSE, 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, MDT_DEFAULT}; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ GetFocus, ReleaseCapture, SetCapture, SetFocus, TrackMouseEvent, TME_LEAVE, TRACKMOUSEEVENT, }; @@ -98,18 +101,37 @@ 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<()> { // SAFETY: This type guarantees the HWND is safe to use, // and the interface pointer comes from a valid InterfaceRef. @@ -151,9 +173,8 @@ impl HWnd { } pub fn resize_and_activate( - &self, client_size: PhysicalSize, window_dpi: Option, user32: &ExtendedUser32, + &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); @@ -277,4 +298,45 @@ 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, + ) -> Option { + let result = unsafe { user32.get_window_dpi_awareness_context?(self.as_raw()) }; + + let Some(raw) = NonNull::new(result) else { + 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 None; + } + + 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..70265f5e 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) == 0) as _ + } + 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 + } + } +}