From d069e18ff7552f7a87793867e43afc0eef177c31 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Wed, 7 Oct 2026 22:03:53 +0200 Subject: [PATCH 01/10] Move present and handler stuff outside of main types --- src/platform/x11/drag_n_drop.rs | 27 +- src/platform/x11/error.rs | 9 +- src/platform/x11/event_loop.rs | 328 ++++++------------ src/platform/x11/handler.rs | 37 ++ src/platform/x11/mod.rs | 53 +-- src/platform/x11/present.rs | 246 +++++++++++++ src/platform/x11/visual_info.rs | 2 +- src/platform/x11/window_shared.rs | 44 +-- src/platform/x11/window_thread.rs | 6 +- .../{xcb_connection.rs => x11_connection.rs} | 0 .../get_property.rs | 0 .../size_hints.rs | 0 src/wrappers/xlib/xlib_xcb.rs | 2 +- 13 files changed, 434 insertions(+), 320 deletions(-) create mode 100644 src/platform/x11/handler.rs create mode 100644 src/platform/x11/present.rs rename src/platform/x11/{xcb_connection.rs => x11_connection.rs} (100%) rename src/platform/x11/{xcb_connection => x11_connection}/get_property.rs (100%) rename src/platform/x11/{xcb_connection => x11_connection}/size_hints.rs (100%) diff --git a/src/platform/x11/drag_n_drop.rs b/src/platform/x11/drag_n_drop.rs index a849b316..25be23ba 100644 --- a/src/platform/x11/drag_n_drop.rs +++ b/src/platform/x11/drag_n_drop.rs @@ -1,8 +1,9 @@ -use super::xcb_connection::{Atoms, GetPropertyError}; +use super::x11_connection::{Atoms, GetPropertyError}; use super::*; use crate::dpi::PhysicalPosition; use crate::handler::WindowHandler; use crate::platform::x11::error::ReplyExt; +use crate::platform::x11::handler::Handler; use crate::warn; use crate::{DropData, Event, MouseEvent}; use core::result::Result; @@ -95,7 +96,7 @@ pub(crate) enum DragNDropState { // Other errors (protocol errors, transfer errors) should be dealt with as gracefully as possible. impl DragNDropState { pub fn handle_enter_event( - &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, + &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let data = event.data.as_data32(); @@ -154,7 +155,7 @@ impl DragNDropState { } pub fn handle_position_event( - &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, + &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let event_data = event.data.as_data32(); @@ -260,7 +261,7 @@ impl DragNDropState { } } - pub fn handle_leave_event(&mut self, handler: &dyn WindowHandler, event: &ClientMessageEvent) { + pub fn handle_leave_event(&mut self, handler: &Handler, event: &ClientMessageEvent) { let data = event.data.as_data32(); let event_source_window = data[0] as xproto::Window; @@ -291,7 +292,7 @@ impl DragNDropState { } pub fn handle_drop_event( - &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, + &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let data = event.data.as_data32(); @@ -395,7 +396,7 @@ impl DragNDropState { } pub fn handle_selection_notify_event( - &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &SelectionNotifyEvent, + &mut self, window: &WindowShared, handler: &Handler, event: &SelectionNotifyEvent, ) -> Result<(), ConnectionError> { // Ignore the event if we weren't actually waiting for a selection notify event let WaitingForData { @@ -488,7 +489,7 @@ impl DragNDropState { } fn send_status_rejected( - source_window: xproto::Window, window: &WindowInner, + source_window: xproto::Window, window: &WindowShared, ) -> Result<(), ConnectionError> { let conn = &window.connection; @@ -509,7 +510,7 @@ fn send_status_rejected( } fn send_status_event( - source_window: xproto::Window, window: &WindowInner, action: Option, + source_window: xproto::Window, window: &WindowShared, action: Option, ) -> Result<(), ConnectionError> { let conn = &window.connection; @@ -532,7 +533,7 @@ fn send_status_event( } pub fn send_finished_rejected( - source_window: xproto::Window, window: &WindowInner, + source_window: xproto::Window, window: &WindowShared, ) -> Result<(), ConnectionError> { let conn = &window.connection; @@ -553,7 +554,7 @@ pub fn send_finished_rejected( } fn send_finished_event( - source_window: xproto::Window, window: &WindowInner, action: Option, + source_window: xproto::Window, window: &WindowShared, action: Option, ) -> Result, ConnectionError> { let conn = &window.connection; let action = @@ -572,7 +573,7 @@ fn send_finished_event( } fn request_convert_selection( - window: &WindowInner, timestamp: Option, + window: &WindowShared, timestamp: Option, ) -> Result, ConnectionError> { window.connection.conn.convert_selection( window.xcb_window.id().get(), @@ -588,7 +589,7 @@ fn decode_xy(data: u32) -> (u16, u16) { } fn translate_root_coordinates( - window: &WindowInner, x: u16, y: u16, + window: &WindowShared, x: u16, y: u16, ) -> Result>, ConnectionError> { let root_id = window.connection.default_screen().root; let x = x.try_into().unwrap_or(i16::MAX); @@ -609,7 +610,7 @@ fn translate_root_coordinates( Ok(Some(PhysicalPosition::new(reply.dst_x, reply.dst_y))) } -fn fetch_dnd_data(window: &WindowInner) -> Result, ConnectionError> { +fn fetch_dnd_data(window: &WindowShared) -> Result, ConnectionError> { let conn = &window.connection; let data: Vec = match conn.get_property( diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index 64fb66d1..df44b07b 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -1,6 +1,6 @@ use crate::platform::x11::drag_n_drop::ParseError; use crate::platform::x11::window_thread::RequestFailed; -use crate::platform::x11::xcb_connection::GetPropertyError; +use crate::platform::x11::x11_connection::GetPropertyError; use crate::warn; use crate::wrappers::xlib::{DisplayOpenFailedError, InitThreadsFailedError}; use crate::HandlerError; @@ -16,6 +16,7 @@ pub enum FatalError { Connection(ConnectionError), Calloop(calloop::Error), SendMainThread, + Redraw(String), } impl Display for FatalError { @@ -26,6 +27,7 @@ impl Display for FatalError { FatalError::SendMainThread => { f.write_str("Failed to send callback from X11 thread to main thread") } + FatalError::Redraw(e) => write!(f, "Fatal error while drawing the window: {}", e), } } } @@ -62,6 +64,7 @@ pub enum PlatformError { Calloop(calloop::Error), RequestFromMainThreadFailed(RequestFailed), SendMainThread, + Redraw(String), #[cfg(feature = "opengl")] XLib(crate::wrappers::xlib::XLibError), #[cfg(feature = "opengl")] @@ -88,7 +91,8 @@ impl Display for PlatformError { PlatformError::Handler(e) => e.fmt(f), PlatformError::MainThreadRecvResult => { f.write_str("Failed to receive Window creation response from X11 thread: channel was closed unexpectedly") - } + }, + PlatformError::Redraw(e) => e.fmt(f), PlatformError::Calloop(e) => e.fmt(f), PlatformError::RequestFromMainThreadFailed(e) => e.fmt(f), PlatformError::SendMainThread => FatalError::SendMainThread.fmt(f), @@ -178,6 +182,7 @@ impl From for PlatformError { FatalError::Connection(e) => Self::Connection(e), FatalError::Calloop(e) => Self::Calloop(e), FatalError::SendMainThread => Self::SendMainThread, + FatalError::Redraw(s) => Self::Redraw(s), } } } diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 82b26fc8..d63ff200 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -6,21 +6,20 @@ use std::result::Result; use crate::dpi::{PhysicalPosition, PhysicalSize}; use crate::host::HostMainThreadCaller; use crate::platform::x11::error::FatalError; +use crate::platform::x11::handler::Handler; +use crate::platform::x11::present::PresentState; use crate::platform::x11::window_thread::{ HostCallback, WindowThreadRequest, WindowThreadResponseMessage, }; +use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; -use crate::{warn, DamageArea}; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowSize}; use calloop::generic::Generic; -use calloop::timer::{TimeoutAction, Timer}; use calloop::{Interest, LoopHandle, LoopSignal, Mode, PostAction}; use std::rc::Rc; use std::sync::mpsc; use std::sync::mpsc::Receiver; -use std::time::{Duration, Instant}; use x11rb::connection::Connection; -use x11rb::protocol::present::CompleteKind; use x11rb::protocol::Event as XEvent; pub struct MainThreadCaller { @@ -48,15 +47,13 @@ impl MainThreadCaller { } pub(crate) struct EventLoop { - handler: Box, - window: Rc, + handler: Handler, + shared: Rc, new_size: Option>, new_parent_size: Option>, - draw_now: bool, - last_requested_serial: Option, - last_received_present: Option<(u32, u64)>, + pub present_state: PresentState, loop_signal: LoopSignal, loop_handle: LoopHandle<'static, Self>, @@ -71,7 +68,7 @@ pub(crate) struct EventLoop { impl EventLoop { pub fn new( - window: Rc, handler: Box, + window: Rc, handler: Box, request_receiver: calloop::channel::Channel, response_sender: mpsc::Sender, main_thread: Option, inner: &mut calloop::EventLoop<'static, Self>, @@ -96,18 +93,16 @@ impl EventLoop { Ok(Self { loop_signal: inner.get_signal(), loop_handle, - handler, + handler: Handler::new(handler), + present_state: PresentState::new(), new_size: None, new_parent_size: None, - draw_now: false, - last_requested_serial: None, - last_received_present: None, drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), run_error: None, main_thread, - window, + shared: window, response_sender, }) } @@ -119,7 +114,7 @@ impl EventLoop { #[inline] fn drain_xcb_events(&mut self) -> Result { let mut event_received = false; - while let Some(event) = self.window.connection.conn.poll_for_event()? { + while let Some(event) = self.shared.connection.conn.poll_for_event()? { event_received = true; self.handle_xcb_event(event)?; } @@ -127,94 +122,12 @@ impl EventLoop { Ok(event_received) } - fn setup_fallback_frame_timer(&self) -> Result<(), calloop::Error> { - const FRAME_INTERVAL: Duration = Duration::from_millis(15); - - fn handle_frame(evloop: &mut EventLoop, previous_deadline: Instant) -> TimeoutAction { - evloop.draw_now = true; - - // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in - // the expected frame time, this will throttle down to prevent multiple frames from - // being queued up. - - let now = Instant::now(); - - let Some(next_deadline) = previous_deadline.checked_add(FRAME_INTERVAL) else { - return TimeoutAction::ToDuration(FRAME_INTERVAL); - }; - - if next_deadline >= now { - return TimeoutAction::ToDuration(FRAME_INTERVAL); - } - - TimeoutAction::ToInstant(next_deadline) - } - - self.loop_handle - .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| handle_frame(e, i)) - .map_err(|e| e.error)?; - - Ok(()) - } - - fn redraw(&mut self) -> Result<(), FatalError> { - self.window.present_notify_requested.set(false); - self.draw_now = false; - - if let Err(e) = self.handler.draw() { - self.trigger_fatal_error(e.into()); - return Ok(()); - } - - self.window.connection.conn.flush()?; - Ok(()) - } - - fn handle_present_notify(&mut self) -> Result<(), FatalError> { - if !self.window.present_notify_requested.get() { - return Ok(()); - } - - if !self.window.xcb_window.present_supported() { - self.window.present_notify_requested.set(false); - return Ok(()); - } - - let (next_serial, target_msc) = - match (self.last_requested_serial, self.last_received_present) { - // First request, always send - (None, None) => (0, 0), - (Some(sent_serial), Some((received_serial, last_msc))) - if sent_serial == received_serial => - { - (sent_serial.wrapping_add(1), last_msc.wrapping_add(2)) - } - // We sent our first request but have not gotten a response yet. - // Or, we sent a request, but the last response we've gotten isn't that one. - // Do not send. - _ => { - self.window.present_notify_requested.set(false); - return Ok(()); - } - }; - - if self.window.xcb_window.present_notify(target_msc, next_serial)?.check_is_ok() { - self.last_requested_serial = Some(next_serial); - } else { - self.last_requested_serial = None; - self.setup_fallback_frame_timer()?; - } - self.window.present_notify_requested.set(false); - - Ok(()) - } - fn handle_coalesced_resize_events(&mut self) -> Result<(), FatalError> { let mut comes_from_parent = false; if let Some(new_parent_size) = self.new_parent_size.take() { - if new_parent_size != self.window.get_size() { + if new_parent_size != self.shared.get_size() { // The parent was resized, which means we should resize ourselves too. - if let Err(e) = self.window.xcb_window.resize(new_parent_size.cast()) { + if let Err(e) = self.shared.xcb_window.resize(new_parent_size.cast()) { crate::warn!("Failed to resize window: {}", e); } else { // Makes the rest of this function run on the new parent size immediately (without waiting for a ConfigureNotify round-trip) @@ -226,19 +139,18 @@ impl EventLoop { } let Some(new_size) = self.new_size.take() else { return Ok(()) }; - let previous = self.window.store_size(new_size); + let previous = self.shared.store_size(new_size); if previous == new_size { return Ok(()); }; - let scale_factor = self.window.scaling_factor.get(); + let scale_factor = self.shared.scaling_factor.get(); let new_size = WindowSize::from_physical(new_size.cast(), scale_factor); - if let Err(e) = self.handler.resized(new_size) { - warn!("Window Handler failed to resize: {}", e); - self.window.store_size(previous); - self.window.xcb_window.resize(previous.cast())?.check_warn(); + if let Err(()) = self.handler.resize(new_size) { + self.shared.store_size(previous); + self.shared.xcb_window.resize(previous.cast())?.check_warn(); return Ok(()); } @@ -255,7 +167,7 @@ impl EventLoop { } // Immediately schedule a redraw, do not wait for an "expose" event - self.window.present_notify_requested.set(true); + self.shared.request_redraw(); Ok(()) } @@ -285,12 +197,12 @@ impl EventLoop { } } - fn stop_now(&self) { + pub fn stop_now(&self) { self.loop_signal.stop(); self.loop_signal.wakeup(); } - fn trigger_fatal_error(&mut self, error: PlatformError) { + pub fn trigger_fatal_error(&mut self, error: PlatformError) { if self.run_error.is_none() { self.run_error = Some(error); } @@ -300,37 +212,37 @@ impl EventLoop { fn handle_request(&mut self, req: WindowThreadRequest) -> Result<(), PlatformError> { match req { WindowThreadRequest::Resize(new_size) => { - let scale_factor = self.window.scaling_factor.get(); + let scale_factor = self.shared.scaling_factor.get(); let new_size = new_size.to_physical(scale_factor); - self.window.resize_immediately(new_size, &*self.handler)?; + self.shared.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) { + if !self.shared.scaling_factor.suggest(scale) { return Ok(()); }; - let current_logical_size = self.window.get_size().to_logical::(1.0); + let current_logical_size = self.shared.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)?; + self.shared.resize_immediately(new_physical_size, &self.handler)?; Ok(()) } WindowThreadRequest::SetParent(new_parent) => { - self.window.xcb_window.reparent(Some(new_parent.window_id))?; + self.shared.xcb_window.reparent(Some(new_parent.window_id))?; Ok(()) } WindowThreadRequest::Show => { - self.window.xcb_window.map_window()?.check()?; + self.shared.xcb_window.map_window()?.check()?; Ok(()) } WindowThreadRequest::Hide => { - self.window.xcb_window.unmap_window()?.check()?; + self.shared.xcb_window.unmap_window()?.check()?; Ok(()) } } @@ -356,29 +268,36 @@ impl EventLoop { self.handle_coalesced_resize_events()?; // Consume all requests from above poll - if let Some(redraw_after) = self.window.main_thread_shared.take_redraw_request() { - self.window.request_redraw_after(redraw_after) + if let Some(redraw_after) = self.shared.main_thread_shared.take_redraw_request() { + self.shared.request_redraw_after(redraw_after) } - let shared_poll_requested = self.window.main_thread_shared.take_poll_request(); - - if self.draw_now { - self.handler.poll(); - self.redraw()?; - self.window.poll_requested.set(false); - } else if shared_poll_requested || self.window.poll_requested.get() { - self.handler.poll(); - self.window.poll_requested.set(false); + let shared_poll_requested = self.shared.main_thread_shared.take_poll_request(); + let did_redraw = self.present_state.redraw_if_needed( + &self.shared.present_state, + &mut self.handler, + &self.shared.connection.conn, + )?; + + if !did_redraw { + if shared_poll_requested || self.shared.poll_requested.get() { + self.handler.poll(); + self.shared.poll_requested.set(false); + } } - self.handle_present_notify()?; + self.present_state.handle_present_notify( + &self.shared.present_state, + &self.shared.xcb_window, + &self.loop_handle, + )?; if !self.drain_xcb_events()? { break; } } - self.window.connection.conn.flush()?; + self.shared.connection.conn.flush()?; Ok(()) } @@ -390,7 +309,7 @@ impl EventLoop { self.handle_event(Event::Window(WindowEvent::WillClose)); // If the event loop doesn't stop because the host asked it to, then we should notify it - if !self.window.main_thread_shared.is_stop_host_requested() { + if !self.shared.main_thread_shared.is_stop_host_requested() { if let Some(main_thread) = self.main_thread.as_mut() { if let Err(e) = main_thread.send(HostCallback::Destroyed) { warn!("Could not notify host that X11 thread is stopping: {}", e) @@ -406,7 +325,11 @@ impl EventLoop { } pub fn request_redraw(&self) { - self.window.request_redraw(); + self.shared.request_redraw(); + } + + pub fn shared(&self) -> &WindowShared { + &self.shared } fn handle_xcb_event(&mut self, event: XEvent) -> Result<(), FatalError> { @@ -431,38 +354,32 @@ impl EventLoop { // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 match event { - //// - // window - //// - XEvent::ClientMessage(event) if event.window == self.window.raw_id() => { + XEvent::ClientMessage(event) if event.window == self.shared.raw_id() => { if event.format != 32 { return Ok(()); } - if event.data.as_data32()[0] == self.window.connection.atoms.WM_DELETE_WINDOW { - self.window.request_close(); + if event.data.as_data32()[0] == self.shared.connection.atoms.WM_DELETE_WINDOW { + self.shared.request_close(); return Ok(()); } - //// - // drag n drop - //// - if event.type_ == self.window.connection.atoms.XdndEnter { - self.drag_n_drop.handle_enter_event(&self.window, &*self.handler, &event)?; - } else if event.type_ == self.window.connection.atoms.XdndPosition { - self.drag_n_drop.handle_position_event(&self.window, &*self.handler, &event)?; - } else if event.type_ == self.window.connection.atoms.XdndDrop { - self.drag_n_drop.handle_drop_event(&self.window, &*self.handler, &event)?; - } else if event.type_ == self.window.connection.atoms.XdndLeave { - self.drag_n_drop.handle_leave_event(&*self.handler, &event); + if event.type_ == self.shared.connection.atoms.XdndEnter { + self.drag_n_drop.handle_enter_event(&self.shared, &self.handler, &event)?; + } else if event.type_ == self.shared.connection.atoms.XdndPosition { + self.drag_n_drop.handle_position_event(&self.shared, &self.handler, &event)?; + } else if event.type_ == self.shared.connection.atoms.XdndDrop { + self.drag_n_drop.handle_drop_event(&self.shared, &self.handler, &event)?; + } else if event.type_ == self.shared.connection.atoms.XdndLeave { + self.drag_n_drop.handle_leave_event(&self.handler, &event); } } XEvent::SelectionNotify(event) => { - if event.property == self.window.connection.atoms.XdndSelection { + if event.property == self.shared.connection.atoms.XdndSelection { self.drag_n_drop.handle_selection_notify_event( - &self.window, - &*self.handler, + &self.shared, + &self.handler, &event, )?; } @@ -475,9 +392,9 @@ impl EventLoop { XEvent::ConfigureNotify(event) => { if let Some(window_id) = NonZero::new(event.window) { // These are coalesced and then handled asynchronously at the end of the event loop - if window_id == self.window.xcb_window.id() { + if window_id == self.shared.xcb_window.id() { self.new_size = Some(PhysicalSize::new(event.width, event.height)); - } else if Some(window_id) == self.window.parent_id.get() { + } else if Some(window_id) == self.shared.parent_id.get() { // Also resize the window if the parent is resized // This works around some hosts that might not call set_size() right away (or at all...) self.new_parent_size = Some(PhysicalSize::new(event.width, event.height)); @@ -485,27 +402,10 @@ impl EventLoop { } } - XEvent::Expose(e) if e.window == self.window.raw_id() => { - if e.count == 0 { - self.window.present_notify_requested.set(true); - } - - let current_window_size = self.new_size.unwrap_or_else(|| self.window.get_size()); - - let damage_rect = DamageRect::new(&e); - let area = if damage_rect.fully_covers(current_window_size) { - DamageArea::FullWindow - } else { - DamageArea::Rect(damage_rect.into()) - }; - - self.handler.damage(area); - } - //// // mouse //// - XEvent::MotionNotify(event) if event.event == self.window.raw_id() => { + XEvent::MotionNotify(event) if event.event == self.shared.raw_id() => { let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); self.handle_event(Event::Mouse(MouseEvent::CursorMoved { @@ -514,7 +414,7 @@ impl EventLoop { })); } - XEvent::EnterNotify(event) if event.event == self.window.raw_id() => { + XEvent::EnterNotify(event) if event.event == self.shared.raw_id() => { self.handle_event(Event::Mouse(MouseEvent::CursorEntered)); // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, // we generate a CursorMoved as well, so the mouse position from here isn't lost @@ -525,11 +425,11 @@ impl EventLoop { })); } - XEvent::LeaveNotify(event) if event.event == self.window.raw_id() => { + XEvent::LeaveNotify(event) if event.event == self.shared.raw_id() => { self.handle_event(Event::Mouse(MouseEvent::CursorLeft)); } - XEvent::ButtonPress(event) if event.event == self.window.raw_id() => { + XEvent::ButtonPress(event) if event.event == self.shared.raw_id() => { match event.detail { 4..=7 => { self.handle_event(Event::Mouse(MouseEvent::WheelScrolled { @@ -553,7 +453,7 @@ impl EventLoop { } XEvent::ButtonRelease(event) - if event.event == self.window.raw_id() && !(4..=7).contains(&event.detail) => + if event.event == self.shared.raw_id() && !(4..=7).contains(&event.detail) => { let button_id = mouse_id(event.detail); self.handle_event(Event::Mouse(MouseEvent::ButtonReleased { @@ -565,79 +465,51 @@ impl EventLoop { //// // keys //// - XEvent::KeyPress(event) if event.event == self.window.raw_id() => { + XEvent::KeyPress(event) if event.event == self.shared.raw_id() => { let ev = Event::Keyboard(convert_key_press_event(&event, &mut self.xkb_state)); self.handle_event(ev); } - XEvent::KeyRelease(event) if event.event == self.window.raw_id() => { + XEvent::KeyRelease(event) if event.event == self.shared.raw_id() => { let ev = Event::Keyboard(convert_key_release_event(&event, &mut self.xkb_state)); self.handle_event(ev); } - XEvent::FocusIn(event) if event.event == self.window.raw_id() => { - self.window.is_focused.set(true); + XEvent::FocusIn(event) if event.event == self.shared.raw_id() => { + self.shared.is_focused.set(true); self.handle_event(Event::Window(WindowEvent::Focused)); } - XEvent::FocusOut(e) if e.event == self.window.raw_id() => { - self.window.is_focused.set(false); + XEvent::FocusOut(e) if e.event == self.shared.raw_id() => { + self.shared.is_focused.set(false); self.handle_event(Event::Window(WindowEvent::Unfocused)); } - XEvent::MapNotify(e) => { - if let Some(window_id) = NonZero::new(e.window) { - if window_id == self.window.xcb_window.id() { - if self.window.xcb_window.present_supported() - && self.window.xcb_window.present_select_input()? - { - self.window.present_notify_requested.set(true); - } else { - self.setup_fallback_frame_timer()?; - } - } - } + XEvent::ReparentNotify(e) if e.window == self.shared.raw_id() => { + self.shared.parent_id.set(NonZero::new(e.parent)); } - XEvent::ReparentNotify(e) => { + XEvent::MapNotify(e) => { if let Some(window_id) = NonZero::new(e.window) { - if window_id == self.window.xcb_window.id() { - self.window.parent_id.set(NonZero::new(e.parent)); + if window_id == self.shared.xcb_window.id() { + self.present_state.handle_window_mapped( + &self.shared.present_state, + &self.shared.xcb_window, + &self.loop_handle, + )?; } } } - XEvent::PresentCompleteNotify(e) => { - if e.kind != CompleteKind::NOTIFY_MSC { - return Ok(()); - } - - if e.window != self.window.raw_id() { - return Ok(()); - } - - let Some(last_requested_serial) = self.last_requested_serial else { - return Ok(()); - }; - - if last_requested_serial != e.serial { - return Ok(()); - } - - if let Some((last_received_serial, last_received_msc)) = self.last_received_present - { - if last_received_serial == e.serial { - return Ok(()); - } - - if e.msc <= last_received_msc { - self.last_received_present = Some((e.serial, e.msc)); - return Ok(()); - } - } - - self.last_received_present = Some((e.serial, e.msc)); - self.draw_now = true; + XEvent::PresentCompleteNotify(e) if e.window != self.shared.raw_id() => { + self.present_state.handle_present_complete_notify(e); + } + XEvent::Expose(e) if e.window == self.shared.raw_id() => { + self.present_state.handle_expose_event( + e, + &self.shared.present_state, + &self.handler, + ); } _ => {} diff --git a/src/platform/x11/handler.rs b/src/platform/x11/handler.rs new file mode 100644 index 00000000..235258f2 --- /dev/null +++ b/src/platform/x11/handler.rs @@ -0,0 +1,37 @@ +use crate::platform::x11::error::FatalError; +use crate::{DamageArea, EventStatus, WindowHandler, WindowSize}; + +pub struct Handler { + handler: Box, +} + +impl Handler { + pub fn new(handler: Box) -> Self { + Self { handler } + } + + pub fn draw(&self) -> Result<(), FatalError> { + self.handler.draw().map_err(|e| FatalError::Redraw(e.to_string())) + } + + pub fn resize(&self, new_size: WindowSize) -> Result<(), ()> { + if let Err(e) = self.handler.resized(new_size) { + crate::warn!("Failed to resize window: {}", e); + Err(()) + } else { + Ok(()) + } + } + + pub fn poll(&self) { + self.handler.poll() + } + + pub fn on_event(&self, event: crate::Event) -> EventStatus { + self.handler.on_event(event) + } + + pub fn damage(&self, area: DamageArea) { + self.handler.damage(area) + } +} diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 9c106b6c..54ac36f1 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -1,6 +1,3 @@ -mod xcb_connection; - -use dpi::{PhysicalPosition, PhysicalSize}; use raw_window_handle::{ DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, XcbWindowHandle, }; @@ -8,32 +5,35 @@ use std::fmt::{Display, Formatter}; use std::num::{NonZero, NonZeroU32, TryFromIntError}; use std::rc::Rc; use std::sync::Arc; -use x11rb::protocol::xproto::ExposeEvent; -pub(crate) use xcb_connection::X11Connection; - -mod window; -pub use window::*; +pub(crate) use x11_connection::X11Connection; mod cursor; mod drag_n_drop; mod error; mod event_loop; +mod handler; mod keyboard; +mod present; mod visual_info; mod xcb_window; mod timer; mod waker; +mod window; mod window_shared; mod window_thread; +mod x11_connection; +mod xcb_window; pub use error::{CookieExt as _, PlatformError}; pub(crate) type Result = std::result::Result; -use crate::platform::x11::window_shared::WindowInner; +use crate::platform::x11::window_shared::WindowShared; use crate::wrappers::xlib::XlibXcbConnection; +pub use window::*; -pub type WindowContext = Rc; +pub type WindowContext = Rc; +pub use present::DamageRect; pub use timer::TimerHandle; pub use waker::WindowWaker; @@ -128,36 +128,3 @@ impl Display for ParentWindowHandleError { pub fn assume_standalone_in_process() { // No-op on X11 } - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct DamageRect { - pos: PhysicalPosition, - size: PhysicalSize, -} - -impl DamageRect { - #[inline] - pub fn new(event: &ExposeEvent) -> Self { - Self { - pos: PhysicalPosition::new(event.x, event.y), - size: PhysicalSize::new(event.width, event.height), - } - } - - #[inline] - pub fn position(&self) -> PhysicalPosition { - PhysicalPosition { x: self.pos.x.into(), y: self.pos.y.into() } - } - - #[inline] - pub fn size(&self) -> PhysicalSize { - PhysicalSize { height: self.size.height.into(), width: self.size.width.into() } - } - - pub fn fully_covers(&self, window_size: PhysicalSize) -> bool { - self.pos.x == 0 - && self.pos.y == 0 - && window_size.width <= self.size.width - && window_size.height <= self.size.height - } -} diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs new file mode 100644 index 00000000..c42151ba --- /dev/null +++ b/src/platform/x11/present.rs @@ -0,0 +1,246 @@ +use crate::platform::x11::error::{CookieExt, FatalError}; +use crate::platform::x11::event_loop::EventLoop; +use crate::platform::x11::handler::Handler; +use crate::platform::x11::xcb_window::XcbWindow; +use crate::wrappers::xlib::XlibXcbConnection; +use crate::DamageArea; +use calloop::timer::{TimeoutAction, Timer}; +use calloop::LoopHandle; +use dpi::{PhysicalPosition, PhysicalSize}; +use std::cell::Cell; +use std::time::{Duration, Instant}; +use tracing::warn; +use x11rb::connection::Connection; +use x11rb::protocol::present::{CompleteKind, CompleteNotifyEvent}; +use x11rb::protocol::xproto::ExposeEvent; + +pub struct PresentStateShared { + present_notify_requested: Cell, +} + +impl PresentStateShared { + pub(crate) fn request_present_notify_after( + &self, duration: Duration, loop_handle: &LoopHandle, + ) { + if duration.is_zero() || duration.as_millis() < 1 { + self.request_present_notify(); + return; + } + + let result = loop_handle.insert_source(Timer::from_duration(duration), |_, _, e| { + e.shared().present_state.request_present_notify(); + TimeoutAction::Drop + }); + + if let Err(e) = result { + warn!("{}", e); + self.request_present_notify(); + } + } +} + +impl PresentStateShared { + pub fn new() -> Self { + Self { present_notify_requested: false.into() } + } + + pub fn request_present_notify(&self) { + self.present_notify_requested.set(true) + } +} + +pub struct PresentState { + draw_now: bool, + last_requested_serial: Option, + last_received_present: Option<(u32, u64)>, +} + +impl PresentState { + pub(crate) fn handle_expose_event( + &self, e: ExposeEvent, shared: &PresentStateShared, handler: &Handler, + ) { + if e.count == 0 { + shared.present_notify_requested.set(true); + } + + let current_window_size = self.new_size.unwrap_or_else(|| self.window.get_size()); + + let damage_rect = DamageRect::new(&e); + let area = if damage_rect.fully_covers(current_window_size) { + DamageArea::FullWindow + } else { + DamageArea::Rect(damage_rect.into()) + }; + + handler.damage(area); + } +} + +impl PresentState { + pub fn handle_window_mapped( + &self, shared: &PresentStateShared, window: &XcbWindow, loop_handle: &LoopHandle, + ) -> Result<(), FatalError> { + if window.present_supported() && window.present_select_input()? { + shared.present_notify_requested.set(true); + } else { + Self::setup_fallback_frame_timer(loop_handle)?; + } + + Ok(()) + } +} + +impl PresentState { + pub fn new() -> Self { + Self { draw_now: false, last_requested_serial: None, last_received_present: None } + } + + // TODO: check this & error handling + pub fn redraw_if_needed( + &mut self, shared: &PresentStateShared, handler: &mut Handler, + connection: &XlibXcbConnection, + ) -> Result { + handler.poll(); + + shared.present_notify_requested.set(false); + self.draw_now = false; + + handler.draw()?; + + connection.flush()?; + Ok(true) + } + + pub fn handle_present_complete_notify(&mut self, e: CompleteNotifyEvent) { + if e.kind != CompleteKind::NOTIFY_MSC { + return; + } + + let Some(last_requested_serial) = self.last_requested_serial else { return }; + + if last_requested_serial != e.serial { + return; + } + + if let Some((last_received_serial, last_received_msc)) = self.last_received_present { + if last_received_serial == e.serial { + return; + } + + if e.msc <= last_received_msc { + self.last_received_present = Some((e.serial, e.msc)); + return; + } + } + + self.last_received_present = Some((e.serial, e.msc)); + self.draw_now = true; + } + + fn setup_fallback_frame_timer( + loop_handle: &LoopHandle, + ) -> Result<(), calloop::Error> { + const FRAME_INTERVAL: Duration = Duration::from_millis(15); + + fn handle_frame(evloop: &mut EventLoop, previous_deadline: Instant) -> TimeoutAction { + evloop.present_state.draw_now = true; + + // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in + // the expected frame time, this will throttle down to prevent multiple frames from + // being queued up. + + let now = Instant::now(); + + let Some(next_deadline) = previous_deadline.checked_add(FRAME_INTERVAL) else { + return TimeoutAction::ToDuration(FRAME_INTERVAL); + }; + + if next_deadline >= now { + return TimeoutAction::ToDuration(FRAME_INTERVAL); + } + + TimeoutAction::ToInstant(next_deadline) + } + + loop_handle + .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| handle_frame(e, i)) + .map_err(|e| e.error)?; + + Ok(()) + } + + pub fn handle_present_notify( + &mut self, shared: &PresentStateShared, window: &XcbWindow, + loop_handle: &LoopHandle, + ) -> Result<(), FatalError> { + if !shared.present_notify_requested.get() { + return Ok(()); + } + + if !window.present_supported() { + shared.present_notify_requested.set(false); + return Ok(()); + } + + let (next_serial, target_msc) = + match (self.last_requested_serial, self.last_received_present) { + // First request, always send + (None, None) => (0, 0), + (Some(sent_serial), Some((received_serial, last_msc))) + if sent_serial == received_serial => + { + (sent_serial.wrapping_add(1), last_msc.wrapping_add(2)) + } + // We sent our first request but have not gotten a response yet. + // Or, we sent a request, but the last response we've gotten isn't that one. + // Do not send. + _ => { + shared.present_notify_requested.set(false); + return Ok(()); + } + }; + + if window.present_notify(target_msc, next_serial)?.check_is_ok() { + self.last_requested_serial = Some(next_serial); + } else { + self.last_requested_serial = None; + Self::setup_fallback_frame_timer(loop_handle)?; + } + shared.present_notify_requested.set(false); + + Ok(()) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct DamageRect { + pos: PhysicalPosition, + size: PhysicalSize, +} + +impl DamageRect { + #[inline] + pub fn new(event: &ExposeEvent) -> Self { + Self { + pos: PhysicalPosition::new(event.x, event.y), + size: PhysicalSize::new(event.width, event.height), + } + } + + #[inline] + pub fn position(&self) -> PhysicalPosition { + PhysicalPosition { x: self.pos.x.into(), y: self.pos.y.into() } + } + + #[inline] + pub fn size(&self) -> PhysicalSize { + PhysicalSize { height: self.size.height.into(), width: self.size.width.into() } + } + + pub fn fully_covers(&self, window_size: PhysicalSize) -> bool { + self.pos.x == 0 + && self.pos.y == 0 + && window_size.width <= self.size.width + && window_size.height <= self.size.height + } +} diff --git a/src/platform/x11/visual_info.rs b/src/platform/x11/visual_info.rs index bdaa7199..1d123c8f 100644 --- a/src/platform/x11/visual_info.rs +++ b/src/platform/x11/visual_info.rs @@ -1,4 +1,4 @@ -use super::xcb_connection::X11Connection; +use super::x11_connection::X11Connection; use crate::platform::*; use x11rb::connection::Connection; use x11rb::protocol::xproto::{ diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 3da45c2b..5de731f6 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,10 +1,12 @@ use crate::dpi::{PhysicalSize, Size}; use crate::platform::x11::event_loop::EventLoop; +use crate::platform::x11::handler::Handler; +use crate::platform::x11::present::{PresentState, PresentStateShared}; use crate::platform::x11::timer::insert_timer; use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::waker::WindowWaker; use crate::platform::x11::window_thread::WindowThreadShared; -use crate::platform::x11::xcb_connection::get_size_hints; +use crate::platform::x11::x11_connection::get_size_hints; use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::*; use crate::utils::SizingStrategy; @@ -46,10 +48,10 @@ impl ScalingFactor { } } -pub(crate) struct WindowInner { - // GlContext should be dropped **before** XcbConnection is dropped +/// Data that is shared between the event loop and the window handler. +pub(crate) struct WindowShared { #[cfg(feature = "opengl")] - gl_context: Option, + gl_context: Option, pub(crate) xcb_window: XcbWindow, pub(crate) parent_id: Cell>, @@ -63,7 +65,7 @@ pub(crate) struct WindowInner { pub(crate) visual_id: Visualid, pub(crate) is_focused: Cell, - pub(crate) present_notify_requested: Cell, + pub present_state: PresentStateShared, pub(crate) poll_requested: Cell, pub(crate) loop_signal: LoopSignal, loop_handle: LoopHandle<'static, EventLoop>, @@ -71,7 +73,7 @@ pub(crate) struct WindowInner { pub(crate) main_thread_shared: Arc, } -impl WindowInner { +impl WindowShared { pub(crate) fn create( options: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, shared: Arc, @@ -145,7 +147,7 @@ impl WindowInner { loop_handle: ev_loop.handle(), is_focused: false.into(), - present_notify_requested: false.into(), + present_state: PresentStateShared::new(), poll_requested: false.into(), main_thread_shared: shared, @@ -196,24 +198,11 @@ impl WindowInner { } pub fn request_redraw(&self) { - self.present_notify_requested.set(true) + self.present_state.request_present_notify() } pub fn request_redraw_after(&self, duration: Duration) { - if duration.is_zero() || duration.as_millis() < 1 { - self.request_redraw(); - return; - } - - let result = self.loop_handle.insert_source(Timer::from_duration(duration), |_, _, e| { - e.request_redraw(); - TimeoutAction::Drop - }); - - if let Err(e) = result { - warn!("{}", e); - self.request_redraw(); - } + self.present_state.request_present_notify_after(duration, &self.loop_handle); } pub fn waker(&self) -> WindowWaker { @@ -261,21 +250,18 @@ impl WindowInner { insert_timer(&self.loop_handle, duration) } - pub fn resize_immediately( - &self, new_size: PhysicalSize, handler: &dyn WindowHandler, - ) -> Result<()> { + pub fn resize_immediately(&self, new_size: PhysicalSize, handler: &Handler) -> Result<()> { let previous = self.store_size(new_size); if previous == new_size { return Ok(()); }; - if let Err(e) = - handler.resized(WindowSize::from_physical(new_size.cast(), self.scaling_factor.get())) + if let Err(()) = + handler.resize(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()); + return Ok(()); } self.xcb_window.resize(new_size.cast())?.check()?; // Will not call handler, as size is the same as above. diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index bb805025..49bda960 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -3,7 +3,7 @@ use crate::dpi::{PhysicalSize, Size}; use crate::handler::WindowHandlerBuilder; use crate::host::HostCallbacks; use crate::platform::x11::event_loop::{EventLoop, MainThreadCaller}; -use crate::platform::x11::window_shared::WindowInner; +use crate::platform::x11::window_shared::WindowShared; use crate::utils::SizingStrategy; use crate::warn; use crate::window::WindowInitializer; @@ -72,7 +72,7 @@ impl WindowThreadShared { } } - fn init(&self, window: &WindowInner) { + fn init(&self, window: &WindowShared) { self.set_size(window.get_size()); self.set_scaling_factor(window.scale_factor()); let Ok(()) = self.sizing_strategy.set(window.sizing_strategy) else { unreachable!() }; @@ -373,7 +373,7 @@ impl WindowThread { main_thread_caller: Option, ) -> Result { let mut ev_loop = calloop::EventLoop::try_new()?; - let inner = WindowInner::create(options, &ev_loop, Arc::clone(&shared))?; + let inner = WindowShared::create(options, &ev_loop, Arc::clone(&shared))?; shared.init(&inner); diff --git a/src/platform/x11/xcb_connection.rs b/src/platform/x11/x11_connection.rs similarity index 100% rename from src/platform/x11/xcb_connection.rs rename to src/platform/x11/x11_connection.rs diff --git a/src/platform/x11/xcb_connection/get_property.rs b/src/platform/x11/x11_connection/get_property.rs similarity index 100% rename from src/platform/x11/xcb_connection/get_property.rs rename to src/platform/x11/x11_connection/get_property.rs diff --git a/src/platform/x11/xcb_connection/size_hints.rs b/src/platform/x11/x11_connection/size_hints.rs similarity index 100% rename from src/platform/x11/xcb_connection/size_hints.rs rename to src/platform/x11/x11_connection/size_hints.rs diff --git a/src/wrappers/xlib/xlib_xcb.rs b/src/wrappers/xlib/xlib_xcb.rs index 3fb374e8..f87efc10 100644 --- a/src/wrappers/xlib/xlib_xcb.rs +++ b/src/wrappers/xlib/xlib_xcb.rs @@ -43,7 +43,7 @@ impl XlibXcbConnection { assert!(!xcb_connection.is_null()); // Wrap the XCB connection object in a x11rb connection object - // SAFETY: The xcb_connection pointer should be valid. We also enforce the drop order in this + // SAFETY: The x11_connection pointer should be valid. We also enforce the drop order in this let xcb_connection = unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection, false)? }; From 2c3cdf28720c867438820b60d821f258a05270d0 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 02:17:32 +0200 Subject: [PATCH 02/10] wip --- src/platform/x11/event_loop.rs | 132 +++--------- src/platform/x11/mod.rs | 1 + src/platform/x11/present.rs | 8 +- src/platform/x11/sizing.rs | 314 +++++++++++++++++++++++++++++ src/platform/x11/window_shared.rs | 160 +++------------ src/platform/x11/window_thread.rs | 66 ++---- src/platform/x11/x11_connection.rs | 2 +- 7 files changed, 395 insertions(+), 288 deletions(-) create mode 100644 src/platform/x11/sizing.rs diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index d63ff200..4d471b0f 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -3,11 +3,12 @@ use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mo use super::*; use std::result::Result; -use crate::dpi::{PhysicalPosition, PhysicalSize}; +use crate::dpi::PhysicalPosition; use crate::host::HostMainThreadCaller; use crate::platform::x11::error::FatalError; use crate::platform::x11::handler::Handler; use crate::platform::x11::present::PresentState; +use crate::platform::x11::sizing::SizingState; use crate::platform::x11::window_thread::{ HostCallback, WindowThreadRequest, WindowThreadResponseMessage, }; @@ -50,10 +51,9 @@ pub(crate) struct EventLoop { handler: Handler, shared: Rc, - new_size: Option>, - new_parent_size: Option>, - + pub sizing_state: SizingState, pub present_state: PresentState, + loop_signal: LoopSignal, loop_handle: LoopHandle<'static, Self>, @@ -68,7 +68,7 @@ pub(crate) struct EventLoop { impl EventLoop { pub fn new( - window: Rc, handler: Box, + window: Rc, handler: Box, parent_id: Option, request_receiver: calloop::channel::Channel, response_sender: mpsc::Sender, main_thread: Option, inner: &mut calloop::EventLoop<'static, Self>, @@ -95,8 +95,8 @@ impl EventLoop { loop_handle, handler: Handler::new(handler), present_state: PresentState::new(), - new_size: None, - new_parent_size: None, + sizing_state: SizingState::new(parent_id), + drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), run_error: None, @@ -122,56 +122,6 @@ impl EventLoop { Ok(event_received) } - fn handle_coalesced_resize_events(&mut self) -> Result<(), FatalError> { - let mut comes_from_parent = false; - if let Some(new_parent_size) = self.new_parent_size.take() { - if new_parent_size != self.shared.get_size() { - // The parent was resized, which means we should resize ourselves too. - if let Err(e) = self.shared.xcb_window.resize(new_parent_size.cast()) { - crate::warn!("Failed to resize window: {}", e); - } else { - // Makes the rest of this function run on the new parent size immediately (without waiting for a ConfigureNotify round-trip) - // Also overrides any new sizes we may have received this event loop iteration,it would probably be invalidated anyway - self.new_size = Some(new_parent_size); - comes_from_parent = true; - } - } - } - - let Some(new_size) = self.new_size.take() else { return Ok(()) }; - let previous = self.shared.store_size(new_size); - - if previous == new_size { - return Ok(()); - }; - - let scale_factor = self.shared.scaling_factor.get(); - let new_size = WindowSize::from_physical(new_size.cast(), scale_factor); - - if let Err(()) = self.handler.resize(new_size) { - self.shared.store_size(previous); - self.shared.xcb_window.resize(previous.cast())?.check_warn(); - return Ok(()); - } - - // Host requests use resize_immediately, which stops the previous == new_size condition - // So if we're here, it's guaranteed not to be from a host request - - if !comes_from_parent { - if let Some(host) = self.main_thread.as_mut() { - host.send(HostCallback::Resized { - new_size, - previous: WindowSize::from_physical(previous.cast(), scale_factor), - })?; - } - } - - // Immediately schedule a redraw, do not wait for an "expose" event - self.shared.request_redraw(); - - Ok(()) - } - fn handle_main_thread_request(&mut self, event: calloop::channel::Event) { match event { calloop::channel::Event::Closed => { @@ -212,40 +162,19 @@ impl EventLoop { fn handle_request(&mut self, req: WindowThreadRequest) -> Result<(), PlatformError> { match req { WindowThreadRequest::Resize(new_size) => { - let scale_factor = self.shared.scaling_factor.get(); - let new_size = new_size.to_physical(scale_factor); - - self.shared.resize_immediately(new_size, &self.handler)?; - - Ok(()) - } - WindowThreadRequest::SuggestScaleFactor(scale) => { - // If the scaling factor is already provided by the system, do nothing - if !self.shared.scaling_factor.suggest(scale) { - return Ok(()); - }; - - let current_logical_size = self.shared.get_size().to_logical::(1.0); - let new_physical_size = current_logical_size.to_physical(scale); - - self.shared.resize_immediately(new_physical_size, &self.handler)?; - - Ok(()) + self.sizing_state.handle_host_resize(new_size, &self.handler, &self.shared)? } + WindowThreadRequest::SuggestScaleFactor(scale) => self + .sizing_state + .handle_host_suggest_scale_factor(scale, &self.handler, &self.shared)?, WindowThreadRequest::SetParent(new_parent) => { - self.shared.xcb_window.reparent(Some(new_parent.window_id))?; - - Ok(()) - } - WindowThreadRequest::Show => { - self.shared.xcb_window.map_window()?.check()?; - Ok(()) - } - WindowThreadRequest::Hide => { - self.shared.xcb_window.unmap_window()?.check()?; - Ok(()) + self.shared.xcb_window.reparent(Some(new_parent.window_id))?.check()? } + WindowThreadRequest::Show => self.shared.xcb_window.map_window()?.check()?, + WindowThreadRequest::Hide => self.shared.xcb_window.unmap_window()?.check()?, } + + Ok(()) } fn handle_connection_event_ready(&mut self) -> Result { @@ -265,7 +194,11 @@ impl EventLoop { self.drain_xcb_events()?; loop { - self.handle_coalesced_resize_events()?; + self.sizing_state.handle_coalesced_resize_events( + &self.shared, + &self.handler, + self.main_thread.as_mut(), + )?; // Consume all requests from above poll if let Some(redraw_after) = self.shared.main_thread_shared.take_redraw_request() { @@ -324,10 +257,6 @@ impl EventLoop { Ok(()) } - pub fn request_redraw(&self) { - self.shared.request_redraw(); - } - pub fn shared(&self) -> &WindowShared { &self.shared } @@ -360,7 +289,8 @@ impl EventLoop { } if event.data.as_data32()[0] == self.shared.connection.atoms.WM_DELETE_WINDOW { - self.shared.request_close(); + self.shared.request_close(); // TODO: this doesn't work anymore? + eprintln!("CLOSE!"); return Ok(()); } @@ -390,16 +320,7 @@ impl EventLoop { } XEvent::ConfigureNotify(event) => { - if let Some(window_id) = NonZero::new(event.window) { - // These are coalesced and then handled asynchronously at the end of the event loop - if window_id == self.shared.xcb_window.id() { - self.new_size = Some(PhysicalSize::new(event.width, event.height)); - } else if Some(window_id) == self.shared.parent_id.get() { - // Also resize the window if the parent is resized - // This works around some hosts that might not call set_size() right away (or at all...) - self.new_parent_size = Some(PhysicalSize::new(event.width, event.height)); - } - } + self.sizing_state.handle_configure_notify_event(event, &self.shared.xcb_window); } //// @@ -486,7 +407,7 @@ impl EventLoop { } XEvent::ReparentNotify(e) if e.window == self.shared.raw_id() => { - self.shared.parent_id.set(NonZero::new(e.parent)); + self.sizing_state.handle_parent_notify(e) } XEvent::MapNotify(e) => { @@ -507,8 +428,9 @@ impl EventLoop { XEvent::Expose(e) if e.window == self.shared.raw_id() => { self.present_state.handle_expose_event( e, - &self.shared.present_state, &self.handler, + &self.shared, + &self.sizing_state, ); } diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 54ac36f1..f1e944ce 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -14,6 +14,7 @@ mod event_loop; mod handler; mod keyboard; mod present; +mod sizing; mod visual_info; mod xcb_window; diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index c42151ba..b8fed18d 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -1,6 +1,8 @@ use crate::platform::x11::error::{CookieExt, FatalError}; use crate::platform::x11::event_loop::EventLoop; use crate::platform::x11::handler::Handler; +use crate::platform::x11::sizing::SizingState; +use crate::platform::x11::window_shared::WindowShared; use crate::platform::x11::xcb_window::XcbWindow; use crate::wrappers::xlib::XlibXcbConnection; use crate::DamageArea; @@ -57,13 +59,13 @@ pub struct PresentState { impl PresentState { pub(crate) fn handle_expose_event( - &self, e: ExposeEvent, shared: &PresentStateShared, handler: &Handler, + &self, e: ExposeEvent, handler: &Handler, shared: &WindowShared, sizing_state: &SizingState, ) { if e.count == 0 { - shared.present_notify_requested.set(true); + shared.present_state.present_notify_requested.set(true); } - let current_window_size = self.new_size.unwrap_or_else(|| self.window.get_size()); + let current_window_size = sizing_state.non_coalesced_current_size(shared); let damage_rect = DamageRect::new(&e); let area = if damage_rect.fully_covers(current_window_size) { diff --git a/src/platform/x11/sizing.rs b/src/platform/x11/sizing.rs new file mode 100644 index 00000000..5d1c333e --- /dev/null +++ b/src/platform/x11/sizing.rs @@ -0,0 +1,314 @@ +use crate::platform::x11::error::{CookieExt, FatalError}; +use crate::platform::x11::event_loop::MainThreadCaller; +use crate::platform::x11::handler::Handler; +use crate::platform::x11::window_shared::WindowShared; +use crate::platform::x11::window_thread::HostCallback; +use crate::platform::x11::x11_connection::get_size_hints; +use crate::platform::x11::xcb_window::XcbWindow; +use crate::platform::{PlatformError, X11Connection}; +use crate::utils::SizingStrategy; +use crate::{WindowSettings, WindowSize}; +use dpi::{PhysicalSize, Size}; +use std::cell::Cell; +use std::num::{NonZero, NonZeroU32}; +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; +use std::sync::OnceLock; +use x11rb::properties::WmSizeHints; +use x11rb::protocol::xproto::{ConfigureNotifyEvent, ReparentNotifyEvent}; + +pub struct SizingState { + new_size: Option>, + new_parent_size: Option>, + parent_id: Option, +} + +impl SizingState { + pub fn new(parent_id: Option) -> Self { + Self { new_size: None, new_parent_size: None, parent_id } + } + + pub fn handle_parent_notify(&mut self, e: ReparentNotifyEvent) { + self.parent_id = NonZero::new(e.parent); + } + + pub fn non_coalesced_current_size(&self, shared: &WindowShared) -> PhysicalSize { + self.new_size.unwrap_or_else(|| shared.sizing_state.size()) + } + + pub fn handle_coalesced_resize_events( + &mut self, shared: &WindowShared, handler: &Handler, + main_thread: Option<&mut MainThreadCaller>, + ) -> Result<(), FatalError> { + let mut comes_from_parent = false; + + if let Some(new_parent_size) = self.new_parent_size.take() { + if new_parent_size != shared.sizing_state.size() { + // The parent was resized, which means we should resize ourselves too. + if let Err(e) = shared.xcb_window.resize(new_parent_size.cast()) { + crate::warn!("Failed to resize window: {}", e); + } else { + // Makes the rest of this function run on the new parent size immediately (without waiting for a ConfigureNotify round-trip) + // Also overrides any new sizes we may have received this event loop iteration,it would probably be invalidated anyway + self.new_size = Some(new_parent_size); + comes_from_parent = true; + } + } + } + + let Some(new_size) = self.new_size.take() else { return Ok(()) }; + let previous = shared.sizing_state.store_size(new_size, &shared.main_thread_shared.sizing); + + if previous == new_size { + return Ok(()); + }; + + let scale_factor = shared.sizing_state.scale_factor(); + let new_size = shared.sizing_state.window_size(); + + if let Err(()) = handler.resize(new_size) { + shared.sizing_state.store_size(previous, &shared.main_thread_shared.sizing); + shared.xcb_window.resize(previous.cast())?.check_warn(); + return Ok(()); + } + + // Host requests use resize_immediately, which stops the previous == new_size condition + // So if we're here, it's guaranteed not to be from a host request + + if !comes_from_parent { + if let Some(host) = main_thread { + host.send(HostCallback::Resized { + new_size, + previous: WindowSize::from_physical(previous.cast(), scale_factor), + })?; + } + } + + // Immediately schedule a redraw, do not wait for an "expose" event + shared.present_state.request_present_notify(); + + Ok(()) + } + + pub fn handle_configure_notify_event( + &mut self, event: ConfigureNotifyEvent, window: &XcbWindow, + ) { + if event.window == 0 { + return; + } + + // These are coalesced and then handled asynchronously at the end of the event loop + if event.window == window.id().get() { + self.new_size = Some(PhysicalSize::new(event.width, event.height)); + } else if self.parent_id.is_some_and(|pid| pid.get() == event.window) { + // Also resize the window if the parent is resized + // This works around some hosts that might not call set_size() right away (or at all...) + self.new_parent_size = Some(PhysicalSize::new(event.width, event.height)); + } + } + + pub fn handle_host_resize( + &mut self, new_size: Size, handler: &Handler, shared: &WindowShared, + ) -> Result<(), PlatformError> { + let scale_factor = shared.sizing_state.scaling_factor.get(); + let new_size = new_size.to_physical(scale_factor); + + shared.sizing_state.resize_from_host(new_size, handler, shared) + } + + pub fn handle_host_suggest_scale_factor( + &mut self, scale: f64, handler: &Handler, shared: &WindowShared, + ) -> Result<(), PlatformError> { + // If the scaling factor is already provided by the system, do nothing + if !shared.sizing_state.scaling_factor.suggest(scale) { + return Ok(()); + }; + + let current_logical_size = shared.sizing_state.size().to_logical::(1.0); + let new_physical_size = current_logical_size.to_physical(scale); + + shared.sizing_state.resize_from_host(new_physical_size, handler, shared) + } +} + +pub struct SizingStateShared { + window_size: Cell>, + sizing_strategy: SizingStrategy, + scaling_factor: ScalingFactor, +} + +impl SizingStateShared { + pub fn load( + connection: &X11Connection, sizing_thread_shared: &SizingThreadShared, + settings: &WindowSettings, + ) -> Result { + let scaling = connection.get_scaling(); + let initial_scale_factor = scaling.unwrap_or(1.0); + + let sizing_strategy = SizingStrategy::from_settings(&settings); + let window_size = settings.size.to_physical(initial_scale_factor); + + sizing_thread_shared.set_scaling_factor(initial_scale_factor); + + Ok(Self { + sizing_strategy, + window_size: window_size.into(), + scaling_factor: ScalingFactor { + system: scaling.into(), + suggested: settings.fallback_scale_factor.into(), + }, + }) + } + + pub fn scale_factor(&self) -> f64 { + self.scaling_factor.get() + } + + pub fn size(&self) -> PhysicalSize { + self.window_size.get() + } + + pub fn window_size(&self) -> WindowSize { + WindowSize::from_physical(self.window_size.get().cast(), self.scaling_factor.get()) + } + + pub fn make_size_hints(&self) -> WmSizeHints { + get_size_hints(&self.sizing_strategy, self.window_size.get(), self.scaling_factor.get()) + } + + pub fn store_size( + &self, size: PhysicalSize, thread_shared: &SizingThreadShared, + ) -> PhysicalSize { + let previous = self.window_size.replace(size); + + if previous != size { + thread_shared.set_size(size); + } + + previous + } + + pub fn resize_from_handler( + &self, size: Size, window: &XcbWindow, + ) -> crate::platform::Result<()> { + let new_size = self.sizing_strategy.adjust_size(size, self.window_size()).physical; + + if new_size == self.window_size.get().cast() { + return Ok(()); + } + + window.resize(new_size)?.check()?; + + if !self.sizing_strategy.is_resizable() { + let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); + window.set_size_hints(size_hints)?.check()?; + } + + // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` + // and notify the window handler about it + + Ok(()) + } + + pub fn resize_from_host( + &self, new_size: PhysicalSize, handler: &Handler, shared: &WindowShared, + ) -> crate::platform::Result<()> { + let previous = self.store_size(new_size, &shared.main_thread_shared.sizing); + + if previous == new_size { + return Ok(()); + }; + + if let Err(()) = + handler.resize(WindowSize::from_physical(new_size.cast(), self.scaling_factor.get())) + { + self.store_size(previous, &shared.main_thread_shared.sizing); + return Ok(()); + } + + shared.xcb_window.resize(new_size.cast())?.check()?; // Will not call handler, as size is the same as above. + if !self.sizing_strategy.is_resizable() { + let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); + shared.xcb_window.set_size_hints(size_hints)?.check()?; + } + + // These come from the Host, no need to notify it about the new size + + Ok(()) + } +} + +pub struct SizingThreadShared { + scaling_factor: AtomicU64, + size: AtomicU32, + sizing_strategy: OnceLock, +} + +impl SizingThreadShared { + pub fn new() -> Self { + Self { size: 0.into(), scaling_factor: 0.into(), sizing_strategy: OnceLock::new() } + } + + pub fn init(&self, state: &SizingStateShared) { + let Ok(()) = self.sizing_strategy.set(state.sizing_strategy) else { unreachable!() }; + self.set_size(state.size()); + self.set_scaling_factor(state.scaling_factor.get()); + } + + pub fn get_scaling_factor(&self) -> f64 { + f64::from_be_bytes(self.scaling_factor.load(Ordering::Relaxed).to_ne_bytes()) + } + + fn set_scaling_factor(&self, scale_factor: f64) { + self.scaling_factor + .store(u64::from_be_bytes(scale_factor.to_ne_bytes()), Ordering::Relaxed); + } + + pub fn sizing_strategy(&self) -> SizingStrategy { + self.sizing_strategy.get().copied().unwrap_or_default() + } + + 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 window_size(&self) -> WindowSize { + let scale_factor = self.get_scaling_factor(); + let size = self.get_size(); + + WindowSize::from_physical(size.cast(), scale_factor) + } +} + +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() + } +} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 5de731f6..4b49a562 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,21 +1,17 @@ -use crate::dpi::{PhysicalSize, Size}; use crate::platform::x11::event_loop::EventLoop; -use crate::platform::x11::handler::Handler; -use crate::platform::x11::present::{PresentState, PresentStateShared}; +use crate::platform::x11::present::PresentStateShared; +use crate::platform::x11::sizing::SizingStateShared; use crate::platform::x11::timer::insert_timer; use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::waker::WindowWaker; use crate::platform::x11::window_thread::WindowThreadShared; -use crate::platform::x11::x11_connection::get_size_hints; use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::*; -use crate::utils::SizingStrategy; -use crate::{warn, MouseCursor, WindowHandler, WindowSettings, WindowSize}; -use calloop::timer::{TimeoutAction, Timer}; +use crate::{MouseCursor, WindowSettings, WindowSize}; use calloop::{LoopHandle, LoopSignal}; +use dpi::Size; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::cell::Cell; -use std::num::NonZeroU32; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; @@ -23,49 +19,20 @@ use x11rb::protocol::xproto; 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() - } -} - /// Data that is shared between the event loop and the window handler. pub(crate) struct WindowShared { #[cfg(feature = "opengl")] gl_context: Option, pub(crate) xcb_window: XcbWindow, - pub(crate) parent_id: Cell>, pub(crate) connection: Rc, - pub(crate) scaling_factor: ScalingFactor, - - window_size: Cell>, - pub(crate) sizing_strategy: SizingStrategy, mouse_cursor: Cell, pub(crate) visual_id: Visualid, pub(crate) is_focused: Cell, pub present_state: PresentStateShared, + pub sizing_state: SizingStateShared, pub(crate) poll_requested: Cell, pub(crate) loop_signal: LoopSignal, loop_handle: LoopHandle<'static, EventLoop>, @@ -75,44 +42,31 @@ pub(crate) struct WindowShared { impl WindowShared { pub(crate) fn create( - options: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, - shared: Arc, + settings: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, + thread_shared: Arc, ) -> Result> { - // Connect to the X server - let xcb_connection = X11Connection::new()?; - - let scaling = xcb_connection.get_scaling(); - - let initial_scale_factor = scaling.unwrap_or(1.0); - shared.set_scaling_factor(initial_scale_factor); + let connection = X11Connection::connect()?; - let physical_size = options.size.to_physical(initial_scale_factor); + let sizing_state = SizingStateShared::load(&connection, &thread_shared.sizing, &settings)?; + let size_hints = sizing_state.make_size_hints(); - let sizing_strategy = SizingStrategy::from_settings(&options); - - let size_hints = get_size_hints(&sizing_strategy, physical_size, initial_scale_factor); - - let connection = Rc::new(xcb_connection); + let connection = Rc::new(connection); #[cfg(feature = "opengl")] let visual_info = - WindowVisualConfig::find_best_visual_config_for_gl(&connection, options.gl_config)?; + WindowVisualConfig::find_best_visual_config_for_gl(&connection, settings.gl_config)?; #[cfg(not(feature = "opengl"))] let visual_info = WindowVisualConfig::find_best_visual_config(&connection)?; - let will_have_parent = options.parent.is_some() || options.wait_for_parent; - let parent_id = options.parent.map(|p| p.inner.window_id); + settings.parent.is_some() || settings.wait_for_parent; + let parent_id = settings.parent.map(|p| p.inner.window_id); let xcb_window = - XcbWindow::new(Rc::clone(&connection), physical_size, &visual_info, parent_id)?; - - if will_have_parent { - connection.register_tree_structure_events()?.check()?; - } + XcbWindow::new(Rc::clone(&connection), sizing_state.size(), &visual_info, parent_id)?; let cookies = [ - xcb_window.set_title(&options.title)?, + xcb_window.set_title(&settings.title)?, xcb_window.enable_wm_protocols()?, xcb_window.enable_dnd_protocols()?, xcb_window.set_size_hints(size_hints)?, @@ -134,22 +88,16 @@ impl WindowShared { Ok(Rc::new(Self { connection, xcb_window, - parent_id: parent_id.into(), visual_id: visual_info.visual_id, - window_size: physical_size.into(), - scaling_factor: ScalingFactor { - system: scaling.into(), - suggested: options.fallback_scale_factor.into(), - }, - sizing_strategy, mouse_cursor: MouseCursor::default().into(), loop_signal: ev_loop.get_signal(), loop_handle: ev_loop.handle(), is_focused: false.into(), present_state: PresentStateShared::new(), + sizing_state, poll_requested: false.into(), - main_thread_shared: shared, + main_thread_shared: thread_shared, #[cfg(feature = "opengl")] gl_context, @@ -178,20 +126,6 @@ impl WindowShared { Ok(()) } - pub fn store_size(&self, size: PhysicalSize) -> PhysicalSize { - let previous = self.window_size.replace(size); - - if previous != 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(); @@ -225,54 +159,8 @@ impl WindowShared { Ok(()) } - pub fn resize(&self, size: Size) -> Result<()> { - let new_size = self.sizing_strategy.adjust_size(size, self.size()).physical; - - if new_size == self.window_size.get().cast() { - return Ok(()); - } - - self.xcb_window.resize(new_size)?.check()?; - - if !self.sizing_strategy.is_resizable() { - let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); - self.xcb_window.set_size_hints(size_hints)?.check()?; - } - - // This will trigger a `ConfigureNotify` event which will in turn change `self.window_info` - // and notify the window handler about it - - Ok(()) - } - - #[inline] - pub fn create_timer(&self, duration: Duration) -> Result { - insert_timer(&self.loop_handle, duration) - } - - pub fn resize_immediately(&self, new_size: PhysicalSize, handler: &Handler) -> Result<()> { - let previous = self.store_size(new_size); - - if previous == new_size { - return Ok(()); - }; - - if let Err(()) = - handler.resize(WindowSize::from_physical(new_size.cast(), self.scaling_factor.get())) - { - self.store_size(previous); - return Ok(()); - } - - self.xcb_window.resize(new_size.cast())?.check()?; // Will not call handler, as size is the same as above. - if !self.sizing_strategy.is_resizable() { - let size_hints = get_size_hints(&self.sizing_strategy, new_size, self.scale_factor()); - self.xcb_window.set_size_hints(size_hints)?.check()?; - } - - // These come from the Host, no need to notify it about the new size - - Ok(()) + pub fn resize(&self, new_size: Size) -> Result<()> { + self.sizing_state.resize_from_handler(new_size, &self.xcb_window) } pub fn window_handle(&self) -> Option> { @@ -297,13 +185,17 @@ impl WindowShared { pub fn gl_context(&self) -> Option { Some(crate::gl::GlContext::new(Rc::clone(self.gl_context.as_ref()?))) } + #[inline] + pub fn create_timer(&self, duration: Duration) -> Result { + insert_timer(&self.loop_handle, duration) + } pub fn scale_factor(&self) -> f64 { - self.scaling_factor.get() + self.sizing_state.scale_factor() } pub fn size(&self) -> WindowSize { - WindowSize::from_physical(self.window_size.get().cast(), self.scaling_factor.get()) + self.sizing_state.window_size() } pub fn raw_id(&self) -> xproto::Window { diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 49bda960..89c13c1a 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -3,6 +3,7 @@ use crate::dpi::{PhysicalSize, Size}; use crate::handler::WindowHandlerBuilder; use crate::host::HostCallbacks; use crate::platform::x11::event_loop::{EventLoop, MainThreadCaller}; +use crate::platform::x11::sizing::SizingThreadShared; use crate::platform::x11::window_shared::WindowShared; use crate::utils::SizingStrategy; use crate::warn; @@ -20,12 +21,12 @@ use std::time::{Duration, Instant}; pub(crate) struct WindowThreadShared { stopped: AtomicBool, - scaling_factor: AtomicU64, - size: AtomicU32, + + pub sizing: SizingThreadShared, + final_error: Mutex>, stopped_requested_from_host: AtomicBool, poll_requested: AtomicBool, - sizing_strategy: OnceLock, redraw_requested_after: Mutex>, } @@ -63,45 +64,15 @@ impl WindowThreadShared { Self { stopped: false.into(), final_error: None.into(), - size: 0.into(), - scaling_factor: 0.into(), stopped_requested_from_host: false.into(), - sizing_strategy: OnceLock::new(), redraw_requested_after: None.into(), poll_requested: false.into(), + sizing: SizingThreadShared::new(), } } fn init(&self, window: &WindowShared) { - self.set_size(window.get_size()); - self.set_scaling_factor(window.scale_factor()); - let Ok(()) = self.sizing_strategy.set(window.sizing_strategy) else { unreachable!() }; - } - - 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 sizing_strategy(&self) -> SizingStrategy { - self.sizing_strategy.get().copied().unwrap_or_default() - } - - 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); + self.sizing.init(&window.sizing_state) } pub fn is_stop_host_requested(&self) -> bool { @@ -211,10 +182,7 @@ impl WindowThreadHandle { } 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) + self.shared.sizing.window_size() } pub fn resize(&self, size: Size) -> Result<()> { @@ -233,7 +201,7 @@ impl WindowThreadHandle { } pub fn sizing_strategy(&self) -> SizingStrategy { - self.shared.sizing_strategy.get().copied().unwrap_or_default() + self.shared.sizing.sizing_strategy() } pub fn run_until_closed(&self) -> Result<()> { @@ -268,15 +236,15 @@ impl WindowThreadHandle { } pub fn is_resizable(&self) -> bool { - self.shared.sizing_strategy().is_resizable() + self.sizing_strategy().is_resizable() } pub fn min_size(&self) -> Option { - self.shared.sizing_strategy().min_size() + self.sizing_strategy().min_size() } pub fn max_size(&self) -> Option { - self.shared.sizing_strategy().max_size() + self.sizing_strategy().max_size() } pub fn handle_main_thread_callback(&self) { @@ -373,13 +341,21 @@ impl WindowThread { main_thread_caller: Option, ) -> Result { let mut ev_loop = calloop::EventLoop::try_new()?; + let parent_id = options.parent.as_ref().map(|p| p.inner.window_id); let inner = WindowShared::create(options, &ev_loop, Arc::clone(&shared))?; shared.init(&inner); let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; - let event_loop = - EventLoop::new(inner, handler, receiver, sender, main_thread_caller, &mut ev_loop)?; + let event_loop = EventLoop::new( + inner, + handler, + parent_id, + receiver, + sender, + main_thread_caller, + &mut ev_loop, + )?; Ok(Self { event_loop, ev_loop, shared }) } diff --git a/src/platform/x11/x11_connection.rs b/src/platform/x11/x11_connection.rs index 920661db..f61d7b9b 100644 --- a/src/platform/x11/x11_connection.rs +++ b/src/platform/x11/x11_connection.rs @@ -60,7 +60,7 @@ pub struct X11Connection { } impl X11Connection { - pub fn new() -> Result { + pub fn connect() -> Result { let conn = XlibXcbConnection::open()?; let screen = conn.default_screen_index(); let xcb_conn = conn.xcb_connection(); From 59896171e1d921c2f4fff12068ea3967e707a767 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 11:45:06 +0200 Subject: [PATCH 03/10] More cleanup --- src/context.rs | 16 ++-- src/gl.rs | 5 +- src/platform/x11/cursor.rs | 15 ++-- src/platform/x11/error.rs | 8 +- src/platform/x11/gl.rs | 76 +++------------- src/platform/x11/gl/egl.rs | 12 +-- src/platform/x11/gl/error.rs | 62 +++++++++++++ src/platform/x11/gl/glx.rs | 14 ++- src/platform/x11/mod.rs | 58 +++++++++---- src/platform/x11/sizing.rs | 86 ++++++++++--------- src/platform/x11/visual_info.rs | 8 +- src/platform/x11/window.rs | 7 -- src/platform/x11/window_shared.rs | 39 +++++---- src/platform/x11/window_thread.rs | 28 +++--- src/platform/x11/x11_connection.rs | 9 +- src/platform/x11/x11_connection/size_hints.rs | 29 ------- src/platform/x11/xcb_window.rs | 10 +-- src/wrappers/egl.rs | 4 +- src/wrappers/egl/config.rs | 4 +- src/wrappers/egl/display.rs | 16 ++-- src/wrappers/egl/sys.rs | 10 +-- src/wrappers/glx.rs | 26 +++--- src/wrappers/xkbcommon.rs | 3 +- src/wrappers/xlib/xlib_connection.rs | 5 +- src/wrappers/xlib/xlib_xcb.rs | 8 +- 25 files changed, 264 insertions(+), 294 deletions(-) create mode 100644 src/platform/x11/gl/error.rs delete mode 100644 src/platform/x11/window.rs delete mode 100644 src/platform/x11/x11_connection/size_hints.rs diff --git a/src/context.rs b/src/context.rs index 0ad95034..3c75b18d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -9,7 +9,7 @@ use raw_window_handle::{ use std::fmt::Debug; use std::time::Duration; -/// A handle to the window given to a [`WindowHandler`](crate::WindowHandler), which it can then +/// A handle to the window given to a [`WindowHandler`](WindowHandler), which it can then /// use to perform various operations on the window itself. #[derive(Clone)] pub struct WindowContext { @@ -31,7 +31,7 @@ impl WindowContext { /// /// This request is not immediate. When exactly it will be closed is platform-dependent. /// - /// However, it is guaranteed to only get closed after all [`WindowHandler`](crate::WindowHandler) + /// However, it is guaranteed to only get closed after all [`WindowHandler`](WindowHandler) /// methods are completed, and soon enough to be perceived as instantaneous by the user. pub fn request_close(&self) { self.inner.request_close(); @@ -88,11 +88,11 @@ impl WindowContext { PlatformHandle { inner: self.inner.platform_handle() } } - /// Returns the [`GlContext`](crate::gl::GlContext) associated to this window. + /// Returns the [`GlContext`](gl::GlContext) associated to this window. /// /// If the window was not created with a GL context, this will return [`None`]. #[cfg(feature = "opengl")] - pub fn gl_context(&self) -> Option { + pub fn gl_context(&self) -> Option { self.inner.gl_context() } @@ -131,13 +131,13 @@ impl WindowContext { } impl HasWindowHandle for WindowContext { - fn window_handle(&self) -> core::result::Result, HandleError> { + fn window_handle(&self) -> Result, HandleError> { self.inner.window_handle().ok_or(HandleError::Unavailable) } } impl HasDisplayHandle for WindowContext { - fn display_handle(&self) -> core::result::Result, HandleError> { + fn display_handle(&self) -> Result, HandleError> { Ok(self.inner.display_handle()) } } @@ -177,13 +177,13 @@ const _: () = { }; impl HasWindowHandle for PlatformHandle { - fn window_handle(&self) -> core::result::Result, HandleError> { + fn window_handle(&self) -> Result, HandleError> { self.inner.window_handle().ok_or(HandleError::Unavailable) } } impl HasDisplayHandle for PlatformHandle { - fn display_handle(&self) -> core::result::Result, HandleError> { + fn display_handle(&self) -> Result, HandleError> { Ok(self.inner.display_handle()) } } diff --git a/src/gl.rs b/src/gl.rs index 5acae154..e1f5f273 100644 --- a/src/gl.rs +++ b/src/gl.rs @@ -1,3 +1,4 @@ +use crate::platform::gl::PlatformGlContext; use std::ffi::{c_void, CStr, CString}; use std::marker::PhantomData; @@ -54,13 +55,13 @@ pub enum Profile { #[derive(Clone)] pub struct GlContext { - inner: crate::platform::gl::GlContext, + inner: PlatformGlContext, // To make sure this is !Send, !Sync, and !UnwindSafe on all platforms phantom: PhantomData<(*mut (), &'static mut ())>, } impl GlContext { - pub(crate) fn new(context: crate::platform::gl::GlContext) -> GlContext { + pub(crate) fn new(context: PlatformGlContext) -> GlContext { GlContext { inner: context, phantom: PhantomData } } diff --git a/src/platform/x11/cursor.rs b/src/platform/x11/cursor.rs index 1cf85faf..bc6536fa 100644 --- a/src/platform/x11/cursor.rs +++ b/src/platform/x11/cursor.rs @@ -1,13 +1,8 @@ -use x11rb::connection::Connection; -use x11rb::cursor::Handle as CursorHandle; -use x11rb::protocol::xproto::{ConnectionExt as _, Cursor}; -use x11rb::xcb_ffi::XCBConnection; - -use crate::platform::*; +use super::prelude::*; use crate::wrappers::xlib::XlibXcbConnection; use crate::MouseCursor; -fn create_empty_cursor(conn: &XlibXcbConnection) -> Result { +fn create_empty_cursor(conn: &XlibXcbConnection) -> PlatformResult { let cursor_id = conn.generate_id()?; let pixmap_id = conn.generate_id()?; let root_window = conn.default_screen().root; @@ -20,7 +15,7 @@ fn create_empty_cursor(conn: &XlibXcbConnection) -> Result { fn load_cursor( conn: &XCBConnection, cursor_handle: &CursorHandle, name: &str, -) -> Result> { +) -> PlatformResult> { let cursor = cursor_handle.load_cursor(conn, name)?; if cursor != x11rb::NONE { Ok(Some(cursor)) @@ -31,7 +26,7 @@ fn load_cursor( fn load_first_existing_cursor( conn: &XCBConnection, cursor_handle: &CursorHandle, names: &[&str], -) -> Result> { +) -> PlatformResult> { for name in names { let cursor = load_cursor(conn, cursor_handle, name)?; if cursor.is_some() { @@ -44,7 +39,7 @@ fn load_first_existing_cursor( pub(crate) fn get_xcursor( conn: &XlibXcbConnection, cursor_handle: &CursorHandle, cursor: MouseCursor, -) -> Result { +) -> PlatformResult { let load = |name: &str| load_cursor(conn, cursor_handle, name); let loadn = |names: &[&str]| load_first_existing_cursor(conn, cursor_handle, names); diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index df44b07b..83bba98e 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -1,7 +1,7 @@ +use super::prelude::*; use crate::platform::x11::drag_n_drop::ParseError; use crate::platform::x11::window_thread::RequestFailed; use crate::platform::x11::x11_connection::GetPropertyError; -use crate::warn; use crate::wrappers::xlib::{DisplayOpenFailedError, InitThreadsFailedError}; use crate::HandlerError; use std::fmt::{Display, Formatter}; @@ -70,7 +70,7 @@ pub enum PlatformError { #[cfg(feature = "opengl")] EGl(crate::wrappers::egl::EglError), #[cfg(feature = "opengl")] - Gl(super::gl::CreationFailedError), + Gl(super::gl::GlCreationFailedError), } impl Display for PlatformError { @@ -234,8 +234,8 @@ impl From for PlatformError { } #[cfg(feature = "opengl")] -impl From for PlatformError { - fn from(value: super::gl::CreationFailedError) -> Self { +impl From for PlatformError { + fn from(value: super::gl::GlCreationFailedError) -> Self { Self::Gl(value) } } diff --git a/src/platform/x11/gl.rs b/src/platform/x11/gl.rs index 5dfe68a5..538d57f6 100644 --- a/src/platform/x11/gl.rs +++ b/src/platform/x11/gl.rs @@ -1,4 +1,4 @@ -use super::*; +use super::prelude::*; use crate::gl::*; use crate::wrappers::glx::*; use crate::wrappers::xlib::XLibError; @@ -7,73 +7,17 @@ use std::error::Error; use crate::platform::gl::egl::EglGlContext; use crate::platform::gl::glx::GlxGlContext; use crate::platform::x11::xcb_window::XcbWindow; -use crate::wrappers::egl::{EglConfig, EglDisplay, EglError, EglVersion, MissingSymbolError}; +use crate::wrappers::egl::*; use std::ffi::{c_void, CStr}; use std::rc::Rc; -use x11_dl::error::OpenError; use x11rb::protocol::xproto::Visualid; mod egl; +mod error; mod glx; -#[derive(Debug)] -pub enum CreationFailedError { - NoValidFBConfig, - NoVisual, - GetProcAddressFailed, - MakeCurrentFailed, - ContextCreationFailed, - X11Error(XLibError), - OpenError(OpenError), - EGLLoadError(libloading::Error), - EGLMissingSymbol(MissingSymbolError), - EglError(EglError), - EglNoDisplay, - EglUnsupportedVersion(EglVersion), - EglUnknownVisualId(Visualid), - EglInvalidVisualId(i32, TryFromIntError), -} - -impl Display for CreationFailedError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CreationFailedError::NoValidFBConfig => { - f.write_str("Could not find a valid Framebuffer configuration") - } - CreationFailedError::NoVisual => { - f.write_str("Could not find a matching visual configuration") - } - CreationFailedError::GetProcAddressFailed => f.write_str("GetProcAddress failed"), - CreationFailedError::MakeCurrentFailed => f.write_str("MakeCurrent failed"), - CreationFailedError::ContextCreationFailed => f.write_str("Faile to create GL context"), - CreationFailedError::X11Error(e) => e.fmt(f), - CreationFailedError::OpenError(e) => e.fmt(f), - CreationFailedError::EGLLoadError(e) => { - write!(f, "Could not load EGL library: {e}, {:?}", e.source()) - } - CreationFailedError::EGLMissingSymbol(e) => e.fmt(f), - CreationFailedError::EglError(e) => e.fmt(f), - CreationFailedError::EglNoDisplay => f.write_str("EGL returned no valid display"), - CreationFailedError::EglUnsupportedVersion(e) => { - write!(f, "Unsupported EGL version: {}.{} (EGL 1.5 is required)", e.major, e.minor) - } - CreationFailedError::EglInvalidVisualId(id, e) => { - write!(f, "Invalid Visual ID ({id}) returned by EGL: {e}") - } - CreationFailedError::EglUnknownVisualId(id) => { - write!(f, "Unknown Visual ID returned by EGL: {id}") - } - } - } -} - -impl From for CreationFailedError { - fn from(err: EglError) -> Self { - CreationFailedError::EglError(err) - } -} - -pub type GlContext = Rc; +pub use error::GlCreationFailedError; +pub type PlatformGlContext = Rc; pub enum GlContextInner { Glx(GlxGlContext), @@ -110,7 +54,7 @@ impl GlContextInner { /// Use [Self::get_fb_config_and_visual] to create both of these things. pub fn create( window: &XcbWindow, connection: &Rc, fb_config: FbConfig, - ) -> Result> { + ) -> PlatformResult> { let inner = match fb_config.fb_config { FbConfigInner::Glx { glx, config } => GlContextInner::Glx(GlxGlContext::create( @@ -133,19 +77,19 @@ impl GlContextInner { /// using the visual also returned from this function. pub fn get_fb_config_and_visual( connection: &Rc, config: GlConfig, - ) -> Result<(FbConfig, WindowConfig)> { + ) -> PlatformResult<(FbConfig, WindowConfig)> { EglGlContext::get_fb_config_and_visual(connection, config.clone()) .or_else(|_| GlxGlContext::get_fb_config_and_visual(connection, config)) } - pub unsafe fn make_current(&self) -> Result<()> { + pub unsafe fn make_current(&self) -> PlatformResult<()> { match self { GlContextInner::Glx(glx) => glx.make_current(), GlContextInner::Egl(egl) => egl.make_current(), } } - pub unsafe fn make_not_current(&self) -> Result<()> { + pub unsafe fn make_not_current(&self) -> PlatformResult<()> { match self { GlContextInner::Glx(glx) => glx.make_not_current(), GlContextInner::Egl(egl) => egl.make_not_current(), @@ -159,7 +103,7 @@ impl GlContextInner { } } - pub fn swap_buffers(&self) -> Result<()> { + pub fn swap_buffers(&self) -> PlatformResult<()> { match self { GlContextInner::Glx(glx) => glx.swap_buffers(), GlContextInner::Egl(egl) => egl.swap_buffers(), diff --git a/src/platform/x11/gl/egl.rs b/src/platform/x11/gl/egl.rs index 18930ba5..4505a102 100644 --- a/src/platform/x11/gl/egl.rs +++ b/src/platform/x11/gl/egl.rs @@ -1,11 +1,6 @@ -use crate::gl::GlConfig; -use crate::platform::gl::{CreationFailedError, FbConfig, FbConfigInner, WindowConfig}; +use super::*; use crate::platform::x11::xcb_window::XcbWindow; -use crate::platform::{PlatformError, X11Connection}; -use crate::wrappers::egl::{Egl, EglConfig, EglContext, EglDisplay, EglSurface}; use std::ffi::{c_void, CStr}; -use std::rc::Rc; -use x11rb::protocol::xproto::Visualid; pub struct EglGlContext { surface: EglSurface, @@ -32,11 +27,12 @@ impl EglGlContext { let egl = Egl::open()?; let display = egl.create_display(connection)?; - let config = display.choose_config(&gl_config)?.ok_or(CreationFailedError::EglNoDisplay)?; + let config = + display.choose_config(&gl_config)?.ok_or(GlCreationFailedError::EglNoDisplay)?; let visual = config.get_visual_id(&display)?; let depth = Self::find_visual_depth_for_id(connection, visual) - .ok_or(CreationFailedError::EglUnknownVisualId(visual))?; + .ok_or(GlCreationFailedError::EglUnknownVisualId(visual))?; let window_config = WindowConfig { depth, visual }; let fb_config = FbConfig { gl_config, fb_config: FbConfigInner::Egl { display, config } }; diff --git a/src/platform/x11/gl/error.rs b/src/platform/x11/gl/error.rs new file mode 100644 index 00000000..e0e5e0f4 --- /dev/null +++ b/src/platform/x11/gl/error.rs @@ -0,0 +1,62 @@ +use super::*; +use crate::wrappers::xlib::XLibError; + +use std::fmt::{Display, Formatter}; +use std::num::TryFromIntError; +use x11_dl::error::OpenError; +use x11rb::protocol::xproto::Visualid; + +#[derive(Debug)] +pub enum GlCreationFailedError { + NoValidFBConfig, + NoVisual, + GetProcAddressFailed, + MakeCurrentFailed, + ContextCreationFailed, + X11Error(XLibError), + OpenError(OpenError), + EGLLoadError(libloading::Error), + EGLMissingSymbol(MissingSymbolError), + Egl(EglError), + EglNoDisplay, + EglUnsupportedVersion(EglVersion), + EglUnknownVisualId(Visualid), + EglInvalidVisualId(i32, TryFromIntError), +} + +use GlCreationFailedError::*; + +impl Display for GlCreationFailedError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + NoValidFBConfig => f.write_str("Could not find a valid Framebuffer configuration"), + NoVisual => f.write_str("Could not find a matching visual configuration"), + GetProcAddressFailed => f.write_str("GetProcAddress failed"), + MakeCurrentFailed => f.write_str("MakeCurrent failed"), + ContextCreationFailed => f.write_str("Failed to create GL context"), + X11Error(e) => e.fmt(f), + OpenError(e) => e.fmt(f), + EGLLoadError(e) => { + write!(f, "Could not load EGL library: {e}, {:?}", e.source()) + } + EGLMissingSymbol(e) => e.fmt(f), + Egl(e) => e.fmt(f), + EglNoDisplay => f.write_str("EGL returned no valid display"), + EglUnsupportedVersion(e) => { + write!(f, "Unsupported EGL version: {}.{} (EGL 1.5 is required)", e.major, e.minor) + } + EglInvalidVisualId(id, e) => { + write!(f, "Invalid Visual ID ({id}) returned by EGL: {e}") + } + EglUnknownVisualId(id) => { + write!(f, "Unknown Visual ID returned by EGL: {id}") + } + } + } +} + +impl From for GlCreationFailedError { + fn from(err: EglError) -> Self { + Egl(err) + } +} diff --git a/src/platform/x11/gl/glx.rs b/src/platform/x11/gl/glx.rs index 26896b23..eee3a744 100644 --- a/src/platform/x11/gl/glx.rs +++ b/src/platform/x11/gl/glx.rs @@ -1,8 +1,6 @@ use super::*; use crate::gl::GlConfig; -use crate::platform::gl::CreationFailedError; use crate::platform::x11::xcb_window::XcbWindow; -use crate::platform::X11Connection; use crate::wrappers::glx::{Glx, GlxFbConfig}; use crate::wrappers::xlib::XErrorHandler; use std::ffi::{c_ulong, c_void, CStr}; @@ -21,12 +19,12 @@ impl GlxGlContext { pub fn create( window: &XcbWindow, connection: &Rc, gl_config: GlConfig, fb_config: GlxFbConfig, glx: Glx, - ) -> Result { + ) -> PlatformResult { let xlib_connection = connection.conn.xlib_connection(); XErrorHandler::handle(xlib_connection, |error_handler| { let Some(create_context) = glx.get_glx_create_context_attribs_arb() else { - return Err(CreationFailedError::GetProcAddressFailed.into()); + return Err(GlCreationFailedError::GetProcAddressFailed.into()); }; let context = @@ -38,7 +36,7 @@ impl GlxGlContext { pub fn get_fb_config_and_visual( connection: &X11Connection, config: GlConfig, - ) -> Result<(FbConfig, WindowConfig)> { + ) -> PlatformResult<(FbConfig, WindowConfig)> { let glx = Glx::open()?; let xlib_connection = connection.conn.xlib_connection(); @@ -61,7 +59,7 @@ impl GlxGlContext { }) } - pub unsafe fn make_current(&self) -> Result<()> { + pub unsafe fn make_current(&self) -> PlatformResult<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { self.glx.make_current( self.connection.conn.xlib_connection(), @@ -72,7 +70,7 @@ impl GlxGlContext { }) } - pub unsafe fn make_not_current(&self) -> Result<()> { + pub unsafe fn make_not_current(&self) -> PlatformResult<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { self.glx.clear_current(self.connection.conn.xlib_connection(), error_handler) }) @@ -89,7 +87,7 @@ impl GlxGlContext { } } - pub fn swap_buffers(&self) -> Result<()> { + pub fn swap_buffers(&self) -> PlatformResult<()> { XErrorHandler::handle(self.connection.conn.xlib_connection(), |error_handler| { self.glx.swap_buffers( self.connection.conn.xlib_connection(), diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index f1e944ce..7f835d40 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -1,16 +1,9 @@ -use raw_window_handle::{ - DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, XcbWindowHandle, -}; -use std::fmt::{Display, Formatter}; -use std::num::{NonZero, NonZeroU32, TryFromIntError}; -use std::rc::Rc; -use std::sync::Arc; -pub(crate) use x11_connection::X11Connection; - mod cursor; mod drag_n_drop; mod error; mod event_loop; +#[cfg(feature = "opengl")] +pub mod gl; mod handler; mod keyboard; mod present; @@ -20,27 +13,52 @@ mod xcb_window; mod timer; mod waker; -mod window; mod window_shared; mod window_thread; mod x11_connection; mod xcb_window; -pub use error::{CookieExt as _, PlatformError}; -pub(crate) type Result = std::result::Result; +pub(crate) mod prelude { + pub use super::error::{CookieExt as _, FatalError, PlatformError, ReplyExt as _}; + pub use super::event_loop::*; + pub use super::handler::Handler; + pub use super::sizing::*; + pub use super::window_shared::WindowShared; + pub use super::window_thread::{HostCallback, WindowThreadHandle}; + pub use super::x11_connection::X11Connection; + pub use super::xcb_window::XcbWindow; + pub use crate::tracing::*; + pub use crate::PlatformHandle; + pub use dpi::*; + pub use std::rc::Rc; + pub use std::sync::Arc; + pub use x11rb::connection::Connection; + pub use x11rb::cursor::Handle as CursorHandle; + pub use x11rb::errors::{ConnectionError, ReplyOrIdError}; + pub use x11rb::protocol::xproto::{ConnectionExt as _, Cursor}; + pub use x11rb::xcb_ffi::XCBConnection; + pub type PlatformResult = Result; + #[cfg(feature = "opengl")] + pub use super::gl::{GlContextInner, GlCreationFailedError, PlatformGlContext}; + #[cfg(feature = "opengl")] + pub use crate::gl::{GlConfig, GlContext}; +} + +use prelude::*; -use crate::platform::x11::window_shared::WindowShared; use crate::wrappers::xlib::XlibXcbConnection; -pub use window::*; +use raw_window_handle::{ + DisplayHandle, HandleError, HasWindowHandle, RawWindowHandle, XcbWindowHandle, +}; +use std::fmt::{Display, Formatter}; +use std::num::{NonZero, NonZeroU32, TryFromIntError}; pub type WindowContext = Rc; +pub use error::PlatformError; pub use present::DamageRect; pub use timer::TimerHandle; pub use waker::WindowWaker; -#[cfg(feature = "opengl")] -pub mod gl; - #[derive(Clone)] pub struct PlatformHandle { connection: Arc, @@ -129,3 +147,9 @@ impl Display for ParentWindowHandleError { pub fn assume_standalone_in_process() { // No-op on X11 } + +pub type WindowHandle = WindowThreadHandle; + +pub fn copy_to_clipboard(_data: &str) { + unimplemented!() +} diff --git a/src/platform/x11/sizing.rs b/src/platform/x11/sizing.rs index 5d1c333e..a18010b7 100644 --- a/src/platform/x11/sizing.rs +++ b/src/platform/x11/sizing.rs @@ -1,11 +1,4 @@ -use crate::platform::x11::error::{CookieExt, FatalError}; -use crate::platform::x11::event_loop::MainThreadCaller; -use crate::platform::x11::handler::Handler; -use crate::platform::x11::window_shared::WindowShared; -use crate::platform::x11::window_thread::HostCallback; -use crate::platform::x11::x11_connection::get_size_hints; -use crate::platform::x11::xcb_window::XcbWindow; -use crate::platform::{PlatformError, X11Connection}; +use super::prelude::*; use crate::utils::SizingStrategy; use crate::{WindowSettings, WindowSize}; use dpi::{PhysicalSize, Size}; @@ -109,7 +102,7 @@ impl SizingState { pub fn handle_host_resize( &mut self, new_size: Size, handler: &Handler, shared: &WindowShared, ) -> Result<(), PlatformError> { - let scale_factor = shared.sizing_state.scaling_factor.get(); + let scale_factor = shared.sizing_state.scale_factor(); let new_size = new_size.to_physical(scale_factor); shared.sizing_state.resize_from_host(new_size, handler, shared) @@ -118,8 +111,10 @@ impl SizingState { pub fn handle_host_suggest_scale_factor( &mut self, scale: f64, handler: &Handler, shared: &WindowShared, ) -> Result<(), PlatformError> { + shared.sizing_state.host_suggested_scale_factor.set(Some(scale)); + // If the scaling factor is already provided by the system, do nothing - if !shared.sizing_state.scaling_factor.suggest(scale) { + if shared.sizing_state.system_scale_factor.get().is_some() { return Ok(()); }; @@ -133,7 +128,9 @@ impl SizingState { pub struct SizingStateShared { window_size: Cell>, sizing_strategy: SizingStrategy, - scaling_factor: ScalingFactor, + + system_scale_factor: Cell>, + host_suggested_scale_factor: Cell>, } impl SizingStateShared { @@ -152,15 +149,21 @@ impl SizingStateShared { Ok(Self { sizing_strategy, window_size: window_size.into(), - scaling_factor: ScalingFactor { - system: scaling.into(), - suggested: settings.fallback_scale_factor.into(), - }, + system_scale_factor: scaling.into(), + host_suggested_scale_factor: settings.fallback_scale_factor.into(), }) } pub fn scale_factor(&self) -> f64 { - self.scaling_factor.get() + if let Some(factor) = self.system_scale_factor.get() { + return factor; + }; + + if let Some(factor) = self.host_suggested_scale_factor.get() { + return factor; + } + + 1.0 } pub fn size(&self) -> PhysicalSize { @@ -168,11 +171,11 @@ impl SizingStateShared { } pub fn window_size(&self) -> WindowSize { - WindowSize::from_physical(self.window_size.get().cast(), self.scaling_factor.get()) + WindowSize::from_physical(self.window_size.get().cast(), self.scale_factor()) } pub fn make_size_hints(&self) -> WmSizeHints { - get_size_hints(&self.sizing_strategy, self.window_size.get(), self.scaling_factor.get()) + get_size_hints(&self.sizing_strategy, self.window_size.get(), self.scale_factor()) } pub fn store_size( @@ -187,9 +190,7 @@ impl SizingStateShared { previous } - pub fn resize_from_handler( - &self, size: Size, window: &XcbWindow, - ) -> crate::platform::Result<()> { + pub fn resize_from_handler(&self, size: Size, window: &XcbWindow) -> PlatformResult<()> { let new_size = self.sizing_strategy.adjust_size(size, self.window_size()).physical; if new_size == self.window_size.get().cast() { @@ -211,7 +212,7 @@ impl SizingStateShared { pub fn resize_from_host( &self, new_size: PhysicalSize, handler: &Handler, shared: &WindowShared, - ) -> crate::platform::Result<()> { + ) -> PlatformResult<()> { let previous = self.store_size(new_size, &shared.main_thread_shared.sizing); if previous == new_size { @@ -219,7 +220,7 @@ impl SizingStateShared { }; if let Err(()) = - handler.resize(WindowSize::from_physical(new_size.cast(), self.scaling_factor.get())) + handler.resize(WindowSize::from_physical(new_size.cast(), self.scale_factor())) { self.store_size(previous, &shared.main_thread_shared.sizing); return Ok(()); @@ -251,7 +252,7 @@ impl SizingThreadShared { pub fn init(&self, state: &SizingStateShared) { let Ok(()) = self.sizing_strategy.set(state.sizing_strategy) else { unreachable!() }; self.set_size(state.size()); - self.set_scaling_factor(state.scaling_factor.get()); + self.set_scaling_factor(state.scale_factor()); } pub fn get_scaling_factor(&self) -> f64 { @@ -288,27 +289,28 @@ impl SizingThreadShared { } } -pub struct ScalingFactor { - system: Cell>, - suggested: Cell>, -} +pub fn get_size_hints( + strategy: &SizingStrategy, current_size: PhysicalSize, scale_factor: f64, +) -> WmSizeHints { + let mut size_hints = WmSizeHints::default(); -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; + match strategy { + SizingStrategy::Fixed => { + size_hints.min_size = Some(to_size_hint(current_size)); + size_hints.max_size = size_hints.min_size; + } + SizingStrategy::Resizable { min_size, max_size } => { + size_hints.min_size = + min_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); + size_hints.max_size = + max_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); } - - 1.0 } - pub fn suggest(&self, value: f64) -> bool { - self.suggested.set(Some(value)); + size_hints +} - self.system.get().is_none() - } +fn to_size_hint(size: PhysicalSize) -> (i32, i32) { + let size = size.cast(); + (size.width, size.height) } diff --git a/src/platform/x11/visual_info.rs b/src/platform/x11/visual_info.rs index 1d123c8f..56f2d744 100644 --- a/src/platform/x11/visual_info.rs +++ b/src/platform/x11/visual_info.rs @@ -1,5 +1,5 @@ +use super::prelude::*; use super::x11_connection::X11Connection; -use crate::platform::*; use x11rb::connection::Connection; use x11rb::protocol::xproto::{ Colormap, ColormapAlloc, ConnectionExt, Screen, VisualClass, Visualid, @@ -20,7 +20,7 @@ impl WindowVisualConfig { #[cfg(feature = "opengl")] pub fn find_best_visual_config_for_gl( connection: &std::rc::Rc, gl_config: Option, - ) -> Result { + ) -> PlatformResult { let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) }; let (fb_config, window_config) = @@ -34,7 +34,7 @@ impl WindowVisualConfig { }) } - pub fn find_best_visual_config(connection: &X11Connection) -> Result { + pub fn find_best_visual_config(connection: &X11Connection) -> PlatformResult { match find_visual_for_depth(connection.default_screen(), 32) { None => Ok(Self::copy_from_parent()), Some(visual_id) => Ok(Self { @@ -60,7 +60,7 @@ impl WindowVisualConfig { // For this 32-bit depth to work, you also need to define a color map and set a border // pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818 -fn create_color_map(connection: &X11Connection, visual_id: Visualid) -> Result { +fn create_color_map(connection: &X11Connection, visual_id: Visualid) -> PlatformResult { let colormap = connection.conn.generate_id()?; connection.conn.create_colormap( ColormapAlloc::NONE, diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs deleted file mode 100644 index f8737f5d..00000000 --- a/src/platform/x11/window.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::platform::x11::window_thread::WindowThreadHandle; - -pub type WindowHandle = WindowThreadHandle; - -pub fn copy_to_clipboard(_data: &str) { - unimplemented!() -} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 4b49a562..4d15fa6f 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,3 +1,4 @@ +use super::prelude::*; use crate::platform::x11::event_loop::EventLoop; use crate::platform::x11::present::PresentStateShared; use crate::platform::x11::sizing::SizingStateShared; @@ -6,7 +7,6 @@ use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::waker::WindowWaker; use crate::platform::x11::window_thread::WindowThreadShared; use crate::platform::x11::xcb_window::XcbWindow; -use crate::platform::*; use crate::{MouseCursor, WindowSettings, WindowSize}; use calloop::{LoopHandle, LoopSignal}; use dpi::Size; @@ -20,31 +20,32 @@ use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt, InputFoc use x11rb::CURRENT_TIME; /// Data that is shared between the event loop and the window handler. -pub(crate) struct WindowShared { +pub struct WindowShared { #[cfg(feature = "opengl")] - gl_context: Option, + gl_context: Option, - pub(crate) xcb_window: XcbWindow, - pub(crate) connection: Rc, + pub xcb_window: XcbWindow, + pub connection: Rc, + visual_id: Visualid, mouse_cursor: Cell, - pub(crate) visual_id: Visualid, + pub poll_requested: Cell, + pub is_focused: Cell, - pub(crate) is_focused: Cell, pub present_state: PresentStateShared, pub sizing_state: SizingStateShared, - pub(crate) poll_requested: Cell, - pub(crate) loop_signal: LoopSignal, + + loop_signal: LoopSignal, loop_handle: LoopHandle<'static, EventLoop>, - pub(crate) main_thread_shared: Arc, + pub main_thread_shared: Arc, } impl WindowShared { pub(crate) fn create( settings: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, thread_shared: Arc, - ) -> Result> { + ) -> PlatformResult> { let connection = X11Connection::connect()?; let sizing_state = SizingStateShared::load(&connection, &thread_shared.sizing, &settings)?; @@ -81,7 +82,7 @@ impl WindowShared { None => None, Some(fb_config) => { // Because of the visual negotation we had to take some extra steps to create this context - Some(super::gl::GlContextInner::create(&xcb_window, &connection, fb_config)?) + Some(GlContextInner::create(&xcb_window, &connection, fb_config)?) } }; @@ -104,7 +105,7 @@ impl WindowShared { })) } - pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> Result<()> { + pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> PlatformResult<()> { if self.mouse_cursor.get() == mouse_cursor { return Ok(()); } @@ -150,7 +151,7 @@ impl WindowShared { self.is_focused.get() } - pub fn focus(&self) -> Result<()> { + pub fn focus(&self) -> PlatformResult<()> { self.connection .conn .set_input_focus(InputFocus::POINTER_ROOT, self.xcb_window.id(), CURRENT_TIME)? @@ -159,7 +160,7 @@ impl WindowShared { Ok(()) } - pub fn resize(&self, new_size: Size) -> Result<()> { + pub fn resize(&self, new_size: Size) -> PlatformResult<()> { self.sizing_state.resize_from_handler(new_size, &self.xcb_window) } @@ -173,8 +174,8 @@ impl WindowShared { self.connection.conn.xlib_display_handle() } - pub fn platform_handle(&self) -> PlatformHandle { - PlatformHandle { + pub fn platform_handle(&self) -> super::PlatformHandle { + super::PlatformHandle { connection: Arc::clone(&self.connection.conn), window_id: self.xcb_window.id(), visual_id: self.visual_id, @@ -182,8 +183,8 @@ impl WindowShared { } #[cfg(feature = "opengl")] - pub fn gl_context(&self) -> Option { - Some(crate::gl::GlContext::new(Rc::clone(self.gl_context.as_ref()?))) + pub fn gl_context(&self) -> Option { + Some(GlContext::new(Rc::clone(self.gl_context.as_ref()?))) } #[inline] pub fn create_timer(&self, duration: Duration) -> Result { diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 89c13c1a..ad9af5ad 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -1,5 +1,5 @@ use super::*; -use crate::dpi::{PhysicalSize, Size}; +use crate::dpi::Size; use crate::handler::WindowHandlerBuilder; use crate::host::HostCallbacks; use crate::platform::x11::event_loop::{EventLoop, MainThreadCaller}; @@ -13,8 +13,8 @@ use calloop::LoopSignal; use std::cell::{Cell, RefCell}; use std::panic::resume_unwind; use std::rc::Rc; -use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; -use std::sync::{mpsc, Mutex, OnceLock}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Mutex}; use std::thread; use std::thread::JoinHandle; use std::time::{Duration, Instant}; @@ -134,7 +134,7 @@ pub struct WindowThreadHandle { } impl WindowThreadHandle { - pub fn create_window(init: WindowInitializer) -> Result { + pub fn create_window(init: WindowInitializer) -> PlatformResult { let (tx, rx) = result_channel(); let shared = Arc::new(WindowThreadShared::new()); let (request_sender, request_receiver) = calloop::channel::sync_channel(1); @@ -185,15 +185,15 @@ impl WindowThreadHandle { self.shared.sizing.window_size() } - pub fn resize(&self, size: Size) -> Result<()> { + pub fn resize(&self, size: Size) -> PlatformResult<()> { self.request(WindowThreadRequest::Resize(size)) } - pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<()> { + pub fn suggest_scale_factor(&self, scale_factor: f64) -> PlatformResult<()> { self.request(WindowThreadRequest::SuggestScaleFactor(scale_factor)) } - fn request(&self, req: WindowThreadRequest) -> Result<()> { + fn request(&self, req: WindowThreadRequest) -> PlatformResult<()> { self.request_sender.send(req).map_err(|_| RequestFailed::Send)?; let result = self.response_receiver.recv().map_err(|_| RequestFailed::Recv)?; @@ -204,7 +204,7 @@ impl WindowThreadHandle { self.shared.sizing.sizing_strategy() } - pub fn run_until_closed(&self) -> Result<()> { + pub fn run_until_closed(&self) -> PlatformResult<()> { if !self.shared.stopped.load(Ordering::Relaxed) { self.request(WindowThreadRequest::Show)?; } @@ -223,11 +223,11 @@ impl WindowThreadHandle { Ok(()) } - pub fn show(&self) -> Result<()> { + pub fn show(&self) -> PlatformResult<()> { self.request(WindowThreadRequest::Show) } - pub fn hide(&self) -> Result<()> { + pub fn hide(&self) -> PlatformResult<()> { self.request(WindowThreadRequest::Hide) } @@ -256,11 +256,11 @@ impl WindowThreadHandle { } } - pub fn set_parent(&self, new_parent: ParentWindowHandle) -> Result<()> { + pub fn set_parent(&self, new_parent: ParentWindowHandle) -> PlatformResult<()> { self.request(WindowThreadRequest::SetParent(new_parent)) } - pub fn request_poll(&self) -> Result<()> { + pub fn request_poll(&self) -> PlatformResult<()> { self.shared.request_poll(); self.loop_signal.wakeup(); @@ -339,7 +339,7 @@ impl WindowThread { receiver: calloop::channel::Channel, sender: mpsc::Sender, main_thread_caller: Option, - ) -> Result { + ) -> PlatformResult { let mut ev_loop = calloop::EventLoop::try_new()?; let parent_id = options.parent.as_ref().map(|p| p.inner.window_id); let inner = WindowShared::create(options, &ev_loop, Arc::clone(&shared))?; @@ -398,7 +398,7 @@ impl WindowResultSender { struct WindowResultReceiver(mpsc::Receiver); impl WindowResultReceiver { - pub fn receive(self) -> Result { + pub fn receive(self) -> PlatformResult { let result = self.0.recv().map_err(|_| PlatformError::MainThreadRecvResult)?; match result { diff --git a/src/platform/x11/x11_connection.rs b/src/platform/x11/x11_connection.rs index f61d7b9b..2d0b83e6 100644 --- a/src/platform/x11/x11_connection.rs +++ b/src/platform/x11/x11_connection.rs @@ -1,10 +1,9 @@ use super::cursor; -use crate::platform::*; +use super::prelude::*; use crate::wrappers::xlib::XlibXcbConnection; use crate::MouseCursor; use std::cell::RefCell; use std::collections::hash_map::{Entry, HashMap}; -use std::sync::Arc; use x11rb::connection::RequestConnection; use x11rb::cookie::VoidCookie; use x11rb::cursor::Handle as CursorHandle; @@ -18,8 +17,6 @@ use x11rb::xcb_ffi::XCBConnection; mod get_property; pub use get_property::GetPropertyError; -mod size_hints; -pub use size_hints::get_size_hints; x11rb::atom_manager! { pub Atoms: AtomsCookie { @@ -60,7 +57,7 @@ pub struct X11Connection { } impl X11Connection { - pub fn connect() -> Result { + pub fn connect() -> PlatformResult { let conn = XlibXcbConnection::open()?; let screen = conn.default_screen_index(); let xcb_conn = conn.xcb_connection(); @@ -90,7 +87,7 @@ impl X11Connection { } #[inline] - pub fn get_cursor(&self, cursor: MouseCursor) -> Result { + pub fn get_cursor(&self, cursor: MouseCursor) -> PlatformResult { // PANIC: this function is the only point where we access the cache, and we never call // external functions that may make a reentrant call to this function let mut cursor_cache = self.cursor_cache.borrow_mut(); diff --git a/src/platform/x11/x11_connection/size_hints.rs b/src/platform/x11/x11_connection/size_hints.rs deleted file mode 100644 index eb11cde4..00000000 --- a/src/platform/x11/x11_connection/size_hints.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::utils::SizingStrategy; -use dpi::{PhysicalSize, Pixel}; -use x11rb::properties::WmSizeHints; - -pub fn get_size_hints( - strategy: &SizingStrategy, current_size: PhysicalSize, scale_factor: f64, -) -> WmSizeHints { - let mut size_hints = WmSizeHints::default(); - - match strategy { - SizingStrategy::Fixed => { - size_hints.min_size = Some(to_size_hint(current_size)); - size_hints.max_size = size_hints.min_size; - } - SizingStrategy::Resizable { min_size, max_size } => { - size_hints.min_size = - min_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); - size_hints.max_size = - max_size.map(|s| to_size_hint(s.to_physical::(scale_factor))); - } - } - - size_hints -} - -fn to_size_hint(size: PhysicalSize) -> (i32, i32) { - let size = size.cast(); - (size.width, size.height) -} diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs index e301e8b3..89365072 100644 --- a/src/platform/x11/xcb_window.rs +++ b/src/platform/x11/xcb_window.rs @@ -1,21 +1,15 @@ -use crate::dpi::PhysicalSize; -use crate::platform::x11::error::CookieExt; +use super::prelude::*; use crate::platform::x11::visual_info::WindowVisualConfig; -use crate::platform::X11Connection; use std::num::{NonZero, NonZeroU32}; -use std::rc::Rc; -use x11rb::connection::Connection; use x11rb::cookie::VoidCookie; -use x11rb::errors::{ConnectionError, ReplyOrIdError}; use x11rb::properties::WmSizeHints; use x11rb::protocol::present; -use x11rb::protocol::present::ConnectionExt; +use x11rb::protocol::present::ConnectionExt as _; use x11rb::protocol::xproto::{ AtomEnum, ConfigureWindowAux, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, WindowClass, }; use x11rb::wrapper::ConnectionExt as _; -use x11rb::xcb_ffi::XCBConnection; pub struct XcbWindow { connection: Rc, diff --git a/src/wrappers/egl.rs b/src/wrappers/egl.rs index df8717b9..6c82e330 100644 --- a/src/wrappers/egl.rs +++ b/src/wrappers/egl.rs @@ -1,4 +1,4 @@ -use crate::platform::gl::CreationFailedError; +use crate::platform::gl::GlCreationFailedError; use libloading::Library; use std::ffi::{c_void, CStr}; use std::rc::Rc; @@ -34,7 +34,7 @@ pub struct Egl { } impl Egl { - pub fn open() -> Result { + pub fn open() -> Result { let library = unsafe { Library::new("libEGL.so.1").or_else(|_| Library::new("libEGL.so")) }?; diff --git a/src/wrappers/egl/config.rs b/src/wrappers/egl/config.rs index 483092e8..42f511c0 100644 --- a/src/wrappers/egl/config.rs +++ b/src/wrappers/egl/config.rs @@ -57,10 +57,10 @@ impl EglConfig { Ok(value) } - pub fn get_visual_id(&self, display: &EglDisplay) -> Result { + pub fn get_visual_id(&self, display: &EglDisplay) -> Result { let value = self.get_attrib(display, EGL_NATIVE_VISUAL_ID)?; let value: Visualid = - value.try_into().map_err(|e| CreationFailedError::EglInvalidVisualId(value, e))?; + value.try_into().map_err(|e| GlCreationFailedError::EglInvalidVisualId(value, e))?; Ok(value) } } diff --git a/src/wrappers/egl/display.rs b/src/wrappers/egl/display.rs index 4185711f..d0fae957 100644 --- a/src/wrappers/egl/display.rs +++ b/src/wrappers/egl/display.rs @@ -1,11 +1,5 @@ -use crate::gl::GlConfig; -use crate::platform::gl::CreationFailedError; -use crate::platform::X11Connection; -use crate::wrappers::egl::bound_api::BoundApi; -use crate::wrappers::egl::config::EglConfig; -use crate::wrappers::egl::context::EglContext; -use crate::wrappers::egl::surface::EglSurface; -use crate::wrappers::egl::{sys, Egl, EglError}; +use super::*; +use crate::platform::prelude::*; use crate::wrappers::xlib::XlibConnection; use std::ffi::c_void; use std::ptr::NonNull; @@ -67,10 +61,10 @@ pub struct EglVersion { impl Egl { pub fn create_display( &self, connection: &Rc, - ) -> Result { + ) -> Result { let display = self .create_display_basic(connection.conn.xlib_connection()) - .ok_or(CreationFailedError::EglNoDisplay)?; + .ok_or(GlCreationFailedError::EglNoDisplay)?; let egl = self.clone(); @@ -80,7 +74,7 @@ impl Egl { let inner = EglDisplayInner { egl, raw: display, _connection: Rc::clone(connection) }; if version.major != 1 || version.minor < 5 { - return Err(CreationFailedError::EglUnsupportedVersion(version)); + return Err(GlCreationFailedError::EglUnsupportedVersion(version)); } Ok(EglDisplay { inner: Rc::new(inner) }) diff --git a/src/wrappers/egl/sys.rs b/src/wrappers/egl/sys.rs index bd5435fd..a2fd96d2 100644 --- a/src/wrappers/egl/sys.rs +++ b/src/wrappers/egl/sys.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case, non_camel_case_types, reason = "To match EGL function naming")] -use crate::platform::gl::CreationFailedError; +use crate::platform::gl::GlCreationFailedError; use libloading::Library; use std::ffi::*; use std::fmt::{Display, Formatter}; @@ -106,13 +106,13 @@ impl Display for MissingSymbolError { } } -impl From for CreationFailedError { +impl From for GlCreationFailedError { fn from(value: MissingSymbolError) -> Self { Self::EGLMissingSymbol(value) } } -impl From for CreationFailedError { +impl From for GlCreationFailedError { fn from(value: libloading::Error) -> Self { Self::EGLLoadError(value) } @@ -139,7 +139,7 @@ pub struct Functions { } impl Functions { - pub unsafe fn load_from(library: &Library) -> Result { + pub unsafe fn load_from(library: &Library) -> Result { Ok(Self { eglGetError: Self::get(library, c"eglGetError")?, eglBindAPI: Self::get(library, c"eglBindAPI")?, @@ -163,7 +163,7 @@ impl Functions { unsafe fn get( library: &Library, name: &'static CStr, - ) -> Result { + ) -> Result { let symbol = library.get::>(name.to_bytes_with_nul())?; let symbol = symbol.lift_option().ok_or(MissingSymbolError { name })?; Ok(*symbol) diff --git a/src/wrappers/glx.rs b/src/wrappers/glx.rs index 60f3ed88..7f71c258 100644 --- a/src/wrappers/glx.rs +++ b/src/wrappers/glx.rs @@ -1,12 +1,10 @@ use super::xlib::*; use crate::gl::{GlConfig, Profile}; -use crate::platform::gl::CreationFailedError; -use crate::platform::*; +use crate::platform::prelude::*; use std::ffi::{c_ulong, c_void, CStr}; use std::os::raw::c_int; use std::ptr::NonNull; -use std::rc::Rc; use x11_dl::glx::{arb::*, *}; use x11_dl::xlib; use x11_dl::xlib::XVisualInfo; @@ -29,7 +27,7 @@ pub struct Glx { } impl Glx { - pub fn open() -> Result { + pub fn open() -> PlatformResult { Ok(Self { inner: Rc::new(x11_dl::glx::Glx::open()?) }) } @@ -58,7 +56,7 @@ impl Glx { pub fn choose_best_fb_config( &self, connection: &XlibConnection, config: &GlConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> PlatformResult { let fb_attribs = Self::get_fb_attribs(config); let mut nelements = 0; @@ -75,7 +73,7 @@ impl Glx { error_handler.check()?; if nelements == 0 || result.is_null() { - return Err(CreationFailedError::NoValidFBConfig.into()); + return Err(GlCreationFailedError::NoValidFBConfig.into()); } // SAFETY: If nelements != 0, the result pointer is non-null, and no Xlib error occured, then @@ -91,14 +89,14 @@ impl Glx { pub fn get_visual_from_fb_config( &self, connection: &XlibConnection, fb_config: GlxFbConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> PlatformResult { // SAFETY: XlibConnection guarantees the inner dpy is valid. let result = unsafe { (self.inner.glXGetVisualFromFBConfig)(connection.as_raw(), fb_config.0) }; error_handler.check()?; if result.is_null() { - return Err(CreationFailedError::NoVisual.into()); + return Err(GlCreationFailedError::NoVisual.into()); } // SAFETY: If the result pointer is non-null, and no Xlib error occured, then @@ -113,7 +111,7 @@ impl Glx { pub fn swap_buffers( &self, connection: &XlibConnection, window_id: c_ulong, error_handler: &XErrorHandler, - ) -> Result<()> { + ) -> PlatformResult<()> { // SAFETY: XlibConnection guarantees the inner dpy is valid. unsafe { (self.inner.glXSwapBuffers)(connection.as_raw(), window_id) }; @@ -143,13 +141,13 @@ impl Glx { pub unsafe fn make_current( &self, connection: &XlibConnection, window_id: c_ulong, context: GLXContext, error_handler: &XErrorHandler, - ) -> Result<()> { + ) -> PlatformResult<()> { // SAFETY: XlibConnection guarantees the inner dpy is valid. let res = unsafe { (self.inner.glXMakeCurrent)(connection.as_raw(), window_id, context) }; error_handler.check()?; if res == 0 { - return Err(CreationFailedError::MakeCurrentFailed.into()); + return Err(GlCreationFailedError::MakeCurrentFailed.into()); } Ok(()) @@ -157,7 +155,7 @@ impl Glx { pub unsafe fn clear_current( &self, connection: &XlibConnection, error_handler: &XErrorHandler, - ) -> Result<()> { + ) -> PlatformResult<()> { self.make_current(connection, 0, core::ptr::null_mut(), error_handler) } } @@ -185,7 +183,7 @@ impl GlxCreateContextAttribsARB { pub fn call( &self, connection: &XlibConnection, gl_config: &GlConfig, glx_fb_config: GlxFbConfig, error_handler: &XErrorHandler, - ) -> Result { + ) -> PlatformResult { let ctx_attribs = Self::get_ctx_attribs(gl_config); let context = unsafe { @@ -201,7 +199,7 @@ impl GlxCreateContextAttribsARB { error_handler.check()?; if context.is_null() { - return Err(CreationFailedError::ContextCreationFailed.into()); + return Err(GlCreationFailedError::ContextCreationFailed.into()); } Ok(context) diff --git a/src/wrappers/xkbcommon.rs b/src/wrappers/xkbcommon.rs index 88ff5a63..f3d96926 100644 --- a/src/wrappers/xkbcommon.rs +++ b/src/wrappers/xkbcommon.rs @@ -1,3 +1,4 @@ +use crate::platform::prelude::*; use xkbcommon_dl as xkbc; pub(crate) type Keycode = xkbcommon_dl::xkb_keycode_t; @@ -8,7 +9,7 @@ pub struct XkbcommonState { } impl XkbcommonState { - pub fn new(xcb_connection: &crate::platform::X11Connection) -> Option { + pub fn new(xcb_connection: &X11Connection) -> Option { let xkb_common = xkbc::xkbcommon_option()?; let xkb_x11 = xkbc::x11::xkbcommon_x11_option()?; diff --git a/src/wrappers/xlib/xlib_connection.rs b/src/wrappers/xlib/xlib_connection.rs index afdffd0b..eb286f92 100644 --- a/src/wrappers/xlib/xlib_connection.rs +++ b/src/wrappers/xlib/xlib_connection.rs @@ -1,4 +1,5 @@ -use crate::platform::*; +use super::*; +use crate::platform::prelude::*; use std::error::Error; use std::ffi::CStr; use std::fmt::Formatter; @@ -25,7 +26,7 @@ unsafe impl Send for XlibConnection {} unsafe impl Sync for XlibConnection {} impl XlibConnection { - pub fn open() -> Result { + pub fn open() -> PlatformResult { let xlib = Box::new(Xlib::open()?); if unsafe { (xlib.XInitThreads)() } == 0 { diff --git a/src/wrappers/xlib/xlib_xcb.rs b/src/wrappers/xlib/xlib_xcb.rs index f87efc10..0db1a16b 100644 --- a/src/wrappers/xlib/xlib_xcb.rs +++ b/src/wrappers/xlib/xlib_xcb.rs @@ -1,12 +1,10 @@ -use crate::platform::*; -use crate::wrappers::xlib::xlib_connection::XlibConnection; -use crate::wrappers::xlib::ScreenIndex; +use super::*; +use crate::platform::prelude::*; use raw_window_handle::{DisplayHandle, XcbDisplayHandle, XlibDisplayHandle}; use std::ops::Deref; use std::os::fd::{AsFd, BorrowedFd}; use std::ptr::NonNull; use x11_dl::xlib_xcb::Xlib_xcb; -use x11rb::connection::Connection; use x11rb::protocol::xproto::Screen; use x11rb::xcb_ffi::XCBConnection; @@ -26,7 +24,7 @@ pub struct XlibXcbConnection { } impl XlibXcbConnection { - pub fn open() -> Result { + pub fn open() -> PlatformResult { let xlib_xcb = Xlib_xcb::open()?; // Open the connection to the X11 server as a Xlib/XCB connection object let xlib_connection = XlibConnection::open()?; From 03e0b67e4f2df2313baf3025c29241aab77e914b Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 13:20:39 +0200 Subject: [PATCH 04/10] cleanup --- src/platform/x11/drag_n_drop.rs | 1 - src/platform/x11/event_loop.rs | 48 +++------------ src/platform/x11/gl.rs | 1 - src/platform/x11/mod.rs | 10 +-- src/platform/x11/present.rs | 91 +++++++++++++++++++++------- src/platform/x11/sizing.rs | 2 +- src/platform/x11/visual_info.rs | 6 +- src/platform/x11/waker.rs | 4 +- src/platform/x11/window_shared.rs | 15 ----- src/platform/x11/window_thread.rs | 31 ++-------- src/wrappers/xlib/xlib_connection.rs | 1 - 11 files changed, 97 insertions(+), 113 deletions(-) diff --git a/src/platform/x11/drag_n_drop.rs b/src/platform/x11/drag_n_drop.rs index 25be23ba..6d95d3c7 100644 --- a/src/platform/x11/drag_n_drop.rs +++ b/src/platform/x11/drag_n_drop.rs @@ -1,7 +1,6 @@ use super::x11_connection::{Atoms, GetPropertyError}; use super::*; use crate::dpi::PhysicalPosition; -use crate::handler::WindowHandler; use crate::platform::x11::error::ReplyExt; use crate::platform::x11::handler::Handler; use crate::warn; diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 4d471b0f..4c35f972 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -14,7 +14,7 @@ use crate::platform::x11::window_thread::{ }; use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; -use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowSize}; +use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler}; use calloop::generic::Generic; use calloop::{Interest, LoopHandle, LoopSignal, Mode, PostAction}; use std::rc::Rc; @@ -200,30 +200,7 @@ impl EventLoop { self.main_thread.as_mut(), )?; - // Consume all requests from above poll - if let Some(redraw_after) = self.shared.main_thread_shared.take_redraw_request() { - self.shared.request_redraw_after(redraw_after) - } - - let shared_poll_requested = self.shared.main_thread_shared.take_poll_request(); - let did_redraw = self.present_state.redraw_if_needed( - &self.shared.present_state, - &mut self.handler, - &self.shared.connection.conn, - )?; - - if !did_redraw { - if shared_poll_requested || self.shared.poll_requested.get() { - self.handler.poll(); - self.shared.poll_requested.set(false); - } - } - - self.present_state.handle_present_notify( - &self.shared.present_state, - &self.shared.xcb_window, - &self.loop_handle, - )?; + self.present_state.handle_requests(&self.shared, &self.handler, &self.loop_handle)?; if !self.drain_xcb_events()? { break; @@ -289,8 +266,7 @@ impl EventLoop { } if event.data.as_data32()[0] == self.shared.connection.atoms.WM_DELETE_WINDOW { - self.shared.request_close(); // TODO: this doesn't work anymore? - eprintln!("CLOSE!"); + self.shared.request_close(); return Ok(()); } @@ -410,19 +386,15 @@ impl EventLoop { self.sizing_state.handle_parent_notify(e) } - XEvent::MapNotify(e) => { - if let Some(window_id) = NonZero::new(e.window) { - if window_id == self.shared.xcb_window.id() { - self.present_state.handle_window_mapped( - &self.shared.present_state, - &self.shared.xcb_window, - &self.loop_handle, - )?; - } - } + XEvent::MapNotify(e) if e.window == self.shared.raw_id() => { + self.present_state.handle_window_mapped( + &self.shared.present_state, + &self.shared.xcb_window, + &self.loop_handle, + )?; } - XEvent::PresentCompleteNotify(e) if e.window != self.shared.raw_id() => { + XEvent::PresentCompleteNotify(e) if e.window == self.shared.raw_id() => { self.present_state.handle_present_complete_notify(e); } XEvent::Expose(e) if e.window == self.shared.raw_id() => { diff --git a/src/platform/x11/gl.rs b/src/platform/x11/gl.rs index 538d57f6..766465f6 100644 --- a/src/platform/x11/gl.rs +++ b/src/platform/x11/gl.rs @@ -1,7 +1,6 @@ use super::prelude::*; use crate::gl::*; use crate::wrappers::glx::*; -use crate::wrappers::xlib::XLibError; use std::error::Error; use crate::platform::gl::egl::EglGlContext; diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 7f835d40..99720d63 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -22,14 +22,16 @@ pub(crate) mod prelude { pub use super::error::{CookieExt as _, FatalError, PlatformError, ReplyExt as _}; pub use super::event_loop::*; pub use super::handler::Handler; + pub use super::present::*; pub use super::sizing::*; + pub use super::visual_info::WindowVisualConfig; + pub use super::waker::WindowWaker; pub use super::window_shared::WindowShared; - pub use super::window_thread::{HostCallback, WindowThreadHandle}; + pub use super::window_thread::{HostCallback, WindowThreadHandle, WindowThreadShared}; pub use super::x11_connection::X11Connection; pub use super::xcb_window::XcbWindow; - pub use crate::tracing::*; - pub use crate::PlatformHandle; - pub use dpi::*; + pub use crate::{dpi::*, tracing::*, MouseCursor, WindowSettings, WindowSize}; + pub use std::cell::Cell; pub use std::rc::Rc; pub use std::sync::Arc; pub use x11rb::connection::Connection; diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index b8fed18d..4392a000 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -1,15 +1,15 @@ -use crate::platform::x11::error::{CookieExt, FatalError}; -use crate::platform::x11::event_loop::EventLoop; +use crate::platform::prelude::*; use crate::platform::x11::handler::Handler; use crate::platform::x11::sizing::SizingState; use crate::platform::x11::window_shared::WindowShared; -use crate::platform::x11::xcb_window::XcbWindow; -use crate::wrappers::xlib::XlibXcbConnection; +use crate::platform::x11::window_thread::RedrawRequested; use crate::DamageArea; use calloop::timer::{TimeoutAction, Timer}; use calloop::LoopHandle; use dpi::{PhysicalPosition, PhysicalSize}; use std::cell::Cell; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; use std::time::{Duration, Instant}; use tracing::warn; use x11rb::connection::Connection; @@ -18,6 +18,7 @@ use x11rb::protocol::xproto::ExposeEvent; pub struct PresentStateShared { present_notify_requested: Cell, + poll_requested: Cell, } impl PresentStateShared { @@ -43,7 +44,7 @@ impl PresentStateShared { impl PresentStateShared { pub fn new() -> Self { - Self { present_notify_requested: false.into() } + Self { present_notify_requested: false.into(), poll_requested: false.into() } } pub fn request_present_notify(&self) { @@ -57,6 +58,40 @@ pub struct PresentState { last_received_present: Option<(u32, u64)>, } +impl PresentState { + pub(crate) fn handle_requests( + &mut self, shared: &WindowShared, handler: &Handler, loop_handle: &LoopHandle, + ) -> Result<(), FatalError> { + let shared_state = &shared.present_state; + let thread_state = &shared.main_thread_shared.present; + + // Consume all requests from above poll + if let Some(redraw_after) = thread_state.take_redraw_request() { + shared.request_redraw_after(redraw_after) + } + + if self.draw_now { + let _ = thread_state.take_poll_request(); + + handler.poll(); + shared.present_state.present_notify_requested.set(false); + + handler.draw()?; + + shared_state.poll_requested.set(false); + self.draw_now = false; + + shared.connection.conn.flush()?; + } else if thread_state.take_poll_request() || shared_state.poll_requested.take() { + handler.poll(); + } + + self.handle_present_notify(&shared.present_state, &shared.xcb_window, loop_handle)?; + + Ok(()) + } +} + impl PresentState { pub(crate) fn handle_expose_event( &self, e: ExposeEvent, handler: &Handler, shared: &WindowShared, sizing_state: &SizingState, @@ -97,22 +132,6 @@ impl PresentState { Self { draw_now: false, last_requested_serial: None, last_received_present: None } } - // TODO: check this & error handling - pub fn redraw_if_needed( - &mut self, shared: &PresentStateShared, handler: &mut Handler, - connection: &XlibXcbConnection, - ) -> Result { - handler.poll(); - - shared.present_notify_requested.set(false); - self.draw_now = false; - - handler.draw()?; - - connection.flush()?; - Ok(true) - } - pub fn handle_present_complete_notify(&mut self, e: CompleteNotifyEvent) { if e.kind != CompleteKind::NOTIFY_MSC { return; @@ -214,6 +233,36 @@ impl PresentState { } } +pub struct PresentThreadShared { + poll_requested: AtomicBool, + redraw_requested_after: Mutex>, +} + +impl PresentThreadShared { + pub(crate) fn new() -> Self { + Self { poll_requested: false.into(), redraw_requested_after: None.into() } + } + + pub fn request_redraw_after(&self, duration: Duration) { + // Ignore a poisoned mutex, we just fully override this value anyway. + let mut guard = self.redraw_requested_after.lock().unwrap_or_else(|g| g.into_inner()); + *guard = Some(RedrawRequested::from_duration(duration)); + } + + fn take_redraw_request(&self) -> Option { + let mut guard = self.redraw_requested_after.lock().unwrap_or_else(|g| g.into_inner()); + guard.take().map(|w| w.to_duration()) + } + + pub fn request_poll(&self) { + self.poll_requested.store(true, Ordering::Relaxed); + } + + fn take_poll_request(&self) -> bool { + self.poll_requested.swap(false, Ordering::Relaxed) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct DamageRect { pos: PhysicalPosition, diff --git a/src/platform/x11/sizing.rs b/src/platform/x11/sizing.rs index a18010b7..ef445b89 100644 --- a/src/platform/x11/sizing.rs +++ b/src/platform/x11/sizing.rs @@ -141,7 +141,7 @@ impl SizingStateShared { let scaling = connection.get_scaling(); let initial_scale_factor = scaling.unwrap_or(1.0); - let sizing_strategy = SizingStrategy::from_settings(&settings); + let sizing_strategy = SizingStrategy::from_settings(settings); let window_size = settings.size.to_physical(initial_scale_factor); sizing_thread_shared.set_scaling_factor(initial_scale_factor); diff --git a/src/platform/x11/visual_info.rs b/src/platform/x11/visual_info.rs index 56f2d744..5367b0bd 100644 --- a/src/platform/x11/visual_info.rs +++ b/src/platform/x11/visual_info.rs @@ -6,7 +6,7 @@ use x11rb::protocol::xproto::{ }; use x11rb::COPY_FROM_PARENT; -pub(crate) struct WindowVisualConfig { +pub struct WindowVisualConfig { #[cfg(feature = "opengl")] pub fb_config: Option, @@ -19,12 +19,12 @@ pub(crate) struct WindowVisualConfig { impl WindowVisualConfig { #[cfg(feature = "opengl")] pub fn find_best_visual_config_for_gl( - connection: &std::rc::Rc, gl_config: Option, + connection: &Rc, gl_config: Option, ) -> PlatformResult { let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) }; let (fb_config, window_config) = - super::gl::GlContextInner::get_fb_config_and_visual(connection, gl_config)?; + GlContextInner::get_fb_config_and_visual(connection, gl_config)?; Ok(Self { fb_config: Some(fb_config), diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index c5872720..109f0a4c 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -15,12 +15,12 @@ impl WindowWaker { } pub fn request_redraw_after(&self, duration: Duration) { - self.shared.request_redraw_after(duration); + self.shared.present.request_redraw_after(duration); self.loop_signal.wakeup(); } pub fn request_poll(&self) { - self.shared.request_poll(); + self.shared.present.request_poll(); self.loop_signal.wakeup(); } } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 4d15fa6f..42ae0bd2 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,19 +1,7 @@ use super::prelude::*; -use crate::platform::x11::event_loop::EventLoop; -use crate::platform::x11::present::PresentStateShared; -use crate::platform::x11::sizing::SizingStateShared; -use crate::platform::x11::timer::insert_timer; -use crate::platform::x11::visual_info::WindowVisualConfig; -use crate::platform::x11::waker::WindowWaker; -use crate::platform::x11::window_thread::WindowThreadShared; -use crate::platform::x11::xcb_window::XcbWindow; -use crate::{MouseCursor, WindowSettings, WindowSize}; use calloop::{LoopHandle, LoopSignal}; use dpi::Size; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; -use std::cell::Cell; -use std::rc::Rc; -use std::sync::Arc; use std::time::Duration; use x11rb::protocol::xproto; use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt, InputFocus, Visualid}; @@ -29,7 +17,6 @@ pub struct WindowShared { visual_id: Visualid, mouse_cursor: Cell, - pub poll_requested: Cell, pub is_focused: Cell, pub present_state: PresentStateShared, @@ -60,7 +47,6 @@ impl WindowShared { #[cfg(not(feature = "opengl"))] let visual_info = WindowVisualConfig::find_best_visual_config(&connection)?; - settings.parent.is_some() || settings.wait_for_parent; let parent_id = settings.parent.map(|p| p.inner.window_id); let xcb_window = @@ -97,7 +83,6 @@ impl WindowShared { is_focused: false.into(), present_state: PresentStateShared::new(), sizing_state, - poll_requested: false.into(), main_thread_shared: thread_shared, #[cfg(feature = "opengl")] diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index ad9af5ad..a5e6e545 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -3,6 +3,7 @@ use crate::dpi::Size; use crate::handler::WindowHandlerBuilder; use crate::host::HostCallbacks; use crate::platform::x11::event_loop::{EventLoop, MainThreadCaller}; +use crate::platform::x11::present::PresentThreadShared; use crate::platform::x11::sizing::SizingThreadShared; use crate::platform::x11::window_shared::WindowShared; use crate::utils::SizingStrategy; @@ -19,16 +20,14 @@ use std::thread; use std::thread::JoinHandle; use std::time::{Duration, Instant}; -pub(crate) struct WindowThreadShared { +pub struct WindowThreadShared { stopped: AtomicBool, pub sizing: SizingThreadShared, + pub present: PresentThreadShared, final_error: Mutex>, stopped_requested_from_host: AtomicBool, - poll_requested: AtomicBool, - - redraw_requested_after: Mutex>, } pub enum RedrawRequested { @@ -65,8 +64,7 @@ impl WindowThreadShared { stopped: false.into(), final_error: None.into(), stopped_requested_from_host: false.into(), - redraw_requested_after: None.into(), - poll_requested: false.into(), + present: PresentThreadShared::new(), sizing: SizingThreadShared::new(), } } @@ -78,25 +76,6 @@ impl WindowThreadShared { pub fn is_stop_host_requested(&self) -> bool { self.stopped_requested_from_host.load(Ordering::Relaxed) } - - pub fn request_redraw_after(&self, duration: Duration) { - // Ignore a poisoned mutex, we just fully override this value anyway. - let mut guard = self.redraw_requested_after.lock().unwrap_or_else(|g| g.into_inner()); - *guard = Some(RedrawRequested::from_duration(duration)); - } - - pub fn take_redraw_request(&self) -> Option { - let mut guard = self.redraw_requested_after.lock().unwrap_or_else(|g| g.into_inner()); - guard.take().map(|w| w.to_duration()) - } - - pub fn request_poll(&self) { - self.poll_requested.store(true, Ordering::Relaxed); - } - - pub fn take_poll_request(&self) -> bool { - self.poll_requested.swap(false, Ordering::Relaxed) - } } struct ThreadStopWatcher(Arc); @@ -261,7 +240,7 @@ impl WindowThreadHandle { } pub fn request_poll(&self) -> PlatformResult<()> { - self.shared.request_poll(); + self.shared.present.request_poll(); self.loop_signal.wakeup(); Ok(()) diff --git a/src/wrappers/xlib/xlib_connection.rs b/src/wrappers/xlib/xlib_connection.rs index eb286f92..21e5f495 100644 --- a/src/wrappers/xlib/xlib_connection.rs +++ b/src/wrappers/xlib/xlib_connection.rs @@ -1,4 +1,3 @@ -use super::*; use crate::platform::prelude::*; use std::error::Error; use std::ffi::CStr; From a7581f9ec2058c6ca7a100787c0a0643b79945fb Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:06:15 +0200 Subject: [PATCH 05/10] more cleanup --- src/platform/x11/mod.rs | 2 +- src/platform/x11/present.rs | 1 - src/platform/x11/visual_info.rs | 44 ++++++++++++++++++------------- src/platform/x11/window_shared.rs | 42 +++++++++++------------------ 4 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 99720d63..f28d0c71 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -30,7 +30,7 @@ pub(crate) mod prelude { pub use super::window_thread::{HostCallback, WindowThreadHandle, WindowThreadShared}; pub use super::x11_connection::X11Connection; pub use super::xcb_window::XcbWindow; - pub use crate::{dpi::*, tracing::*, MouseCursor, WindowSettings, WindowSize}; + pub(crate) use crate::{dpi::*, tracing::*, MouseCursor, WindowSettings, WindowSize}; pub use std::cell::Cell; pub use std::rc::Rc; pub use std::sync::Arc; diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index 4392a000..b73a0aa0 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -11,7 +11,6 @@ use std::cell::Cell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex; use std::time::{Duration, Instant}; -use tracing::warn; use x11rb::connection::Connection; use x11rb::protocol::present::{CompleteKind, CompleteNotifyEvent}; use x11rb::protocol::xproto::ExposeEvent; diff --git a/src/platform/x11/visual_info.rs b/src/platform/x11/visual_info.rs index 5367b0bd..04ec5f49 100644 --- a/src/platform/x11/visual_info.rs +++ b/src/platform/x11/visual_info.rs @@ -7,34 +7,32 @@ use x11rb::protocol::xproto::{ use x11rb::COPY_FROM_PARENT; pub struct WindowVisualConfig { - #[cfg(feature = "opengl")] - pub fb_config: Option, - pub visual_depth: u8, pub visual_id: Visualid, pub color_map: Option, + + #[cfg(feature = "opengl")] + pub fb_config: Option, } // TODO: make visual negotiation actually check all of a visual's parameters impl WindowVisualConfig { - #[cfg(feature = "opengl")] - pub fn find_best_visual_config_for_gl( - connection: &Rc, gl_config: Option, + pub fn find_best_visual_config( + connection: &Rc, settings: &mut WindowSettings, ) -> PlatformResult { - let Some(gl_config) = gl_config else { return Self::find_best_visual_config(connection) }; + #[cfg(feature = "opengl")] + if let Some(gl_config) = settings.gl_config.take() { + let (fb_config, window_config) = + GlContextInner::get_fb_config_and_visual(connection, gl_config)?; - let (fb_config, window_config) = - GlContextInner::get_fb_config_and_visual(connection, gl_config)?; - - Ok(Self { - fb_config: Some(fb_config), - visual_depth: window_config.depth, - visual_id: window_config.visual, - color_map: Some(create_color_map(connection, window_config.visual)?), - }) - } + return Ok(Self { + fb_config: Some(fb_config), + visual_depth: window_config.depth, + visual_id: window_config.visual, + color_map: Some(create_color_map(connection, window_config.visual)?), + }); + } - pub fn find_best_visual_config(connection: &X11Connection) -> PlatformResult { match find_visual_for_depth(connection.default_screen(), 32) { None => Ok(Self::copy_from_parent()), Some(visual_id) => Ok(Self { @@ -56,6 +54,16 @@ impl WindowVisualConfig { color_map: None, } } + + #[cfg(feature = "opengl")] + pub fn make_gl_context( + self, window: &XcbWindow, connection: &Rc, + ) -> PlatformResult>> { + match self.fb_config { + None => Ok(None), + Some(fb_config) => Ok(Some(GlContextInner::create(window, connection, fb_config)?)), + } + } } // For this 32-bit depth to work, you also need to define a color map and set a border diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 42ae0bd2..16a2fbeb 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -9,9 +9,6 @@ use x11rb::CURRENT_TIME; /// Data that is shared between the event loop and the window handler. pub struct WindowShared { - #[cfg(feature = "opengl")] - gl_context: Option, - pub xcb_window: XcbWindow, pub connection: Rc, visual_id: Visualid, @@ -26,56 +23,47 @@ pub struct WindowShared { loop_handle: LoopHandle<'static, EventLoop>, pub main_thread_shared: Arc, + + #[cfg(feature = "opengl")] + gl_context: Option, } impl WindowShared { pub(crate) fn create( - settings: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, + mut settings: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, thread_shared: Arc, ) -> PlatformResult> { let connection = X11Connection::connect()?; let sizing_state = SizingStateShared::load(&connection, &thread_shared.sizing, &settings)?; - let size_hints = sizing_state.make_size_hints(); let connection = Rc::new(connection); - #[cfg(feature = "opengl")] - let visual_info = - WindowVisualConfig::find_best_visual_config_for_gl(&connection, settings.gl_config)?; - - #[cfg(not(feature = "opengl"))] - let visual_info = WindowVisualConfig::find_best_visual_config(&connection)?; + let visual_config = + WindowVisualConfig::find_best_visual_config(&connection, &mut settings)?; let parent_id = settings.parent.map(|p| p.inner.window_id); let xcb_window = - XcbWindow::new(Rc::clone(&connection), sizing_state.size(), &visual_info, parent_id)?; + XcbWindow::new(Rc::clone(&connection), sizing_state.size(), &visual_config, parent_id)?; let cookies = [ xcb_window.set_title(&settings.title)?, xcb_window.enable_wm_protocols()?, xcb_window.enable_dnd_protocols()?, - xcb_window.set_size_hints(size_hints)?, + xcb_window.set_size_hints(sizing_state.make_size_hints())?, ]; for cookie in cookies { cookie.check()?; } - #[cfg(feature = "opengl")] - let gl_context = match visual_info.fb_config { - None => None, - Some(fb_config) => { - // Because of the visual negotation we had to take some extra steps to create this context - Some(GlContextInner::create(&xcb_window, &connection, fb_config)?) - } - }; - Ok(Rc::new(Self { - connection, - xcb_window, - visual_id: visual_info.visual_id, + visual_id: visual_config.visual_id, + + #[cfg(feature = "opengl")] + gl_context: visual_config.make_gl_context(&xcb_window, &connection)?, + mouse_cursor: MouseCursor::default().into(), loop_signal: ev_loop.get_signal(), loop_handle: ev_loop.handle(), @@ -85,8 +73,8 @@ impl WindowShared { sizing_state, main_thread_shared: thread_shared, - #[cfg(feature = "opengl")] - gl_context, + xcb_window, + connection, })) } From 2cb5fd503e9545bd5a59fb53f03118acc350ff08 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:44:39 +0200 Subject: [PATCH 06/10] cursor cleanup --- src/platform/x11/cursor.rs | 80 +++++++++++++++++++++++++----- src/platform/x11/mod.rs | 2 +- src/platform/x11/sizing.rs | 2 +- src/platform/x11/window_shared.rs | 27 ++-------- src/platform/x11/x11_connection.rs | 80 ++++++++++++------------------ src/platform/x11/xcb_window.rs | 4 ++ 6 files changed, 111 insertions(+), 84 deletions(-) diff --git a/src/platform/x11/cursor.rs b/src/platform/x11/cursor.rs index bc6536fa..cc750d72 100644 --- a/src/platform/x11/cursor.rs +++ b/src/platform/x11/cursor.rs @@ -1,6 +1,64 @@ use super::prelude::*; use crate::wrappers::xlib::XlibXcbConnection; use crate::MouseCursor; +use std::cell::RefCell; +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use x11rb::protocol::xproto::ChangeWindowAttributesAux; + +pub struct CursorStateShared { + mouse_cursor: Cell, + cursor_cache: RefCell>, +} + +impl CursorStateShared { + pub(crate) fn set_mouse_cursor( + &self, mouse_cursor: MouseCursor, window: &XcbWindow, + ) -> PlatformResult<()> { + if self.mouse_cursor.get() == mouse_cursor { + return Ok(()); + } + + let xid = self.get_cursor(mouse_cursor, window.connection())?; + + if xid != 0 { + window + .connection() + .conn + .change_window_attributes( + window.id().get(), + &ChangeWindowAttributesAux::new().cursor(xid), + )? + .check()?; + } + + self.mouse_cursor.set(mouse_cursor); + + Ok(()) + } + + #[inline] + fn get_cursor(&self, cursor: MouseCursor, conn: &X11Connection) -> PlatformResult { + // PANIC: this function is the only point where we access the cache, and we never call + // external functions that may make a reentrant call to this function + let mut cursor_cache = self.cursor_cache.borrow_mut(); + + match cursor_cache.entry(cursor) { + Entry::Occupied(entry) => Ok(*entry.get()), + Entry::Vacant(entry) => { + let cursor = get_xcursor(conn, cursor)?; + entry.insert(cursor); + Ok(cursor) + } + } + } +} + +impl CursorStateShared { + pub fn new() -> Self { + Self { mouse_cursor: MouseCursor::Default.into(), cursor_cache: HashMap::new().into() } + } +} fn create_empty_cursor(conn: &XlibXcbConnection) -> PlatformResult { let cursor_id = conn.generate_id()?; @@ -13,10 +71,9 @@ fn create_empty_cursor(conn: &XlibXcbConnection) -> PlatformResult { Ok(cursor_id) } -fn load_cursor( - conn: &XCBConnection, cursor_handle: &CursorHandle, name: &str, -) -> PlatformResult> { - let cursor = cursor_handle.load_cursor(conn, name)?; +#[inline(never)] +fn load_cursor(conn: &X11Connection, name: &str) -> PlatformResult> { + let cursor = conn.resources.cursor_handle.load_cursor(&conn.conn as &XCBConnection, name)?; if cursor != x11rb::NONE { Ok(Some(cursor)) } else { @@ -24,11 +81,12 @@ fn load_cursor( } } +#[inline(never)] fn load_first_existing_cursor( - conn: &XCBConnection, cursor_handle: &CursorHandle, names: &[&str], + conn: &X11Connection, names: &[&str], ) -> PlatformResult> { for name in names { - let cursor = load_cursor(conn, cursor_handle, name)?; + let cursor = load_cursor(conn, name)?; if cursor.is_some() { return Ok(cursor); } @@ -37,11 +95,9 @@ fn load_first_existing_cursor( Ok(None) } -pub(crate) fn get_xcursor( - conn: &XlibXcbConnection, cursor_handle: &CursorHandle, cursor: MouseCursor, -) -> PlatformResult { - let load = |name: &str| load_cursor(conn, cursor_handle, name); - let loadn = |names: &[&str]| load_first_existing_cursor(conn, cursor_handle, names); +pub(crate) fn get_xcursor(conn: &X11Connection, cursor: MouseCursor) -> PlatformResult { + let load = |name: &str| load_cursor(conn, name); + let loadn = |names: &[&str]| load_first_existing_cursor(conn, names); let cursor = match cursor { MouseCursor::Default => None, // catch this in the fallback case below @@ -50,7 +106,7 @@ pub(crate) fn get_xcursor( MouseCursor::HandGrabbing => loadn(&["closedhand", "grabbing"])?, MouseCursor::Help => load("question_arrow")?, - MouseCursor::Hidden => Some(create_empty_cursor(conn)?), + MouseCursor::Hidden => Some(create_empty_cursor(&conn.conn)?), MouseCursor::Text => loadn(&["text", "xterm"])?, MouseCursor::VerticalText => load("vertical-text")?, diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index f28d0c71..4cb6b877 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -19,6 +19,7 @@ mod x11_connection; mod xcb_window; pub(crate) mod prelude { + pub use super::cursor::*; pub use super::error::{CookieExt as _, FatalError, PlatformError, ReplyExt as _}; pub use super::event_loop::*; pub use super::handler::Handler; @@ -35,7 +36,6 @@ pub(crate) mod prelude { pub use std::rc::Rc; pub use std::sync::Arc; pub use x11rb::connection::Connection; - pub use x11rb::cursor::Handle as CursorHandle; pub use x11rb::errors::{ConnectionError, ReplyOrIdError}; pub use x11rb::protocol::xproto::{ConnectionExt as _, Cursor}; pub use x11rb::xcb_ffi::XCBConnection; diff --git a/src/platform/x11/sizing.rs b/src/platform/x11/sizing.rs index ef445b89..0c08e221 100644 --- a/src/platform/x11/sizing.rs +++ b/src/platform/x11/sizing.rs @@ -138,7 +138,7 @@ impl SizingStateShared { connection: &X11Connection, sizing_thread_shared: &SizingThreadShared, settings: &WindowSettings, ) -> Result { - let scaling = connection.get_scaling(); + let scaling = connection.resources.xft_dpi.map(|dpi| dpi as f64 / 96.0); let initial_scale_factor = scaling.unwrap_or(1.0); let sizing_strategy = SizingStrategy::from_settings(settings); diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 16a2fbeb..44bd3714 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -4,7 +4,7 @@ use dpi::Size; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::time::Duration; use x11rb::protocol::xproto; -use x11rb::protocol::xproto::{ChangeWindowAttributesAux, ConnectionExt, InputFocus, Visualid}; +use x11rb::protocol::xproto::{ConnectionExt, InputFocus, Visualid}; use x11rb::CURRENT_TIME; /// Data that is shared between the event loop and the window handler. @@ -13,9 +13,9 @@ pub struct WindowShared { pub connection: Rc, visual_id: Visualid, - mouse_cursor: Cell, pub is_focused: Cell, + pub cursor_state: CursorStateShared, pub present_state: PresentStateShared, pub sizing_state: SizingStateShared, @@ -64,11 +64,12 @@ impl WindowShared { #[cfg(feature = "opengl")] gl_context: visual_config.make_gl_context(&xcb_window, &connection)?, - mouse_cursor: MouseCursor::default().into(), loop_signal: ev_loop.get_signal(), loop_handle: ev_loop.handle(), is_focused: false.into(), + + cursor_state: CursorStateShared::new(), present_state: PresentStateShared::new(), sizing_state, main_thread_shared: thread_shared, @@ -79,25 +80,7 @@ impl WindowShared { } pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> PlatformResult<()> { - if self.mouse_cursor.get() == mouse_cursor { - return Ok(()); - } - - let xid = self.connection.get_cursor(mouse_cursor)?; - - if xid != 0 { - self.connection - .conn - .change_window_attributes( - self.xcb_window.id().get(), - &ChangeWindowAttributesAux::new().cursor(xid), - )? - .check()?; - } - - self.mouse_cursor.set(mouse_cursor); - - Ok(()) + self.cursor_state.set_mouse_cursor(mouse_cursor, &self.xcb_window) } pub fn request_close(&self) { diff --git a/src/platform/x11/x11_connection.rs b/src/platform/x11/x11_connection.rs index 2d0b83e6..e04596de 100644 --- a/src/platform/x11/x11_connection.rs +++ b/src/platform/x11/x11_connection.rs @@ -1,16 +1,12 @@ -use super::cursor; use super::prelude::*; use crate::wrappers::xlib::XlibXcbConnection; -use crate::MouseCursor; -use std::cell::RefCell; -use std::collections::hash_map::{Entry, HashMap}; use x11rb::connection::RequestConnection; use x11rb::cookie::VoidCookie; use x11rb::cursor::Handle as CursorHandle; use x11rb::errors::ConnectionError; use x11rb::protocol::present; use x11rb::protocol::xproto::{ - self, ChangeWindowAttributesAux, ConnectionExt, Cursor, EventMask, Screen, + self, Atom, ChangeWindowAttributesAux, ConnectionExt, EventMask, Screen, }; use x11rb::resource_manager; use x11rb::xcb_ffi::XCBConnection; @@ -49,9 +45,7 @@ x11rb::atom_manager! { pub struct X11Connection { pub(crate) conn: Arc, pub(crate) atoms: Atoms, - pub(crate) resources: resource_manager::Database, - pub(crate) cursor_handle: CursorHandle, - pub(crate) cursor_cache: RefCell>, + pub(crate) resources: ConnectionResources, pub(crate) present_supported: bool, } @@ -59,47 +53,15 @@ pub struct X11Connection { impl X11Connection { pub fn connect() -> PlatformResult { let conn = XlibXcbConnection::open()?; - let screen = conn.default_screen_index(); - let xcb_conn = conn.xcb_connection(); - - let atoms = Atoms::new(xcb_conn)?.reply()?; - let resources = resource_manager::new_from_default(xcb_conn)?; - let cursor_handle = CursorHandle::new(xcb_conn, screen.into(), &resources)?.reply()?; - - let present_supported = conn.extension_information(present::X11_EXTENSION_NAME)?.is_some(); + let atoms = Atoms::new(&*conn)?.reply()?; Ok(Self { - conn: Arc::new(conn), atoms, - resources, - cursor_handle, - cursor_cache: RefCell::new(HashMap::new()), - present_supported, - }) - } + present_supported: conn.extension_information(present::X11_EXTENSION_NAME)?.is_some(), + resources: ConnectionResources::load(&conn)?, - pub fn get_scaling(&self) -> Option { - if let Ok(Some(dpi)) = self.resources.get_value::("Xft.dpi", "") { - Some(dpi as f64 / 96.0) - } else { - None - } - } - - #[inline] - pub fn get_cursor(&self, cursor: MouseCursor) -> PlatformResult { - // PANIC: this function is the only point where we access the cache, and we never call - // external functions that may make a reentrant call to this function - let mut cursor_cache = self.cursor_cache.borrow_mut(); - - match cursor_cache.entry(cursor) { - Entry::Occupied(entry) => Ok(*entry.get()), - Entry::Vacant(entry) => { - let cursor = cursor::get_xcursor(&self.conn, &self.cursor_handle, cursor)?; - entry.insert(cursor); - Ok(cursor) - } - } + conn: Arc::new(conn), + }) } pub fn default_screen(&self) -> &Screen { @@ -107,14 +69,14 @@ impl X11Connection { } pub fn get_property( - &self, window: xproto::Window, property: xproto::Atom, property_type: xproto::Atom, - ) -> core::result::Result, GetPropertyError> { + &self, window: xproto::Window, property: Atom, property_type: Atom, + ) -> Result, GetPropertyError> { get_property::get_property(window, property, property_type, &self.conn) } pub fn register_tree_structure_events( &self, - ) -> core::result::Result, ConnectionError> { + ) -> Result, ConnectionError> { let root = self.default_screen().root; self.conn.change_window_attributes( @@ -123,3 +85,25 @@ impl X11Connection { ) } } + +pub struct ConnectionResources { + pub cursor_handle: CursorHandle, + pub xft_dpi: Option, +} + +impl ConnectionResources { + fn load(conn: &XlibXcbConnection) -> PlatformResult { + let resources = resource_manager::new_from_default(conn as &XCBConnection)?; + + Ok(Self { + xft_dpi: resources.get_value::("Xft.dpi", "").ok().flatten(), + + cursor_handle: CursorHandle::new( + conn as &XCBConnection, + conn.default_screen_index().into(), + &resources, + )? + .reply()?, + }) + } +} diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs index 89365072..d171bf78 100644 --- a/src/platform/x11/xcb_window.rs +++ b/src/platform/x11/xcb_window.rs @@ -163,6 +163,10 @@ impl XcbWindow { pub fn id(&self) -> NonZeroU32 { self.window_id } + + pub fn connection(&self) -> &X11Connection { + &self.connection + } } impl Drop for XcbWindow { From 9b71c62010e514424fe1b0bab64be526e6914575 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:47:10 +0200 Subject: [PATCH 07/10] mouse cleanup --- src/platform/x11/event_loop.rs | 131 +++++++----------------------- src/platform/x11/mod.rs | 2 + src/platform/x11/mouse.rs | 94 +++++++++++++++++++++ src/platform/x11/window_shared.rs | 7 +- src/platform/x11/xcb_window.rs | 14 +++- 5 files changed, 137 insertions(+), 111 deletions(-) create mode 100644 src/platform/x11/mouse.rs diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 4c35f972..122dc66f 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -1,9 +1,6 @@ use super::drag_n_drop::DragNDropState; -use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mods}; -use super::*; -use std::result::Result; - -use crate::dpi::PhysicalPosition; +use super::keyboard::{convert_key_press_event, convert_key_release_event}; +use super::prelude::*; use crate::host::HostMainThreadCaller; use crate::platform::x11::error::FatalError; use crate::platform::x11::handler::Handler; @@ -14,10 +11,11 @@ use crate::platform::x11::window_thread::{ }; use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; -use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler}; +use crate::{Event, WindowEvent, WindowHandler}; use calloop::generic::Generic; use calloop::{Interest, LoopHandle, LoopSignal, Mode, PostAction}; -use std::rc::Rc; +use std::num::NonZeroU32; +use std::result::Result; use std::sync::mpsc; use std::sync::mpsc::Receiver; use x11rb::connection::Connection; @@ -48,22 +46,21 @@ impl MainThreadCaller { } pub(crate) struct EventLoop { + response_sender: mpsc::Sender, + main_thread: Option, + handler: Handler, shared: Rc, - pub sizing_state: SizingState, + drag_n_drop: DragNDropState, + sizing_state: SizingState, pub present_state: PresentState, + xkb_state: Option, loop_signal: LoopSignal, loop_handle: LoopHandle<'static, Self>, - drag_n_drop: DragNDropState, - xkb_state: Option, - run_error: Option, - - response_sender: mpsc::Sender, - main_thread: Option, } impl EventLoop { @@ -216,7 +213,7 @@ impl EventLoop { self.drain_xcb_events()?; inner.run(None, &mut self, Self::handle_idle)?; - self.handle_event(Event::Window(WindowEvent::WillClose)); + self.handler.on_event(Event::Window(WindowEvent::WillClose)); // If the event loop doesn't stop because the host asked it to, then we should notify it if !self.shared.main_thread_shared.is_stop_host_requested() { @@ -239,26 +236,6 @@ impl EventLoop { } fn handle_xcb_event(&mut self, event: XEvent) -> Result<(), FatalError> { - // For all the keyboard and mouse events, you can fetch - // `x`, `y`, `detail`, and `state`. - // - `x` and `y` are the position inside the window where the cursor currently is - // when the event happened. - // - `detail` will tell you which keycode was pressed/released (for keyboard events) - // or which mouse button was pressed/released (for mouse events). - // For mouse events, here's what the value means (at least on my current mouse): - // 1 = left mouse button - // 2 = middle mouse button (scroll wheel) - // 3 = right mouse button - // 4 = scroll wheel up - // 5 = scroll wheel down - // 8 = lower side button ("back" button) - // 9 = upper side button ("forward" button) - // Note that you *will* get a "button released" event for even the scroll wheel - // events, which you can probably ignore. - // - `state` will tell you the state of the main three mouse buttons and some of - // the keyboard modifier keys at the time of the event. - // http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 - match event { XEvent::ClientMessage(event) if event.window == self.shared.raw_id() => { if event.format != 32 { @@ -303,83 +280,46 @@ impl EventLoop { // mouse //// XEvent::MotionNotify(event) if event.event == self.shared.raw_id() => { - let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); - - self.handle_event(Event::Mouse(MouseEvent::CursorMoved { - position: physical_pos.cast(), - modifiers: key_mods(event.state), - })); + handle_motion_notify(event, &self.handler) } - XEvent::EnterNotify(event) if event.event == self.shared.raw_id() => { - self.handle_event(Event::Mouse(MouseEvent::CursorEntered)); - // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, - // we generate a CursorMoved as well, so the mouse position from here isn't lost - let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); - self.handle_event(Event::Mouse(MouseEvent::CursorMoved { - position: physical_pos.cast(), - modifiers: key_mods(event.state), - })); + handle_enter_notify(event, &self.handler) } - XEvent::LeaveNotify(event) if event.event == self.shared.raw_id() => { - self.handle_event(Event::Mouse(MouseEvent::CursorLeft)); + handle_leave_notify(event, &self.handler) } - XEvent::ButtonPress(event) if event.event == self.shared.raw_id() => { - match event.detail { - 4..=7 => { - self.handle_event(Event::Mouse(MouseEvent::WheelScrolled { - delta: match event.detail { - 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, - 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, - 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, - 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, - _ => unreachable!(), - }, - modifiers: key_mods(event.state), - })); - } - detail => { - self.handle_event(Event::Mouse(MouseEvent::ButtonPressed { - button: mouse_id(detail), - modifiers: key_mods(event.state), - })); - } - } + handle_button_press(event, &self.handler) } - - XEvent::ButtonRelease(event) - if event.event == self.shared.raw_id() && !(4..=7).contains(&event.detail) => - { - let button_id = mouse_id(event.detail); - self.handle_event(Event::Mouse(MouseEvent::ButtonReleased { - button: button_id, - modifiers: key_mods(event.state), - })); + XEvent::ButtonRelease(event) if event.event == self.shared.raw_id() => { + handle_button_release(event, &self.handler); } //// // keys //// XEvent::KeyPress(event) if event.event == self.shared.raw_id() => { - let ev = Event::Keyboard(convert_key_press_event(&event, &mut self.xkb_state)); - self.handle_event(ev); + self.handler.on_event(Event::Keyboard(convert_key_press_event( + &event, + &mut self.xkb_state, + ))); } XEvent::KeyRelease(event) if event.event == self.shared.raw_id() => { - let ev = Event::Keyboard(convert_key_release_event(&event, &mut self.xkb_state)); - self.handle_event(ev); + self.handler.on_event(Event::Keyboard(convert_key_release_event( + &event, + &mut self.xkb_state, + ))); } XEvent::FocusIn(event) if event.event == self.shared.raw_id() => { self.shared.is_focused.set(true); - self.handle_event(Event::Window(WindowEvent::Focused)); + self.handler.on_event(Event::Window(WindowEvent::Focused)); } XEvent::FocusOut(e) if e.event == self.shared.raw_id() => { self.shared.is_focused.set(false); - self.handle_event(Event::Window(WindowEvent::Unfocused)); + self.handler.on_event(Event::Window(WindowEvent::Unfocused)); } XEvent::ReparentNotify(e) if e.window == self.shared.raw_id() => { @@ -411,19 +351,4 @@ impl EventLoop { Ok(()) } - - fn handle_event(&mut self, event: Event) { - self.handler.on_event(event); - } -} - -fn mouse_id(id: u8) -> MouseButton { - match id { - 1 => MouseButton::Left, - 2 => MouseButton::Middle, - 3 => MouseButton::Right, - 8 => MouseButton::Back, - 9 => MouseButton::Forward, - id => MouseButton::Other(id), - } } diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 4cb6b877..2b7e8d6b 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -6,6 +6,7 @@ mod event_loop; pub mod gl; mod handler; mod keyboard; +mod mouse; mod present; mod sizing; mod visual_info; @@ -23,6 +24,7 @@ pub(crate) mod prelude { pub use super::error::{CookieExt as _, FatalError, PlatformError, ReplyExt as _}; pub use super::event_loop::*; pub use super::handler::Handler; + pub use super::mouse::*; pub use super::present::*; pub use super::sizing::*; pub use super::visual_info::WindowVisualConfig; diff --git a/src/platform/x11/mouse.rs b/src/platform/x11/mouse.rs new file mode 100644 index 00000000..5ba1b1f9 --- /dev/null +++ b/src/platform/x11/mouse.rs @@ -0,0 +1,94 @@ +// For all the keyboard and mouse events, you can fetch +// `x`, `y`, `detail`, and `state`. +// - `x` and `y` are the position inside the window where the cursor currently is +// when the event happened. +// - `detail` will tell you which keycode was pressed/released (for keyboard events) +// or which mouse button was pressed/released (for mouse events). +// For mouse events, here's what the value means (at least on my current mouse): +// 1 = left mouse button +// 2 = middle mouse button (scroll wheel) +// 3 = right mouse button +// 4 = scroll wheel up +// 5 = scroll wheel down +// 8 = lower side button ("back" button) +// 9 = upper side button ("forward" button) +// Note that you *will* get a "button released" event for even the scroll wheel +// events, which you can probably ignore. +// - `state` will tell you the state of the main three mouse buttons and some of +// the keyboard modifier keys at the time of the event. +// http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445 + +use crate::platform::prelude::Handler; +use crate::platform::x11::keyboard::key_mods; +use crate::{Event, MouseButton, MouseEvent, ScrollDelta}; +use dpi::PhysicalPosition; +use x11rb::protocol::xproto::{ + ButtonPressEvent, ButtonReleaseEvent, EnterNotifyEvent, LeaveNotifyEvent, MotionNotifyEvent, +}; + +pub fn handle_motion_notify(event: MotionNotifyEvent, handler: &Handler) { + let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); + + handler.on_event(Event::Mouse(MouseEvent::CursorMoved { + position: physical_pos.cast(), + modifiers: key_mods(event.state), + })); +} + +pub fn handle_enter_notify(event: EnterNotifyEvent, handler: &Handler) { + handler.on_event(Event::Mouse(MouseEvent::CursorEntered)); + // since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated, + // we generate a CursorMoved as well, so the mouse position from here isn't lost + let physical_pos = PhysicalPosition::new(event.event_x, event.event_y); + handler.on_event(Event::Mouse(MouseEvent::CursorMoved { + position: physical_pos.cast(), + modifiers: key_mods(event.state), + })); +} + +pub fn handle_leave_notify(_: LeaveNotifyEvent, handler: &Handler) { + handler.on_event(Event::Mouse(MouseEvent::CursorLeft)); +} + +pub fn handle_button_press(event: ButtonPressEvent, handler: &Handler) { + match event.detail { + 4..=7 => { + handler.on_event(Event::Mouse(MouseEvent::WheelScrolled { + delta: match event.detail { + 4 => ScrollDelta::Lines { x: 0.0, y: 1.0 }, + 5 => ScrollDelta::Lines { x: 0.0, y: -1.0 }, + 6 => ScrollDelta::Lines { x: -1.0, y: 0.0 }, + 7 => ScrollDelta::Lines { x: 1.0, y: 0.0 }, + _ => unreachable!(), + }, + modifiers: key_mods(event.state), + })); + } + detail => { + handler.on_event(Event::Mouse(MouseEvent::ButtonPressed { + button: button_from_id(detail), + modifiers: key_mods(event.state), + })); + } + } +} + +pub fn handle_button_release(event: ButtonReleaseEvent, handler: &Handler) { + if !(4..=7).contains(&event.detail) { + handler.on_event(Event::Mouse(MouseEvent::ButtonReleased { + button: button_from_id(event.detail), + modifiers: key_mods(event.state), + })); + } +} + +fn button_from_id(id: u8) -> MouseButton { + match id { + 1 => MouseButton::Left, + 2 => MouseButton::Middle, + 3 => MouseButton::Right, + 8 => MouseButton::Back, + 9 => MouseButton::Forward, + id => MouseButton::Other(id), + } +} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 44bd3714..d35f6509 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -108,12 +108,7 @@ impl WindowShared { } pub fn focus(&self) -> PlatformResult<()> { - self.connection - .conn - .set_input_focus(InputFocus::POINTER_ROOT, self.xcb_window.id(), CURRENT_TIME)? - .check()?; - - Ok(()) + self.xcb_window.focus() } pub fn resize(&self, new_size: Size) -> PlatformResult<()> { diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs index d171bf78..d3501b9d 100644 --- a/src/platform/x11/xcb_window.rs +++ b/src/platform/x11/xcb_window.rs @@ -6,10 +6,11 @@ use x11rb::properties::WmSizeHints; use x11rb::protocol::present; use x11rb::protocol::present::ConnectionExt as _; use x11rb::protocol::xproto::{ - AtomEnum, ConfigureWindowAux, ConnectionExt as _, CreateWindowAux, EventMask, PropMode, - WindowClass, + AtomEnum, ConfigureWindowAux, ConnectionExt as _, CreateWindowAux, EventMask, InputFocus, + PropMode, WindowClass, }; use x11rb::wrapper::ConnectionExt as _; +use x11rb::CURRENT_TIME; pub struct XcbWindow { connection: Rc, @@ -143,6 +144,15 @@ impl XcbWindow { ) } + pub fn focus(&self) -> PlatformResult<()> { + self.connection + .conn + .set_input_focus(InputFocus::POINTER_ROOT, self.id(), CURRENT_TIME)? + .check()?; + + Ok(()) + } + pub fn set_size_hints( &self, size_hints: WmSizeHints, ) -> Result, ConnectionError> { From 22c61ce01c01e3621833457ec83b60f32dc37c44 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:47:46 +0200 Subject: [PATCH 08/10] fixes --- src/platform/x11/handler.rs | 6 +++++- src/platform/x11/mod.rs | 5 ++--- src/platform/x11/window_shared.rs | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/platform/x11/handler.rs b/src/platform/x11/handler.rs index 235258f2..5859b158 100644 --- a/src/platform/x11/handler.rs +++ b/src/platform/x11/handler.rs @@ -1,5 +1,5 @@ use crate::platform::x11::error::FatalError; -use crate::{DamageArea, EventStatus, WindowHandler, WindowSize}; +use crate::{DamageArea, EventStatus, TimerHandle, WindowHandler, WindowSize}; pub struct Handler { handler: Box, @@ -31,6 +31,10 @@ impl Handler { self.handler.on_event(event) } + pub fn on_timer(&self, handle: &TimerHandle) { + self.handler.on_timer(handle) + } + pub fn damage(&self, area: DamageArea) { self.handler.damage(area) } diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 2b7e8d6b..edeea9d7 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -9,10 +9,8 @@ mod keyboard; mod mouse; mod present; mod sizing; -mod visual_info; -mod xcb_window; - mod timer; +mod visual_info; mod waker; mod window_shared; mod window_thread; @@ -27,6 +25,7 @@ pub(crate) mod prelude { pub use super::mouse::*; pub use super::present::*; pub use super::sizing::*; + pub use super::timer::*; pub use super::visual_info::WindowVisualConfig; pub use super::waker::WindowWaker; pub use super::window_shared::WindowShared; diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index d35f6509..a44a5ad7 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -4,8 +4,7 @@ use dpi::Size; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::time::Duration; use x11rb::protocol::xproto; -use x11rb::protocol::xproto::{ConnectionExt, InputFocus, Visualid}; -use x11rb::CURRENT_TIME; +use x11rb::protocol::xproto::Visualid; /// Data that is shared between the event loop and the window handler. pub struct WindowShared { @@ -137,8 +136,9 @@ impl WindowShared { pub fn gl_context(&self) -> Option { Some(GlContext::new(Rc::clone(self.gl_context.as_ref()?))) } + #[inline] - pub fn create_timer(&self, duration: Duration) -> Result { + pub fn create_timer(&self, duration: Duration) -> PlatformResult { insert_timer(&self.loop_handle, duration) } From 0616cf597ea392569fb42c084ac32d69cf07e6df Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 23:31:04 +0200 Subject: [PATCH 09/10] fixes --- src/platform/x11/visual_info.rs | 3 +++ src/platform/x11/x11_connection.rs | 17 +---------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/platform/x11/visual_info.rs b/src/platform/x11/visual_info.rs index 04ec5f49..a75bcc13 100644 --- a/src/platform/x11/visual_info.rs +++ b/src/platform/x11/visual_info.rs @@ -33,6 +33,9 @@ impl WindowVisualConfig { }); } + #[cfg(not(feature = "opengl"))] + let _ = settings; + match find_visual_for_depth(connection.default_screen(), 32) { None => Ok(Self::copy_from_parent()), Some(visual_id) => Ok(Self { diff --git a/src/platform/x11/x11_connection.rs b/src/platform/x11/x11_connection.rs index e04596de..27502d57 100644 --- a/src/platform/x11/x11_connection.rs +++ b/src/platform/x11/x11_connection.rs @@ -1,13 +1,9 @@ use super::prelude::*; use crate::wrappers::xlib::XlibXcbConnection; use x11rb::connection::RequestConnection; -use x11rb::cookie::VoidCookie; use x11rb::cursor::Handle as CursorHandle; -use x11rb::errors::ConnectionError; use x11rb::protocol::present; -use x11rb::protocol::xproto::{ - self, Atom, ChangeWindowAttributesAux, ConnectionExt, EventMask, Screen, -}; +use x11rb::protocol::xproto::{self, Atom, Screen}; use x11rb::resource_manager; use x11rb::xcb_ffi::XCBConnection; @@ -73,17 +69,6 @@ impl X11Connection { ) -> Result, GetPropertyError> { get_property::get_property(window, property, property_type, &self.conn) } - - pub fn register_tree_structure_events( - &self, - ) -> Result, ConnectionError> { - let root = self.default_screen().root; - - self.conn.change_window_attributes( - root, - &ChangeWindowAttributesAux::new().event_mask(EventMask::SUBSTRUCTURE_NOTIFY), - ) - } } pub struct ConnectionResources { From abfba3ab8391d0ffec9df28f8d970ce306bc3218 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 8 Oct 2026 23:38:27 +0200 Subject: [PATCH 10/10] fixes --- src/platform/macos/gl.rs | 8 ++++---- src/platform/macos/view.rs | 5 +++-- src/platform/win/gl.rs | 2 +- src/platform/win/window_state.rs | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/platform/macos/gl.rs b/src/platform/macos/gl.rs index 58333972..e550363c 100644 --- a/src/platform/macos/gl.rs +++ b/src/platform/macos/gl.rs @@ -51,16 +51,16 @@ impl Display for GlError { } #[derive(Clone)] -pub struct GlContext { +pub struct PlatformGlContext { pub(crate) view: Retained, context: Retained, gl_bundle: CFRetained, } -impl GlContext { +impl PlatformGlContext { pub(crate) fn create( parent_view: &NSView, config: GlConfig, marker: MainThreadMarker, - ) -> Result { + ) -> Result { let version = if config.version < (3, 2) && config.profile == Profile::Compatibility { NSOpenGLProfileVersionLegacy } else if config.version == (3, 2) && config.profile == Profile::Core { @@ -133,7 +133,7 @@ impl GlContext { let gl_bundle = CFBundle::bundle_with_identifier(Some(&framework_name)) .ok_or(GlError::OpenGlBundleNotFound)?; - Ok(GlContext { view, context, gl_bundle }) + Ok(PlatformGlContext { view, context, gl_bundle }) } pub unsafe fn make_current(&self) -> Result<()> { diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 7b53e4be..07cd39c6 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -84,7 +84,7 @@ pub(crate) struct BaseviewView { timers: TimerManager, #[cfg(feature = "opengl")] - pub(crate) gl_context: OnceCell, + pub(crate) gl_context: OnceCell, } impl BaseviewView { @@ -133,7 +133,8 @@ impl BaseviewView { #[cfg(feature = "opengl")] if let Some(gl_config) = init.settings.gl_config { - let gl_context = super::gl::GlContext::create(view.view, gl_config, view.mtm)?; + let gl_context = + super::gl::PlatformGlContext::create(view.view, gl_config, view.mtm)?; let Ok(()) = view.gl_context.set(gl_context) else { unreachable!() }; } diff --git a/src/platform/win/gl.rs b/src/platform/win/gl.rs index aac73f6d..ea403674 100644 --- a/src/platform/win/gl.rs +++ b/src/platform/win/gl.rs @@ -11,7 +11,7 @@ use crate::wrappers::win32::window::{ }; use crate::wrappers::win32::RawLibrary; -pub type GlContext = Rc; +pub type PlatformGlContext = Rc; pub struct GlContextInner { hdc: OwnDeviceContext, diff --git a/src/platform/win/window_state.rs b/src/platform/win/window_state.rs index 4a9610c8..4e2c6e78 100644 --- a/src/platform/win/window_state.rs +++ b/src/platform/win/window_state.rs @@ -35,7 +35,7 @@ pub(crate) struct WindowState { pub redraw_requested: Cell, #[cfg(feature = "opengl")] - pub gl_context: std::cell::OnceCell, + pub gl_context: std::cell::OnceCell, } impl WindowState {