From 9d2f5698be0cd70d61cae8620b21b3dbe953019e Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:11:34 +0200 Subject: [PATCH 1/7] wip --- Cargo.toml | 2 +- src/platform/x11/error.rs | 7 ++ src/platform/x11/event_loop.rs | 128 +++++++++++++++++------------- src/platform/x11/mod.rs | 1 + src/platform/x11/window.rs | 75 ----------------- src/platform/x11/window_shared.rs | 65 ++++++++++++++- src/platform/x11/window_thread.rs | 0 src/wrappers.rs | 3 - src/wrappers/connection_poller.rs | 66 --------------- 9 files changed, 144 insertions(+), 203 deletions(-) create mode 100644 src/platform/x11/window_thread.rs delete mode 100644 src/wrappers/connection_poller.rs diff --git a/Cargo.toml b/Cargo.toml index 461eb535..d18e4098 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ tracing = { version = "0.1", optional = true } x11rb = { version = "0.13.2", features = ["cursor", "resource_manager", "allow-unsafe-code", "dl-libxcb"], default-features = false } xkbcommon-dl = { version = "0.4.2", features = ["x11"] } x11-dl = { version = "2.21.0" } -polling = "3.11.0" +calloop = "0.14.4" percent-encoding = "2.3.2" bytemuck = "1.25.0" diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index f1f622b1..7ec1c06a 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -25,6 +25,7 @@ pub enum Error { DisplayOpenFailed(DisplayOpenFailedError), Handler(HandlerError), Channel(RecvError), + Calloop(calloop::Error), #[cfg(feature = "opengl")] XLib(crate::wrappers::xlib::XLibError), #[cfg(feature = "opengl")] @@ -98,6 +99,12 @@ impl From for Error { } } +impl From for Error { + fn from(value: calloop::Error) -> Self { + Self::Calloop(value) + } +} + #[cfg(feature = "opengl")] impl From for Error { fn from(value: crate::wrappers::xlib::XLibError) -> Self { diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index e5ce2edc..deae7c3f 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -1,11 +1,15 @@ 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::warn; use crate::wrappers::connection_poller::{ConnectionPoller, PollStatus}; use crate::wrappers::xkbcommon::XkbcommonState; 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, RegistrationToken}; use dpi::{PhysicalPosition, PhysicalSize}; use std::rc::Rc; use std::time::{Duration, Instant}; @@ -16,31 +20,45 @@ use x11rb::protocol::Event as XEvent; pub(crate) struct EventLoop { handler: Box, window: Rc, - parent_handle: Option, new_physical_size: Option>, - frame_interval: Duration, - event_loop_running: bool, - drag_n_drop: DragNDropState, + frame_timer: RegistrationToken, + loop_handle: LoopHandle<'static, Self>, + loop_signal: LoopSignal, + drag_n_drop: DragNDropState, xkb_state: Option, } +const FRAME_INTERVAL: Duration = Duration::from_millis(15); + impl EventLoop { pub fn new( window: Rc, handler: Box, - parent_handle: Option, + inner: &mut calloop::EventLoop<'static, Self>, ) -> Self { + let loop_handle = inner.handle(); + let frame_timer = loop_handle + .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| e.handle_frame(i)) + .unwrap(); + + loop_handle + .insert_source( + Generic::new_with_error(window.connection.conn.clone(), Interest::READ, Mode::Edge), + |_, _, e| e.handle_connection_event_ready(), + ) + .unwrap(); + Self { - xkb_state: XkbcommonState::new(&window.connection), - window, + loop_handle, + loop_signal: inner.get_signal(), handler, - parent_handle, - frame_interval: Duration::from_millis(15), - event_loop_running: false, + frame_timer, new_physical_size: None, drag_n_drop: DragNDropState::NoCurrentSession, + xkb_state: XkbcommonState::new(&window.connection), + window, } } @@ -49,7 +67,7 @@ impl EventLoop { } #[inline] - fn drain_xcb_events(&mut self) -> core::result::Result<(), ConnectionError> { + fn drain_xcb_events(&mut self) -> Result<(), ConnectionError> { // the X server has a tendency to send spurious/extraneous configure notify events when a // window is resized, and we need to batch those together and just send one resize event // when they've all been coalesced. @@ -75,53 +93,54 @@ impl EventLoop { Ok(()) } - // Event loop - pub fn run(mut self) -> Result<()> { - let connection = Rc::clone(&self.window.connection); - let mut poller = ConnectionPoller::new(&connection.conn)?; - - let mut last_frame = Instant::now(); - self.event_loop_running = true; - - while self.event_loop_running { - // 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. The conditional here is needed because event handling and frame - // drawing is interleaved. The `poll()` function below will wait until the next frame - // can be drawn, or until the window receives an event. We thus need to manually check - // if it's already time to draw a new frame. - let next_frame = last_frame + self.frame_interval; - if Instant::now() >= next_frame { - self.handler.on_frame()?; - last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval); - } + fn handle_connection_event_ready(&mut self) -> Result { + self.drain_xcb_events()?; - // Check for any events in the internal buffers - // before going to sleep: - self.drain_xcb_events()?; + Ok(PostAction::Continue) + } - // FIXME: handle errors - if let PollStatus::ReadAvailable = poller.wait(next_frame)? { - self.drain_xcb_events()?; - } + fn handle_frame(&mut self, previous_deadline: Instant) -> TimeoutAction { + self.handler.on_frame(); // TODO: handle error - // Check if the parents's handle was dropped (such as when the host - // requested the window to close) - if let Some(parent_handle) = &self.parent_handle { - if parent_handle.parent_did_drop() { - self.handle_must_close(); - self.window.close_requested.set(false); - } - } + // 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 next_deadline = if previous_deadline + FRAME_INTERVAL >= now { + now + FRAME_INTERVAL + } else { + previous_deadline + FRAME_INTERVAL + }; + + TimeoutAction::ToInstant(next_deadline) + } + + fn handle_idle(&mut self) { + // Check for any events in the internal buffers + // before going to sleep: + self.drain_xcb_events().unwrap(); - // Check if the user has requested the window to close - if self.window.close_requested.get() { + /* + // Check if the parents's handle was dropped (such as when the host + // requested the window to close) + if let Some(parent_handle) = &self.parent_handle { + if parent_handle.parent_did_drop() { self.handle_must_close(); self.window.close_requested.set(false); } + }*/ + + // Check if the user has requested the window to close + if self.window.close_requested.get() { + self.handle_must_close(); + self.loop_signal.stop(); + self.window.close_requested.set(false); } + } - poller.delete()?; + pub fn run(&mut self, mut inner: calloop::EventLoop) -> Result<(), Error> { + inner.run(None, self, Self::handle_idle)?; Ok(()) } @@ -157,7 +176,7 @@ impl EventLoop { } if event.data.as_data32()[0] == self.window.connection.atoms.WM_DELETE_WINDOW { - self.handle_close_requested(); + self.handle_must_close(); return; } @@ -301,15 +320,10 @@ impl EventLoop { self.handler.on_event(event); } - fn handle_close_requested(&mut self) { - // FIXME: handler should decide whether window stays open or not - self.handle_must_close(); - } - fn handle_must_close(&mut self) { self.handle_event(Event::Window(WindowEvent::WillClose)); - - self.event_loop_running = false; + self.loop_signal.stop(); + self.loop_signal.wakeup(); } } diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 858915e8..009970c4 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -21,6 +21,7 @@ mod visual_info; mod xcb_window; mod window_shared; +mod window_thread; pub use error::{CookieExt as _, Error}; pub(crate) type Result = std::result::Result; diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs index cd99e990..f10078de 100644 --- a/src/platform/x11/window.rs +++ b/src/platform/x11/window.rs @@ -1,16 +1,11 @@ use std::cell::Cell; use std::num::NonZero; -use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; use std::sync::Arc; use std::thread::{self, JoinHandle}; -use super::X11Connection; -use super::{event_loop::EventLoop, visual_info::WindowVisualConfig}; use crate::handler::WindowHandlerBuilder; -use crate::platform::x11::window_shared::WindowInner; -use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::Result; use crate::*; @@ -102,76 +97,6 @@ impl Drop for ParentHandle { } } -type WindowOpenResult = core::result::Result, String>; - -fn create_window( - options: WindowOpenOptions, build: WindowHandlerBuilder, parent_handle: Option, -) -> Result { - // Connect to the X server - let xcb_connection = X11Connection::new()?; - - let scaling = match options.scale { - WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling(), - WindowScalePolicy::ScaleFactor(scale) => scale, - }; - - let physical_size = options.size.to_physical(scaling); - - #[cfg(feature = "opengl")] - let visual_info = - WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; - - #[cfg(not(feature = "opengl"))] - let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; - - let xcb_connection = Rc::new(xcb_connection); - - let x_window = XcbWindow::new( - Rc::clone(&xcb_connection), - physical_size, - &visual_info, - options.parent.map(|p| p.window_id), - )?; - - let cookies = [ - x_window.map_window()?, - x_window.set_title(&options.title)?, - x_window.enable_wm_protocols()?, - x_window.enable_dnd_protocols()?, - ]; - - 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(super::gl::GlContextInner::create( - &x_window, - Rc::clone(&xcb_connection), - fb_config, - )?) - } - }; - - let inner = Rc::new(WindowInner::new( - xcb_connection, - x_window, - physical_size, - scaling, - visual_info.visual_id, - #[cfg(feature = "opengl")] - gl_context, - )); - - let handler = build.build(WindowContext::new(Rc::clone(&inner)))?; - - Ok(EventLoop::new(inner, handler, parent_handle)) -} - pub fn copy_to_clipboard(_data: &str) { todo!() } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 8b69cd0b..a63cc67c 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,6 +1,7 @@ +use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::*; -use crate::{MouseCursor, WindowSize}; +use crate::{MouseCursor, WindowOpenOptions, WindowScalePolicy, WindowSize}; use dpi::{PhysicalSize, Size}; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::cell::Cell; @@ -26,6 +27,68 @@ pub(crate) struct WindowInner { } impl WindowInner { + pub(crate) fn create(options: WindowOpenOptions) -> Result> { + // Connect to the X server + let xcb_connection = X11Connection::new()?; + + let scaling = match options.scale { + WindowScalePolicy::SystemScaleFactor => xcb_connection.get_scaling(), + WindowScalePolicy::ScaleFactor(scale) => scale, + }; + + let physical_size = options.size.to_physical(scaling); + + #[cfg(feature = "opengl")] + let visual_info = + WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; + + #[cfg(not(feature = "opengl"))] + let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; + + let xcb_connection = Rc::new(xcb_connection); + + let x_window = XcbWindow::new( + Rc::clone(&xcb_connection), + physical_size, + &visual_info, + options.parent.map(|p| p.window_id), + )?; + + let cookies = [ + x_window.map_window()?, + x_window.set_title(&options.title)?, + x_window.enable_wm_protocols()?, + x_window.enable_dnd_protocols()?, + ]; + + 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(super::gl::GlContextInner::create( + &x_window, + Rc::clone(&xcb_connection), + fb_config, + )?) + } + }; + + Ok(Rc::new(WindowInner::new( + xcb_connection, + x_window, + physical_size, + scaling, + visual_info.visual_id, + #[cfg(feature = "opengl")] + gl_context, + ))) + } + pub(crate) fn new( connection: Rc, xcb_window: XcbWindow, window_size: PhysicalSize, scale_factor: f64, visual_id: Visualid, diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/wrappers.rs b/src/wrappers.rs index d5d30536..01483e1e 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -23,9 +23,6 @@ pub mod xkbcommon; #[cfg(all(target_os = "linux", feature = "opengl"))] pub mod glx; -#[cfg(target_os = "linux")] -pub mod connection_poller; - /// Wrappers and utilities around the Win32 API #[cfg(target_os = "windows")] pub mod win32; diff --git a/src/wrappers/connection_poller.rs b/src/wrappers/connection_poller.rs deleted file mode 100644 index 19a8b913..00000000 --- a/src/wrappers/connection_poller.rs +++ /dev/null @@ -1,66 +0,0 @@ -use polling::{Event, Events, Poller}; -use std::io; -use std::os::fd::{AsFd, BorrowedFd}; -use std::time::Instant; - -pub struct ConnectionPoller<'a> { - poller: Poller, - events: Events, - fd: BorrowedFd<'a>, -} - -const CONNECTION_KEY: usize = 42; - -impl<'a> ConnectionPoller<'a> { - pub fn new(source: &'a impl AsFd) -> io::Result { - let poller = Poller::new()?; - let fd = source.as_fd(); - unsafe { poller.add(&fd, Event::readable(CONNECTION_KEY))? }; - - Ok(Self { poller, fd, events: Events::new() }) - } - - pub fn wait(&mut self, deadline: Instant) -> io::Result { - self.events.clear(); - // NOTE: polling crate already handles retrying on EINTR - let new_events_count = self.poller.wait_deadline(&mut self.events, deadline)?; - - if new_events_count == 0 { - return Ok(PollStatus::Nothing); - } - - for event in self.events.iter() { - if event.key != CONNECTION_KEY { - continue; - } - - if let Some(true) = event.is_err() { - panic!("xcb connection poll error") - } - - if event.is_interrupt() { - return Ok(PollStatus::ConnectionClosed); - } - - return Ok(PollStatus::ReadAvailable); - } - - Ok(PollStatus::Nothing) - } - - pub fn delete(self) -> io::Result<()> { - self.poller.delete(self.fd) - } -} - -impl<'a> Drop for ConnectionPoller<'a> { - fn drop(&mut self) { - let _ = self.poller.delete(self.fd); - } -} - -pub enum PollStatus { - Nothing, - ReadAvailable, - ConnectionClosed, -} From 197cb101970b31ae348e589f445d92aece311bc5 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:54:32 +0200 Subject: [PATCH 2/7] wip --- Cargo.toml | 3 + examples/render_femtovg/Cargo.toml | 3 +- examples/render_femtovg/src/main.rs | 2 + src/lib.rs | 2 +- src/platform/x11/error.rs | 6 ++ src/platform/x11/event_loop.rs | 15 ++-- src/platform/x11/window.rs | 99 +-------------------- src/platform/x11/window_thread.rs | 130 ++++++++++++++++++++++++++++ src/tracing.rs | 3 +- 9 files changed, 155 insertions(+), 108 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d18e4098..35d4f3c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,3 +99,6 @@ missing-safety-doc = "allow" [package.metadata.docs.rs] all-features = true + +[workspace.dependencies] +tracing-subscriber = "0.3.23" diff --git a/examples/render_femtovg/Cargo.toml b/examples/render_femtovg/Cargo.toml index 2271a344..72b19485 100644 --- a/examples/render_femtovg/Cargo.toml +++ b/examples/render_femtovg/Cargo.toml @@ -5,5 +5,6 @@ edition = "2021" publish = false [dependencies] -baseview = { path = "../..", features = ["opengl"] } +baseview = { path = "../..", features = ["opengl", "tracing"] } femtovg = "0.25.1" +tracing-subscriber = { workspace = true } diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index c609f829..749a3af1 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -116,6 +116,8 @@ impl WindowHandler for FemtovgExample { } fn main() -> Result<(), baseview::Error> { + tracing_subscriber::fmt::init(); + let window_open_options = WindowOpenOptions::new() .with_title("Femtovg on Baseview") .with_size(LogicalSize::new(512, 512)) diff --git a/src/lib.rs b/src/lib.rs index a781de81..b5aa10ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,6 @@ pub use window::*; pub use window_open_options::*; #[allow(unused)] -pub(crate) use tracing::warn; +pub(crate) use tracing::*; pub(crate) mod wrappers; diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index 7ec1c06a..c416a130 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -105,6 +105,12 @@ impl From for Error { } } +impl From for Error { + fn from(value: RecvError) -> Self { + Self::Channel(value) + } +} + #[cfg(feature = "opengl")] impl From for Error { fn from(value: crate::wrappers::xlib::XLibError) -> Self { diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index deae7c3f..afa62905 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -4,7 +4,6 @@ use super::*; use std::result::Result; use crate::warn; -use crate::wrappers::connection_poller::{ConnectionPoller, PollStatus}; use crate::wrappers::xkbcommon::XkbcommonState; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowSize}; use calloop::generic::Generic; @@ -37,20 +36,20 @@ impl EventLoop { pub fn new( window: Rc, handler: Box, inner: &mut calloop::EventLoop<'static, Self>, - ) -> Self { + ) -> Result { let loop_handle = inner.handle(); let frame_timer = loop_handle .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| e.handle_frame(i)) - .unwrap(); + .map_err(|e| e.error)?; loop_handle .insert_source( Generic::new_with_error(window.connection.conn.clone(), Interest::READ, Mode::Edge), |_, _, e| e.handle_connection_event_ready(), ) - .unwrap(); + .map_err(|e| e.error)?; - Self { + Ok(Self { loop_handle, loop_signal: inner.get_signal(), handler, @@ -59,7 +58,7 @@ impl EventLoop { drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), window, - } + }) } pub fn window_id(&self) -> NonZeroU32 { @@ -139,8 +138,8 @@ impl EventLoop { } } - pub fn run(&mut self, mut inner: calloop::EventLoop) -> Result<(), Error> { - inner.run(None, self, Self::handle_idle)?; + pub fn run(mut self, mut inner: calloop::EventLoop) -> Result<(), Error> { + inner.run(None, &mut self, Self::handle_idle)?; Ok(()) } diff --git a/src/platform/x11/window.rs b/src/platform/x11/window.rs index f10078de..d5326e17 100644 --- a/src/platform/x11/window.rs +++ b/src/platform/x11/window.rs @@ -1,101 +1,6 @@ -use std::cell::Cell; -use std::num::NonZero; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::mpsc; -use std::sync::Arc; -use std::thread::{self, JoinHandle}; +use crate::platform::x11::window_thread::WindowThreadHandle; -use crate::handler::WindowHandlerBuilder; -use crate::platform::Result; -use crate::*; - -pub struct WindowHandle { - window_id: Option>, - event_loop_handle: Cell>>, - close_requested: Arc, - is_open: Arc, -} - -impl WindowHandle { - pub fn create_window( - options: WindowOpenOptions, handler: WindowHandlerBuilder, - ) -> Result { - let (tx, rx) = mpsc::sync_channel::(1); - let (parent_handle, mut window_handle) = ParentHandle::new(); - - let join_handle = - thread::spawn(move || match create_window(options, handler, Some(parent_handle)) { - Ok(ev_loop) => { - tx.send(Ok(ev_loop.window_id())).unwrap(); - ev_loop.run().unwrap(); - } - Err(e) => { - tx.send(Err(format!("{}", e))).unwrap(); - } - }); - - let id = match rx.recv() { - Ok(Ok(id)) => id, - Err(e) => return Err(super::Error::Channel(e)), - Ok(Err(s)) => return Err(super::error::Error::CreationFailed(s)), - }; - - window_handle.window_id = Some(id); - window_handle.event_loop_handle = Some(join_handle).into(); - Ok(window_handle) - } - - pub fn run_until_closed(self) { - let Some(thread) = self.event_loop_handle.take() else { return }; - - thread.join().unwrap_or_else(|err| { - eprintln!("Window thread panicked: {:#?}", err); - }); - } - - pub fn is_open(&self) -> bool { - self.is_open.load(Ordering::Relaxed) - } -} - -impl Drop for WindowHandle { - fn drop(&mut self) { - self.close_requested.store(true, Ordering::Relaxed); - if let Some(event_loop) = self.event_loop_handle.take() { - let _ = event_loop.join(); - } - } -} - -pub(crate) struct ParentHandle { - close_requested: Arc, - is_open: Arc, -} - -impl ParentHandle { - pub fn new() -> (Self, WindowHandle) { - let close_requested = Arc::new(AtomicBool::new(false)); - let is_open = Arc::new(AtomicBool::new(true)); - let handle = WindowHandle { - window_id: None, - event_loop_handle: None.into(), - close_requested: Arc::clone(&close_requested), - is_open: Arc::clone(&is_open), - }; - - (Self { close_requested, is_open }, handle) - } - - pub fn parent_did_drop(&self) -> bool { - self.close_requested.load(Ordering::Relaxed) - } -} - -impl Drop for ParentHandle { - fn drop(&mut self) { - self.is_open.store(false, Ordering::Relaxed); - } -} +pub type WindowHandle = WindowThreadHandle; pub fn copy_to_clipboard(_data: &str) { todo!() diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index e69de29b..6c89c1a0 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -0,0 +1,130 @@ +use super::*; +use crate::handler::WindowHandlerBuilder; +use crate::platform::x11::event_loop::EventLoop; +use crate::platform::x11::window_shared::WindowInner; +use crate::{WindowContext, WindowOpenOptions}; +use calloop::LoopSignal; +use std::cell::Cell; +use std::num::NonZeroU32; +use std::panic::resume_unwind; +use std::rc::Rc; +use std::sync::mpsc; +use std::thread; +use std::thread::JoinHandle; +use std::time::Duration; + +pub struct WindowThreadHandle { + window_id: NonZeroU32, + event_loop_handle: Cell>>, +} + +impl WindowThreadHandle { + pub fn create_window( + options: WindowOpenOptions, handler: WindowHandlerBuilder, + ) -> Result { + let (tx, rx) = result_channel(); + + let join_handle = thread::spawn(move || { + let thread = match WindowThread::create(options, handler) { + Err(e) => return tx.send_error(e), + Ok(thread) => thread, + }; + + if tx.send_success(&thread) { + thread.run() + } + }); + + thread::sleep(Duration::from_millis(2000)); + + rx.receive(join_handle) + } + + pub fn run_until_closed(self) { + let Some(thread) = self.event_loop_handle.take() else { return }; + + if let Err(panic) = thread.join() { + resume_unwind(panic); + } + } + + pub fn is_open(&self) -> bool { + todo!() + } +} + +impl Drop for WindowThreadHandle { + fn drop(&mut self) { + // TODO: stop event loop + if let Some(event_loop) = self.event_loop_handle.take() { + let _ = event_loop.join(); + } + } +} + +enum WindowOpenResult { + Success { window_id: NonZeroU32, loop_signal: LoopSignal }, + Error(String), +} + +struct WindowThread { + event_loop: EventLoop, + ev_loop: calloop::EventLoop<'static, EventLoop>, +} + +impl WindowThread { + pub fn create(options: WindowOpenOptions, handler: WindowHandlerBuilder) -> Result { + let mut ev_loop = calloop::EventLoop::try_new()?; + let inner = WindowInner::create(options)?; + let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; + let event_loop = EventLoop::new(inner, handler, &mut ev_loop)?; + + Ok(Self { event_loop, ev_loop }) + } + + pub fn run(self) { + self.event_loop.run(self.ev_loop).unwrap(); + } +} + +fn result_channel() -> (WindowResultSender, WindowResultReceiver) { + let (tx, rx) = mpsc::sync_channel::(1); + (WindowResultSender(tx), WindowResultReceiver(rx)) +} + +struct WindowResultSender(mpsc::SyncSender); +impl WindowResultSender { + pub fn send_error(self, error: Error) { + if let Err(err) = self.0.send(WindowOpenResult::Error(format!("{}", error))) { + crate::error!("Window creation failed: {}", error); + crate::warn!("Failed to send error to main thread: {err}"); + } + } + + pub fn send_success(self, thread: &WindowThread) -> bool { + let msg = WindowOpenResult::Success { + loop_signal: thread.ev_loop.get_signal(), + window_id: thread.event_loop.window_id(), + }; + + if let Err(err) = self.0.send(msg) { + crate::error!("Failed to send created window to main thread: {}. Aborting.", err); + return false; + } + + true + } +} + +struct WindowResultReceiver(mpsc::Receiver); +impl WindowResultReceiver { + pub fn receive(self, join_handle: JoinHandle<()>) -> Result { + let result = self.0.recv()?; + match result { + WindowOpenResult::Error(e) => Err(Error::CreationFailed(e)), + WindowOpenResult::Success { window_id, loop_signal } => { + Ok(WindowThreadHandle { event_loop_handle: Some(join_handle).into(), window_id }) + } + } + } +} diff --git a/src/tracing.rs b/src/tracing.rs index bc23b156..88ce3c2b 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -1,5 +1,5 @@ #[cfg(feature = "tracing")] -pub use tracing::*; +pub use tracing::{error, warn}; #[cfg(not(feature = "tracing"))] mod tracing_impl { @@ -13,6 +13,7 @@ mod tracing_impl { } pub(crate) use __warn as warn; + pub(crate) use __warn as error; } #[cfg(not(feature = "tracing"))] From aeff7a039058e7495fd6b2f9a9c75b64ff737980 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 16 Jul 2026 19:18:56 +0200 Subject: [PATCH 3/7] wip --- src/platform/x11/window_thread.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 6c89c1a0..b424f7df 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -97,7 +97,7 @@ impl WindowResultSender { pub fn send_error(self, error: Error) { if let Err(err) = self.0.send(WindowOpenResult::Error(format!("{}", error))) { crate::error!("Window creation failed: {}", error); - crate::warn!("Failed to send error to main thread: {err}"); + crate::warn!("Failed to send error to main thread: {}", err); } } From 58ed4200d66a3b85744fc3111cb77b3b56bd4a00 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 16 Jul 2026 19:42:11 +0200 Subject: [PATCH 4/7] wip --- src/platform/x11/drag_n_drop.rs | 16 ++---- src/platform/x11/event_loop.rs | 83 +++++++++---------------------- src/platform/x11/window_shared.rs | 58 +++++++++------------ src/platform/x11/window_thread.rs | 21 ++++---- src/platform/x11/xcb_window.rs | 1 + 5 files changed, 66 insertions(+), 113 deletions(-) diff --git a/src/platform/x11/drag_n_drop.rs b/src/platform/x11/drag_n_drop.rs index 892d4246..1b3b0eac 100644 --- a/src/platform/x11/drag_n_drop.rs +++ b/src/platform/x11/drag_n_drop.rs @@ -95,8 +95,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: &mut dyn WindowHandler, - event: &ClientMessageEvent, + &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let data = event.data.as_data32(); @@ -155,8 +154,7 @@ impl DragNDropState { } pub fn handle_position_event( - &mut self, window: &WindowInner, handler: &mut dyn WindowHandler, - event: &ClientMessageEvent, + &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let event_data = event.data.as_data32(); @@ -262,9 +260,7 @@ impl DragNDropState { } } - pub fn handle_leave_event( - &mut self, handler: &mut dyn WindowHandler, event: &ClientMessageEvent, - ) { + pub fn handle_leave_event(&mut self, handler: &dyn WindowHandler, event: &ClientMessageEvent) { let data = event.data.as_data32(); let event_source_window = data[0] as xproto::Window; @@ -295,8 +291,7 @@ impl DragNDropState { } pub fn handle_drop_event( - &mut self, window: &WindowInner, handler: &mut dyn WindowHandler, - event: &ClientMessageEvent, + &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &ClientMessageEvent, ) -> Result<(), ConnectionError> { let data = event.data.as_data32(); @@ -400,8 +395,7 @@ impl DragNDropState { } pub fn handle_selection_notify_event( - &mut self, window: &WindowInner, handler: &mut dyn WindowHandler, - event: &SelectionNotifyEvent, + &mut self, window: &WindowInner, handler: &dyn WindowHandler, event: &SelectionNotifyEvent, ) -> Result<(), ConnectionError> { // Ignore the event if we weren't actually waiting for a selection notify event let WaitingForData { diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index afa62905..0824f7ff 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -73,7 +73,7 @@ impl EventLoop { self.new_physical_size = None; while let Some(event) = self.window.connection.conn.poll_for_event()? { - self.handle_xcb_event(event); + self.handle_xcb_event(event)?; } if let Some(size) = self.new_physical_size.take() { @@ -99,7 +99,11 @@ impl EventLoop { } fn handle_frame(&mut self, previous_deadline: Instant) -> TimeoutAction { - self.handler.on_frame(); // TODO: handle error + if let Err(e) = self.handler.on_frame() { + // TODO: properly log/bubble up error + self.loop_signal.stop(); + return TimeoutAction::Drop; + } // 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 @@ -116,35 +120,19 @@ impl EventLoop { } fn handle_idle(&mut self) { - // Check for any events in the internal buffers - // before going to sleep: - self.drain_xcb_events().unwrap(); - - /* - // Check if the parents's handle was dropped (such as when the host - // requested the window to close) - if let Some(parent_handle) = &self.parent_handle { - if parent_handle.parent_did_drop() { - self.handle_must_close(); - self.window.close_requested.set(false); - } - }*/ - - // Check if the user has requested the window to close - if self.window.close_requested.get() { - self.handle_must_close(); - self.loop_signal.stop(); - self.window.close_requested.set(false); - } + // Check for any events in the internal buffers before going to sleep: + let _ = self.drain_xcb_events(); } pub fn run(mut self, mut inner: calloop::EventLoop) -> Result<(), Error> { inner.run(None, &mut self, Self::handle_idle)?; + self.handle_event(Event::Window(WindowEvent::WillClose)); + Ok(()) } - fn handle_xcb_event(&mut self, event: XEvent) { + fn handle_xcb_event(&mut self, event: XEvent) -> Result<(), ConnectionError> { // 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 @@ -171,53 +159,35 @@ impl EventLoop { //// XEvent::ClientMessage(event) => { if event.format != 32 { - return; + return Ok(()); } if event.data.as_data32()[0] == self.window.connection.atoms.WM_DELETE_WINDOW { - self.handle_must_close(); - return; + self.window.request_close(); + return Ok(()); } //// // drag n drop //// if event.type_ == self.window.connection.atoms.XdndEnter { - if let Err(_e) = self.drag_n_drop.handle_enter_event( - &self.window, - &mut *self.handler, - &event, - ) { - // TODO: log warning - } + self.drag_n_drop.handle_enter_event(&self.window, &*self.handler, &event)?; } else if event.type_ == self.window.connection.atoms.XdndPosition { - if let Err(_e) = self.drag_n_drop.handle_position_event( - &self.window, - &mut *self.handler, - &event, - ) { - // TODO: log warning - } + self.drag_n_drop.handle_position_event(&self.window, &*self.handler, &event)?; } else if event.type_ == self.window.connection.atoms.XdndDrop { - if let Err(_e) = - self.drag_n_drop.handle_drop_event(&self.window, &mut *self.handler, &event) - { - // TODO: log warning - } + 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(&mut *self.handler, &event); + self.drag_n_drop.handle_leave_event(&*self.handler, &event); } } XEvent::SelectionNotify(event) => { if event.property == self.window.connection.atoms.XdndSelection { - if let Err(_e) = self.drag_n_drop.handle_selection_notify_event( + self.drag_n_drop.handle_selection_notify_event( &self.window, - &mut *self.handler, + &*self.handler, &event, - ) { - // TODO: Log warning - } + )?; } } @@ -272,9 +242,8 @@ impl EventLoop { })); } detail => { - let button_id = mouse_id(detail); self.handle_event(Event::Mouse(MouseEvent::ButtonPressed { - button: button_id, + button: mouse_id(detail), modifiers: key_mods(event.state), })); } @@ -313,17 +282,13 @@ impl EventLoop { _ => {} } + + Ok(()) } fn handle_event(&mut self, event: Event) { self.handler.on_event(event); } - - fn handle_must_close(&mut self) { - self.handle_event(Event::Window(WindowEvent::WillClose)); - self.loop_signal.stop(); - self.loop_signal.wakeup(); - } } fn mouse_id(id: u8) -> MouseButton { diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index a63cc67c..2c5ea66b 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,7 +1,9 @@ +use crate::platform::x11::event_loop::EventLoop; use crate::platform::x11::visual_info::WindowVisualConfig; use crate::platform::x11::xcb_window::XcbWindow; use crate::platform::*; use crate::{MouseCursor, WindowOpenOptions, WindowScalePolicy, WindowSize}; +use calloop::LoopSignal; use dpi::{PhysicalSize, Size}; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::cell::Cell; @@ -22,12 +24,14 @@ pub(crate) struct WindowInner { mouse_cursor: Cell, pub(crate) visual_id: Visualid, - pub(crate) close_requested: Cell, pub(crate) is_focused: Cell, + pub(crate) loop_signal: LoopSignal, } impl WindowInner { - pub(crate) fn create(options: WindowOpenOptions) -> Result> { + pub(crate) fn create( + options: WindowOpenOptions, ev_loop: &calloop::EventLoop<'static, EventLoop>, + ) -> Result> { // Connect to the X server let xcb_connection = X11Connection::new()?; @@ -43,22 +47,22 @@ impl WindowInner { WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; #[cfg(not(feature = "opengl"))] - let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; + let visual_info = WindowVisualConfig::find_best_visual_config(&connection)?; - let xcb_connection = Rc::new(xcb_connection); + let connection = Rc::new(xcb_connection); - let x_window = XcbWindow::new( - Rc::clone(&xcb_connection), + let xcb_window = XcbWindow::new( + Rc::clone(&connection), physical_size, &visual_info, options.parent.map(|p| p.window_id), )?; let cookies = [ - x_window.map_window()?, - x_window.set_title(&options.title)?, - x_window.enable_wm_protocols()?, - x_window.enable_dnd_protocols()?, + xcb_window.map_window()?, + xcb_window.set_title(&options.title)?, + xcb_window.enable_wm_protocols()?, + xcb_window.enable_dnd_protocols()?, ]; for cookie in cookies { @@ -71,43 +75,28 @@ impl WindowInner { Some(fb_config) => { // Because of the visual negotation we had to take some extra steps to create this context Some(super::gl::GlContextInner::create( - &x_window, - Rc::clone(&xcb_connection), + &xcb_window, + Rc::clone(&connection), fb_config, )?) } }; - Ok(Rc::new(WindowInner::new( - xcb_connection, - x_window, - physical_size, - scaling, - visual_info.visual_id, - #[cfg(feature = "opengl")] - gl_context, - ))) - } - - pub(crate) fn new( - connection: Rc, xcb_window: XcbWindow, window_size: PhysicalSize, - scale_factor: f64, visual_id: Visualid, - #[cfg(feature = "opengl")] gl_context: Option, - ) -> Self { - Self { + let visual_id = visual_info.visual_id; + Ok(Rc::new(Self { connection, xcb_window, visual_id, - window_size: window_size.into(), - scaling_factor: scale_factor.into(), + window_size: physical_size.into(), + scaling_factor: scaling.into(), mouse_cursor: MouseCursor::default().into(), + loop_signal: ev_loop.get_signal(), - close_requested: false.into(), is_focused: false.into(), #[cfg(feature = "opengl")] gl_context, - } + })) } pub fn set_mouse_cursor(&self, mouse_cursor: MouseCursor) -> Result<()> { @@ -133,7 +122,8 @@ impl WindowInner { } pub fn request_close(&self) { - self.close_requested.set(true); + self.loop_signal.stop(); + self.loop_signal.wakeup(); } pub fn has_focus(&self) -> bool { diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index b424f7df..f685a890 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -15,6 +15,7 @@ use std::time::Duration; pub struct WindowThreadHandle { window_id: NonZeroU32, + loop_signal: LoopSignal, event_loop_handle: Cell>>, } @@ -40,7 +41,7 @@ impl WindowThreadHandle { rx.receive(join_handle) } - pub fn run_until_closed(self) { + pub fn run_until_closed(&self) { let Some(thread) = self.event_loop_handle.take() else { return }; if let Err(panic) = thread.join() { @@ -55,10 +56,10 @@ impl WindowThreadHandle { impl Drop for WindowThreadHandle { fn drop(&mut self) { - // TODO: stop event loop - if let Some(event_loop) = self.event_loop_handle.take() { - let _ = event_loop.join(); - } + self.loop_signal.stop(); + self.loop_signal.wakeup(); + + self.run_until_closed(); } } @@ -75,7 +76,7 @@ struct WindowThread { impl WindowThread { pub fn create(options: WindowOpenOptions, handler: WindowHandlerBuilder) -> Result { let mut ev_loop = calloop::EventLoop::try_new()?; - let inner = WindowInner::create(options)?; + let inner = WindowInner::create(options, &ev_loop)?; let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; let event_loop = EventLoop::new(inner, handler, &mut ev_loop)?; @@ -122,9 +123,11 @@ impl WindowResultReceiver { let result = self.0.recv()?; match result { WindowOpenResult::Error(e) => Err(Error::CreationFailed(e)), - WindowOpenResult::Success { window_id, loop_signal } => { - Ok(WindowThreadHandle { event_loop_handle: Some(join_handle).into(), window_id }) - } + WindowOpenResult::Success { window_id, loop_signal } => Ok(WindowThreadHandle { + event_loop_handle: Some(join_handle).into(), + window_id, + loop_signal, + }), } } } diff --git a/src/platform/x11/xcb_window.rs b/src/platform/x11/xcb_window.rs index 7e390d9a..7e012645 100644 --- a/src/platform/x11/xcb_window.rs +++ b/src/platform/x11/xcb_window.rs @@ -111,6 +111,7 @@ impl XcbWindow { impl Drop for XcbWindow { fn drop(&mut self) { + // TODO: log error let Ok(cookie) = self.connection.conn.destroy_window(self.window_id.get()) else { return }; let _ = cookie.check(); } From fd69f5cc064266abd53cb09d3b9bfae0aea45ac1 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 16 Jul 2026 23:57:25 +0200 Subject: [PATCH 5/7] wip --- src/platform/x11/error.rs | 1 + src/platform/x11/event_loop.rs | 23 ++++++--- src/platform/x11/window_thread.rs | 82 ++++++++++++++++++++----------- src/window.rs | 5 +- 4 files changed, 72 insertions(+), 39 deletions(-) diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index c416a130..6634a053 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -13,6 +13,7 @@ use x11rb::x11_utils::{TryParse, X11Error}; #[derive(Debug)] pub enum Error { CreationFailed(String), + RunError(String), Io(std::io::Error), DylibOpen(OpenError), InitThreadsFailed(InitThreadsFailedError), diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 0824f7ff..2c8ffbb4 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -3,12 +3,13 @@ use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mo use super::*; use std::result::Result; +use crate::platform::x11::window_thread::WindowThreadShared; use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; 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, RegistrationToken}; +use calloop::{Interest, LoopSignal, Mode, PostAction}; use dpi::{PhysicalPosition, PhysicalSize}; use std::rc::Rc; use std::time::{Duration, Instant}; @@ -22,23 +23,25 @@ pub(crate) struct EventLoop { new_physical_size: Option>, - frame_timer: RegistrationToken, - loop_handle: LoopHandle<'static, Self>, loop_signal: LoopSignal, drag_n_drop: DragNDropState, xkb_state: Option, + + run_error: Option, + pub(crate) shared: Arc, } const FRAME_INTERVAL: Duration = Duration::from_millis(15); impl EventLoop { pub fn new( - window: Rc, handler: Box, + window: Rc, handler: Box, shared: Arc, inner: &mut calloop::EventLoop<'static, Self>, ) -> Result { let loop_handle = inner.handle(); - let frame_timer = loop_handle + + loop_handle .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| e.handle_frame(i)) .map_err(|e| e.error)?; @@ -50,13 +53,13 @@ impl EventLoop { .map_err(|e| e.error)?; Ok(Self { - loop_handle, loop_signal: inner.get_signal(), handler, - frame_timer, new_physical_size: None, drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), + run_error: None, + shared, window, }) } @@ -100,7 +103,7 @@ impl EventLoop { fn handle_frame(&mut self, previous_deadline: Instant) -> TimeoutAction { if let Err(e) = self.handler.on_frame() { - // TODO: properly log/bubble up error + self.run_error = Some(e.into()); self.loop_signal.stop(); return TimeoutAction::Drop; } @@ -129,6 +132,10 @@ impl EventLoop { self.handle_event(Event::Window(WindowEvent::WillClose)); + if let Some(err) = self.run_error { + return Err(err); + }; + Ok(()) } diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index f685a890..6b417f3b 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -8,13 +8,24 @@ use std::cell::Cell; use std::num::NonZeroU32; use std::panic::resume_unwind; use std::rc::Rc; -use std::sync::mpsc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Mutex}; use std::thread; use std::thread::JoinHandle; -use std::time::Duration; + +pub(crate) struct WindowThreadShared { + stopped: AtomicBool, + final_error: Mutex>, +} + +impl WindowThreadShared { + pub fn new() -> Self { + Self { stopped: false.into(), final_error: None.into() } + } +} pub struct WindowThreadHandle { - window_id: NonZeroU32, + shared: Arc, loop_signal: LoopSignal, event_loop_handle: Cell>>, } @@ -24,33 +35,42 @@ impl WindowThreadHandle { options: WindowOpenOptions, handler: WindowHandlerBuilder, ) -> Result { let (tx, rx) = result_channel(); + let shared = Arc::new(WindowThreadShared::new()); - let join_handle = thread::spawn(move || { - let thread = match WindowThread::create(options, handler) { - Err(e) => return tx.send_error(e), - Ok(thread) => thread, - }; + let join_handle = { + let shared = shared.clone(); - if tx.send_success(&thread) { - thread.run() - } - }); + thread::spawn(move || { + let thread = match WindowThread::create(options, handler, shared) { + Err(e) => return tx.send_error(e), + Ok(thread) => thread, + }; - thread::sleep(Duration::from_millis(2000)); + if tx.send_success(&thread) { + thread.run() + } + }) + }; - rx.receive(join_handle) + rx.receive(join_handle, shared) } - pub fn run_until_closed(&self) { - let Some(thread) = self.event_loop_handle.take() else { return }; + pub fn run_until_closed(&self) -> Result<()> { + let Some(thread) = self.event_loop_handle.take() else { return Ok(()) }; if let Err(panic) = thread.join() { resume_unwind(panic); } + + if let Some(e) = self.shared.final_error.lock().unwrap().take() { + return Err(Error::RunError(e)); + } + + Ok(()) } pub fn is_open(&self) -> bool { - todo!() + !self.shared.stopped.load(Ordering::Relaxed) } } @@ -64,27 +84,32 @@ impl Drop for WindowThreadHandle { } enum WindowOpenResult { - Success { window_id: NonZeroU32, loop_signal: LoopSignal }, + Success { loop_signal: LoopSignal }, Error(String), } struct WindowThread { event_loop: EventLoop, ev_loop: calloop::EventLoop<'static, EventLoop>, + shared: Arc, } impl WindowThread { - pub fn create(options: WindowOpenOptions, handler: WindowHandlerBuilder) -> Result { + pub fn create( + options: WindowOpenOptions, handler: WindowHandlerBuilder, shared: Arc, + ) -> Result { let mut ev_loop = calloop::EventLoop::try_new()?; let inner = WindowInner::create(options, &ev_loop)?; let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; - let event_loop = EventLoop::new(inner, handler, &mut ev_loop)?; + let event_loop = EventLoop::new(inner, handler, shared.clone(), &mut ev_loop)?; - Ok(Self { event_loop, ev_loop }) + Ok(Self { event_loop, ev_loop, shared }) } pub fn run(self) { - self.event_loop.run(self.ev_loop).unwrap(); + if let Err(e) = self.event_loop.run(self.ev_loop) { + self.shared.final_error.lock().unwrap().replace(e.to_string()); + } } } @@ -103,10 +128,7 @@ impl WindowResultSender { } pub fn send_success(self, thread: &WindowThread) -> bool { - let msg = WindowOpenResult::Success { - loop_signal: thread.ev_loop.get_signal(), - window_id: thread.event_loop.window_id(), - }; + let msg = WindowOpenResult::Success { loop_signal: thread.ev_loop.get_signal() }; if let Err(err) = self.0.send(msg) { crate::error!("Failed to send created window to main thread: {}. Aborting.", err); @@ -119,13 +141,15 @@ impl WindowResultSender { struct WindowResultReceiver(mpsc::Receiver); impl WindowResultReceiver { - pub fn receive(self, join_handle: JoinHandle<()>) -> Result { + pub fn receive( + self, join_handle: JoinHandle<()>, shared: Arc, + ) -> Result { let result = self.0.recv()?; match result { WindowOpenResult::Error(e) => Err(Error::CreationFailed(e)), - WindowOpenResult::Success { window_id, loop_signal } => Ok(WindowThreadHandle { + WindowOpenResult::Success { loop_signal } => Ok(WindowThreadHandle { event_loop_handle: Some(join_handle).into(), - window_id, + shared, loop_signal, }), } diff --git a/src/window.rs b/src/window.rs index bedd4e67..5234ca5e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -15,8 +15,9 @@ impl WindowHandle { Self { window_handle, phantom: PhantomData } } - pub fn run_until_closed(self) { - self.window_handle.run_until_closed() + pub fn run_until_closed(self) -> Result<(), Error> { + self.window_handle.run_until_closed()?; + Ok(()) } /// Close the window From 379f7c06382a42bbe5eeadc778d03012ec7bf5aa Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 17 Jul 2026 02:03:30 +0200 Subject: [PATCH 6/7] wip --- examples/open_parented/src/main.rs | 2 +- examples/open_window/src/main.rs | 2 +- examples/render_femtovg/src/main.rs | 2 +- examples/render_wgpu/src/main.rs | 2 +- src/platform/x11/error.rs | 2 +- src/platform/x11/event_loop.rs | 9 +-------- src/platform/x11/window_shared.rs | 2 +- src/platform/x11/window_thread.rs | 9 +++++---- 8 files changed, 12 insertions(+), 18 deletions(-) diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index ef2def04..b5e13eaa 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -130,7 +130,7 @@ impl WindowHandler for ChildWindowHandler { fn main() -> Result<(), baseview::Error> { let window_open_options = WindowOpenOptions::new().with_size(LogicalSize::new(512.0, 512.0)); - baseview::create_window(window_open_options, ParentWindowHandler::new)?.run_until_closed(); + baseview::create_window(window_open_options, ParentWindowHandler::new)?.run_until_closed()?; Ok(()) } diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index fed1ecd5..93f1bfd6 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -167,7 +167,7 @@ fn main() -> Result<(), baseview::Error> { damaged: true.into(), }) })? - .run_until_closed(); + .run_until_closed()?; Ok(()) } diff --git a/examples/render_femtovg/src/main.rs b/examples/render_femtovg/src/main.rs index 749a3af1..f1f34d4e 100644 --- a/examples/render_femtovg/src/main.rs +++ b/examples/render_femtovg/src/main.rs @@ -123,7 +123,7 @@ fn main() -> Result<(), baseview::Error> { .with_size(LogicalSize::new(512, 512)) .with_gl_config(GlConfig { alpha_bits: 8, ..GlConfig::default() }); - baseview::create_window(window_open_options, FemtovgExample::new)?.run_until_closed(); + baseview::create_window(window_open_options, FemtovgExample::new)?.run_until_closed()?; Ok(()) } diff --git a/examples/render_wgpu/src/main.rs b/examples/render_wgpu/src/main.rs index 1828e1cb..b10fad7b 100644 --- a/examples/render_wgpu/src/main.rs +++ b/examples/render_wgpu/src/main.rs @@ -214,7 +214,7 @@ fn main() -> Result<(), baseview::Error> { .with_size(LogicalSize::new(512, 512)); baseview::create_window(window_open_options, |c| pollster::block_on(WgpuExample::new(c)))? - .run_until_closed(); + .run_until_closed()?; Ok(()) } diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index 6634a053..a5c0674c 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -13,7 +13,7 @@ use x11rb::x11_utils::{TryParse, X11Error}; #[derive(Debug)] pub enum Error { CreationFailed(String), - RunError(String), + Run(String), Io(std::io::Error), DylibOpen(OpenError), InitThreadsFailed(InitThreadsFailedError), diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 2c8ffbb4..36aa1a8d 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -3,7 +3,6 @@ use super::keyboard::{convert_key_press_event, convert_key_release_event, key_mo use super::*; use std::result::Result; -use crate::platform::x11::window_thread::WindowThreadShared; use crate::warn; use crate::wrappers::xkbcommon::XkbcommonState; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler, WindowSize}; @@ -29,14 +28,13 @@ pub(crate) struct EventLoop { xkb_state: Option, run_error: Option, - pub(crate) shared: Arc, } const FRAME_INTERVAL: Duration = Duration::from_millis(15); impl EventLoop { pub fn new( - window: Rc, handler: Box, shared: Arc, + window: Rc, handler: Box, inner: &mut calloop::EventLoop<'static, Self>, ) -> Result { let loop_handle = inner.handle(); @@ -59,15 +57,10 @@ impl EventLoop { drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&window.connection), run_error: None, - shared, window, }) } - pub fn window_id(&self) -> NonZeroU32 { - self.window.xcb_window.id() - } - #[inline] fn drain_xcb_events(&mut self) -> Result<(), ConnectionError> { // the X server has a tendency to send spurious/extraneous configure notify events when a diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 2c5ea66b..a5390fc0 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -47,7 +47,7 @@ impl WindowInner { WindowVisualConfig::find_best_visual_config_for_gl(&xcb_connection, options.gl_config)?; #[cfg(not(feature = "opengl"))] - let visual_info = WindowVisualConfig::find_best_visual_config(&connection)?; + let visual_info = WindowVisualConfig::find_best_visual_config(&xcb_connection)?; let connection = Rc::new(xcb_connection); diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 6b417f3b..cf167317 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -5,7 +5,6 @@ use crate::platform::x11::window_shared::WindowInner; use crate::{WindowContext, WindowOpenOptions}; use calloop::LoopSignal; use std::cell::Cell; -use std::num::NonZeroU32; use std::panic::resume_unwind; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -63,7 +62,7 @@ impl WindowThreadHandle { } if let Some(e) = self.shared.final_error.lock().unwrap().take() { - return Err(Error::RunError(e)); + return Err(Error::Run(e)); } Ok(()) @@ -79,7 +78,9 @@ impl Drop for WindowThreadHandle { self.loop_signal.stop(); self.loop_signal.wakeup(); - self.run_until_closed(); + if let Err(e) = self.run_until_closed() { + crate::warn!("Error while closing window: {}", e) + } } } @@ -101,7 +102,7 @@ impl WindowThread { let mut ev_loop = calloop::EventLoop::try_new()?; let inner = WindowInner::create(options, &ev_loop)?; let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; - let event_loop = EventLoop::new(inner, handler, shared.clone(), &mut ev_loop)?; + let event_loop = EventLoop::new(inner, handler, &mut ev_loop)?; Ok(Self { event_loop, ev_loop, shared }) } From 73e0e9666a07a17ebc5fa94f1056ab546c4e2af2 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 17 Jul 2026 08:08:31 +0200 Subject: [PATCH 7/7] fixes --- src/platform/macos/window.rs | 3 ++- src/platform/win/window.rs | 5 +++-- src/tracing.rs | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index ccc3e477..4d47a294 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -83,8 +83,9 @@ impl WindowHandle { Ok(Self { mtm, state, view: Weak::from_retained(&view), _window: Some(window) }) } - pub fn run_until_closed(self) { + pub fn run_until_closed(self) -> Result<()> { NSApplication::sharedApplication(self.mtm).run(); + Ok(()) } pub fn is_open(&self) -> bool { diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 8f5abbee..21ace30e 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -57,8 +57,9 @@ pub struct WindowHandle { } impl WindowHandle { - pub fn run_until_closed(self) { - run_thread_message_loop_until(|| !self.is_open()).unwrap(); + pub fn run_until_closed(self) -> Result<()> { + run_thread_message_loop_until(|| !self.is_open())?; + Ok(()) } pub fn is_open(&self) -> bool { diff --git a/src/tracing.rs b/src/tracing.rs index 88ce3c2b..6eea95c7 100644 --- a/src/tracing.rs +++ b/src/tracing.rs @@ -1,7 +1,9 @@ #[cfg(feature = "tracing")] +#[allow(unused)] pub use tracing::{error, warn}; #[cfg(not(feature = "tracing"))] +#[allow(unused)] mod tracing_impl { macro_rules! __warn { ($($f:tt)*) => {