From 6abc403a58b21f10727ea87b2a5a3eba7c1018de Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:28:48 +0200 Subject: [PATCH 01/10] wip --- examples/plugin_clack/src/gui.rs | 19 ++++++++++++++++--- src/platform/macos/window.rs | 6 +++++- src/window.rs | 5 +++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 4430d244..3671d921 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -2,7 +2,7 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::PhysicalSize; use baseview::gl::GlConfig; -use baseview::{WindowHandle, WindowOpenOptions}; +use baseview::{WindowHandle, WindowOpenOptions, WindowSize}; use clack_extensions::gui::{ GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, Window as ClapWindow, }; @@ -45,8 +45,7 @@ impl PluginGuiImpl for ExamplePluginMainThread { } fn get_size(&mut self) -> Option { - // Unsupported - Some(GuiSize { width: 400, height: 200 }) + Some(window_size_to_gui_size(self.gui.as_ref()?.handle.size())) } fn can_resize(&mut self) -> bool { @@ -98,3 +97,17 @@ impl PluginGuiImpl for ExamplePluginMainThread { Ok(()) // Not supported yet } } + +fn window_size_to_gui_size(size: WindowSize) -> GuiSize { + #[cfg(target_os = "macos")] + { + let size = size.logical.cast(); + GuiSize { width: size.width, height: size.height } + } + + #[cfg(not(target_os = "macos"))] + { + let size = size.physical.cast(); + GuiSize { width: size.width, height: size.height } + } +} diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 4d47a294..40143e2e 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,4 +1,4 @@ -use dpi::LogicalSize; +use dpi::{LogicalSize, PhysicalSize}; use objc2::rc::{autoreleasepool, Retained, Weak}; use objc2::MainThreadMarker; use objc2_app_kit::{NSApplication, NSPasteboard, NSPasteboardTypeString, NSView, NSWindow}; @@ -91,6 +91,10 @@ impl WindowHandle { pub fn is_open(&self) -> bool { self.state.closed.get() } + + pub fn size(&self) -> WindowSize { + WindowSize::from_logical(self.state.size.get(), self.state.scale_factor.get()) + } } fn create_window_with_options( diff --git a/src/window.rs b/src/window.rs index 5234ca5e..70379da3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -20,6 +20,11 @@ impl WindowHandle { Ok(()) } + /// The current size of the window. + pub fn size(&self) -> WindowSize { + self.window_handle.size() + } + /// Close the window pub fn close(self) { drop(self) From de195303393dbbbda57e4acb727a34ad5d711202 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:00:12 +0200 Subject: [PATCH 02/10] wip --- examples/plugin_clack/src/gui.rs | 38 +++++++++++++++++++++++++------- src/platform/macos/window.rs | 16 +++++++++++++- src/window.rs | 12 +++++++++- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 3671d921..e65c31b3 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,6 +1,6 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; -use baseview::dpi::PhysicalSize; +use baseview::dpi::{LogicalSize, PhysicalSize, Size}; use baseview::gl::GlConfig; use baseview::{WindowHandle, WindowOpenOptions, WindowSize}; use clack_extensions::gui::{ @@ -39,8 +39,12 @@ impl PluginGuiImpl for ExamplePluginMainThread { gui.handle.close() } - fn set_scale(&mut self, _scale: f64) -> Result<(), PluginError> { - // Unsupported + 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_scale_factor(scale)?; + Ok(()) } @@ -49,19 +53,26 @@ impl PluginGuiImpl for ExamplePluginMainThread { } fn can_resize(&mut self) -> bool { - false // Non-resizeable windows not supported yet + true // Non-resizeable windows not supported yet } fn get_resize_hints(&mut self) -> Option { None // Not supported yet } - fn adjust_size(&mut self, _size: GuiSize) -> Option { - None // Not supported yet + fn adjust_size(&mut self, size: GuiSize) -> Option { + Some(size) // Not supported yet } - fn set_size(&mut self, _size: GuiSize) -> Result<(), PluginError> { - Ok(()) // Not supported yet + 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")); + }; + + let size = gui_size_to_window_size(size); + gui.handle.resize(size)?; + + Ok(()) } #[allow(deprecated)] @@ -111,3 +122,14 @@ fn window_size_to_gui_size(size: WindowSize) -> GuiSize { GuiSize { width: size.width, height: size.height } } } + +fn gui_size_to_window_size(size: GuiSize) -> Size { + #[cfg(target_os = "macos")] + { + Size::Logical(LogicalSize::new(size.width, size.height).cast()) + } + #[cfg(not(target_os = "macos"))] + { + Size::Physical(PhysicalSize::new(size.width, size.height)) + } +} diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 40143e2e..49493fbe 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -1,4 +1,4 @@ -use dpi::{LogicalSize, PhysicalSize}; +use dpi::{LogicalSize, Size}; use objc2::rc::{autoreleasepool, Retained, Weak}; use objc2::MainThreadMarker; use objc2_app_kit::{NSApplication, NSPasteboard, NSPasteboardTypeString, NSView, NSWindow}; @@ -95,6 +95,20 @@ impl WindowHandle { pub fn size(&self) -> WindowSize { WindowSize::from_logical(self.state.size.get(), self.state.scale_factor.get()) } + + pub fn resize(&self, size: Size) -> Result<()> { + let Some(view) = self.view.load() else { return Ok(()) }; + let Some(view) = view.inner_ref() else { return Ok(()) }; + + BaseviewView::resize(view, size); + + Ok(()) + } + + pub fn suggest_scale_factor(&self, _scale_factor: f64) -> Result<()> { + // This does not do anything on macOS: all coordinates are already logical + Ok(()) + } } fn create_window_with_options( diff --git a/src/window.rs b/src/window.rs index 70379da3..ec10f9eb 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,7 +1,7 @@ use crate::handler::WindowHandlerBuilder; use crate::platform; use crate::*; -use dpi::{LogicalSize, PhysicalSize, Pixel}; +use dpi::{LogicalSize, PhysicalSize, Pixel, Size}; use std::marker::PhantomData; pub struct WindowHandle { @@ -25,6 +25,16 @@ impl WindowHandle { self.window_handle.size() } + pub fn resize(&self, size: Size) -> Result<(), Error> { + self.window_handle.resize(size)?; + Ok(()) + } + + pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<(), Error> { + self.window_handle.suggest_scale_factor(scale_factor)?; + Ok(()) + } + /// Close the window pub fn close(self) { drop(self) From a18c31bfd0d08e7e9d177d9df36a80cd955b993a Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:03:23 +0200 Subject: [PATCH 03/10] wip --- examples/open_parented/src/main.rs | 13 ++-- src/platform/x11/event_loop.rs | 77 ++++++++++++++++++-- src/platform/x11/window_shared.rs | 23 ++++-- src/platform/x11/window_thread.rs | 111 +++++++++++++++++++++++++---- 4 files changed, 192 insertions(+), 32 deletions(-) diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index b5e13eaa..a1e5d278 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -1,4 +1,4 @@ -use baseview::dpi::LogicalSize; +use baseview::dpi::{LogicalSize, PhysicalSize}; use baseview::{ Event, EventStatus, HandlerError, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions, WindowSize, @@ -10,7 +10,7 @@ struct ParentWindowHandler { surface: RefCell>, damaged: Cell, - _child_window: Option, + child_window: WindowHandle, } impl ParentWindowHandler { @@ -27,11 +27,7 @@ impl ParentWindowHandler { let child_window = baseview::create_window(window_open_options, ChildWindowHandler::new)?; - Ok(Self { - surface: surface.into(), - damaged: true.into(), - _child_window: Some(child_window), - }) + Ok(Self { surface: surface.into(), damaged: true.into(), child_window }) } } @@ -58,6 +54,9 @@ impl WindowHandler for ParentWindowHandler { self.damaged.set(true); } + let child_size = + PhysicalSize::new(new_size.physical.width / 2, new_size.physical.height / 2); + self.child_window.resize(child_size.into())?; Ok(()) } diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 36aa1a8d..1122afe0 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -3,6 +3,9 @@ use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mo use super::*; use std::result::Result; +use crate::platform::x11::window_thread::{ + WindowThreadRequest, WindowThreadResponse, WindowThreadResponseMessage, +}; use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowSize}; @@ -11,6 +14,7 @@ use calloop::timer::{TimeoutAction, Timer}; use calloop::{Interest, LoopSignal, Mode, PostAction}; use dpi::{PhysicalPosition, PhysicalSize}; use std::rc::Rc; +use std::sync::mpsc; use std::time::{Duration, Instant}; use x11rb::connection::Connection; use x11rb::errors::ConnectionError; @@ -28,6 +32,8 @@ pub(crate) struct EventLoop { xkb_state: Option, run_error: Option, + + response_sender: mpsc::Sender, } const FRAME_INTERVAL: Duration = Duration::from_millis(15); @@ -35,6 +41,8 @@ const FRAME_INTERVAL: Duration = Duration::from_millis(15); impl EventLoop { pub fn new( window: Rc, handler: Box, + request_receiver: calloop::channel::Channel, + response_sender: mpsc::Sender, inner: &mut calloop::EventLoop<'static, Self>, ) -> Result { let loop_handle = inner.handle(); @@ -50,6 +58,10 @@ impl EventLoop { ) .map_err(|e| e.error)?; + loop_handle + .insert_source(request_receiver, |e, _, l| l.handle_main_thread_request(e)) + .map_err(|e| e.error)?; + Ok(Self { loop_signal: inner.get_signal(), handler, @@ -58,6 +70,7 @@ impl EventLoop { xkb_state: XkbcommonState::new(&window.connection), run_error: None, window, + response_sender, }) } @@ -73,14 +86,14 @@ impl EventLoop { } if let Some(size) = self.new_physical_size.take() { - let previous = self.window.window_size.replace(size); + let previous = self.window.store_size(size); let scale_factor = self.window.scaling_factor.get(); if let Err(e) = self.handler.resized(WindowSize::from_physical(size.cast(), scale_factor)) { warn!("Window Handler failed to resize: {}", e); - self.window.window_size.set(previous); + self.window.store_size(previous); self.window.xcb_window.resize(previous.cast())?.check_warn(); } } @@ -88,6 +101,60 @@ impl EventLoop { Ok(()) } + fn handle_main_thread_request(&mut self, event: calloop::channel::Event) { + match event { + calloop::channel::Event::Closed => { + // Closed channel means the sender, i.e. the Window Handle has been dropped. + // It should already stop this event loop on drop, but we'll take the hint. + self.stop_now(); + } + calloop::channel::Event::Msg(req) => match self.handle_request(req) { + Ok(()) => self.send_response(Ok(WindowThreadResponse::Ok)), + Err(e) => self.send_response(Err(e.to_string())), + }, + } + } + + fn send_response(&mut self, response: WindowThreadResponseMessage) { + if let Err(e) = self.response_sender.send(response) { + warn!("Failed to send response back to main thread: {}", &e); + if let Err(e) = e.0 { + crate::error!("Request failed: {}", e) + } + + self.stop_now(); + } + } + + fn stop_now(&self) { + self.loop_signal.stop(); + self.loop_signal.wakeup(); + } + + fn handle_request(&mut self, req: WindowThreadRequest) -> Result<(), Error> { + match req { + WindowThreadRequest::Resize(new_size) => { + let scale_factor = self.window.scaling_factor.get(); + let new_size = new_size.to_physical(scale_factor); + + // Handle everything directly, so we can return a handler error immediately. + let previous = self.window.store_size(new_size); + self.window.xcb_window.resize(new_size.cast())?; // Will not call handler, as size is the same as above. + + if let Err(e) = + self.handler.resized(WindowSize::from_physical(new_size.cast(), scale_factor)) + { + warn!("Window Handler failed to resize: {}. Reverting to previous size", &e); + self.window.store_size(previous); + return Err(e.into()); + } + + Ok(()) + } + _ => todo!(), + } + } + fn handle_connection_event_ready(&mut self) -> Result { self.drain_xcb_events()?; @@ -97,7 +164,7 @@ impl EventLoop { fn handle_frame(&mut self, previous_deadline: Instant) -> TimeoutAction { if let Err(e) = self.handler.on_frame() { self.run_error = Some(e.into()); - self.loop_signal.stop(); + self.stop_now(); return TimeoutAction::Drop; } @@ -194,9 +261,7 @@ impl EventLoop { XEvent::ConfigureNotify(event) => { let new_physical_size = PhysicalSize::new(event.width, event.height); - if self.new_physical_size.is_some() - || new_physical_size != self.window.window_size.get() - { + if self.new_physical_size.is_some() || new_physical_size != self.window.get_size() { self.new_physical_size = Some(new_physical_size); } } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index a5390fc0..8b61836d 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,5 +1,6 @@ use crate::platform::x11::event_loop::EventLoop; use crate::platform::x11::visual_info::WindowVisualConfig; +use crate::platform::x11::window_thread::WindowThreadShared; use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::*; use crate::{MouseCursor, WindowOpenOptions, WindowScalePolicy, WindowSize}; @@ -20,17 +21,20 @@ pub(crate) struct WindowInner { pub(crate) xcb_window: XcbWindow, pub(crate) connection: Rc, pub(crate) scaling_factor: Cell, - pub(crate) window_size: Cell>, + window_size: Cell>, mouse_cursor: Cell, pub(crate) visual_id: Visualid, pub(crate) is_focused: Cell, pub(crate) loop_signal: LoopSignal, + + main_thread_shared: Arc, } impl WindowInner { pub(crate) fn create( options: WindowOpenOptions, ev_loop: &calloop::EventLoop<'static, EventLoop>, + shared: Arc, ) -> Result> { // Connect to the X server let xcb_connection = X11Connection::new()?; @@ -40,6 +44,7 @@ impl WindowInner { WindowScalePolicy::ScaleFactor(scale) => scale, }; + shared.set_scaling_factor(scaling); let physical_size = options.size.to_physical(scaling); #[cfg(feature = "opengl")] @@ -82,17 +87,17 @@ impl WindowInner { } }; - let visual_id = visual_info.visual_id; Ok(Rc::new(Self { connection, xcb_window, - visual_id, + visual_id: visual_info.visual_id, window_size: physical_size.into(), scaling_factor: scaling.into(), mouse_cursor: MouseCursor::default().into(), loop_signal: ev_loop.get_signal(), is_focused: false.into(), + main_thread_shared: shared, #[cfg(feature = "opengl")] gl_context, @@ -121,6 +126,16 @@ impl WindowInner { Ok(()) } + pub fn store_size(&self, size: PhysicalSize) -> PhysicalSize { + let previous = self.window_size.replace(size); + self.main_thread_shared.set_size(size); + previous + } + + pub fn get_size(&self) -> PhysicalSize { + self.window_size.get() + } + pub fn request_close(&self) { self.loop_signal.stop(); self.loop_signal.wakeup(); @@ -140,7 +155,7 @@ impl WindowInner { } pub fn resize(&self, size: Size) -> Result<()> { - let new_physical_size = size.to_physical::(self.scaling_factor.get()); + let new_physical_size = size.to_physical(self.scaling_factor.get()); self.xcb_window.resize(new_physical_size)?; // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index cf167317..f0cefc50 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -2,31 +2,75 @@ use super::*; use crate::handler::WindowHandlerBuilder; use crate::platform::x11::event_loop::EventLoop; use crate::platform::x11::window_shared::WindowInner; -use crate::{WindowContext, WindowOpenOptions}; +use crate::{WindowContext, WindowOpenOptions, WindowSize}; use calloop::LoopSignal; +use dpi::{PhysicalSize, Size}; use std::cell::Cell; use std::panic::resume_unwind; use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::sync::{mpsc, Mutex}; use std::thread; use std::thread::JoinHandle; pub(crate) struct WindowThreadShared { stopped: AtomicBool, + scaling_factor: AtomicU64, + size: AtomicU32, final_error: Mutex>, } impl WindowThreadShared { pub fn new() -> Self { - Self { stopped: false.into(), final_error: None.into() } + Self { + stopped: false.into(), + final_error: None.into(), + size: 0.into(), + scaling_factor: 0.into(), + } + } + + pub fn get_size(&self) -> PhysicalSize { + let bytes = self.size.load(Ordering::Relaxed); + let low = (bytes & u16::MAX as u32) as u16; + let high = (bytes >> 16) as u16; + + PhysicalSize::new(low, high) + } + + pub fn set_size(&self, size: PhysicalSize) { + let bytes = ((size.height as u32) << 16) | (size.width as u32); + self.size.store(bytes, Ordering::Relaxed); + } + + pub fn get_scaling_factor(&self) -> f64 { + f64::from_be_bytes(self.scaling_factor.load(Ordering::Relaxed).to_ne_bytes()) } + + pub fn set_scaling_factor(&self, scale_factor: f64) { + self.scaling_factor + .store(u64::from_be_bytes(scale_factor.to_ne_bytes()), Ordering::Relaxed); + } +} + +pub enum WindowThreadRequest { + SuggestScaleFactor(f64), + Resize(Size), } +pub enum WindowThreadResponse { + Ok, +} + +pub type WindowThreadResponseMessage = core::result::Result; + pub struct WindowThreadHandle { shared: Arc, loop_signal: LoopSignal, event_loop_handle: Cell>>, + + request_sender: calloop::channel::SyncSender, + response_receiver: mpsc::Receiver, } impl WindowThreadHandle { @@ -35,12 +79,20 @@ impl WindowThreadHandle { ) -> Result { let (tx, rx) = result_channel(); let shared = Arc::new(WindowThreadShared::new()); + let (request_sender, request_receiver) = calloop::channel::sync_channel(1); + let (response_sender, response_receiver) = mpsc::channel(); let join_handle = { let shared = shared.clone(); thread::spawn(move || { - let thread = match WindowThread::create(options, handler, shared) { + let thread = match WindowThread::create( + options, + handler, + shared, + request_receiver, + response_sender, + ) { Err(e) => return tx.send_error(e), Ok(thread) => thread, }; @@ -51,7 +103,40 @@ impl WindowThreadHandle { }) }; - rx.receive(join_handle, shared) + let loop_signal = rx.receive()?; + + Ok(WindowThreadHandle { + event_loop_handle: Some(join_handle).into(), + shared, + loop_signal, + request_sender, + response_receiver, + }) + } + + pub fn size(&self) -> WindowSize { + let scale_factor = self.shared.get_scaling_factor(); + let size = self.shared.get_size(); + + WindowSize::from_physical(size.cast(), scale_factor) + } + + pub fn resize(&self, size: Size) -> Result<()> { + self.request(WindowThreadRequest::Resize(size)) + } + + pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<()> { + self.request(WindowThreadRequest::SuggestScaleFactor(scale_factor)) + } + + fn request(&self, req: WindowThreadRequest) -> Result<()> { + self.request_sender.send(req).unwrap(); // TODO: handle error + let result = self.response_receiver.recv().unwrap(); // TODO: handle error + + match result { + Ok(WindowThreadResponse::Ok) => Ok(()), + Err(e) => Err(Error::Run(e)), // TODO: better error type? + } } pub fn run_until_closed(&self) -> Result<()> { @@ -98,11 +183,13 @@ struct WindowThread { impl WindowThread { pub fn create( options: WindowOpenOptions, handler: WindowHandlerBuilder, shared: Arc, + receiver: calloop::channel::Channel, + sender: mpsc::Sender, ) -> Result { let mut ev_loop = calloop::EventLoop::try_new()?; - let inner = WindowInner::create(options, &ev_loop)?; + let inner = WindowInner::create(options, &ev_loop, shared.clone())?; let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; - let event_loop = EventLoop::new(inner, handler, &mut ev_loop)?; + let event_loop = EventLoop::new(inner, handler, receiver, sender, &mut ev_loop)?; Ok(Self { event_loop, ev_loop, shared }) } @@ -142,17 +229,11 @@ impl WindowResultSender { struct WindowResultReceiver(mpsc::Receiver); impl WindowResultReceiver { - pub fn receive( - self, join_handle: JoinHandle<()>, shared: Arc, - ) -> Result { + pub fn receive(self) -> Result { let result = self.0.recv()?; match result { WindowOpenResult::Error(e) => Err(Error::CreationFailed(e)), - WindowOpenResult::Success { loop_signal } => Ok(WindowThreadHandle { - event_loop_handle: Some(join_handle).into(), - shared, - loop_signal, - }), + WindowOpenResult::Success { loop_signal } => Ok(loop_signal), } } } From bb46eb32a6df89b7112464ab486a3255ae4cb8fb Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 18 Jul 2026 00:02:15 +0200 Subject: [PATCH 04/10] wip --- examples/open_parented/src/main.rs | 3 ++ examples/plugin_clack/src/gui.rs | 2 +- src/platform/win/window.rs | 28 +++++------- src/platform/win/window_state.rs | 23 +++++++--- src/platform/x11/event_loop.rs | 26 ++++++------ src/platform/x11/window_shared.rs | 68 +++++++++++++++++++++++++++--- src/platform/x11/xcb_connection.rs | 7 ++- 7 files changed, 112 insertions(+), 45 deletions(-) diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index a1e5d278..a9d9749e 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -5,6 +5,8 @@ use baseview::{ }; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; +use std::thread; +use std::time::Duration; struct ParentWindowHandler { surface: RefCell>, @@ -56,6 +58,7 @@ impl WindowHandler for ParentWindowHandler { let child_size = PhysicalSize::new(new_size.physical.width / 2, new_size.physical.height / 2); + self.child_window.suggest_scale_factor(new_size.scale_factor)?; self.child_window.resize(child_size.into())?; Ok(()) } diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index e65c31b3..f369bc73 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,6 +1,6 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; -use baseview::dpi::{LogicalSize, PhysicalSize, Size}; +use baseview::dpi::{PhysicalSize, Size}; use baseview::gl::GlConfig; use baseview::{WindowHandle, WindowOpenOptions, WindowSize}; use clack_extensions::gui::{ diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 21ace30e..292fff3f 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -23,7 +23,7 @@ pub(crate) const BV_WINDOW_MUST_CLOSE: u32 = WM_USER + 1; use super::drop_target::DropTarget; use super::*; use crate::handler::WindowHandlerBuilder; -use crate::platform::win::window_state::WindowState; +use crate::platform::win::window_state::{WindowSharedState, WindowState}; use crate::platform::Error; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; @@ -53,7 +53,7 @@ const WIN_FRAME_TIMER: NonZeroUsize = match NonZeroUsize::new(4242) { pub struct WindowHandle { hwnd: Cell>, - is_open: Rc>, + state: Rc, } impl WindowHandle { @@ -63,7 +63,7 @@ impl WindowHandle { } pub fn is_open(&self) -> bool { - self.is_open.get() + self.state.is_alive.get() } } @@ -75,24 +75,14 @@ impl Drop for WindowHandle { } } -struct ParentHandle { - is_open: Rc>, -} - -impl Drop for ParentHandle { - fn drop(&mut self) { - self.is_open.set(false); - } -} - pub struct BaseviewWindow { window_state: Rc, + shared_state: Rc, initial_size: Size, handler_builder: Cell>, // Things not directly used, but kept so their Drop impl runs when the window is destroyed - _parent_handle: ParentHandle, _keyboard_hook: Cell>, _drop_target: Cell>>, @@ -100,6 +90,12 @@ pub struct BaseviewWindow { pub gl_config: Option, } +impl Drop for BaseviewWindow { + fn drop(&mut self) { + self.shared_state.is_alive.set(false); + } +} + impl WindowImpl for BaseviewWindow { fn after_create(&self, window: HWnd) -> core::result::Result<(), Error> { let hwnd = window.as_raw(); @@ -442,10 +438,7 @@ impl WindowHandle { let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, Dpi::default())?; - let is_open = Rc::new(Cell::new(true)); - let initializer = { - let parent_handle = ParentHandle { is_open: is_open.clone() }; let extended_user_32 = extended_user_32.clone(); move |hwnd: HWnd| { @@ -457,7 +450,6 @@ impl WindowHandle { initial_size: options.size, handler_builder: Cell::new(Some(build)), - _parent_handle: parent_handle, _drop_target: None.into(), _keyboard_hook: None.into(), diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 2e56ab1c..75795717 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -10,14 +10,13 @@ use dpi::{PhysicalSize, Size}; use raw_window_handle::{DisplayHandle, Win32WindowHandle}; use std::cell::{Cell, OnceCell, Ref, RefCell}; use std::num::NonZeroIsize; +use std::rc::Rc; use windows_sys::Win32::UI::WindowsAndMessaging::PostMessageW; /// All data associated with the window. pub(crate) struct WindowState { /// The HWND belonging to this window. pub hwnd: HWnd, - pub current_size: Cell>, - pub current_dpi: Cell, // None if in non-system scale policy pub keyboard_state: RefCell, pub mouse_button_counter: Cell, pub mouse_was_outside_window: Cell, @@ -39,8 +38,6 @@ impl WindowState { ) -> Self { Self { hwnd, - current_dpi: Dpi::default().into(), - current_size: current_size.into(), keyboard_state: RefCell::new(KeyboardState::new()), mouse_button_counter: Cell::new(0), mouse_was_outside_window: true.into(), @@ -127,7 +124,6 @@ impl WindowState { #[cfg(feature = "opengl")] pub fn gl_context(&self) -> Option { - use std::rc::Rc; Some(crate::gl::GlContext::new(Rc::clone(self.gl_context.get()?))) } @@ -148,3 +144,20 @@ impl WindowState { PlatformHandle { hwnd } } } + +pub struct WindowSharedState { + pub is_alive: Cell, + pub current_size: Cell>, + pub current_dpi: Cell, // None if in non-system scale policy +} + +impl WindowSharedState { + pub fn new(current_size: PhysicalSize) -> Rc { + Self { + is_alive: true.into(), + current_dpi: Dpi::default().into(), + current_size: current_size.into(), + } + .into() + } +} diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 1122afe0..db9a9170 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -137,21 +137,23 @@ impl EventLoop { let scale_factor = self.window.scaling_factor.get(); let new_size = new_size.to_physical(scale_factor); - // Handle everything directly, so we can return a handler error immediately. - let previous = self.window.store_size(new_size); - self.window.xcb_window.resize(new_size.cast())?; // Will not call handler, as size is the same as above. - - if let Err(e) = - self.handler.resized(WindowSize::from_physical(new_size.cast(), scale_factor)) - { - warn!("Window Handler failed to resize: {}. Reverting to previous size", &e); - self.window.store_size(previous); - return Err(e.into()); - } + self.window.resize_immediately(new_size, &*self.handler)?; + + Ok(()) + } + WindowThreadRequest::SuggestScaleFactor(scale) => { + // If the scaling factor is already provided by the system, do nothing + if !self.window.scaling_factor.suggest(scale) { + return Ok(()); + }; + + let current_logical_size = self.window.get_size().to_logical::(1.0); + let new_physical_size = current_logical_size.to_physical(scale); + + self.window.resize_immediately(new_physical_size, &*self.handler)?; Ok(()) } - _ => todo!(), } } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 8b61836d..3635f133 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -3,7 +3,7 @@ use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::window_thread::WindowThreadShared; use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::*; -use crate::{MouseCursor, WindowOpenOptions, WindowScalePolicy, WindowSize}; +use crate::{warn, MouseCursor, WindowHandler, WindowOpenOptions, WindowScalePolicy, WindowSize}; use calloop::LoopSignal; use dpi::{PhysicalSize, Size}; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; @@ -13,6 +13,38 @@ use std::sync::Arc; use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt, InputFocus, Visualid}; use x11rb::CURRENT_TIME; +pub struct ScalingFactor { + system: Cell>, + suggested: Cell>, +} + +impl ScalingFactor { + pub fn get(&self) -> f64 { + if let Some(factor) = self.system.get() { + return factor; + }; + + if let Some(factor) = self.suggested.get() { + return factor; + } + + 1.0 + } + + pub fn suggest(&self, value: f64) -> bool { + self.suggested.set(Some(value)); + + self.system.get().is_none() + } +} + +impl From> for ScalingFactor { + fn from(value: Option) -> Self { + // FIXME + Self { system: None.into(), suggested: None.into() } + } +} + pub(crate) struct WindowInner { // GlContext should be dropped **before** XcbConnection is dropped #[cfg(feature = "opengl")] @@ -20,7 +52,9 @@ pub(crate) struct WindowInner { pub(crate) xcb_window: XcbWindow, pub(crate) connection: Rc, - pub(crate) scaling_factor: Cell, + + pub(crate) scaling_factor: ScalingFactor, + window_size: Cell>, mouse_cursor: Cell, pub(crate) visual_id: Visualid, @@ -41,11 +75,13 @@ impl WindowInner { let scaling = match options.scale { WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling(), - WindowScalePolicy::ScaleFactor(scale) => scale, + WindowScalePolicy::ScaleFactor(scale) => Some(scale), }; - shared.set_scaling_factor(scaling); - let physical_size = options.size.to_physical(scaling); + let initial_scale_factor = scaling.unwrap_or(1.0); + shared.set_scaling_factor(initial_scale_factor); + + let physical_size = options.size.to_physical(initial_scale_factor); #[cfg(feature = "opengl")] let visual_info = @@ -164,6 +200,28 @@ impl WindowInner { Ok(()) } + pub fn resize_immediately( + &self, new_size: PhysicalSize, handler: &dyn WindowHandler, + ) -> Result<()> { + let previous = self.store_size(new_size); + + if previous == new_size { + return Ok(()); + }; + + self.xcb_window.resize(new_size.cast())?; // Will not call handler, as size is the same as above. + + if let Err(e) = + handler.resized(WindowSize::from_physical(new_size.cast(), self.scaling_factor.get())) + { + warn!("Window Handler failed to resize: {}. Reverting to previous size", &e); + self.store_size(previous); + return Err(e.into()); + } + + Ok(()) + } + pub fn window_handle(&self) -> Option> { let mut handle = XlibWindowHandle::new(self.xcb_window.id().get() as _); handle.visual_id = self.visual_id.into(); diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/xcb_connection.rs index 7a5608f4..e56ef37c 100644 --- a/src/platform/x11/xcb_connection.rs +++ b/src/platform/x11/xcb_connection.rs @@ -69,12 +69,11 @@ impl X11Connection { }) } - pub fn get_scaling(&self) -> f64 { - // If the WM didn't set any display scaling, assume a scaling factor of 1.0 (i.e. don't do any scaling) + pub fn get_scaling(&self) -> Option { if let Ok(Some(dpi)) = self.resources.get_value::("Xft.dpi", "") { - dpi as f64 / 96.0 + Some(dpi as f64 / 96.0) } else { - 1.0 + None } } From a4abf48cfdb6e3c70c29ff3ee116ea7507d53dd6 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 18 Jul 2026 01:28:08 +0200 Subject: [PATCH 05/10] wip --- examples/open_parented/src/main.rs | 2 - src/platform/win/error.rs | 3 ++ src/platform/win/window.rs | 87 ++++++++++++++++++------------ src/platform/win/window_state.rs | 35 +++++++----- src/platform/x11/window_shared.rs | 10 ++-- src/window_open_options.rs | 20 ------- 6 files changed, 79 insertions(+), 78 deletions(-) diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index a9d9749e..912d0f1d 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -5,8 +5,6 @@ use baseview::{ }; use std::cell::{Cell, RefCell}; use std::num::NonZeroU32; -use std::thread; -use std::time::Duration; struct ParentWindowHandler { surface: RefCell>, diff --git a/src/platform/win/error.rs b/src/platform/win/error.rs index fa776297..5353373e 100644 --- a/src/platform/win/error.rs +++ b/src/platform/win/error.rs @@ -6,6 +6,7 @@ pub type Result = std::result::Result; #[derive(Debug)] pub enum Error { Win32(windows_core::Error), + ResizeFailed, Handler(HandlerError), } @@ -26,6 +27,7 @@ impl Display for Error { match self { Error::Win32(e) => Display::fmt(e, f), Error::Handler(e) => Display::fmt(e, f), + Error::ResizeFailed => f.write_str("Window resize request failed."), } } } @@ -35,6 +37,7 @@ impl std::error::Error for Error { match self { Error::Win32(e) => Some(e), Error::Handler(e) => Some(e.source()), + _ => None, } } } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 292fff3f..859b3474 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -32,8 +32,7 @@ use crate::wrappers::win32::{ WindowStyle, }; use crate::{ - Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowOpenOptions, WindowScalePolicy, - WindowSize, + Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowOpenOptions, WindowSize, }; #[allow(non_snake_case)] @@ -65,6 +64,26 @@ impl WindowHandle { pub fn is_open(&self) -> bool { self.state.is_alive.get() } + + pub fn size(&self) -> WindowSize { + self.state.size() + } + + pub fn resize(&self, new_size: Size) -> Result<()> { + let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; + let new_size = new_size.to_physical(self.state.scale_factor()); + hwnd.resize_and_activate(new_size, self.state.current_dpi.get(), &self.state.user32)?; + + if self.state.current_size.get() == new_size { + Ok(()) + } else { + Err(Error::ResizeFailed) + } + } + + pub fn suggest_scale_factor(&self, _scale_factor: f64) -> Result<()> { + todo!() + } } impl Drop for WindowHandle { @@ -106,22 +125,19 @@ impl WindowImpl for BaseviewWindow { // Now we can get the actual dpi of the window. let dpi = window.get_dpi(&self.window_state.user32)?; - if dpi != window_state.current_dpi.get() { - window_state.current_dpi.set(dpi); - - // If the user's requested initial size was in system-scaled logical pixels - if let WindowScalePolicy::SystemScaleFactor = self.window_state.scale_policy { - // 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.current_size.set(new_size); - window.resize_and_activate(new_size, dpi, &window_state.user32)?; - } + if dpi != window_state.shared.current_dpi.get() { + window_state.shared.current_dpi.set(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); + window.resize_and_activate(new_size, dpi, &window_state.user32)?; } let drop_target = ComObject::new(DropTarget::new(Rc::downgrade(window_state), window)); @@ -330,24 +346,26 @@ unsafe fn wnd_proc_inner( let height = ((lparam >> 16) & 0xFFFF) as u16 as u32; let new_size = PhysicalSize { width, height }; - let current_size = window_state.current_size.get(); + let current_size = window_state.shared.current_size.get(); // Only send the event if anything changed if current_size == new_size { return None; } - let previous = window_state.current_size.replace(new_size); - let new_size = WindowSize::from_physical(new_size, window_state.scale_factor()); + let previous = window_state.shared.current_size.replace(new_size); + let new_size = WindowSize::from_physical(new_size, window_state.shared.scale_factor()); let handler = window_state.handler.get()?; if let Err(e) = handler.resized(new_size) { warn!("Window Handler failed to resize: {}", e); - window_state.current_size.set(previous); + window_state.shared.current_size.set(previous); if let Err(e) = window_state.resize(previous.into()) { warn!("Failed to resize back to previous window size: {}", e); } + + return Some(-1); } None @@ -363,11 +381,11 @@ unsafe fn wnd_proc_inner( let new_size = suggested_rect.size(); - let changed = window_state.current_size.get() != new_size - || window_state.current_dpi.get() != dpi; + let changed = window_state.shared.current_size.get() != new_size + || window_state.shared.current_dpi.get() != dpi; - window_state.current_dpi.replace(dpi); - let previous_size = window_state.current_size.replace(new_size); + window_state.shared.current_dpi.replace(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, // hence why we are notifying the window handler manually below. @@ -379,7 +397,7 @@ unsafe fn wnd_proc_inner( if let Err(e) = handler.resized(new_size) { warn!("Window Handler failed to resize: {}", e); - window_state.current_size.set(previous_size); + window_state.shared.current_size.set(previous_size); if let Err(e) = window_state.resize(previous_size.into()) { warn!("Failed to resize back to previous window size: {}", e); @@ -422,10 +440,7 @@ impl WindowHandle { let extended_user_32 = ExtendedUser32::load()?; let title = HSTRING::from(options.title); - let scaling_factor = match options.scale { - WindowScalePolicy::SystemScaleFactor => 1.0, - WindowScalePolicy::ScaleFactor(scale) => scale, - }; + let scaling_factor = 1.0; let window_size = options.size.to_physical(scaling_factor); @@ -435,20 +450,21 @@ impl WindowHandle { WindowStyle::embedded() }; let dpi_ctx = DpiAwarenessContext::new(&extended_user_32)?; - - let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, Dpi::default())?; + let shared_state = WindowSharedState::new(window_size, extended_user_32.clone()); let initializer = { let extended_user_32 = extended_user_32.clone(); + let shared_state = shared_state.clone(); move |hwnd: HWnd| { let window_state = - Rc::new(WindowState::new(hwnd, window_size, options.scale, extended_user_32)); + Rc::new(WindowState::new(hwnd, extended_user_32, shared_state.clone())); BaseviewWindow { window_state, initial_size: options.size, handler_builder: Cell::new(Some(build)), + shared_state, _drop_target: None.into(), _keyboard_hook: None.into(), @@ -460,6 +476,7 @@ impl WindowHandle { }; let parent = options.parent.map(|p| p.handle); + let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, Dpi::default())?; let window = create_window(&title, style, rect.size(), parent, &dpi_ctx, initializer)?; @@ -472,7 +489,7 @@ impl WindowHandle { window.show_and_activate(); - Ok(WindowHandle { hwnd: Some(window).into(), is_open: Rc::clone(&is_open) }) + Ok(WindowHandle { hwnd: Some(window).into(), state: shared_state }) } } diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 75795717..b166aabd 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -5,7 +5,7 @@ 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::{Event, EventStatus, MouseCursor, WindowHandler, WindowScalePolicy, WindowSize}; +use crate::{Event, EventStatus, MouseCursor, WindowHandler, WindowSize}; use dpi::{PhysicalSize, Size}; use raw_window_handle::{DisplayHandle, Win32WindowHandle}; use std::cell::{Cell, OnceCell, Ref, RefCell}; @@ -23,19 +23,16 @@ pub(crate) struct WindowState { pub cursor_icon: Cell, // Initialized late so the `Window` can hold a reference to this `WindowState` pub handler: OnceCell>, - pub scale_policy: WindowScalePolicy, pub user32: ExtendedUser32, + pub shared: Rc, #[cfg(feature = "opengl")] pub gl_context: OnceCell, } impl WindowState { - pub fn new( - hwnd: HWnd, current_size: PhysicalSize, scale_policy: WindowScalePolicy, - user32: ExtendedUser32, - ) -> Self { + pub fn new(hwnd: HWnd, user32: ExtendedUser32, shared: Rc) -> Self { Self { hwnd, keyboard_state: RefCell::new(KeyboardState::new()), @@ -43,8 +40,8 @@ impl WindowState { mouse_was_outside_window: true.into(), cursor_icon: Cell::new(MouseCursor::Default), handler: OnceCell::new(), - scale_policy, user32, + shared, #[cfg(feature = "opengl")] gl_context: OnceCell::new(), @@ -68,15 +65,14 @@ impl WindowState { handler.on_event(event) } + /// Returns the current size of this window. pub fn size(&self) -> WindowSize { - WindowSize::from_physical(self.current_size.get(), self.scale_factor()) + self.shared.size() } + /// Returns the current scale factor of this window. pub fn scale_factor(&self) -> f64 { - match self.scale_policy { - WindowScalePolicy::ScaleFactor(scale) => scale, - WindowScalePolicy::SystemScaleFactor => self.current_dpi.get().scale_factor(), - } + self.shared.scale_factor() } pub(crate) fn keyboard_state(&self) -> Ref<'_, KeyboardState> { @@ -106,7 +102,7 @@ impl WindowState { pub fn resize(&self, size: Size) -> Result<(), super::Error> { // `self.window_info` will be modified in response to the `WM_SIZE` event that // follows the `SetWindowPos()` call - let dpi = self.current_dpi.get(); + let dpi = self.shared.current_dpi.get(); let new_size = size.to_physical(dpi.scale_factor()); self.hwnd.resize_and_activate(new_size, dpi, &self.user32)?; @@ -149,15 +145,26 @@ pub struct WindowSharedState { pub is_alive: Cell, pub current_size: Cell>, pub current_dpi: Cell, // None if in non-system scale policy + + pub user32: ExtendedUser32, } impl WindowSharedState { - pub fn new(current_size: PhysicalSize) -> Rc { + pub fn new(current_size: PhysicalSize, user32: ExtendedUser32) -> Rc { Self { is_alive: true.into(), current_dpi: Dpi::default().into(), current_size: current_size.into(), + user32, } .into() } + + pub fn size(&self) -> WindowSize { + WindowSize::from_physical(self.current_size.get(), self.scale_factor()) + } + + pub fn scale_factor(&self) -> f64 { + self.current_dpi.get().scale_factor() + } } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 3635f133..7e44150e 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -3,7 +3,7 @@ use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::window_thread::WindowThreadShared; use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::*; -use crate::{warn, MouseCursor, WindowHandler, WindowOpenOptions, WindowScalePolicy, WindowSize}; +use crate::{warn, MouseCursor, WindowHandler, WindowOpenOptions, WindowSize}; use calloop::LoopSignal; use dpi::{PhysicalSize, Size}; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; @@ -40,8 +40,7 @@ impl ScalingFactor { impl From> for ScalingFactor { fn from(value: Option) -> Self { - // FIXME - Self { system: None.into(), suggested: None.into() } + Self { system: value.into(), suggested: None.into() } } } @@ -73,10 +72,7 @@ impl WindowInner { // Connect to the X server let xcb_connection = X11Connection::new()?; - let scaling = match options.scale { - WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling(), - WindowScalePolicy::ScaleFactor(scale) => Some(scale), - }; + let scaling = xcb_connection.get_scaling(); let initial_scale_factor = scaling.unwrap_or(1.0); shared.set_scaling_factor(initial_scale_factor); diff --git a/src/window_open_options.rs b/src/window_open_options.rs index 501427c7..9d9b48de 100644 --- a/src/window_open_options.rs +++ b/src/window_open_options.rs @@ -4,16 +4,6 @@ use crate::platform::ParentWindowHandle; use dpi::{LogicalSize, Size}; use raw_window_handle::HasWindowHandle; -/// The dpi scaling policy of the window -#[derive(Default, Debug, Clone, Copy, PartialEq)] -pub enum WindowScalePolicy { - /// Use the system's dpi scale factor - #[default] - SystemScaleFactor, - /// Use the given dpi scale factor (e.g. `1.0` = 96 dpi) - ScaleFactor(f64), -} - /// The options for opening a new window #[derive(Debug, Clone, PartialEq)] pub struct WindowOpenOptions { @@ -22,9 +12,6 @@ pub struct WindowOpenOptions { /// The size of the window, either in physical or logical coordinates pub size: Size, - /// The dpi scaling policy - pub scale: WindowScalePolicy, - pub(crate) parent: Option, /// If provided, then an OpenGL context will be created for this window. You'll be able to @@ -53,12 +40,6 @@ impl WindowOpenOptions { self } - #[inline] - pub fn with_scale_policy(mut self, scale: WindowScalePolicy) -> Self { - self.scale = scale; - self - } - #[inline] pub fn with_parent(mut self, parent: &impl HasWindowHandle) -> Self { let parent = match ParentWindowHandle::extract(parent) { @@ -85,7 +66,6 @@ impl Default for WindowOpenOptions { Self { title: String::from("baseview window"), size: LogicalSize { width: 500.0, height: 400.0 }.into(), - scale: WindowScalePolicy::default(), parent: None, #[cfg(feature = "opengl")] gl_config: None, From 08f2b41bb6b332dcd41691014a5b6b17cc226e0a Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:08:17 +0200 Subject: [PATCH 06/10] wip --- src/platform/win/window.rs | 64 +++++++++++++++++++++-------- src/platform/win/window_state.rs | 14 +++++-- src/wrappers/win32/dpi.rs | 10 +++-- src/wrappers/win32/window/handle.rs | 8 ++-- 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 859b3474..9e8d81d2 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -81,8 +81,36 @@ impl WindowHandle { } } - pub fn suggest_scale_factor(&self, _scale_factor: f64) -> Result<()> { - todo!() + pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<()> { + let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; + + let current_scale_factor = self.state.scale_factor(); + self.state.fallback_scale_factor.set(Some(scale_factor)); + + if self.state.current_dpi.get().is_some() { + return Ok(()); + } + + let current_size = self.state.current_size.get(); + let new_size = self + .state + .current_size + .get() + .to_logical::(current_scale_factor) + .to_physical(self.state.scale_factor()); + + // This call doesn't meaningfully change the scaling factor, ignore the result + if current_size == new_size { + return Ok(()); + } + + hwnd.resize_and_activate(new_size, None, &self.state.user32)?; + + if self.state.current_size.get() == new_size { + Ok(()) + } else { + Err(Error::ResizeFailed) + } } } @@ -125,19 +153,21 @@ impl WindowImpl for BaseviewWindow { // Now we can get the actual dpi of the window. let dpi = window.get_dpi(&self.window_state.user32)?; - if dpi != window_state.shared.current_dpi.get() { - window_state.shared.current_dpi.set(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, 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); + window.resize_and_activate(new_size, Some(dpi), &window_state.user32)?; + } } let drop_target = ComObject::new(DropTarget::new(Rc::downgrade(window_state), window)); @@ -377,14 +407,14 @@ unsafe fn wnd_proc_inner( let dpi_ctx = DpiAwarenessContext::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, dpi).unwrap(); + dpi_ctx.nc_area_to_client_area(suggested_nc_rect, style, Some(dpi)).unwrap(); let new_size = suggested_rect.size(); let changed = window_state.shared.current_size.get() != new_size - || window_state.shared.current_dpi.get() != dpi; + || window_state.shared.current_dpi.get() != Some(dpi); - window_state.shared.current_dpi.replace(dpi); + window_state.shared.current_dpi.replace(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, @@ -476,7 +506,7 @@ impl WindowHandle { }; let parent = options.parent.map(|p| p.handle); - let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, Dpi::default())?; + let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, None)?; let window = create_window(&title, style, rect.size(), parent, &dpi_ctx, initializer)?; diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index b166aabd..65eb231b 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -103,7 +103,7 @@ impl WindowState { // `self.window_info` will be modified in response to the `WM_SIZE` event that // follows the `SetWindowPos()` call let dpi = self.shared.current_dpi.get(); - let new_size = size.to_physical(dpi.scale_factor()); + let new_size = size.to_physical(self.shared.scale_factor()); self.hwnd.resize_and_activate(new_size, dpi, &self.user32)?; Ok(()) @@ -144,7 +144,8 @@ impl WindowState { pub struct WindowSharedState { pub is_alive: Cell, pub current_size: Cell>, - pub current_dpi: Cell, // None if in non-system scale policy + pub current_dpi: Cell>, // None if Win32 HiDPI isn't supported + pub fallback_scale_factor: Cell>, pub user32: ExtendedUser32, } @@ -153,8 +154,9 @@ impl WindowSharedState { pub fn new(current_size: PhysicalSize, user32: ExtendedUser32) -> Rc { Self { is_alive: true.into(), - current_dpi: Dpi::default().into(), + current_dpi: None.into(), current_size: current_size.into(), + fallback_scale_factor: None.into(), user32, } .into() @@ -165,6 +167,10 @@ impl WindowSharedState { } pub fn scale_factor(&self) -> f64 { - self.current_dpi.get().scale_factor() + if let Some(dpi) = self.current_dpi.get() { + dpi.scale_factor() + } else { + self.fallback_scale_factor.get().unwrap_or(1.0) + } } } diff --git a/src/wrappers/win32/dpi.rs b/src/wrappers/win32/dpi.rs index 579c0dfc..4bc1cf3c 100644 --- a/src/wrappers/win32/dpi.rs +++ b/src/wrappers/win32/dpi.rs @@ -42,9 +42,11 @@ impl<'a> DpiAwarenessContext<'a> { } pub fn client_area_to_nc_area( - &self, mut rect: Rect, style: WindowStyle, dpi: Dpi, + &self, mut rect: Rect, style: WindowStyle, dpi: Option, ) -> Result { - let Some(adjust_window_rect_ex_for_dpi) = self.user32.adjust_window_rect_ex_for_dpi else { + 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 { @@ -67,7 +69,9 @@ impl<'a> DpiAwarenessContext<'a> { Ok(rect) } - pub fn nc_area_to_client_area(&self, rect: Rect, style: WindowStyle, dpi: Dpi) -> Result { + pub fn nc_area_to_client_area( + &self, rect: Rect, style: WindowStyle, dpi: Option, + ) -> Result { let result = self.client_area_to_nc_area(Rect::EMPTY, style, dpi)?; Ok(Rect(RECT { diff --git a/src/wrappers/win32/window/handle.rs b/src/wrappers/win32/window/handle.rs index 226253ff..14475368 100644 --- a/src/wrappers/win32/window/handle.rs +++ b/src/wrappers/win32/window/handle.rs @@ -87,15 +87,15 @@ impl HWnd { }) } - pub fn get_dpi(&self, extended_user32: &ExtendedUser32) -> Result { + pub fn get_dpi(&self, extended_user32: &ExtendedUser32) -> Result> { let Some(get_dpi_for_window) = extended_user32.get_dpi_for_window else { - return Ok(Dpi::default()); + return Ok(None); }; // 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(Dpi(dpi)), + dpi => Ok(Some(Dpi(dpi))), } } @@ -140,7 +140,7 @@ impl HWnd { } pub fn resize_and_activate( - &self, client_size: PhysicalSize, window_dpi: Dpi, user32: &ExtendedUser32, + &self, client_size: PhysicalSize, window_dpi: Option, user32: &ExtendedUser32, ) -> Result<()> { let dpi_ctx = DpiAwarenessContext::new(user32)?; let style = self.get_style()?; From eaf9e63fbb7e0cd569f75f91c586dfcea53e3e4d Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:20:52 +0200 Subject: [PATCH 07/10] fixes --- examples/plugin_clack/src/gui.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index f369bc73..e5d9e23e 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,6 +1,6 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; -use baseview::dpi::{PhysicalSize, Size}; +use baseview::dpi::*; use baseview::gl::GlConfig; use baseview::{WindowHandle, WindowOpenOptions, WindowSize}; use clack_extensions::gui::{ From 691ba8c80d2d8fe45c7e497c1103c6f174a424fb Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:30:41 +0200 Subject: [PATCH 08/10] docs --- examples/open_parented/src/main.rs | 4 ++-- src/window.rs | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index 912d0f1d..213b2e4d 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -1,4 +1,4 @@ -use baseview::dpi::{LogicalSize, PhysicalSize}; +use baseview::dpi::LogicalSize; use baseview::{ Event, EventStatus, HandlerError, WindowContext, WindowHandle, WindowHandler, WindowOpenOptions, WindowSize, @@ -55,7 +55,7 @@ impl WindowHandler for ParentWindowHandler { } let child_size = - PhysicalSize::new(new_size.physical.width / 2, new_size.physical.height / 2); + LogicalSize::new(new_size.logical.width / 2., new_size.logical.height / 2.); self.child_window.suggest_scale_factor(new_size.scale_factor)?; self.child_window.resize(child_size.into())?; Ok(()) diff --git a/src/window.rs b/src/window.rs index ec10f9eb..8adbf8be 100644 --- a/src/window.rs +++ b/src/window.rs @@ -25,17 +25,41 @@ impl WindowHandle { self.window_handle.size() } + /// Resizes the window to the given [`Size`]. + /// + /// The `size` can be provided in either physical or logical pixels. pub fn resize(&self, size: Size) -> Result<(), Error> { self.window_handle.resize(size)?; Ok(()) } + /// Suggests a fallback scale factor, if Baseview couldn't get one from the platform. + /// + /// If the platform does already provide an accurate scaling factor, this doesn't do anything. + /// + /// If the given fallback scale factor is actually useful and different from the current one + /// (1.0 by default), this will resize and redraw the window accordingly. + /// + /// # Platform compatibility notes. + /// + /// On Win32, this is used if running on early versions of Windows 10 (or earlier). + /// + /// On X11, this is used if no `Xft.dpi`setting is set. + /// + /// On macOS, this is always a no-op. pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<(), Error> { self.window_handle.suggest_scale_factor(scale_factor)?; Ok(()) } - /// Close the window + /// Closes and destroys the window. + /// + /// This releases all resources the window uses. + /// + /// It is guaranteed that no other objects (e.g. the parent window) are used by this window after + /// this call. + /// + /// Calling this method is more explicit, but otherwise identical to just dropping this [`WindowHandle`]. pub fn close(self) { drop(self) } From d9ac89b09b758d19c10cbcdb2e725c16352e4797 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:58:15 +0200 Subject: [PATCH 09/10] fixes --- examples/plugin_clack/src/gui.rs | 18 +++++++++++++++--- examples/plugin_clack/src/lib.rs | 2 +- examples/plugin_clack/src/window_handler.rs | 11 ----------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index e5d9e23e..3336dd1e 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -4,7 +4,8 @@ use baseview::dpi::*; use baseview::gl::GlConfig; use baseview::{WindowHandle, WindowOpenOptions, WindowSize}; use clack_extensions::gui::{ - GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, Window as ClapWindow, + AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, PluginGuiImpl, + Window as ClapWindow, }; use clack_plugin::plugin::PluginError; @@ -49,7 +50,12 @@ impl PluginGuiImpl for ExamplePluginMainThread { } fn get_size(&mut self) -> Option { - Some(window_size_to_gui_size(self.gui.as_ref()?.handle.size())) + let Some(gui) = self.gui.as_ref() else { + // Because we delayed the window creation, this will get called without a GUI active. + // During that time, return the default UI size. + return Some(GuiSize { width: 400, height: 200 }); + }; + Some(window_size_to_gui_size(gui.handle.size())) } fn can_resize(&mut self) -> bool { @@ -57,7 +63,13 @@ impl PluginGuiImpl for ExamplePluginMainThread { } fn get_resize_hints(&mut self) -> Option { - None // Not supported yet + Some(GuiResizeHints { + strategy: AspectRatioStrategy::Disregard, // Not supported + + // Non-resizeable windows not supported yet + can_resize_vertically: true, + can_resize_horizontally: true, + }) } fn adjust_size(&mut self, size: GuiSize) -> Option { diff --git a/examples/plugin_clack/src/lib.rs b/examples/plugin_clack/src/lib.rs index 31893f06..4fb4855e 100644 --- a/examples/plugin_clack/src/lib.rs +++ b/examples/plugin_clack/src/lib.rs @@ -26,7 +26,7 @@ impl DefaultPluginFactory for ExamplePlugin { fn get_descriptor() -> PluginDescriptor { use clack_plugin::plugin::features::*; - PluginDescriptor::new("org.rust-audio.clack.gain-egui", "Clack Gain EGUI Example") + PluginDescriptor::new("org.rust-audio.clack.gain-baseview", "Clack Gain Baseview Example") .with_features([AUDIO_EFFECT, STEREO]) } diff --git a/examples/plugin_clack/src/window_handler.rs b/examples/plugin_clack/src/window_handler.rs index ca498d62..36abc94e 100644 --- a/examples/plugin_clack/src/window_handler.rs +++ b/examples/plugin_clack/src/window_handler.rs @@ -114,8 +114,6 @@ impl WindowHandler for OpenWindowExample { _ => {} } - log_event(&event); - EventStatus::Captured } } @@ -136,12 +134,3 @@ impl OpenWindowExample { }) } } - -fn log_event(event: &Event) { - match event { - Event::Mouse(e) => println!("Mouse event: {:?}", e), - Event::Keyboard(e) => println!("Keyboard event: {:?}", e), - Event::Window(e) => println!("Window event: {:?}", e), - _ => {} - } -} From e3ec0fb26ef1a54e47340314222fab776b41c618 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 18 Jul 2026 03:48:55 +0200 Subject: [PATCH 10/10] rename --- examples/open_parented/src/main.rs | 2 +- examples/plugin_clack/src/gui.rs | 2 +- src/window.rs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index 213b2e4d..000d7b60 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -56,7 +56,7 @@ impl WindowHandler for ParentWindowHandler { let child_size = LogicalSize::new(new_size.logical.width / 2., new_size.logical.height / 2.); - self.child_window.suggest_scale_factor(new_size.scale_factor)?; + self.child_window.suggest_fallback_scale_factor(new_size.scale_factor)?; self.child_window.resize(child_size.into())?; Ok(()) } diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 3336dd1e..56a33af6 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -44,7 +44,7 @@ impl PluginGuiImpl for ExamplePluginMainThread { let Some(gui) = &self.gui else { return Err(PluginError::Message("set_scale called without a GUI active")); }; - gui.handle.suggest_scale_factor(scale)?; + gui.handle.suggest_fallback_scale_factor(scale)?; Ok(()) } diff --git a/src/window.rs b/src/window.rs index 8adbf8be..6fd97b69 100644 --- a/src/window.rs +++ b/src/window.rs @@ -42,12 +42,12 @@ impl WindowHandle { /// /// # Platform compatibility notes. /// - /// On Win32, this is used if running on early versions of Windows 10 (or earlier). + /// On Win32, this value is used if running on early versions of Windows 10 (or earlier). /// - /// On X11, this is used if no `Xft.dpi`setting is set. + /// On X11, this value is used if no `Xft.dpi`setting is set. /// - /// On macOS, this is always a no-op. - pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<(), Error> { + /// On macOS, this function is always a no-op. + pub fn suggest_fallback_scale_factor(&self, scale_factor: f64) -> Result<(), Error> { self.window_handle.suggest_scale_factor(scale_factor)?; Ok(()) }