From f87c807dcfa1749e6496c605419ff1a7fbdcf3e6 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:21:06 +0200 Subject: [PATCH 01/26] wip --- Cargo.toml | 1 + host/src/host.rs | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index f3aa6f04..ef30c2d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ calloop = "=0.14.4" # 0.14.5 has MSRV of 1.86 percent-encoding = "2.3.2" bytemuck = { version = "1.25.2", features = ["extern_crate_alloc"] } libloading = "0.8.9" # Libloading 0.9 is out but its MSRV is 1.88 +bitflags = "2.13.2" [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.62.2", features = [ diff --git a/host/src/host.rs b/host/src/host.rs index db5602b1..18f513a1 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -1,5 +1,6 @@ use crate::dpi::WindowSize; use std::error::Error; +use std::time::Duration; /// A special handler for the Window thread to wake up and call methods on the main thread. /// @@ -21,6 +22,22 @@ pub trait HostMainThreadCaller: Send + 'static { fn call_main_thread(&mut self); } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct TimerHandle(pub u32); + +pub trait HostTimerSupport: 'static { + fn register_timer(&mut self, period: Duration) -> Result; + fn unregister_timer(&mut self, timer: TimerHandle) -> Result<(), HandlerError>; +} + +#[cfg(unix)] +use std::os::fd::*; +#[cfg(unix)] +pub trait HostFdSupport: 'static { + fn register_fd(&self, fd: impl AsFd) -> Result<(), HandlerError>; + fn unregister_fd(&self, fd: RawFd) -> Result<(), HandlerError>; +} + /// A handler for baseview windows to interact with their host. pub trait HostCallbacks: 'static { /// Requests the parent window to be resized to accommodate the child window with the given new @@ -72,6 +89,8 @@ impl Host { Self { #[cfg(target_os = "linux")] main_thread: None, + #[cfg(target_os = "linux")] + timer_support: None, callbacks: None, } } From f7907e45ee95e4f23ee8db579656343a764babc0 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:07:32 +0200 Subject: [PATCH 02/26] wip --- examples/plugin_clack/Cargo.toml | 2 +- examples/plugin_clack/src/gui.rs | 64 ++++++++++++++++++++++++++++---- examples/plugin_clack/src/lib.rs | 17 ++++++++- host/src/host.rs | 51 +++++++++++++++++++++---- 4 files changed, 117 insertions(+), 17 deletions(-) diff --git a/examples/plugin_clack/Cargo.toml b/examples/plugin_clack/Cargo.toml index 2171f35a..61b6ce8e 100644 --- a/examples/plugin_clack/Cargo.toml +++ b/examples/plugin_clack/Cargo.toml @@ -8,6 +8,6 @@ crate-type = ["cdylib"] [dependencies] clack-plugin = "0.2.0" -clack-extensions = { version = "0.2.0", features = ["gui", "state", "clack-plugin", "raw-window-handle_06"] } +clack-extensions = { version = "0.2.0", features = ["clack-plugin", "raw-window-handle_06", "gui", "state", "timer", "posix-fd"] } baseview = { path = "../..", features = ["tracing"] } softbuffer = "0.4.8" diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index ccc0ed93..6adb593d 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,15 +1,21 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::*; -use baseview::host::{Host, HostCallbacks, HostMainThreadCaller}; +use baseview::host::{ + Host, HostCallbacks, HostFdSupport, HostMainThreadCaller, HostTimerSupport, TimerHandle, +}; use baseview::{Window, WindowSettings, WindowSize}; use clack_extensions::gui::{ AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui, PluginGuiImpl, Window as ClapWindow, }; +use clack_extensions::posix_fd::{FdFlags, HostPosixFd}; +use clack_extensions::timer::{HostTimer, TimerId}; use clack_plugin::plugin::PluginError; use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle}; use std::error::Error; +use std::os::fd::{AsRawFd, BorrowedFd, RawFd}; +use std::time::Duration; pub struct ExamplePluginGui { pub handle: Window, @@ -35,14 +41,23 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { .with_min_size(LogicalSize::new(200.0, 100.0)) .with_max_size(LogicalSize::new(600.0, 400.0)); - let mut host = Host::new().with_main_thread(unsafe { - MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() } - }); + // SAFETY: this handle is valid for the lifetime of the plugin, and baseview guarantees + // it releases all references to host callbacks on `drop` (which we do unconditionally) + let host_handle = unsafe { self.host.with_arbitrary_lifetime() }; + + let mut host = + Host::new().with_main_thread(MainThreadHandler { host: host_handle.shared() }); if let Some(gui) = self.host_gui { - host = host.with_callbacks(unsafe { - HostGuiCallbacks { ext: gui, host: self.host.with_arbitrary_lifetime() } - }); + host = host.with_callbacks(HostGuiCallbacks { ext: gui, host: host_handle }); + } + + if let Some(fd) = self.host_posix_fd { + host = host.with_fd(HostFdCallbacks { ext: fd, host: host_handle }); + } + + if let Some(timer) = self.host_timer { + host = host.with_timer(HostTimerCallbacks { ext: timer, host: host_handle }) } let window = Window::create_with_host(options, OpenWindowExample::new, host)?; @@ -179,3 +194,38 @@ impl HostCallbacks for HostGuiCallbacks { self.ext.closed(&self.host, true); } } + +struct HostTimerCallbacks { + ext: HostTimer, + host: HostMainThreadHandle<'static>, +} + +impl HostTimerSupport for HostTimerCallbacks { + fn register_timer(&mut self, period: Duration) -> Result> { + let period_ms = period.as_millis().try_into().unwrap_or(u32::MAX); + let timer = self.ext.register_timer(&self.host, period_ms)?; + Ok(TimerHandle(timer.0)) + } + + fn unregister_timer(&mut self, timer: TimerHandle) -> Result<(), Box> { + self.ext.unregister_timer(&self.host, TimerId(timer.0))?; + Ok(()) + } +} + +struct HostFdCallbacks { + ext: HostPosixFd, + host: HostMainThreadHandle<'static>, +} + +impl HostFdSupport for HostFdCallbacks { + fn register_fd(&self, fd: BorrowedFd) -> Result<(), Box> { + self.ext.register_fd(&self.host, fd.as_raw_fd(), FdFlags::READ)?; + Ok(()) + } + + fn unregister_fd(&self, fd: RawFd) -> Result<(), Box> { + self.ext.unregister_fd(&self.host, fd)?; + Ok(()) + } +} diff --git a/examples/plugin_clack/src/lib.rs b/examples/plugin_clack/src/lib.rs index ba52d508..ffb43bd3 100644 --- a/examples/plugin_clack/src/lib.rs +++ b/examples/plugin_clack/src/lib.rs @@ -1,7 +1,9 @@ use crate::audio::ExamplePluginAudioProcessor; use crate::gui::ExamplePluginGui; use clack_extensions::gui::{HostGui, PluginGui}; +use clack_extensions::posix_fd::HostPosixFd; use clack_extensions::state::{PluginState, PluginStateImpl}; +use clack_extensions::timer::HostTimer; use clack_plugin::prelude::*; use clack_plugin::stream::{InputStream, OutputStream}; use std::cell::{Ref, RefCell}; @@ -40,7 +42,13 @@ impl DefaultPluginFactory for ExamplePlugin { fn new_main_thread<'a>( host: HostMainThreadHandle<'a>, _shared: &'a Self::Shared<'a>, ) -> Result, PluginError> { - Ok(Self::MainThread { gui: None.into(), host_gui: host.get_extension(), host }) + Ok(Self::MainThread { + gui: None.into(), + host_gui: host.get_extension(), + host_posix_fd: host.get_extension(), + host_timer: host.get_extension(), + host, + }) } } @@ -48,8 +56,13 @@ impl DefaultPluginFactory for ExamplePlugin { pub struct ExamplePluginMainThread<'a> { /// The host handle host: HostMainThreadHandle<'a>, - // The host GUI extension handle + /// The host GUI extension handle host_gui: Option, + /// The host FD extension handle + #[cfg(unix)] + host_posix_fd: Option, + /// The host Timer extension handle + host_timer: Option, /// The plugin's GUI state and context gui: RefCell>, } diff --git a/host/src/host.rs b/host/src/host.rs index 18f513a1..e18d5676 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -26,16 +26,17 @@ pub trait HostMainThreadCaller: Send + 'static { pub struct TimerHandle(pub u32); pub trait HostTimerSupport: 'static { - fn register_timer(&mut self, period: Duration) -> Result; - fn unregister_timer(&mut self, timer: TimerHandle) -> Result<(), HandlerError>; + fn register_timer(&mut self, period: Duration) -> Result>; + fn unregister_timer(&mut self, timer: TimerHandle) -> Result<(), Box>; } #[cfg(unix)] use std::os::fd::*; -#[cfg(unix)] pub trait HostFdSupport: 'static { - fn register_fd(&self, fd: impl AsFd) -> Result<(), HandlerError>; - fn unregister_fd(&self, fd: RawFd) -> Result<(), HandlerError>; + #[cfg(unix)] + fn register_fd(&self, fd: BorrowedFd) -> Result<(), Box>; + #[cfg(unix)] + fn unregister_fd(&self, fd: RawFd) -> Result<(), Box>; } /// A handler for baseview windows to interact with their host. @@ -74,6 +75,10 @@ pub struct Host { callbacks: Option>, #[cfg(target_os = "linux")] main_thread: Option>, + #[cfg(target_os = "linux")] + timer: Option>, + #[cfg(target_os = "linux")] + fd: Option>, } impl Default for Host { @@ -87,11 +92,13 @@ impl Host { #[inline] pub fn new() -> Self { Self { + callbacks: None, #[cfg(target_os = "linux")] main_thread: None, #[cfg(target_os = "linux")] - timer_support: None, - callbacks: None, + timer: None, + #[cfg(target_os = "linux")] + fd: None, } } @@ -117,6 +124,36 @@ impl Host { } } + #[inline] + pub fn with_fd(self, fd: impl HostFdSupport) -> Self { + #[cfg(target_os = "linux")] + { + let mut this = self; + this.fd = Some(Box::new(fd)); + this + } + #[cfg(not(target_os = "linux"))] + { + let _ = fd; + self + } + } + + #[inline] + pub fn with_timer(self, timer: impl HostTimerSupport) -> Self { + #[cfg(target_os = "linux")] + { + let mut this = self; + this.timer = Some(Box::new(timer)); + this + } + #[cfg(not(target_os = "linux"))] + { + let _ = timer; + self + } + } + /// Sets the [`HostCallbacks`] handler to be used. /// /// If another callback handler was already set, it is replaced. From 7e2e1d615ac4f9c7fef1f060fe608bd934fc2d1d Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:58:36 +0200 Subject: [PATCH 03/26] wip --- examples/open_window/src/main.rs | 4 +- examples/plugin_clack/src/gui.rs | 27 +- examples/plugin_clack_femtovg/src/gui.rs | 22 +- host/src/host.rs | 70 ++--- src/platform/x11/error.rs | 20 -- src/platform/x11/event_loop.rs | 243 +++++++----------- src/platform/x11/host_handle.rs | 45 ++++ src/platform/x11/keyboard.rs | 4 +- src/platform/x11/mod.rs | 8 +- src/platform/x11/present.rs | 104 +++----- src/platform/x11/sizing.rs | 122 ++------- src/platform/x11/timer.rs | 7 +- src/platform/x11/waker.rs | 9 +- src/platform/x11/window_handle.rs | 22 ++ src/platform/x11/window_shared.rs | 29 +-- src/platform/x11/window_thread.rs | 10 +- src/platform/x11/xcb_connection/size_hints.rs | 0 src/wrappers/xkbcommon.rs | 6 +- 18 files changed, 285 insertions(+), 467 deletions(-) create mode 100644 src/platform/x11/host_handle.rs create mode 100644 src/platform/x11/window_handle.rs delete mode 100644 src/platform/x11/xcb_connection/size_hints.rs diff --git a/examples/open_window/src/main.rs b/examples/open_window/src/main.rs index 5074225e..82a99788 100644 --- a/examples/open_window/src/main.rs +++ b/examples/open_window/src/main.rs @@ -155,14 +155,14 @@ fn main() -> Result<(), baseview::Error> { }) })?; - let waker = window.waker(); + //let waker = window.waker(); std::thread::spawn(move || loop { std::thread::sleep(Duration::from_secs(5)); if tx.push(Message::Hello).is_err() { println!("Failed sending message"); } else { - waker.request_poll(); + //waker.request_poll(); } }); diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 6adb593d..a8feb96d 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,9 +1,7 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::*; -use baseview::host::{ - Host, HostCallbacks, HostFdSupport, HostMainThreadCaller, HostTimerSupport, TimerHandle, -}; +use baseview::host::{Host, HostCallbacks, HostFdSupport, HostTimerSupport, TimerHandle}; use baseview::{Window, WindowSettings, WindowSize}; use clack_extensions::gui::{ AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui, @@ -12,7 +10,7 @@ use clack_extensions::gui::{ use clack_extensions::posix_fd::{FdFlags, HostPosixFd}; use clack_extensions::timer::{HostTimer, TimerId}; use clack_plugin::plugin::PluginError; -use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle}; +use clack_plugin::prelude::HostMainThreadHandle; use std::error::Error; use std::os::fd::{AsRawFd, BorrowedFd, RawFd}; use std::time::Duration; @@ -45,8 +43,7 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { // it releases all references to host callbacks on `drop` (which we do unconditionally) let host_handle = unsafe { self.host.with_arbitrary_lifetime() }; - let mut host = - Host::new().with_main_thread(MainThreadHandler { host: host_handle.shared() }); + let mut host = Host::new(); if let Some(gui) = self.host_gui { host = host.with_callbacks(HostGuiCallbacks { ext: gui, host: host_handle }); @@ -168,29 +165,19 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } } -struct MainThreadHandler { - host: HostSharedHandle<'static>, -} - -impl HostMainThreadCaller for MainThreadHandler { - fn call_main_thread(&mut self) { - self.host.request_callback(); - } -} - struct HostGuiCallbacks { ext: HostGui, host: HostMainThreadHandle<'static>, } impl HostCallbacks for HostGuiCallbacks { - fn request_resize(&mut self, new_size: WindowSize) -> Result<(), Box> { + fn request_resize(&self, new_size: WindowSize) -> Result<(), Box> { let new_size = new_size.to_native_size(); self.ext.request_resize(&self.host, new_size.width, new_size.height)?; Ok(()) } - fn destroyed(&mut self) { + fn destroyed(&self) { self.ext.closed(&self.host, true); } } @@ -201,13 +188,13 @@ struct HostTimerCallbacks { } impl HostTimerSupport for HostTimerCallbacks { - fn register_timer(&mut self, period: Duration) -> Result> { + fn register_timer(&self, period: Duration) -> Result> { let period_ms = period.as_millis().try_into().unwrap_or(u32::MAX); let timer = self.ext.register_timer(&self.host, period_ms)?; Ok(TimerHandle(timer.0)) } - fn unregister_timer(&mut self, timer: TimerHandle) -> Result<(), Box> { + fn unregister_timer(&self, timer: TimerHandle) -> Result<(), Box> { self.ext.unregister_timer(&self.host, TimerId(timer.0))?; Ok(()) } diff --git a/examples/plugin_clack_femtovg/src/gui.rs b/examples/plugin_clack_femtovg/src/gui.rs index 8da7cbe2..a3b443b1 100644 --- a/examples/plugin_clack_femtovg/src/gui.rs +++ b/examples/plugin_clack_femtovg/src/gui.rs @@ -2,14 +2,14 @@ use crate::window_handler::FemtovgExample; use crate::ExamplePluginMainThread; use baseview::dpi::*; use baseview::gl::GlConfig; -use baseview::host::{Host, HostCallbacks, HostMainThreadCaller}; +use baseview::host::{Host, HostCallbacks}; use baseview::{Window, WindowSettings, WindowSize}; use clack_extensions::gui::{ AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui, PluginGuiImpl, Window as ClapWindow, }; use clack_plugin::plugin::PluginError; -use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle}; +use clack_plugin::prelude::HostMainThreadHandle; use std::error::Error; pub struct ExamplePluginGui { @@ -35,9 +35,7 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { .with_size(PhysicalSize::new(400, 200)) .with_gl_config(GlConfig::default()); - let mut host = Host::new().with_main_thread(unsafe { - MainThreadHandler { host: self.host.shared().with_arbitrary_lifetime() } - }); + let mut host = Host::new(); if let Some(gui) = self.host_gui { host = host.with_callbacks(unsafe { @@ -153,29 +151,19 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { } } -struct MainThreadHandler { - host: HostSharedHandle<'static>, -} - -impl HostMainThreadCaller for MainThreadHandler { - fn call_main_thread(&mut self) { - self.host.request_callback(); - } -} - struct HostGuiCallbacks { ext: HostGui, host: HostMainThreadHandle<'static>, } impl HostCallbacks for HostGuiCallbacks { - fn request_resize(&mut self, new_size: WindowSize) -> Result<(), Box> { + fn request_resize(&self, new_size: WindowSize) -> Result<(), Box> { let new_size = new_size.to_native_size(); self.ext.request_resize(&self.host, new_size.width, new_size.height)?; Ok(()) } - fn destroyed(&mut self) { + fn destroyed(&self) { self.ext.closed(&self.host, true); } } diff --git a/host/src/host.rs b/host/src/host.rs index e18d5676..e2b20fc8 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -2,32 +2,12 @@ use crate::dpi::WindowSize; use std::error::Error; use std::time::Duration; -/// A special handler for the Window thread to wake up and call methods on the main thread. -/// -/// [`HostedWindow::host_main_thread_callback`](crate::HostedWindow::host_main_thread_callback) -/// should be called as a response to this. -/// -/// # Platform compatibility notes -/// -/// This is only needed on X11, as Windows and macOS windows already run on the main thread. -pub trait HostMainThreadCaller: Send + 'static { - /// Schedules a callback on the main thread. - /// - /// [`HostedWindow::host_main_thread_callback`](crate::HostedWindow::host_main_thread_callback) - /// should be called as a response to this. - /// - /// # Platform compatibility notes - /// - /// Only X11 needs this. This can be implemented as a no-op on Windows and macOS. - fn call_main_thread(&mut self); -} - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct TimerHandle(pub u32); pub trait HostTimerSupport: 'static { - fn register_timer(&mut self, period: Duration) -> Result>; - fn unregister_timer(&mut self, timer: TimerHandle) -> Result<(), Box>; + fn register_timer(&self, period: Duration) -> Result>; + fn unregister_timer(&self, timer: TimerHandle) -> Result<(), Box>; } #[cfg(unix)] @@ -49,7 +29,7 @@ pub trait HostCallbacks: 'static { /// This can return any type of error, indicating the host either failed or denied to handle the /// resize request. /// If it does, the error is logged and the resize operation is canceled or reverted. - fn request_resize(&mut self, new_size: WindowSize) -> Result<(), Box>; + fn request_resize(&self, new_size: WindowSize) -> Result<(), Box>; /// Notifies the host that the child window has been destroyed for a reason outside the host's /// control. /// @@ -57,7 +37,7 @@ pub trait HostCallbacks: 'static { /// because the window handler decided to close the window itself. /// /// The host should close its parent window, as it will not show anything useful anymore. - fn destroyed(&mut self); + fn destroyed(&self); } /// Configuration and callbacks for a window's host. @@ -74,8 +54,6 @@ pub trait HostCallbacks: 'static { pub struct Host { callbacks: Option>, #[cfg(target_os = "linux")] - main_thread: Option>, - #[cfg(target_os = "linux")] timer: Option>, #[cfg(target_os = "linux")] fd: Option>, @@ -94,36 +72,12 @@ impl Host { Self { callbacks: None, #[cfg(target_os = "linux")] - main_thread: None, - #[cfg(target_os = "linux")] timer: None, #[cfg(target_os = "linux")] fd: None, } } - /// Sets the [`HostMainThreadCaller`] handler to be used. - /// - /// If another callback handler was already set, it is replaced. - /// - /// # Platform Compatibility notes - /// - /// This is only useful on X11. On Windows and macOS, this is a no-op. - #[inline] - pub fn with_main_thread(self, main_thread: impl HostMainThreadCaller) -> Self { - #[cfg(target_os = "linux")] - { - let mut this = self; - this.main_thread = Some(Box::new(main_thread)); - this - } - #[cfg(not(target_os = "linux"))] - { - let _ = main_thread; - self - } - } - #[inline] pub fn with_fd(self, fd: impl HostFdSupport) -> Self { #[cfg(target_os = "linux")] @@ -169,10 +123,22 @@ impl Host { } #[inline] - pub fn take_main_thread(&mut self) -> Option> { + pub fn take_timer(&mut self) -> Option> { + #[cfg(target_os = "linux")] + { + self.timer.take() + } + #[cfg(not(target_os = "linux"))] + { + None + } + } + + #[inline] + pub fn take_fd(&mut self) -> Option> { #[cfg(target_os = "linux")] { - self.main_thread.take() + self.fd.take() } #[cfg(not(target_os = "linux"))] { diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index 83bba98e..4568fd63 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -1,6 +1,5 @@ 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::wrappers::xlib::{DisplayOpenFailedError, InitThreadsFailedError}; use crate::HandlerError; @@ -15,7 +14,6 @@ use x11rb::x11_utils::{TryParse, X11Error}; pub enum FatalError { Connection(ConnectionError), Calloop(calloop::Error), - SendMainThread, Redraw(String), } @@ -24,9 +22,6 @@ impl Display for FatalError { match self { FatalError::Connection(e) => e.fmt(f), FatalError::Calloop(e) => e.fmt(f), - 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), } } @@ -60,10 +55,7 @@ pub enum PlatformError { Connect(ConnectError), DisplayOpenFailed(DisplayOpenFailedError), Handler(HandlerError), - MainThreadRecvResult, Calloop(calloop::Error), - RequestFromMainThreadFailed(RequestFailed), - SendMainThread, Redraw(String), #[cfg(feature = "opengl")] XLib(crate::wrappers::xlib::XLibError), @@ -89,13 +81,8 @@ impl Display for PlatformError { PlatformError::Connect(e) => e.fmt(f), PlatformError::DisplayOpenFailed(e) => e.fmt(f), 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), #[cfg(feature = "opengl")] PlatformError::XLib(e) => e.fmt(f), #[cfg(feature = "opengl")] @@ -170,18 +157,11 @@ impl From for PlatformError { } } -impl From for PlatformError { - fn from(value: RequestFailed) -> Self { - Self::RequestFromMainThreadFailed(value) - } -} - impl From for PlatformError { fn from(value: FatalError) -> Self { match value { 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 122dc66f..720f0afa 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -1,54 +1,23 @@ use super::drag_n_drop::DragNDropState; 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; +use crate::platform::x11::host_handle::HostHandle; use crate::platform::x11::present::PresentState; use crate::platform::x11::sizing::SizingState; -use crate::platform::x11::window_thread::{ - HostCallback, WindowThreadRequest, WindowThreadResponseMessage, -}; -use crate::warn; +use crate::platform::ParentWindowHandle; +use crate::utils::SizingStrategy; +use crate::window::WindowInitializer; use crate::wrappers::xkbcommon::XkbcommonState; -use crate::{Event, WindowEvent, WindowHandler}; -use calloop::generic::Generic; -use calloop::{Interest, LoopHandle, LoopSignal, Mode, PostAction}; -use std::num::NonZeroU32; +use crate::{warn, WindowContext}; +use crate::{Event, WindowEvent}; +use calloop::PostAction; use std::result::Result; -use std::sync::mpsc; -use std::sync::mpsc::Receiver; use x11rb::connection::Connection; use x11rb::protocol::Event as XEvent; -pub struct MainThreadCaller { - sender: mpsc::Sender, - caller: Box, -} - -impl MainThreadCaller { - pub(crate) fn new( - main_thread: Option>, - ) -> (Option, Option>) { - let Some(main_thread) = main_thread else { - return (None, None); - }; - - let (sender, receiver) = mpsc::channel(); - (Some(Self { sender, caller: main_thread }), Some(receiver)) - } - - pub fn send(&mut self, msg: HostCallback) -> Result<(), FatalError> { - self.sender.send(msg).map_err(|_| FatalError::SendMainThread)?; - self.caller.call_main_thread(); - Ok(()) - } -} - pub(crate) struct EventLoop { - response_sender: mpsc::Sender, - main_thread: Option, - handler: Handler, shared: Rc, @@ -57,50 +26,71 @@ pub(crate) struct EventLoop { pub present_state: PresentState, xkb_state: Option, - loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, Self>, + host: Rc, run_error: Option, } impl EventLoop { - pub fn new( - 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>, - ) -> Result { - let loop_handle = inner.handle(); - - loop_handle - .insert_source( - Generic::new_with_error( - Arc::clone(&window.connection.conn), - Interest::READ, - Mode::Edge, - ), - |_, _, e| e.handle_connection_event_ready(), - ) - .map_err(|e| e.error)?; - - loop_handle - .insert_source(request_receiver, |e, _, l| l.handle_main_thread_request(e)) - .map_err(|e| e.error)?; + pub fn waker(&self) -> WindowWaker { + todo!() + } + + pub fn request_poll(&self) -> PlatformResult<()> { + todo!() + } + + pub fn sizing_strategy(&self) -> SizingStrategy { + todo!() + } + + pub fn handle_main_thread_callback(&self) { + todo!() + } + + pub fn max_size(&self) -> Option { + todo!() + } + + pub fn min_size(&self) -> Option { + todo!() + } + + pub fn is_resizable(&self) -> bool { + todo!() + } + + pub fn is_open(&self) -> bool { + todo!() + } + + pub fn run_until_closed(&self) -> PlatformResult<()> { + todo!() + } + + pub fn size(&self) -> WindowSize { + todo!() + } +} + +impl EventLoop { + pub fn create_window(init: WindowInitializer) -> PlatformResult { + let parent_id = init.settings.parent.as_ref().map(|p| p.inner.window_id); + let loop_handle = HostHandle::new(init.host); + let shared = WindowShared::create(init.settings, Rc::clone(&loop_handle))?; + let handler = init.builder.build(WindowContext::new(Rc::clone(&shared)))?; Ok(Self { - loop_signal: inner.get_signal(), - loop_handle, handler: Handler::new(handler), present_state: PresentState::new(), sizing_state: SizingState::new(parent_id), drag_n_drop: DragNDropState::NoCurrentSession, - xkb_state: XkbcommonState::new(&window.connection), + xkb_state: XkbcommonState::new(&shared.connection), run_error: None, - main_thread, - shared: window, - response_sender, + shared, + host: loop_handle, }) } @@ -109,7 +99,7 @@ impl EventLoop { } #[inline] - fn drain_xcb_events(&mut self) -> Result { + fn drain_xcb_events(&self) -> Result { let mut event_received = false; while let Some(event) = self.shared.connection.conn.poll_for_event()? { event_received = true; @@ -119,34 +109,10 @@ impl EventLoop { Ok(event_received) } - fn handle_main_thread_request(&mut self, event: calloop::channel::Event) { - match event { - calloop::channel::Event::Closed => { - // Closed channel means the sender, i.e. the Window Handle has been dropped. - // It should already stop this event loop on drop, but we'll take the hint. - self.stop_now(); - } - calloop::channel::Event::Msg(req) => match self.handle_request(req) { - Ok(()) => self.send_response(Ok(())), - Err(e) => self.send_response(Err(e.to_string())), - }, - } - } - - fn send_response(&mut self, response: WindowThreadResponseMessage) { - if let Err(e) = self.response_sender.send(response) { - warn!("Failed to send response back to main thread: {}", &e); - if let Err(e) = e.0 { - crate::error!("Request failed: {}", e) - } - - self.stop_now(); - } - } - pub fn stop_now(&self) { - self.loop_signal.stop(); - self.loop_signal.wakeup(); + todo!(); + //self.loop_signal.stop(); + //self.loop_signal.wakeup(); } pub fn trigger_fatal_error(&mut self, error: PlatformError) { @@ -156,21 +122,26 @@ impl EventLoop { self.stop_now(); } - fn handle_request(&mut self, req: WindowThreadRequest) -> Result<(), PlatformError> { - match req { - WindowThreadRequest::Resize(new_size) => { - 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))?.check()? - } - WindowThreadRequest::Show => self.shared.xcb_window.map_window()?.check()?, - WindowThreadRequest::Hide => self.shared.xcb_window.unmap_window()?.check()?, - } + pub fn resize(&self, new_size: Size) -> PlatformResult<()> { + self.sizing_state.handle_host_resize(new_size, &self.handler, &self.shared) + } + + pub fn suggest_scale_factor(&self, scale: f64) -> PlatformResult<()> { + self.sizing_state.handle_host_suggest_scale_factor(scale, &self.handler, &self.shared) + } + + pub fn set_parent(&self, new_parent: ParentWindowHandle) -> PlatformResult<()> { + self.shared.xcb_window.reparent(Some(new_parent.window_id))?.check()?; + Ok(()) + } + pub fn show(&self) -> PlatformResult<()> { + self.shared.xcb_window.map_window()?.check()?; + Ok(()) + } + + pub fn hide(&self) -> PlatformResult<()> { + self.shared.xcb_window.unmap_window()?.check()?; Ok(()) } @@ -186,7 +157,7 @@ impl EventLoop { } } - fn try_handle_idle(&mut self) -> Result<(), FatalError> { + fn try_handle_idle(&self) -> Result<(), FatalError> { // Check for any events in the internal buffers before going to sleep: self.drain_xcb_events()?; @@ -194,10 +165,10 @@ impl EventLoop { self.sizing_state.handle_coalesced_resize_events( &self.shared, &self.handler, - self.main_thread.as_mut(), + &self.host, )?; - self.present_state.handle_requests(&self.shared, &self.handler, &self.loop_handle)?; + self.present_state.handle_requests(&self.shared, &self.handler, &self.host)?; if !self.drain_xcb_events()? { break; @@ -209,33 +180,11 @@ impl EventLoop { Ok(()) } - pub fn run(mut self, mut inner: calloop::EventLoop) -> Result<(), PlatformError> { - self.drain_xcb_events()?; - inner.run(None, &mut self, Self::handle_idle)?; - - 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() { - 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) - } - } - } - - if let Some(err) = self.run_error { - return Err(err); - }; - - Ok(()) - } - pub fn shared(&self) -> &WindowShared { &self.shared } - fn handle_xcb_event(&mut self, event: XEvent) -> Result<(), FatalError> { + fn handle_xcb_event(&self, event: XEvent) -> Result<(), FatalError> { match event { XEvent::ClientMessage(event) if event.window == self.shared.raw_id() => { if event.format != 32 { @@ -247,6 +196,8 @@ impl EventLoop { return Ok(()); } + todo!(); + /* 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 { @@ -255,17 +206,19 @@ impl EventLoop { 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) => { + todo!() + /* if event.property == self.shared.connection.atoms.XdndSelection { self.drag_n_drop.handle_selection_notify_event( &self.shared, &self.handler, &event, )?; - } + }*/ } XEvent::Error(e) => { @@ -299,17 +252,13 @@ impl EventLoop { // keys //// XEvent::KeyPress(event) if event.event == self.shared.raw_id() => { - self.handler.on_event(Event::Keyboard(convert_key_press_event( - &event, - &mut self.xkb_state, - ))); + self.handler + .on_event(Event::Keyboard(convert_key_press_event(&event, &self.xkb_state))); } XEvent::KeyRelease(event) if event.event == self.shared.raw_id() => { - self.handler.on_event(Event::Keyboard(convert_key_release_event( - &event, - &mut self.xkb_state, - ))); + self.handler + .on_event(Event::Keyboard(convert_key_release_event(&event, &self.xkb_state))); } XEvent::FocusIn(event) if event.event == self.shared.raw_id() => { @@ -330,7 +279,7 @@ impl EventLoop { self.present_state.handle_window_mapped( &self.shared.present_state, &self.shared.xcb_window, - &self.loop_handle, + &self.host, )?; } diff --git a/src/platform/x11/host_handle.rs b/src/platform/x11/host_handle.rs new file mode 100644 index 00000000..16e54cdf --- /dev/null +++ b/src/platform/x11/host_handle.rs @@ -0,0 +1,45 @@ +use baseview_host::dpi::WindowSize; +use baseview_host::host::*; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct HostHandle { + callbacks: RefCell, +} + +impl HostHandle { + pub(crate) fn new(mut host: Host) -> Rc { + Rc::new(Self { + callbacks: RefCell::new(HostCallbacksInner { + callbacks: host.take_callbacks(), + fd: host.take_fd(), + timer: host.take_timer(), + }), + }) + } + + pub fn notify_destroyed(&self) { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callbacks) = &mut callbacks.callbacks { + callbacks.destroyed() + } + } + + pub fn request_resize(&self, new_size: WindowSize) -> Result<(), ()> { + let mut callbacks = self.callbacks.borrow_mut(); + let Some(callbacks) = &mut callbacks.callbacks else { return Ok(()) }; + + if let Err(e) = callbacks.request_resize(new_size) { + crate::error!("Host failed to resize parent window: {}", e); + Err(()) + } else { + Ok(()) + } + } +} + +struct HostCallbacksInner { + callbacks: Option>, + fd: Option>, + timer: Option>, +} diff --git a/src/platform/x11/keyboard.rs b/src/platform/x11/keyboard.rs index b18facda..ef480f9d 100644 --- a/src/platform/x11/keyboard.rs +++ b/src/platform/x11/keyboard.rs @@ -399,7 +399,7 @@ pub(crate) fn key_mods(mods: KeyButMask) -> Modifiers { } pub(crate) fn convert_key_press_event( - key_press: &KeyPressEvent, state: &mut Option, + key_press: &KeyPressEvent, state: &Option, ) -> KeyboardEvent { let hw_keycode = key_press.detail; @@ -419,7 +419,7 @@ pub(crate) fn convert_key_press_event( } pub(crate) fn convert_key_release_event( - key_release: &KeyReleaseEvent, state: &mut Option, + key_release: &KeyReleaseEvent, state: &Option, ) -> KeyboardEvent { let hw_keycode = key_release.detail; diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index b54f9785..1310814b 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -5,6 +5,7 @@ mod event_loop; #[cfg(feature = "opengl")] pub mod gl; mod handler; +mod host_handle; mod keyboard; mod mouse; mod present; @@ -12,8 +13,9 @@ mod sizing; mod timer; mod visual_info; mod waker; +mod window_handle; mod window_shared; -mod window_thread; +// mod window_thread; mod x11_connection; mod xcb_window; @@ -29,7 +31,6 @@ pub(crate) mod prelude { pub use super::visual_info::WindowVisualConfig; pub use super::waker::WindowWaker; pub use super::window_shared::WindowShared; - pub use super::window_thread::{HostCallback, WindowThreadHandle, WindowThreadShared}; pub use super::x11_connection::X11Connection; pub use super::xcb_window::XcbWindow; pub(crate) use crate::{dpi::*, tracing::*, MouseCursor, WindowSettings, WindowSize}; @@ -61,6 +62,7 @@ pub use error::PlatformError; pub use present::DamageRect; pub use timer::TimerHandle; pub use waker::WindowWaker; +pub type WindowHandle = EventLoop; #[derive(Clone)] pub struct PlatformHandle { @@ -151,8 +153,6 @@ 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/present.rs b/src/platform/x11/present.rs index cc226700..2b67499c 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -1,14 +1,11 @@ use crate::platform::prelude::*; use crate::platform::x11::handler::Handler; +use crate::platform::x11::host_handle::HostHandle; use crate::platform::x11::sizing::SizingState; use crate::platform::x11::window_shared::WindowShared; -use crate::platform::x11::window_thread::RedrawRequested; use crate::DamageArea; -use calloop::timer::{TimeoutAction, Timer}; -use calloop::LoopHandle; +use calloop::timer::TimeoutAction; use std::cell::Cell; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Mutex; use std::time::{Duration, Instant}; use x11rb::connection::Connection; use x11rb::protocol::present::{CompleteKind, CompleteNotifyEvent}; @@ -21,13 +18,15 @@ pub struct PresentStateShared { impl PresentStateShared { pub(crate) fn request_present_notify_after( - &self, duration: Duration, loop_handle: &LoopHandle, + &self, duration: Duration, loop_handle: &HostHandle, ) { if duration.is_zero() || duration.as_millis() < 1 { self.request_present_notify(); return; } + todo!() + /* let result = loop_handle.insert_source(Timer::from_duration(duration), |_, _, e| { e.shared().present_state.request_present_notify(); TimeoutAction::Drop @@ -36,7 +35,7 @@ impl PresentStateShared { if let Err(e) = result { warn!("{}", e); self.request_present_notify(); - } + }*/ } } @@ -51,36 +50,28 @@ impl PresentStateShared { } pub struct PresentState { - draw_now: bool, - last_requested_serial: Option, - last_received_present: Option<(u32, u64)>, + draw_now: Cell, + last_requested_serial: Cell>, + last_received_present: Cell>, } impl PresentState { pub(crate) fn handle_requests( - &mut self, shared: &WindowShared, handler: &Handler, loop_handle: &LoopHandle, + &self, shared: &WindowShared, handler: &Handler, loop_handle: &HostHandle, ) -> 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(); + if self.draw_now.get() { handler.poll(); shared.present_state.present_notify_requested.set(false); handler.draw()?; shared_state.poll_requested.set(false); - self.draw_now = false; + self.draw_now.set(false); shared.connection.conn.flush()?; - } else if thread_state.take_poll_request() || shared_state.poll_requested.take() { + } else if shared_state.poll_requested.take() { handler.poll(); } @@ -113,7 +104,7 @@ impl PresentState { impl PresentState { pub fn handle_window_mapped( - &self, shared: &PresentStateShared, window: &XcbWindow, loop_handle: &LoopHandle, + &self, shared: &PresentStateShared, window: &XcbWindow, loop_handle: &HostHandle, ) -> Result<(), FatalError> { if window.present_supported() && window.present_select_input()? { shared.present_notify_requested.set(true); @@ -127,42 +118,44 @@ impl PresentState { impl PresentState { pub fn new() -> Self { - Self { draw_now: false, last_requested_serial: None, last_received_present: None } + Self { + draw_now: false.into(), + last_requested_serial: None.into(), + last_received_present: None.into(), + } } - pub fn handle_present_complete_notify(&mut self, e: CompleteNotifyEvent) { + pub fn handle_present_complete_notify(&self, e: CompleteNotifyEvent) { if e.kind != CompleteKind::NOTIFY_MSC { return; } - let Some(last_requested_serial) = self.last_requested_serial else { return }; + let Some(last_requested_serial) = self.last_requested_serial.get() else { return }; if last_requested_serial != e.serial { return; } - if let Some((last_received_serial, last_received_msc)) = self.last_received_present { + if let Some((last_received_serial, last_received_msc)) = self.last_received_present.get() { if last_received_serial == e.serial { return; } if e.msc <= last_received_msc { - self.last_received_present = Some((e.serial, e.msc)); + self.last_received_present.set(Some((e.serial, e.msc))); return; } } - self.last_received_present = Some((e.serial, e.msc)); - self.draw_now = true; + self.last_received_present.set(Some((e.serial, e.msc))); + self.draw_now.set(true); } - fn setup_fallback_frame_timer( - loop_handle: &LoopHandle, - ) -> Result<(), calloop::Error> { + fn setup_fallback_frame_timer(loop_handle: &HostHandle) -> Result<(), FatalError> { const FRAME_INTERVAL: Duration = Duration::from_millis(15); fn handle_frame(evloop: &mut EventLoop, previous_deadline: Instant) -> TimeoutAction { - evloop.present_state.draw_now = true; + evloop.present_state.draw_now.set(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 @@ -181,16 +174,19 @@ impl PresentState { TimeoutAction::ToInstant(next_deadline) } + todo!(); + /* 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, + &self, shared: &PresentStateShared, window: &XcbWindow, loop_handle: &HostHandle, ) -> Result<(), FatalError> { if !shared.present_notify_requested.get() { return Ok(()); @@ -202,7 +198,7 @@ impl PresentState { } let (next_serial, target_msc) = - match (self.last_requested_serial, self.last_received_present) { + match (self.last_requested_serial.get(), self.last_received_present.get()) { // First request, always send (None, None) => (0, 0), (Some(sent_serial), Some((received_serial, last_msc))) @@ -220,9 +216,9 @@ impl PresentState { }; if window.present_notify(target_msc, next_serial)?.check_is_ok() { - self.last_requested_serial = Some(next_serial); + self.last_requested_serial.set(Some(next_serial)); } else { - self.last_requested_serial = None; + self.last_requested_serial.set(None); Self::setup_fallback_frame_timer(loop_handle)?; } shared.present_notify_requested.set(false); @@ -231,36 +227,6 @@ 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 adbd6b31..7caa237d 100644 --- a/src/platform/x11/sizing.rs +++ b/src/platform/x11/sizing.rs @@ -1,4 +1,5 @@ use super::prelude::*; +use crate::platform::x11::host_handle::HostHandle; use crate::utils::SizingStrategy; use crate::{WindowSettings, WindowSize}; use std::cell::Cell; @@ -9,27 +10,26 @@ use x11rb::properties::WmSizeHints; use x11rb::protocol::xproto::{ConfigureNotifyEvent, ReparentNotifyEvent}; pub struct SizingState { - new_size: Option>, - new_parent_size: Option>, - parent_id: Option, + new_size: Cell>>, + new_parent_size: Cell>>, + parent_id: Cell>, } impl SizingState { pub fn new(parent_id: Option) -> Self { - Self { new_size: None, new_parent_size: None, parent_id } + Self { new_size: None.into(), new_parent_size: None.into(), parent_id: parent_id.into() } } - pub fn handle_parent_notify(&mut self, e: ReparentNotifyEvent) { - self.parent_id = NonZero::new(e.parent); + pub fn handle_parent_notify(&self, e: ReparentNotifyEvent) { + self.parent_id.set(NonZero::new(e.parent)); } pub fn non_coalesced_current_size(&self, shared: &WindowShared) -> PhysicalSize { - self.new_size.unwrap_or_else(|| shared.sizing_state.size()) + self.new_size.get().unwrap_or_else(|| shared.sizing_state.size()) } pub fn handle_coalesced_resize_events( - &mut self, shared: &WindowShared, handler: &Handler, - main_thread: Option<&mut MainThreadCaller>, + &self, shared: &WindowShared, handler: &Handler, host_handle: &HostHandle, ) -> Result<(), FatalError> { let mut comes_from_parent = false; @@ -41,14 +41,14 @@ impl SizingState { } 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); + self.new_size.set(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); + let previous = shared.sizing_state.store_size(new_size); if previous == new_size { return Ok(()); @@ -58,7 +58,7 @@ impl SizingState { 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.sizing_state.store_size(previous); shared.xcb_window.resize(previous.cast())?.check_warn(); return Ok(()); } @@ -67,11 +67,9 @@ impl SizingState { // 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), - })?; + if let Err(()) = host_handle.request_resize(new_size) { + let previous = WindowSize::from_physical(previous.cast(), scale_factor); + todo!() // roll back } } @@ -81,25 +79,23 @@ impl SizingState { Ok(()) } - pub fn handle_configure_notify_event( - &mut self, event: ConfigureNotifyEvent, window: &XcbWindow, - ) { + pub fn handle_configure_notify_event(&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) { + self.new_size.set(Some(PhysicalSize::new(event.width, event.height))); + } else if self.parent_id.get().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)); + self.new_parent_size.set(Some(PhysicalSize::new(event.width, event.height))); } } pub fn handle_host_resize( - &mut self, new_size: Size, handler: &Handler, shared: &WindowShared, + &self, new_size: Size, handler: &Handler, shared: &WindowShared, ) -> Result<(), PlatformError> { let scale_factor = shared.sizing_state.scale_factor(); let new_size = new_size.to_physical(scale_factor); @@ -108,7 +104,7 @@ impl SizingState { } pub fn handle_host_suggest_scale_factor( - &mut self, scale: f64, handler: &Handler, shared: &WindowShared, + &self, scale: f64, handler: &Handler, shared: &WindowShared, ) -> Result<(), PlatformError> { shared.sizing_state.host_suggested_scale_factor.set(Some(scale)); @@ -133,18 +129,13 @@ pub struct SizingStateShared { } impl SizingStateShared { - pub fn load( - connection: &X11Connection, sizing_thread_shared: &SizingThreadShared, - settings: &WindowSettings, - ) -> Result { + pub fn load(connection: &X11Connection, settings: &WindowSettings) -> Result { 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); 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(), @@ -177,16 +168,8 @@ impl SizingStateShared { get_size_hints(&self.sizing_strategy, self.window_size.get(), self.scale_factor()) } - 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 store_size(&self, size: PhysicalSize) -> PhysicalSize { + self.window_size.replace(size) } pub fn resize_from_handler(&self, size: Size, window: &XcbWindow) -> PlatformResult<()> { @@ -209,10 +192,10 @@ impl SizingStateShared { Ok(()) } - pub fn resize_from_host( + fn resize_from_host( &self, new_size: PhysicalSize, handler: &Handler, shared: &WindowShared, ) -> PlatformResult<()> { - let previous = self.store_size(new_size, &shared.main_thread_shared.sizing); + let previous = self.store_size(new_size); if previous == new_size { return Ok(()); @@ -221,7 +204,7 @@ impl SizingStateShared { if let Err(()) = handler.resize(WindowSize::from_physical(new_size.cast(), self.scale_factor())) { - self.store_size(previous, &shared.main_thread_shared.sizing); + self.store_size(previous); return Ok(()); } @@ -237,57 +220,6 @@ impl SizingStateShared { } } -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.scale_factor()); - } - - 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 fn get_size_hints( strategy: &SizingStrategy, current_size: PhysicalSize, scale_factor: f64, ) -> WmSizeHints { diff --git a/src/platform/x11/timer.rs b/src/platform/x11/timer.rs index 5df8aba6..5024e989 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -1,4 +1,5 @@ use crate::platform::x11::event_loop::EventLoop; +use crate::platform::x11::host_handle::HostHandle; use crate::platform::PlatformError; use calloop::timer::{TimeoutAction, Timer}; use calloop::{LoopHandle, RegistrationToken}; @@ -23,13 +24,15 @@ pub struct TimerHandleInner { impl TimerHandleInner {} pub(crate) fn insert_timer( - loop_handle: &LoopHandle, duration: Duration, + loop_handle: &HostHandle, duration: Duration, ) -> Result { let timer = Timer::from_duration(duration); let handle = Rc::::new_cyclic(move |this| { let this = Weak::clone(this); + todo!() + /* let result = loop_handle.insert_source(timer, move |_, _, e| { if let Some(this) = this.upgrade() { e.handle_timer(&TimerHandle(this)); @@ -42,7 +45,7 @@ pub(crate) fn insert_timer( panic!("Failed to insert timer: {:?}", e); } Ok(token) => TimerHandleInner { token }, - } + }*/ }); Ok(TimerHandle(handle)) diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index 109f0a4c..2bfa1ae0 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -1,12 +1,9 @@ -use crate::platform::x11::window_thread::WindowThreadShared; use calloop::LoopSignal; -use std::sync::Arc; use std::time::Duration; #[derive(Clone)] pub struct WindowWaker { pub(crate) loop_signal: LoopSignal, - pub(crate) shared: Arc, } impl WindowWaker { @@ -15,12 +12,10 @@ impl WindowWaker { } pub fn request_redraw_after(&self, duration: Duration) { - self.shared.present.request_redraw_after(duration); - self.loop_signal.wakeup(); + todo!() } pub fn request_poll(&self) { - self.shared.present.request_poll(); - self.loop_signal.wakeup(); + todo!() } } diff --git a/src/platform/x11/window_handle.rs b/src/platform/x11/window_handle.rs new file mode 100644 index 00000000..15abc66a --- /dev/null +++ b/src/platform/x11/window_handle.rs @@ -0,0 +1,22 @@ +use crate::platform::prelude::{Handler, PlatformResult, WindowShared}; +use crate::platform::x11::host_handle::HostHandle; +use crate::window::WindowInitializer; +use crate::WindowContext; +use std::rc::Rc; + +pub struct WindowHandle { + shared: Rc, + ev_loop: Rc, + handler: Handler, +} + +impl WindowHandle { + pub fn create_window(init: WindowInitializer) -> PlatformResult { + //let parent_id = init.settings.parent.as_ref().map(|p| p.inner.window_id); + let event_loop_handle = HostHandle::new(init.host); + let shared = WindowShared::create(init.settings, Rc::clone(&event_loop_handle))?; + let handler = init.builder.build(WindowContext::new(Rc::clone(&shared)))?; + + Ok(Self { shared, handler: Handler::new(handler), ev_loop: event_loop_handle }) + } +} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index b2273f06..6bd996f2 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,5 +1,5 @@ use super::prelude::*; -use calloop::{LoopHandle, LoopSignal}; +use crate::platform::x11::host_handle::HostHandle; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::time::Duration; use x11rb::protocol::xproto; @@ -9,6 +9,7 @@ use x11rb::protocol::xproto::Visualid; pub struct WindowShared { pub xcb_window: XcbWindow, pub connection: Rc, + host: Rc, visual_id: Visualid, pub is_focused: Cell, @@ -17,23 +18,17 @@ pub struct WindowShared { pub present_state: PresentStateShared, pub sizing_state: SizingStateShared, - loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, EventLoop>, - - pub main_thread_shared: Arc, - #[cfg(feature = "opengl")] gl_context: Option, } impl WindowShared { pub(crate) fn create( - mut settings: WindowSettings, ev_loop: &calloop::EventLoop<'static, EventLoop>, - thread_shared: Arc, + mut settings: WindowSettings, host: Rc, ) -> PlatformResult> { let connection = X11Connection::connect()?; - let sizing_state = SizingStateShared::load(&connection, &thread_shared.sizing, &settings)?; + let sizing_state = SizingStateShared::load(&connection, &settings)?; let connection = Rc::new(connection); @@ -62,15 +57,12 @@ impl WindowShared { #[cfg(feature = "opengl")] gl_context: visual_config.make_gl_context(&xcb_window, &connection)?, - 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, + host, xcb_window, connection, @@ -82,8 +74,7 @@ impl WindowShared { } pub fn request_close(&self) { - self.loop_signal.stop(); - self.loop_signal.wakeup(); + todo!() } pub fn request_redraw(&self) { @@ -91,14 +82,16 @@ impl WindowShared { } pub fn request_redraw_after(&self, duration: Duration) { - self.present_state.request_present_notify_after(duration, &self.loop_handle); + self.present_state.request_present_notify_after(duration, &self.host); } pub fn waker(&self) -> WindowWaker { + todo!() + /* WindowWaker { loop_signal: self.loop_signal.clone(), shared: Arc::clone(&self.main_thread_shared), - } + }*/ } pub fn has_focus(&self) -> bool { @@ -138,7 +131,7 @@ impl WindowShared { #[inline] pub fn create_timer(&self, duration: Duration) -> PlatformResult { - insert_timer(&self.loop_handle, duration) + insert_timer(&self.host, duration) } pub fn scale_factor(&self) -> f64 { diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 48f7b823..f08ebb6c 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -326,15 +326,7 @@ impl WindowThread { shared.init(&inner); let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; - let event_loop = EventLoop::new( - inner, - handler, - parent_id, - receiver, - sender, - main_thread_caller, - &mut ev_loop, - )?; + let event_loop = EventLoop::new(inner, handler, parent_id)?; Ok(Self { event_loop, ev_loop, shared }) } diff --git a/src/platform/x11/xcb_connection/size_hints.rs b/src/platform/x11/xcb_connection/size_hints.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/wrappers/xkbcommon.rs b/src/wrappers/xkbcommon.rs index f3d96926..e50b5502 100644 --- a/src/wrappers/xkbcommon.rs +++ b/src/wrappers/xkbcommon.rs @@ -65,17 +65,17 @@ impl XkbcommonState { } } - pub fn update_key(&mut self, code: Keycode, dir: xkbc::xkb_key_direction) { + pub fn update_key(&self, code: Keycode, dir: xkbc::xkb_key_direction) { unsafe { (self.xkb_common.xkb_state_update_key)(self.state, code, dir); } } - pub fn update_key_down(&mut self, code: Keycode) { + pub fn update_key_down(&self, code: Keycode) { self.update_key(code, xkbc::xkb_key_direction::XKB_KEY_DOWN) } - pub fn update_key_up(&mut self, code: Keycode) { + pub fn update_key_up(&self, code: Keycode) { self.update_key(code, xkbc::xkb_key_direction::XKB_KEY_UP) } } From a0adeb73492574b474a2a300a4fe68e349e2e26f Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 9 Oct 2026 21:46:14 +0200 Subject: [PATCH 04/26] wip --- Cargo.toml | 1 + src/platform/x11/event_loop.rs | 40 ++++++++++++++++-- src/platform/x11/window_shared.rs | 5 ++- src/wrappers.rs | 3 ++ src/wrappers/poller.rs | 67 +++++++++++++++++++++++++++++++ 5 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 src/wrappers/poller.rs diff --git a/Cargo.toml b/Cargo.toml index ef30c2d5..ab3f2afb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ x11rb = { version = "0.13.2", features = ["cursor", "dri3", "present", "resource xkbcommon-dl = { version = "0.4.2", features = ["x11"] } x11-dl = { version = "2.21.0" } calloop = "=0.14.4" # 0.14.5 has MSRV of 1.86 +polling = "3.11.0" percent-encoding = "2.3.2" bytemuck = { version = "1.25.2", features = ["extern_crate_alloc"] } libloading = "0.8.9" # Libloading 0.9 is out but its MSRV is 1.88 diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 720f0afa..9553366c 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -9,11 +9,13 @@ use crate::platform::x11::sizing::SizingState; use crate::platform::ParentWindowHandle; use crate::utils::SizingStrategy; use crate::window::WindowInitializer; +use crate::wrappers::poller::{ConnectionPoller, PollStatus}; use crate::wrappers::xkbcommon::XkbcommonState; use crate::{warn, WindowContext}; use crate::{Event, WindowEvent}; use calloop::PostAction; use std::result::Result; +use std::time::Instant; use x11rb::connection::Connection; use x11rb::protocol::Event as XEvent; @@ -65,7 +67,37 @@ impl EventLoop { } pub fn run_until_closed(&self) -> PlatformResult<()> { - todo!() + self.show()?; + + let connection = Rc::clone(&self.shared.connection); + let mut poller = ConnectionPoller::new(&connection.conn)?; + + let mut last_frame = Instant::now(); + self.shared.stop_own_event_loop.set(false); + + while !self.shared.stop_own_event_loop.get() { + // TODO: handle timers + + // Check for any events in the internal buffers + // before going to sleep: + self.handle_idle(); + + eprintln!("WAITING..."); + // FIXME: handle errors + if let PollStatus::ReadAvailable = dbg!(poller.wait())? { + self.drain_xcb_events()?; + } + /* + // Check if the user has requested the window to close + if self.window.close_requested.get() { + self.handle_must_close(); + self.window.close_requested.set(false); + }*/ + } + + poller.delete()?; + + Ok(()) } pub fn size(&self) -> WindowSize { @@ -115,9 +147,9 @@ impl EventLoop { //self.loop_signal.wakeup(); } - pub fn trigger_fatal_error(&mut self, error: PlatformError) { + pub fn trigger_fatal_error(&self, error: PlatformError) { if self.run_error.is_none() { - self.run_error = Some(error); + //self.run_error = Some(error); TODO } self.stop_now(); } @@ -151,7 +183,7 @@ impl EventLoop { Ok(PostAction::Continue) } - fn handle_idle(&mut self) { + fn handle_idle(&self) { if let Err(e) = self.try_handle_idle() { self.trigger_fatal_error(e.into()); } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 6bd996f2..b3fed1a4 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -13,6 +13,7 @@ pub struct WindowShared { visual_id: Visualid, pub is_focused: Cell, + pub stop_own_event_loop: Cell, pub cursor_state: CursorStateShared, pub present_state: PresentStateShared, @@ -58,6 +59,7 @@ impl WindowShared { gl_context: visual_config.make_gl_context(&xcb_window, &connection)?, is_focused: false.into(), + stop_own_event_loop: false.into(), cursor_state: CursorStateShared::new(), present_state: PresentStateShared::new(), @@ -74,7 +76,8 @@ impl WindowShared { } pub fn request_close(&self) { - todo!() + self.stop_own_event_loop.set(true); + // TODO: external event loop } pub fn request_redraw(&self) { diff --git a/src/wrappers.rs b/src/wrappers.rs index b145f05c..7e098e03 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -19,6 +19,9 @@ pub mod xlib; #[cfg(target_os = "linux")] pub mod xkbcommon; +#[cfg(target_os = "linux")] +pub mod poller; + /// Wrappers and utilities around GLX. #[cfg(all(target_os = "linux", feature = "opengl"))] pub mod glx; diff --git a/src/wrappers/poller.rs b/src/wrappers/poller.rs new file mode 100644 index 00000000..c3f90319 --- /dev/null +++ b/src/wrappers/poller.rs @@ -0,0 +1,67 @@ +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) -> io::Result { + self.events.clear(); + // NOTE: polling crate already handles retrying on EINTR + let new_events_count = self.poller.wait(&mut self.events, None)?; + + 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); + } +} + +#[derive(Debug)] +pub enum PollStatus { + Nothing, + ReadAvailable, + ConnectionClosed, +} From fd62ef67c09955cf846ff90939e9da44f6921972 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:18:52 +0200 Subject: [PATCH 05/26] timers work! --- Cargo.toml | 1 + src/platform/x11/event_loop.rs | 41 ++++------ src/platform/x11/host_handle.rs | 27 +++---- src/platform/x11/timer.rs | 121 +++++++++++++++++++++--------- src/platform/x11/window_handle.rs | 6 ++ src/platform/x11/window_shared.rs | 4 +- src/wrappers/poller.rs | 15 ++-- 7 files changed, 131 insertions(+), 84 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab3f2afb..dcbb44cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ xkbcommon-dl = { version = "0.4.2", features = ["x11"] } x11-dl = { version = "2.21.0" } calloop = "=0.14.4" # 0.14.5 has MSRV of 1.86 polling = "3.11.0" +slotmap = "1.1.1" percent-encoding = "2.3.2" bytemuck = { version = "1.25.2", features = ["extern_crate_alloc"] } libloading = "0.8.9" # Libloading 0.9 is out but its MSRV is 1.88 diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 9553366c..e7268252 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -76,16 +76,16 @@ impl EventLoop { self.shared.stop_own_event_loop.set(false); while !self.shared.stop_own_event_loop.get() { - // TODO: handle timers + let (next_deadline, triggered_timer) = self.shared.timer_manager.tick_next_timer(); - // Check for any events in the internal buffers - // before going to sleep: - self.handle_idle(); + if let Some(triggered_timer) = triggered_timer { + self.handler.on_timer(&triggered_timer.into()); + } + + self.handle_idle()?; - eprintln!("WAITING..."); - // FIXME: handle errors - if let PollStatus::ReadAvailable = dbg!(poller.wait())? { - self.drain_xcb_events()?; + if let PollStatus::ReadAvailable = poller.wait(next_deadline)? { + self.flush_drain_xcb_events()?; } /* // Check if the user has requested the window to close @@ -96,6 +96,7 @@ impl EventLoop { } poller.delete()?; + self.hide()?; Ok(()) } @@ -131,7 +132,9 @@ impl EventLoop { } #[inline] - fn drain_xcb_events(&self) -> Result { + fn flush_drain_xcb_events(&self) -> Result { + self.shared.connection.conn.flush()?; + let mut event_received = false; while let Some(event) = self.shared.connection.conn.poll_for_event()? { event_received = true; @@ -177,21 +180,9 @@ impl EventLoop { Ok(()) } - fn handle_connection_event_ready(&mut self) -> Result { - self.drain_xcb_events()?; - - Ok(PostAction::Continue) - } - - fn handle_idle(&self) { - if let Err(e) = self.try_handle_idle() { - self.trigger_fatal_error(e.into()); - } - } - - fn try_handle_idle(&self) -> Result<(), FatalError> { + fn handle_idle(&self) -> Result<(), FatalError> { // Check for any events in the internal buffers before going to sleep: - self.drain_xcb_events()?; + self.flush_drain_xcb_events()?; loop { self.sizing_state.handle_coalesced_resize_events( @@ -202,13 +193,11 @@ impl EventLoop { self.present_state.handle_requests(&self.shared, &self.handler, &self.host)?; - if !self.drain_xcb_events()? { + if !self.flush_drain_xcb_events()? { break; } } - self.shared.connection.conn.flush()?; - Ok(()) } diff --git a/src/platform/x11/host_handle.rs b/src/platform/x11/host_handle.rs index 16e54cdf..769c1f54 100644 --- a/src/platform/x11/host_handle.rs +++ b/src/platform/x11/host_handle.rs @@ -1,33 +1,30 @@ use baseview_host::dpi::WindowSize; use baseview_host::host::*; -use std::cell::RefCell; use std::rc::Rc; pub struct HostHandle { - callbacks: RefCell, + callbacks: Option>, + fd: Option>, + timer: Option>, } impl HostHandle { pub(crate) fn new(mut host: Host) -> Rc { Rc::new(Self { - callbacks: RefCell::new(HostCallbacksInner { - callbacks: host.take_callbacks(), - fd: host.take_fd(), - timer: host.take_timer(), - }), + callbacks: host.take_callbacks(), + fd: host.take_fd(), + timer: host.take_timer(), }) } pub fn notify_destroyed(&self) { - let mut callbacks = self.callbacks.borrow_mut(); - if let Some(callbacks) = &mut callbacks.callbacks { + if let Some(callbacks) = &self.callbacks { callbacks.destroyed() } } pub fn request_resize(&self, new_size: WindowSize) -> Result<(), ()> { - let mut callbacks = self.callbacks.borrow_mut(); - let Some(callbacks) = &mut callbacks.callbacks else { return Ok(()) }; + let Some(callbacks) = &self.callbacks else { return Ok(()) }; if let Err(e) = callbacks.request_resize(new_size) { crate::error!("Host failed to resize parent window: {}", e); @@ -36,10 +33,8 @@ impl HostHandle { Ok(()) } } -} -struct HostCallbacksInner { - callbacks: Option>, - fd: Option>, - timer: Option>, + pub fn timer_support(&self) -> Option<&dyn HostTimerSupport> { + self.timer.as_deref() + } } diff --git a/src/platform/x11/timer.rs b/src/platform/x11/timer.rs index 5024e989..2b939e44 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -1,52 +1,101 @@ -use crate::platform::x11::event_loop::EventLoop; +use crate::platform::prelude::PlatformResult; use crate::platform::x11::host_handle::HostHandle; -use crate::platform::PlatformError; -use calloop::timer::{TimeoutAction, Timer}; -use calloop::{LoopHandle, RegistrationToken}; -use std::rc::{Rc, Weak}; -use std::time::Duration; +use slotmap::{DefaultKey, DenseSlotMap, Key}; +use std::cell::RefCell; +use std::time::{Duration, Instant}; -#[derive(Clone, Eq)] -pub struct TimerHandle(Rc); +// Compatible with both TimerHandles from host (u32) and +pub type TimerHandle = u64; -impl PartialEq for TimerHandle { - #[inline] - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) +pub enum TimerManager { + Hosted(), + Standalone(StandaloneTimerStore), +} + +impl TimerManager { + pub fn new(host: &HostHandle) -> Self { + if host.timer_support().is_some() { + todo!() + } else { + Self::Standalone(StandaloneTimerStore::new()) + } + } + + pub fn create_timer( + &self, duration: Duration, host: &HostHandle, + ) -> PlatformResult { + match self { + TimerManager::Hosted() => todo!(), + TimerManager::Standalone(store) => Ok(store.insert_new_timer(duration).data().as_ffi()), + } + } + + pub fn tick_next_timer(&self) -> (Option, Option) { + match self { + TimerManager::Hosted() => todo!(), + TimerManager::Standalone(store) => store.tick_next_timer(), + } + } + + pub fn destroy_all(&self) { + match self { + TimerManager::Hosted() => todo!(), + TimerManager::Standalone(store) => store.destroy_all(), + } } } -#[derive(PartialEq, Eq)] -pub struct TimerHandleInner { - token: RegistrationToken, +#[derive(Debug)] +struct Timer { + interval: Duration, + next_trigger: Option, } -impl TimerHandleInner {} +struct StandaloneTimerStore(RefCell>); -pub(crate) fn insert_timer( - loop_handle: &HostHandle, duration: Duration, -) -> Result { - let timer = Timer::from_duration(duration); +impl StandaloneTimerStore { + pub fn new() -> Self { + Self(DenseSlotMap::new().into()) + } - let handle = Rc::::new_cyclic(move |this| { - let this = Weak::clone(this); - todo!() + pub fn insert_new_timer(&self, interval: Duration) -> DefaultKey { + let mut store = self.0.borrow_mut(); + store.insert(Timer { interval, next_trigger: Instant::now().checked_add(interval) }) + } + + pub fn tick_next_timer(&self) -> (Option, Option) { + let mut store = self.0.borrow_mut(); + let now = Instant::now(); + + let mut soonest_trigger = None; + let mut triggered_key = None; + + for (key, timer) in store.iter_mut() { + // Interval was so long it overflowed. This will essentially never trigger, so we just never trigger it. + let Some(next_trigger) = timer.next_trigger else { continue }; - /* - let result = loop_handle.insert_source(timer, move |_, _, e| { - if let Some(this) = this.upgrade() { - e.handle_timer(&TimerHandle(this)); + if triggered_key.is_none() && next_trigger <= now { + // Timer triggered! + triggered_key = Some(key.data().as_ffi()); + timer.next_trigger = now.checked_add(timer.interval); } - TimeoutAction::ToDuration(duration) - }); - match result { - Err(e) => { - panic!("Failed to insert timer: {:?}", e); + let Some(next_trigger) = timer.next_trigger else { continue }; + + match soonest_trigger { + Some(previous_soonest_trigger) => { + if previous_soonest_trigger > next_trigger { + soonest_trigger = Some(next_trigger); + } + } + None => soonest_trigger = Some(next_trigger), } - Ok(token) => TimerHandleInner { token }, - }*/ - }); + } + + (soonest_trigger, triggered_key) + } - Ok(TimerHandle(handle)) + fn destroy_all(&self) { + self.0.borrow_mut().clear(); + } } diff --git a/src/platform/x11/window_handle.rs b/src/platform/x11/window_handle.rs index 15abc66a..9e73000d 100644 --- a/src/platform/x11/window_handle.rs +++ b/src/platform/x11/window_handle.rs @@ -20,3 +20,9 @@ impl WindowHandle { Ok(Self { shared, handler: Handler::new(handler), ev_loop: event_loop_handle }) } } + +impl Drop for WindowHandle { + fn drop(&mut self) { + todo!() // Deinit timers & FD watchers + } +} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index b3fed1a4..6a16519d 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -18,6 +18,7 @@ pub struct WindowShared { pub cursor_state: CursorStateShared, pub present_state: PresentStateShared, pub sizing_state: SizingStateShared, + pub timer_manager: TimerManager, #[cfg(feature = "opengl")] gl_context: Option, @@ -64,6 +65,7 @@ impl WindowShared { cursor_state: CursorStateShared::new(), present_state: PresentStateShared::new(), sizing_state, + timer_manager: TimerManager::new(&host), host, xcb_window, @@ -134,7 +136,7 @@ impl WindowShared { #[inline] pub fn create_timer(&self, duration: Duration) -> PlatformResult { - insert_timer(&self.host, duration) + self.timer_manager.create_timer(duration, &self.host) } pub fn scale_factor(&self) -> f64 { diff --git a/src/wrappers/poller.rs b/src/wrappers/poller.rs index c3f90319..b90c30ea 100644 --- a/src/wrappers/poller.rs +++ b/src/wrappers/poller.rs @@ -1,5 +1,6 @@ -use polling::{Event, Events, Poller}; +use polling::{Event, Events, PollMode, Poller}; use std::io; +use std::io::ErrorKind; use std::os::fd::{AsFd, BorrowedFd}; use std::time::Instant; @@ -15,15 +16,19 @@ 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))? }; + unsafe { poller.add_with_mode(&fd, Event::readable(CONNECTION_KEY), PollMode::Level)? }; Ok(Self { poller, fd, events: Events::new() }) } - pub fn wait(&mut self) -> io::Result { + pub fn wait(&mut self, deadline: Option) -> io::Result { self.events.clear(); + // NOTE: polling crate already handles retrying on EINTR - let new_events_count = self.poller.wait(&mut self.events, None)?; + let new_events_count = match deadline { + Some(deadline) => self.poller.wait_deadline(&mut self.events, deadline)?, + None => self.poller.wait(&mut self.events, None)?, + }; if new_events_count == 0 { return Ok(PollStatus::Nothing); @@ -35,7 +40,7 @@ impl<'a> ConnectionPoller<'a> { } if let Some(true) = event.is_err() { - panic!("xcb connection poll error") + return Err(io::Error::new(ErrorKind::BrokenPipe, "X11 connection closed")); } if event.is_interrupt() { From ec1ed73a132ab25a76332ad8cc65aca9a713112c Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 10 Oct 2026 05:18:55 +0200 Subject: [PATCH 06/26] hosted timers --- examples/plugin_clack/src/gui.rs | 8 ++-- host/src/host.rs | 6 +-- src/platform/x11/error.rs | 7 +++ src/platform/x11/timer.rs | 78 ++++++++++++++++++++++++++------ 4 files changed, 77 insertions(+), 22 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index a8feb96d..6602fe76 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,7 +1,7 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::*; -use baseview::host::{Host, HostCallbacks, HostFdSupport, HostTimerSupport, TimerHandle}; +use baseview::host::{Host, HostCallbacks, HostFdSupport, HostTimerSupport, TimerId as BvTimerId}; use baseview::{Window, WindowSettings, WindowSize}; use clack_extensions::gui::{ AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui, @@ -188,13 +188,13 @@ struct HostTimerCallbacks { } impl HostTimerSupport for HostTimerCallbacks { - fn register_timer(&self, period: Duration) -> Result> { + fn register_timer(&self, period: Duration) -> Result> { let period_ms = period.as_millis().try_into().unwrap_or(u32::MAX); let timer = self.ext.register_timer(&self.host, period_ms)?; - Ok(TimerHandle(timer.0)) + Ok(BvTimerId(timer.0)) } - fn unregister_timer(&self, timer: TimerHandle) -> Result<(), Box> { + fn unregister_timer(&self, timer: BvTimerId) -> Result<(), Box> { self.ext.unregister_timer(&self.host, TimerId(timer.0))?; Ok(()) } diff --git a/host/src/host.rs b/host/src/host.rs index e2b20fc8..9dfdf807 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -3,11 +3,11 @@ use std::error::Error; use std::time::Duration; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct TimerHandle(pub u32); +pub struct TimerId(pub u32); pub trait HostTimerSupport: 'static { - fn register_timer(&self, period: Duration) -> Result>; - fn unregister_timer(&self, timer: TimerHandle) -> Result<(), Box>; + fn register_timer(&self, period: Duration) -> Result>; + fn unregister_timer(&self, timer: TimerId) -> Result<(), Box>; } #[cfg(unix)] diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index 4568fd63..ed00c091 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -3,6 +3,7 @@ use crate::platform::x11::drag_n_drop::ParseError; use crate::platform::x11::x11_connection::GetPropertyError; use crate::wrappers::xlib::{DisplayOpenFailedError, InitThreadsFailedError}; use crate::HandlerError; +use std::error::Error; use std::fmt::{Display, Formatter}; use x11_dl::error::OpenError; use x11rb::connection::RequestConnection; @@ -115,6 +116,12 @@ impl From for PlatformError { } } +impl From> for PlatformError { + fn from(value: Box) -> Self { + Self::Handler(HandlerError::from_boxed(value)) + } +} + impl From for PlatformError { fn from(value: OpenError) -> Self { Self::DylibOpen(value) diff --git a/src/platform/x11/timer.rs b/src/platform/x11/timer.rs index 2b939e44..2403d403 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -1,21 +1,23 @@ use crate::platform::prelude::PlatformResult; use crate::platform::x11::host_handle::HostHandle; +use baseview_host::host::TimerId; use slotmap::{DefaultKey, DenseSlotMap, Key}; use std::cell::RefCell; use std::time::{Duration, Instant}; -// Compatible with both TimerHandles from host (u32) and -pub type TimerHandle = u64; +// Compatible with both TimerHandles from host (u32) and slotmap keys (u64) +#[derive(PartialEq, Eq, Copy, Clone)] +pub struct TimerHandle(u64); pub enum TimerManager { - Hosted(), + Hosted(HostedTimerStore), Standalone(StandaloneTimerStore), } impl TimerManager { pub fn new(host: &HostHandle) -> Self { if host.timer_support().is_some() { - todo!() + Self::Hosted(HostedTimerStore::new()) } else { Self::Standalone(StandaloneTimerStore::new()) } @@ -25,22 +27,45 @@ impl TimerManager { &self, duration: Duration, host: &HostHandle, ) -> PlatformResult { match self { - TimerManager::Hosted() => todo!(), - TimerManager::Standalone(store) => Ok(store.insert_new_timer(duration).data().as_ffi()), + TimerManager::Standalone(store) => Ok(store.insert_new_timer(duration)), + TimerManager::Hosted(store) => { + let Some(timer_support) = host.timer_support() else { unreachable!() }; + let id = timer_support.register_timer(duration)?; + store.insert_new_timer(id); + + Ok(TimerHandle(id.0.into())) + } } } pub fn tick_next_timer(&self) -> (Option, Option) { match self { - TimerManager::Hosted() => todo!(), + TimerManager::Hosted(_) => (None, None), // Timers are not manually ticked in hosted mode TimerManager::Standalone(store) => store.tick_next_timer(), } } - pub fn destroy_all(&self) { + pub fn get_handle_if_exists(&self, id: TimerId) -> Option { + let TimerManager::Hosted(store) = self else { return None }; + + if store.exists(id) { + Some(TimerHandle(id.0.into())) + } else { + None + } + } + + pub fn destroy_all(&self, host: &HostHandle) { match self { - TimerManager::Hosted() => todo!(), TimerManager::Standalone(store) => store.destroy_all(), + TimerManager::Hosted(store) => { + let Some(timer_support) = host.timer_support() else { unreachable!() }; + for id in store.take_all() { + if let Err(e) = timer_support.unregister_timer(id) { + crate::warn!("Failed to unregister host timer: {}", e); + } + } + } } } } @@ -51,19 +76,42 @@ struct Timer { next_trigger: Option, } -struct StandaloneTimerStore(RefCell>); +pub(crate) struct HostedTimerStore(RefCell>); + +impl HostedTimerStore { + fn new() -> Self { + Self(vec![].into()) + } + + fn insert_new_timer(&self, handle: TimerId) { + self.0.borrow_mut().push(handle) + } + + fn exists(&self, handle: TimerId) -> bool { + self.0.borrow().contains(&handle) + } + + fn take_all(&self) -> Vec { + std::mem::take(&mut self.0.borrow_mut()) + } +} + +pub(crate) struct StandaloneTimerStore(RefCell>); impl StandaloneTimerStore { - pub fn new() -> Self { + fn new() -> Self { Self(DenseSlotMap::new().into()) } - pub fn insert_new_timer(&self, interval: Duration) -> DefaultKey { + fn insert_new_timer(&self, interval: Duration) -> TimerHandle { let mut store = self.0.borrow_mut(); - store.insert(Timer { interval, next_trigger: Instant::now().checked_add(interval) }) + let entry = Timer { interval, next_trigger: Instant::now().checked_add(interval) }; + + let key = store.insert(entry); + TimerHandle(key.data().as_ffi()) } - pub fn tick_next_timer(&self) -> (Option, Option) { + fn tick_next_timer(&self) -> (Option, Option) { let mut store = self.0.borrow_mut(); let now = Instant::now(); @@ -76,7 +124,7 @@ impl StandaloneTimerStore { if triggered_key.is_none() && next_trigger <= now { // Timer triggered! - triggered_key = Some(key.data().as_ffi()); + triggered_key = Some(TimerHandle(key.data().as_ffi())); timer.next_trigger = now.checked_add(timer.interval); } From ba250361c94c70c2a8578e7983d619158578e6b8 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:29:48 +0200 Subject: [PATCH 07/26] redraw request timers --- src/platform/x11/event_loop.rs | 38 ++- src/platform/x11/mod.rs | 1 - src/platform/x11/present.rs | 13 +- src/platform/x11/timer.rs | 32 ++- src/platform/x11/window_handle.rs | 28 --- src/platform/x11/window_shared.rs | 8 +- src/platform/x11/window_thread.rs | 380 ------------------------------ 7 files changed, 68 insertions(+), 432 deletions(-) delete mode 100644 src/platform/x11/window_handle.rs delete mode 100644 src/platform/x11/window_thread.rs diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index e7268252..45d8ffec 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -13,7 +13,6 @@ use crate::wrappers::poller::{ConnectionPoller, PollStatus}; use crate::wrappers::xkbcommon::XkbcommonState; use crate::{warn, WindowContext}; use crate::{Event, WindowEvent}; -use calloop::PostAction; use std::result::Result; use std::time::Instant; use x11rb::connection::Connection; @@ -72,16 +71,10 @@ impl EventLoop { let connection = Rc::clone(&self.shared.connection); let mut poller = ConnectionPoller::new(&connection.conn)?; - let mut last_frame = Instant::now(); self.shared.stop_own_event_loop.set(false); while !self.shared.stop_own_event_loop.get() { - let (next_deadline, triggered_timer) = self.shared.timer_manager.tick_next_timer(); - - if let Some(triggered_timer) = triggered_timer { - self.handler.on_timer(&triggered_timer.into()); - } - + let next_deadline = self.tick_all_timers(); self.handle_idle()?; if let PollStatus::ReadAvailable = poller.wait(next_deadline)? { @@ -127,8 +120,25 @@ impl EventLoop { }) } - pub(crate) fn handle_timer(&mut self, handle: &TimerHandle) { - self.handler.on_timer(handle.into()) + fn tick_all_timers(&self) -> Option { + let (deadline_1, triggered_timer) = (&self.shared.timer_manager).tick_next_timer(); + + if let Some(triggered_timer) = triggered_timer { + self.handler.on_timer(&triggered_timer.into()); + } + + let (deadline_2, triggered_timer) = (&self.shared.redraw_delayed_timers).tick_next_timer(); + + if let Some(h) = triggered_timer { + self.shared.present_state.request_present_notify(); + self.shared.redraw_delayed_timers.destroy_timer(h, &self.host); + } + + match (deadline_1, deadline_2) { + (Some(deadline_1), Some(deadline_2)) => Some(deadline_2.min(deadline_1)), + (None, Some(d)) | (Some(d), None) => Some(d), + (None, None) => None, + } } #[inline] @@ -322,3 +332,11 @@ impl EventLoop { Ok(()) } } + +impl Drop for EventLoop { + fn drop(&mut self) { + // Deinit timers & FD watchers + self.shared.timer_manager.destroy_all(&self.host); + self.shared.redraw_delayed_timers.destroy_all(&self.host); + } +} diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 1310814b..562c218d 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -13,7 +13,6 @@ mod sizing; mod timer; mod visual_info; mod waker; -mod window_handle; mod window_shared; // mod window_thread; mod x11_connection; diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index 2b67499c..b2075894 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -18,24 +18,17 @@ pub struct PresentStateShared { impl PresentStateShared { pub(crate) fn request_present_notify_after( - &self, duration: Duration, loop_handle: &HostHandle, + &self, duration: Duration, loop_handle: &HostHandle, redraw_timers: &TimerManager, ) { if duration.is_zero() || duration.as_millis() < 1 { self.request_present_notify(); return; } - todo!() - /* - 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 { + if let Err(e) = redraw_timers.create_timer(duration, loop_handle) { warn!("{}", e); self.request_present_notify(); - }*/ + } } } diff --git a/src/platform/x11/timer.rs b/src/platform/x11/timer.rs index 2403d403..e8dd5ba3 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -1,7 +1,7 @@ use crate::platform::prelude::PlatformResult; use crate::platform::x11::host_handle::HostHandle; use baseview_host::host::TimerId; -use slotmap::{DefaultKey, DenseSlotMap, Key}; +use slotmap::{DefaultKey, DenseSlotMap, Key, KeyData}; use std::cell::RefCell; use std::time::{Duration, Instant}; @@ -45,8 +45,24 @@ impl TimerManager { } } + pub fn destroy_timer(&self, handle: TimerHandle, host: &HostHandle) { + match self { + TimerManager::Standalone(s) => s.remove(handle), + TimerManager::Hosted(s) => { + let Some(timer_support) = host.timer_support() else { unreachable!() }; + let Ok(id) = handle.0.try_into() else { return }; + + if let Err(e) = timer_support.unregister_timer(TimerId(id)) { + crate::warn!("Failed to unregister host timer: {}", e); + } + + s.remove(TimerId(id)) + } + } + } + pub fn get_handle_if_exists(&self, id: TimerId) -> Option { - let TimerManager::Hosted(store) = self else { return None }; + let TimerManager::Hosted(store) = self else { return None }; // Only hosted mode needs this if store.exists(id) { Some(TimerHandle(id.0.into())) @@ -94,6 +110,13 @@ impl HostedTimerStore { fn take_all(&self) -> Vec { std::mem::take(&mut self.0.borrow_mut()) } + + fn remove(&self, handle: TimerId) { + let mut store = self.0.borrow_mut(); + let Some(index) = store.iter().position(|&x| x == handle) else { return }; + + store.swap_remove(index); + } } pub(crate) struct StandaloneTimerStore(RefCell>); @@ -143,6 +166,11 @@ impl StandaloneTimerStore { (soonest_trigger, triggered_key) } + fn remove(&self, handle: TimerHandle) { + let mut store = self.0.borrow_mut(); + store.remove(DefaultKey::from(KeyData::from_ffi(handle.0))); + } + fn destroy_all(&self) { self.0.borrow_mut().clear(); } diff --git a/src/platform/x11/window_handle.rs b/src/platform/x11/window_handle.rs deleted file mode 100644 index 9e73000d..00000000 --- a/src/platform/x11/window_handle.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::platform::prelude::{Handler, PlatformResult, WindowShared}; -use crate::platform::x11::host_handle::HostHandle; -use crate::window::WindowInitializer; -use crate::WindowContext; -use std::rc::Rc; - -pub struct WindowHandle { - shared: Rc, - ev_loop: Rc, - handler: Handler, -} - -impl WindowHandle { - pub fn create_window(init: WindowInitializer) -> PlatformResult { - //let parent_id = init.settings.parent.as_ref().map(|p| p.inner.window_id); - let event_loop_handle = HostHandle::new(init.host); - let shared = WindowShared::create(init.settings, Rc::clone(&event_loop_handle))?; - let handler = init.builder.build(WindowContext::new(Rc::clone(&shared)))?; - - Ok(Self { shared, handler: Handler::new(handler), ev_loop: event_loop_handle }) - } -} - -impl Drop for WindowHandle { - fn drop(&mut self) { - todo!() // Deinit timers & FD watchers - } -} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 6a16519d..aaf494fe 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -19,6 +19,7 @@ pub struct WindowShared { pub present_state: PresentStateShared, pub sizing_state: SizingStateShared, pub timer_manager: TimerManager, + pub redraw_delayed_timers: TimerManager, #[cfg(feature = "opengl")] gl_context: Option, @@ -66,6 +67,7 @@ impl WindowShared { present_state: PresentStateShared::new(), sizing_state, timer_manager: TimerManager::new(&host), + redraw_delayed_timers: TimerManager::new(&host), host, xcb_window, @@ -87,7 +89,11 @@ impl WindowShared { } pub fn request_redraw_after(&self, duration: Duration) { - self.present_state.request_present_notify_after(duration, &self.host); + self.present_state.request_present_notify_after( + duration, + &self.host, + &self.redraw_delayed_timers, + ); } pub fn waker(&self) -> WindowWaker { diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs deleted file mode 100644 index f08ebb6c..00000000 --- a/src/platform/x11/window_thread.rs +++ /dev/null @@ -1,380 +0,0 @@ -use super::*; -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; -use crate::warn; -use crate::window::WindowInitializer; -use crate::{WindowContext, WindowSettings, WindowSize}; -use calloop::LoopSignal; -use std::cell::{Cell, RefCell}; -use std::panic::resume_unwind; -use std::rc::Rc; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{mpsc, Mutex}; -use std::thread; -use std::thread::JoinHandle; -use std::time::{Duration, Instant}; - -pub struct WindowThreadShared { - stopped: AtomicBool, - - pub sizing: SizingThreadShared, - pub present: PresentThreadShared, - - final_error: Mutex>, - stopped_requested_from_host: AtomicBool, -} - -pub enum RedrawRequested { - Now, - Later(Instant), -} - -impl RedrawRequested { - pub fn from_duration(duration: Duration) -> Self { - if duration.is_zero() { - RedrawRequested::Now - } else { - if let Some(dur) = Instant::now().checked_add(duration) { - RedrawRequested::Later(dur) - } else { - RedrawRequested::Now - } - } - } - - pub fn to_duration(&self) -> Duration { - match self { - RedrawRequested::Now => Duration::ZERO, - RedrawRequested::Later(instant) => { - instant.checked_duration_since(Instant::now()).unwrap_or(Duration::ZERO) - } - } - } -} - -impl WindowThreadShared { - pub fn new() -> Self { - Self { - stopped: false.into(), - final_error: None.into(), - stopped_requested_from_host: false.into(), - present: PresentThreadShared::new(), - sizing: SizingThreadShared::new(), - } - } - - fn init(&self, window: &WindowShared) { - self.sizing.init(&window.sizing_state) - } - - pub fn is_stop_host_requested(&self) -> bool { - self.stopped_requested_from_host.load(Ordering::Relaxed) - } -} - -struct ThreadStopWatcher(Arc); - -impl Drop for ThreadStopWatcher { - fn drop(&mut self) { - self.0.stopped.store(true, Ordering::Relaxed); - } -} - -pub enum WindowThreadRequest { - SuggestScaleFactor(f64), - Resize(Size), - SetParent(ParentWindowHandle), - Show, - Hide, -} - -pub type WindowThreadResponseMessage = core::result::Result<(), String>; - -pub enum HostCallback { - Resized { new_size: WindowSize, previous: WindowSize }, - Destroyed, -} - -pub struct WindowThreadHandle { - shared: Arc, - loop_signal: LoopSignal, - event_loop_handle: Cell>>, - - request_sender: calloop::channel::SyncSender, - response_receiver: mpsc::Receiver, - callback_receiver: Option>, - host_callbacks: Option>>, -} - -impl WindowThreadHandle { - pub fn create_window(mut init: WindowInitializer) -> PlatformResult { - let (tx, rx) = result_channel(); - let shared = Arc::new(WindowThreadShared::new()); - let (request_sender, request_receiver) = calloop::channel::sync_channel(1); - let (response_sender, response_receiver) = mpsc::channel(); - let (main_thread_caller, main_thread_receiver) = - MainThreadCaller::new(init.host.take_main_thread()); - - let join_handle = { - let shared = Arc::clone(&shared); - let stop_watcher = ThreadStopWatcher(Arc::clone(&shared)); - - thread::spawn(move || { - let thread = match WindowThread::create( - init.settings, - init.builder, - shared, - request_receiver, - response_sender, - main_thread_caller, - ) { - Err(e) => return tx.send_error(e), - Ok(thread) => thread, - }; - - if tx.send_success(&thread) { - thread.run() - } - - // Forces the stop_watcher to be moved to this closure - drop(stop_watcher); - }) - }; - - let loop_signal = rx.receive()?; - - Ok(WindowThreadHandle { - event_loop_handle: Some(join_handle).into(), - shared, - loop_signal, - request_sender, - response_receiver, - host_callbacks: init.host.take_callbacks().map(|c| c.into()), - callback_receiver: main_thread_receiver, - }) - } - - pub fn size(&self) -> WindowSize { - self.shared.sizing.window_size() - } - - pub fn resize(&self, size: Size) -> PlatformResult<()> { - self.request(WindowThreadRequest::Resize(size)) - } - - pub fn suggest_scale_factor(&self, scale_factor: f64) -> PlatformResult<()> { - self.request(WindowThreadRequest::SuggestScaleFactor(scale_factor)) - } - - 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)?; - - result.map_err(|e| RequestFailed::Response(e).into()) - } - - pub fn sizing_strategy(&self) -> SizingStrategy { - self.shared.sizing.sizing_strategy() - } - - pub fn run_until_closed(&self) -> PlatformResult<()> { - if !self.shared.stopped.load(Ordering::Relaxed) { - self.request(WindowThreadRequest::Show)?; - } - - let Some(thread) = self.event_loop_handle.take() else { return Ok(()) }; - - if let Err(panic) = thread.join() { - resume_unwind(panic); - } - - // Ignore poisoned mutex - if let Some(e) = self.shared.final_error.lock().unwrap_or_else(|g| g.into_inner()).take() { - return Err(PlatformError::Run(e)); - } - - Ok(()) - } - - pub fn show(&self) -> PlatformResult<()> { - self.request(WindowThreadRequest::Show) - } - - pub fn hide(&self) -> PlatformResult<()> { - self.request(WindowThreadRequest::Hide) - } - - pub fn is_open(&self) -> bool { - !self.shared.stopped.load(Ordering::Relaxed) - } - - pub fn is_resizable(&self) -> bool { - self.sizing_strategy().is_resizable() - } - - pub fn min_size(&self) -> Option { - self.sizing_strategy().min_size() - } - - pub fn max_size(&self) -> Option { - self.sizing_strategy().max_size() - } - - pub fn handle_main_thread_callback(&self) { - loop { - let Some(receiver) = self.callback_receiver.as_ref() else { return }; - let Some(callback) = receiver.try_recv().ok() else { return }; - - self.handle_main_thread_message(callback); - } - } - - pub fn set_parent(&self, new_parent: ParentWindowHandle) -> PlatformResult<()> { - self.request(WindowThreadRequest::SetParent(new_parent)) - } - - pub fn request_poll(&self) -> PlatformResult<()> { - self.shared.present.request_poll(); - self.loop_signal.wakeup(); - - Ok(()) - } - - pub fn waker(&self) -> WindowWaker { - WindowWaker { loop_signal: self.loop_signal.clone(), shared: Arc::clone(&self.shared) } - } - - fn handle_main_thread_message(&self, msg: HostCallback) { - let Some(host_callbacks) = self.host_callbacks.as_ref() else { return }; - let mut host_callbacks = host_callbacks.borrow_mut(); - - match msg { - HostCallback::Destroyed => host_callbacks.destroyed(), - HostCallback::Resized { new_size: new, previous } => { - if let Err(e) = host_callbacks.request_resize(new) { - warn!("Host failed to resize parent window: {}. Reverting.", e); - - if let Err(e) = self.resize(previous.physical.into()) { - warn!( - "Failed to revert to previous size while handling previous error: {}", - e - ); - } - } - } - } - } -} - -impl Drop for WindowThreadHandle { - fn drop(&mut self) { - self.shared.stopped_requested_from_host.store(true, Ordering::Relaxed); - self.loop_signal.stop(); - self.loop_signal.wakeup(); - - if let Err(e) = self.run_until_closed() { - warn!("Error while closing window: {}", e) - } - } -} - -enum WindowOpenResult { - Success { loop_signal: LoopSignal }, - Error(String), -} - -struct WindowThread { - event_loop: EventLoop, - ev_loop: calloop::EventLoop<'static, EventLoop>, - shared: Arc, -} - -#[derive(Debug)] -pub enum RequestFailed { - Send, - Recv, - Response(String), -} - -impl Display for RequestFailed { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - RequestFailed::Send => f.write_str("Request to X11 thread failed: Could not send request (X11 thread disconnected)"), - RequestFailed::Recv => f.write_str("Request to X11 thread failed: Could not receive response (X11 thread disconnected)"), - RequestFailed::Response(e) => f.write_str(e), - } - } -} - -impl WindowThread { - pub fn create( - options: WindowSettings, handler: WindowHandlerBuilder, shared: Arc, - receiver: calloop::channel::Channel, - sender: mpsc::Sender, - main_thread_caller: Option, - ) -> 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))?; - - shared.init(&inner); - - let handler = handler.build(WindowContext::new(Rc::clone(&inner)))?; - let event_loop = EventLoop::new(inner, handler, parent_id)?; - - Ok(Self { event_loop, ev_loop, shared }) - } - - pub fn run(self) { - if let Err(e) = self.event_loop.run(self.ev_loop) { - // Ignore a poisoned mutex, we just fully override this value anyway. - let mut guard = self.shared.final_error.lock().unwrap_or_else(|g| g.into_inner()); - - guard.replace(e.to_string()); - } - } -} - -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: PlatformError) { - 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() }; - - 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) -> PlatformResult { - let result = self.0.recv().map_err(|_| PlatformError::MainThreadRecvResult)?; - - match result { - WindowOpenResult::Error(e) => Err(PlatformError::CreationFailed(e)), - WindowOpenResult::Success { loop_signal } => Ok(loop_signal), - } - } -} From a704ca531b0c5049f0b7832ee6bc004571a7c5f6 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:18:17 +0200 Subject: [PATCH 08/26] wip --- src/platform/x11/event_loop.rs | 10 +-- src/platform/x11/host_handle.rs | 11 ++++ src/platform/x11/present.rs | 108 ++++++++++++++------------------ src/platform/x11/timer.rs | 6 ++ 4 files changed, 70 insertions(+), 65 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 45d8ffec..705f6cf8 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -102,21 +102,21 @@ impl EventLoop { impl EventLoop { pub fn create_window(init: WindowInitializer) -> PlatformResult { let parent_id = init.settings.parent.as_ref().map(|p| p.inner.window_id); - let loop_handle = HostHandle::new(init.host); - let shared = WindowShared::create(init.settings, Rc::clone(&loop_handle))?; + let host = HostHandle::new(init.host); + let shared = WindowShared::create(init.settings, Rc::clone(&host))?; let handler = init.builder.build(WindowContext::new(Rc::clone(&shared)))?; Ok(Self { handler: Handler::new(handler), - present_state: PresentState::new(), - sizing_state: SizingState::new(parent_id), + present_state: PresentState::new(&host), + sizing_state: SizingState::new(parent_id), drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&shared.connection), run_error: None, shared, - host: loop_handle, + host, }) } diff --git a/src/platform/x11/host_handle.rs b/src/platform/x11/host_handle.rs index 769c1f54..67639e0e 100644 --- a/src/platform/x11/host_handle.rs +++ b/src/platform/x11/host_handle.rs @@ -37,4 +37,15 @@ impl HostHandle { pub fn timer_support(&self) -> Option<&dyn HostTimerSupport> { self.timer.as_deref() } + + pub fn unregister_timer(&self, id: TimerId) -> Result<(), ()> { + let Some(callbacks) = &self.timer else { unreachable!() }; + + if let Err(e) = callbacks.unregister_timer(id) { + crate::warn!("Host failed to unregister timer: {}", e); + Err(()) + } else { + Ok(()) + } + } } diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index b2075894..64d5af9b 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -17,40 +17,51 @@ pub struct PresentStateShared { } impl PresentStateShared { - pub(crate) fn request_present_notify_after( - &self, duration: Duration, loop_handle: &HostHandle, redraw_timers: &TimerManager, + pub fn new() -> Self { + Self { present_notify_requested: false.into(), poll_requested: false.into() } + } + + pub fn request_present_notify(&self) { + self.present_notify_requested.set(true) + } + + pub fn request_present_notify_after( + &self, duration: Duration, host: &HostHandle, redraw_timers: &TimerManager, ) { if duration.is_zero() || duration.as_millis() < 1 { self.request_present_notify(); return; } - if let Err(e) = redraw_timers.create_timer(duration, loop_handle) { + if let Err(e) = redraw_timers.create_timer(duration, host) { warn!("{}", e); self.request_present_notify(); } } } -impl PresentStateShared { - pub fn new() -> Self { - Self { present_notify_requested: false.into(), poll_requested: false.into() } - } - - pub fn request_present_notify(&self) { - self.present_notify_requested.set(true) - } -} - pub struct PresentState { draw_now: Cell, last_requested_serial: Cell>, last_received_present: Cell>, + + fallback_frame_timer_manager: TimerManager, + fallback_frame_timer: Cell>, } impl PresentState { - pub(crate) fn handle_requests( - &self, shared: &WindowShared, handler: &Handler, loop_handle: &HostHandle, + pub fn new(host: &HostHandle) -> Self { + Self { + draw_now: false.into(), + last_requested_serial: None.into(), + last_received_present: None.into(), + fallback_frame_timer_manager: TimerManager::new(host), + fallback_frame_timer: None.into(), + } + } + + pub fn handle_requests( + &self, shared: &WindowShared, handler: &Handler, host: &HostHandle, ) -> Result<(), FatalError> { let shared_state = &shared.present_state; @@ -68,14 +79,12 @@ impl PresentState { handler.poll(); } - self.handle_present_notify(&shared.present_state, &shared.xcb_window, loop_handle)?; + self.handle_present_notify(&shared.present_state, &shared.xcb_window, host)?; Ok(()) } -} -impl PresentState { - pub(crate) fn handle_expose_event( + pub fn handle_expose_event( &self, e: ExposeEvent, handler: &Handler, shared: &WindowShared, sizing_state: &SizingState, ) { if e.count == 0 { @@ -93,30 +102,18 @@ impl PresentState { handler.damage(area); } -} -impl PresentState { pub fn handle_window_mapped( - &self, shared: &PresentStateShared, window: &XcbWindow, loop_handle: &HostHandle, + &self, shared: &PresentStateShared, window: &XcbWindow, host: &HostHandle, ) -> Result<(), FatalError> { if window.present_supported() && window.present_select_input()? { shared.present_notify_requested.set(true); } else { - Self::setup_fallback_frame_timer(loop_handle)?; + self.setup_fallback_frame_timer(host)?; } Ok(()) } -} - -impl PresentState { - pub fn new() -> Self { - Self { - draw_now: false.into(), - last_requested_serial: None.into(), - last_received_present: None.into(), - } - } pub fn handle_present_complete_notify(&self, e: CompleteNotifyEvent) { if e.kind != CompleteKind::NOTIFY_MSC { @@ -144,43 +141,34 @@ impl PresentState { self.draw_now.set(true); } - fn setup_fallback_frame_timer(loop_handle: &HostHandle) -> Result<(), FatalError> { - const FRAME_INTERVAL: Duration = Duration::from_millis(15); - - fn handle_frame(evloop: &mut EventLoop, previous_deadline: Instant) -> TimeoutAction { - evloop.present_state.draw_now.set(true); + fn tick_fallback_frame_timer(&self) -> Option { + let Some(timer_handle) = self.fallback_frame_timer.get() else { return None }; - // 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 (deadline, triggered) = self.fallback_frame_timer_manager.tick_next_timer(); + if let Some(triggered) = triggered { + if triggered == timer_handle { + self.draw_now.set(true); + } + } - let Some(next_deadline) = previous_deadline.checked_add(FRAME_INTERVAL) else { - return TimeoutAction::ToDuration(FRAME_INTERVAL); - }; + deadline + } - if next_deadline >= now { - return TimeoutAction::ToDuration(FRAME_INTERVAL); - } + fn setup_fallback_frame_timer(&self, host: &HostHandle) -> Result<(), PlatformError> { + const FRAME_INTERVAL: Duration = Duration::from_millis(15); - TimeoutAction::ToInstant(next_deadline) + if let Some(previous) = self.fallback_frame_timer.take() { + self.fallback_frame_timer_manager.destroy_timer(previous, host); } - todo!(); - /* - loop_handle - .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| handle_frame(e, i)) - .map_err(|e| e.error)?; - - */ + self.fallback_frame_timer_manager.create_timer(FRAME_INTERVAL, host)?; Ok(()) } pub fn handle_present_notify( - &self, shared: &PresentStateShared, window: &XcbWindow, loop_handle: &HostHandle, - ) -> Result<(), FatalError> { + &self, shared: &PresentStateShared, window: &XcbWindow, host: &HostHandle, + ) -> Result<(), PlatformError> { if !shared.present_notify_requested.get() { return Ok(()); } @@ -212,7 +200,7 @@ impl PresentState { self.last_requested_serial.set(Some(next_serial)); } else { self.last_requested_serial.set(None); - Self::setup_fallback_frame_timer(loop_handle)?; + self.setup_fallback_frame_timer(host)?; } shared.present_notify_requested.set(false); diff --git a/src/platform/x11/timer.rs b/src/platform/x11/timer.rs index e8dd5ba3..144ed40e 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -9,6 +9,12 @@ use std::time::{Duration, Instant}; #[derive(PartialEq, Eq, Copy, Clone)] pub struct TimerHandle(u64); +impl TimerHandle { + pub fn to_id(self) -> Option { + Some(TimerId(self.0.try_into().ok()?)) + } +} + pub enum TimerManager { Hosted(HostedTimerStore), Standalone(StandaloneTimerStore), From fdea793de54739a7e21a853b1deaff212a908ff3 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:26:37 +0200 Subject: [PATCH 09/26] frame pacing fallback timer --- src/platform/x11/error.rs | 20 +++++++++++++------- src/platform/x11/event_loop.rs | 18 ++++++++++++------ src/platform/x11/present.rs | 14 +++++++++----- src/platform/x11/timer.rs | 14 +++++--------- src/platform/x11/window_shared.rs | 2 +- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index ed00c091..3a24beec 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -16,6 +16,7 @@ pub enum FatalError { Connection(ConnectionError), Calloop(calloop::Error), Redraw(String), + Host(Box), } impl Display for FatalError { @@ -24,11 +25,12 @@ impl Display for FatalError { FatalError::Connection(e) => e.fmt(f), FatalError::Calloop(e) => e.fmt(f), FatalError::Redraw(e) => write!(f, "Fatal error while drawing the window: {}", e), + FatalError::Host(e) => e.fmt(f), } } } -impl std::error::Error for FatalError {} +impl Error for FatalError {} impl From for FatalError { fn from(err: ConnectionError) -> FatalError { @@ -56,6 +58,7 @@ pub enum PlatformError { Connect(ConnectError), DisplayOpenFailed(DisplayOpenFailedError), Handler(HandlerError), + Host(Box), Calloop(calloop::Error), Redraw(String), #[cfg(feature = "opengl")] @@ -63,11 +66,11 @@ pub enum PlatformError { #[cfg(feature = "opengl")] EGl(crate::wrappers::egl::EglError), #[cfg(feature = "opengl")] - Gl(super::gl::GlCreationFailedError), + Gl(GlCreationFailedError), } impl Display for PlatformError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { PlatformError::Io(e) => e.fmt(f), Self::IdsExhausted => f.write_str("X11 IDs have been exhausted"), @@ -82,6 +85,7 @@ impl Display for PlatformError { PlatformError::Connect(e) => e.fmt(f), PlatformError::DisplayOpenFailed(e) => e.fmt(f), PlatformError::Handler(e) => e.fmt(f), + PlatformError::Host(e) => e.fmt(f), PlatformError::Redraw(e) => e.fmt(f), PlatformError::Calloop(e) => e.fmt(f), #[cfg(feature = "opengl")] @@ -94,13 +98,14 @@ impl Display for PlatformError { } } -impl std::error::Error for PlatformError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl Error for PlatformError { + fn source(&self) -> Option<&(dyn Error + 'static)> { match self { PlatformError::Io(e) => Some(e), PlatformError::DylibOpen(e) => Some(e), PlatformError::Connect(e) => Some(e), PlatformError::Handler(e) => Some(e.source()), + PlatformError::Host(e) => e.source(), #[cfg(feature = "opengl")] PlatformError::XLib(e) => Some(e), #[cfg(feature = "opengl")] @@ -170,6 +175,7 @@ impl From for PlatformError { FatalError::Connection(e) => Self::Connection(e), FatalError::Calloop(e) => Self::Calloop(e), FatalError::Redraw(s) => Self::Redraw(s), + FatalError::Host(e) => Self::Handler(HandlerError::from_boxed(e)), } } } @@ -221,8 +227,8 @@ impl From for PlatformError { } #[cfg(feature = "opengl")] -impl From for PlatformError { - fn from(value: super::gl::GlCreationFailedError) -> Self { +impl From for PlatformError { + fn from(value: GlCreationFailedError) -> Self { Self::Gl(value) } } diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 705f6cf8..277048db 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -121,24 +121,30 @@ impl EventLoop { } fn tick_all_timers(&self) -> Option { - let (deadline_1, triggered_timer) = (&self.shared.timer_manager).tick_next_timer(); + let (deadline_1, triggered_timer) = self.shared.timer_manager.tick_next_timer(); if let Some(triggered_timer) = triggered_timer { self.handler.on_timer(&triggered_timer.into()); } - let (deadline_2, triggered_timer) = (&self.shared.redraw_delayed_timers).tick_next_timer(); + let (deadline_2, triggered_timer) = self.shared.redraw_delayed_timers.tick_next_timer(); if let Some(h) = triggered_timer { self.shared.present_state.request_present_notify(); self.shared.redraw_delayed_timers.destroy_timer(h, &self.host); } - match (deadline_1, deadline_2) { - (Some(deadline_1), Some(deadline_2)) => Some(deadline_2.min(deadline_1)), - (None, Some(d)) | (Some(d), None) => Some(d), - (None, None) => None, + let deadline_3 = self.present_state.tick_fallback_frame_timer(); + + fn min_deadline(d1: Option, d2: Option) -> Option { + match (d1, d2) { + (Some(d1), Some(d2)) => Some(d2.min(d1)), + (None, Some(d)) | (Some(d), None) => Some(d), + (None, None) => None, + } } + + min_deadline(min_deadline(deadline_1, deadline_2), deadline_3) } #[inline] diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index 64d5af9b..479f4cd7 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -4,7 +4,6 @@ use crate::platform::x11::host_handle::HostHandle; use crate::platform::x11::sizing::SizingState; use crate::platform::x11::window_shared::WindowShared; use crate::DamageArea; -use calloop::timer::TimeoutAction; use std::cell::Cell; use std::time::{Duration, Instant}; use x11rb::connection::Connection; @@ -141,7 +140,7 @@ impl PresentState { self.draw_now.set(true); } - fn tick_fallback_frame_timer(&self) -> Option { + pub fn tick_fallback_frame_timer(&self) -> Option { let Some(timer_handle) = self.fallback_frame_timer.get() else { return None }; let (deadline, triggered) = self.fallback_frame_timer_manager.tick_next_timer(); @@ -154,21 +153,26 @@ impl PresentState { deadline } - fn setup_fallback_frame_timer(&self, host: &HostHandle) -> Result<(), PlatformError> { + fn setup_fallback_frame_timer(&self, host: &HostHandle) -> Result<(), FatalError> { const FRAME_INTERVAL: Duration = Duration::from_millis(15); if let Some(previous) = self.fallback_frame_timer.take() { self.fallback_frame_timer_manager.destroy_timer(previous, host); } - self.fallback_frame_timer_manager.create_timer(FRAME_INTERVAL, host)?; + let handle = self + .fallback_frame_timer_manager + .create_timer(FRAME_INTERVAL, host) + .map_err(FatalError::Host)?; + + self.fallback_frame_timer.set(Some(handle)); Ok(()) } pub fn handle_present_notify( &self, shared: &PresentStateShared, window: &XcbWindow, host: &HostHandle, - ) -> Result<(), PlatformError> { + ) -> Result<(), FatalError> { if !shared.present_notify_requested.get() { return Ok(()); } diff --git a/src/platform/x11/timer.rs b/src/platform/x11/timer.rs index 144ed40e..7b6c9b92 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -3,6 +3,7 @@ use crate::platform::x11::host_handle::HostHandle; use baseview_host::host::TimerId; use slotmap::{DefaultKey, DenseSlotMap, Key, KeyData}; use std::cell::RefCell; +use std::error::Error; use std::time::{Duration, Instant}; // Compatible with both TimerHandles from host (u32) and slotmap keys (u64) @@ -31,7 +32,7 @@ impl TimerManager { pub fn create_timer( &self, duration: Duration, host: &HostHandle, - ) -> PlatformResult { + ) -> Result> { match self { TimerManager::Standalone(store) => Ok(store.insert_new_timer(duration)), TimerManager::Hosted(store) => { @@ -55,14 +56,9 @@ impl TimerManager { match self { TimerManager::Standalone(s) => s.remove(handle), TimerManager::Hosted(s) => { - let Some(timer_support) = host.timer_support() else { unreachable!() }; - let Ok(id) = handle.0.try_into() else { return }; - - if let Err(e) = timer_support.unregister_timer(TimerId(id)) { - crate::warn!("Failed to unregister host timer: {}", e); - } - - s.remove(TimerId(id)) + let Some(id) = handle.to_id() else { return }; + let _ = host.unregister_timer(id); + s.remove(id) } } } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index aaf494fe..078027e7 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -142,7 +142,7 @@ impl WindowShared { #[inline] pub fn create_timer(&self, duration: Duration) -> PlatformResult { - self.timer_manager.create_timer(duration, &self.host) + self.timer_manager.create_timer(duration, &self.host).map_err(|e| PlatformError::Host(e)) } pub fn scale_factor(&self) -> f64 { From bb0a07092210abff4b9bac81509062453c26ea88 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 11 Oct 2026 06:39:30 +0200 Subject: [PATCH 10/26] wip --- src/platform/x11/event_loop.rs | 13 +++++++------ src/platform/x11/sizing.rs | 6 +++++- src/platform/x11/waker.rs | 15 +++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 277048db..6f29dcc0 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -38,11 +38,12 @@ impl EventLoop { } pub fn request_poll(&self) -> PlatformResult<()> { - todo!() + self.handler.poll(); + Ok(()) } pub fn sizing_strategy(&self) -> SizingStrategy { - todo!() + self.shared.sizing_state.strategy() } pub fn handle_main_thread_callback(&self) { @@ -50,15 +51,15 @@ impl EventLoop { } pub fn max_size(&self) -> Option { - todo!() + self.sizing_strategy().max_size() } pub fn min_size(&self) -> Option { - todo!() + self.sizing_strategy().min_size() } pub fn is_resizable(&self) -> bool { - todo!() + self.sizing_strategy().is_resizable() } pub fn is_open(&self) -> bool { @@ -95,7 +96,7 @@ impl EventLoop { } pub fn size(&self) -> WindowSize { - todo!() + self.shared.sizing_state.window_size() } } diff --git a/src/platform/x11/sizing.rs b/src/platform/x11/sizing.rs index 7caa237d..bb1ee3c8 100644 --- a/src/platform/x11/sizing.rs +++ b/src/platform/x11/sizing.rs @@ -168,7 +168,11 @@ impl SizingStateShared { get_size_hints(&self.sizing_strategy, self.window_size.get(), self.scale_factor()) } - pub fn store_size(&self, size: PhysicalSize) -> PhysicalSize { + pub fn strategy(&self) -> SizingStrategy { + self.sizing_strategy + } + + fn store_size(&self, size: PhysicalSize) -> PhysicalSize { self.window_size.replace(size) } diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index 2bfa1ae0..abd677b0 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -1,6 +1,21 @@ +use crate::platform::prelude::PlatformResult; +use crate::platform::x11::host_handle::HostHandle; use calloop::LoopSignal; +use std::task::Waker; use std::time::Duration; +pub(crate) struct WakerSource {} + +impl WakerSource { + pub fn new(host: &HostHandle) -> Self { + todo!() + } + + pub fn new_waker(&self) -> Waker { + todo!() + } +} + #[derive(Clone)] pub struct WindowWaker { pub(crate) loop_signal: LoopSignal, From 53b6c01752fe5c65612726564c1f6b63823d08cf Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 11 Oct 2026 08:24:20 +0200 Subject: [PATCH 11/26] Fd support works! --- examples/plugin_clack/src/gui.rs | 22 ++++++++++++-- examples/plugin_clack/src/lib.rs | 10 ++++-- host/src/window.rs | 7 +++++ src/platform/x11/event_loop.rs | 52 ++++++++++++++++++++++++++++++-- src/platform/x11/host_handle.rs | 4 +++ src/platform/x11/present.rs | 15 ++++++++- src/window.rs | 22 ++++++++++++++ 7 files changed, 124 insertions(+), 8 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 6602fe76..5085bcd6 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -7,8 +7,8 @@ use clack_extensions::gui::{ AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui, PluginGuiImpl, Window as ClapWindow, }; -use clack_extensions::posix_fd::{FdFlags, HostPosixFd}; -use clack_extensions::timer::{HostTimer, TimerId}; +use clack_extensions::posix_fd::{FdFlags, HostPosixFd, PluginPosixFdImpl}; +use clack_extensions::timer::{HostTimer, PluginTimerImpl, TimerId}; use clack_plugin::plugin::PluginError; use clack_plugin::prelude::HostMainThreadHandle; use std::error::Error; @@ -207,6 +207,7 @@ struct HostFdCallbacks { impl HostFdSupport for HostFdCallbacks { fn register_fd(&self, fd: BorrowedFd) -> Result<(), Box> { + dbg!(fd); self.ext.register_fd(&self.host, fd.as_raw_fd(), FdFlags::READ)?; Ok(()) } @@ -216,3 +217,20 @@ impl HostFdSupport for HostFdCallbacks { Ok(()) } } + +impl PluginTimerImpl for ExamplePluginMainThread<'_> { + fn on_timer(&self, timer_id: TimerId) { + if let Some(gui) = self.gui.borrow().as_ref() { + gui.handle.host_timer_callback(BvTimerId(timer_id.0)); + } + } +} + +impl PluginPosixFdImpl for ExamplePluginMainThread<'_> { + fn on_fd(&self, fd: RawFd, _: FdFlags) { + dbg!(fd); + if let Some(gui) = self.gui.borrow().as_ref() { + gui.handle.host_fd_callback(fd) + } + } +} diff --git a/examples/plugin_clack/src/lib.rs b/examples/plugin_clack/src/lib.rs index ffb43bd3..000aff63 100644 --- a/examples/plugin_clack/src/lib.rs +++ b/examples/plugin_clack/src/lib.rs @@ -1,9 +1,9 @@ use crate::audio::ExamplePluginAudioProcessor; use crate::gui::ExamplePluginGui; use clack_extensions::gui::{HostGui, PluginGui}; -use clack_extensions::posix_fd::HostPosixFd; +use clack_extensions::posix_fd::{HostPosixFd, PluginPosixFd}; use clack_extensions::state::{PluginState, PluginStateImpl}; -use clack_extensions::timer::HostTimer; +use clack_extensions::timer::{HostTimer, PluginTimer}; use clack_plugin::prelude::*; use clack_plugin::stream::{InputStream, OutputStream}; use std::cell::{Ref, RefCell}; @@ -23,7 +23,11 @@ impl Plugin for ExamplePlugin { type MainThread<'a> = ExamplePluginMainThread<'a>; fn declare_extensions(builder: &mut PluginExtensions, _shared: Option<&()>) { - builder.register::().register::(); + builder + .register::() + .register::() + .register::() + .register::(); } } diff --git a/host/src/window.rs b/host/src/window.rs index 9fd9a480..016bb866 100644 --- a/host/src/window.rs +++ b/host/src/window.rs @@ -1,7 +1,9 @@ use crate::dpi::{NativeSize, WindowSize}; +use crate::host::TimerId; use dpi::Size; use raw_window_handle::HasWindowHandle; use std::error::Error; +use std::os::fd::RawFd; /// A handle to a hostable Window. /// @@ -114,6 +116,11 @@ pub trait HostedWindow { /// On Windows and macOS, this is always a no-op. fn host_main_thread_callback(&self); + fn host_timer_callback(&self, timer_id: TimerId); + + #[cfg(unix)] + fn host_fd_callback(&self, fd: RawFd); + /// Reparents this window using the given `parent`. /// /// # Panics diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 6f29dcc0..13dfde3e 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -13,6 +13,8 @@ use crate::wrappers::poller::{ConnectionPoller, PollStatus}; use crate::wrappers::xkbcommon::XkbcommonState; use crate::{warn, WindowContext}; use crate::{Event, WindowEvent}; +use baseview_host::host::TimerId; +use std::os::fd::{AsFd, AsRawFd, RawFd}; use std::result::Result; use std::time::Instant; use x11rb::connection::Connection; @@ -30,6 +32,7 @@ pub(crate) struct EventLoop { host: Rc, run_error: Option, + fd_registered: Cell, } impl EventLoop { @@ -50,6 +53,28 @@ impl EventLoop { todo!() } + pub fn host_timer_callback(&self, timer_id: TimerId) { + if let Some(timer) = self.shared.timer_manager.get_handle_if_exists(timer_id) { + self.handler.on_timer(&timer.into()) + } else if let Some(redraw_timer) = + self.shared.redraw_delayed_timers.get_handle_if_exists(timer_id) + { + self.shared.present_state.request_present_notify(); + self.shared.redraw_delayed_timers.destroy_timer(redraw_timer, &self.host); + } else { + self.present_state.trigger_possible_fallback_frame_timer(timer_id); + } + } + + pub fn host_fd_callback(&self, fd: RawFd) { + if fd != self.shared.connection.conn.as_raw_fd() { + return; + }; + + self.flush_drain_xcb_events().unwrap(); // TODO: unwraps + self.handle_idle().unwrap(); + } + pub fn max_size(&self) -> Option { self.sizing_strategy().max_size() } @@ -104,10 +129,11 @@ impl EventLoop { pub fn create_window(init: WindowInitializer) -> PlatformResult { let parent_id = init.settings.parent.as_ref().map(|p| p.inner.window_id); let host = HostHandle::new(init.host); + // TODO: check deinit runs on initialization error let shared = WindowShared::create(init.settings, Rc::clone(&host))?; let handler = init.builder.build(WindowContext::new(Rc::clone(&shared)))?; - Ok(Self { + let this = Self { handler: Handler::new(handler), present_state: PresentState::new(&host), @@ -115,10 +141,26 @@ impl EventLoop { drag_n_drop: DragNDropState::NoCurrentSession, xkb_state: XkbcommonState::new(&shared.connection), run_error: None, + fd_registered: false.into(), shared, host, - }) + }; + + this.register_fd()?; + + Ok(this) + } + + fn register_fd(&self) -> PlatformResult<()> { + if let Some(fd_support) = self.host.fd_support() { + fd_support.register_fd(self.shared.connection.conn.as_fd())?; + self.fd_registered.set(true); + } else { + todo!() + } + + Ok(()) } fn tick_all_timers(&self) -> Option { @@ -345,5 +387,11 @@ impl Drop for EventLoop { // Deinit timers & FD watchers self.shared.timer_manager.destroy_all(&self.host); self.shared.redraw_delayed_timers.destroy_all(&self.host); + + if self.fd_registered.get() { + let Some(fd_support) = self.host.fd_support() else { unreachable!() }; + fd_support.unregister_fd(self.shared.connection.conn.as_raw_fd()).unwrap(); + // TODO: unwrap + } } } diff --git a/src/platform/x11/host_handle.rs b/src/platform/x11/host_handle.rs index 67639e0e..314d3737 100644 --- a/src/platform/x11/host_handle.rs +++ b/src/platform/x11/host_handle.rs @@ -38,6 +38,10 @@ impl HostHandle { self.timer.as_deref() } + pub fn fd_support(&self) -> Option<&dyn HostFdSupport> { + self.fd.as_deref() + } + pub fn unregister_timer(&self, id: TimerId) -> Result<(), ()> { let Some(callbacks) = &self.timer else { unreachable!() }; diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index 479f4cd7..b93e4ca3 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -4,6 +4,7 @@ use crate::platform::x11::host_handle::HostHandle; use crate::platform::x11::sizing::SizingState; use crate::platform::x11::window_shared::WindowShared; use crate::DamageArea; +use baseview_host::host::TimerId; use std::cell::Cell; use std::time::{Duration, Instant}; use x11rb::connection::Connection; @@ -141,7 +142,7 @@ impl PresentState { } pub fn tick_fallback_frame_timer(&self) -> Option { - let Some(timer_handle) = self.fallback_frame_timer.get() else { return None }; + let timer_handle = self.fallback_frame_timer.get()?; let (deadline, triggered) = self.fallback_frame_timer_manager.tick_next_timer(); if let Some(triggered) = triggered { @@ -153,6 +154,18 @@ impl PresentState { deadline } + pub fn trigger_possible_fallback_frame_timer(&self, id: TimerId) { + let Some(fallback_timer_handle) = self.fallback_frame_timer.get() else { return }; + + let Some(handle) = self.fallback_frame_timer_manager.get_handle_if_exists(id) else { + return; + }; + + if handle == fallback_timer_handle { + self.draw_now.set(true); + } + } + fn setup_fallback_frame_timer(&self, host: &HostHandle) -> Result<(), FatalError> { const FRAME_INTERVAL: Duration = Duration::from_millis(15); diff --git a/src/window.rs b/src/window.rs index 0b62b5a8..abe5459d 100644 --- a/src/window.rs +++ b/src/window.rs @@ -6,6 +6,7 @@ use crate::waker::WindowWaker; use crate::*; use raw_window_handle::HasWindowHandle; use std::marker::PhantomData; +use std::os::fd::RawFd; /// A handle to a Window created by baseview. /// @@ -191,6 +192,16 @@ impl Window { self.inner.handle_main_thread_callback() } + #[inline] + pub fn host_timer_callback(&self, timer_id: TimerId) { + self.inner.host_timer_callback(timer_id) + } + + #[inline] + pub fn host_fd_callback(&self, fd: RawFd) { + self.inner.host_fd_callback(fd) + } + /// Reparents this window using the given `parent`. /// /// # Panics @@ -248,6 +259,7 @@ pub(crate) struct WindowInitializer { } pub use baseview_host::dpi::WindowSize; +use baseview_host::host::TimerId; use baseview_host::HostedWindow; impl HostedWindow for Window { @@ -310,6 +322,16 @@ impl HostedWindow for Window { self.host_main_thread_callback() } + #[inline] + fn host_timer_callback(&self, timer_id: TimerId) { + self.host_timer_callback(timer_id) + } + + #[inline] + fn host_fd_callback(&self, fd: RawFd) { + self.host_fd_callback(fd) + } + #[inline] fn set_parent(&self, parent: &dyn HasWindowHandle) -> Result<(), Box> { self.set_parent(parent)?; From bf49be4cb53fac366ed69ca11c821cc7fe82dd40 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 11 Oct 2026 16:39:57 +0200 Subject: [PATCH 12/26] Waker support in standalone! --- Cargo.toml | 1 + src/platform/x11/event_loop.rs | 40 ++++---- src/platform/x11/present.rs | 44 +++++++-- src/platform/x11/waker.rs | 159 +++++++++++++++++++++++++++--- src/platform/x11/waker/eventfd.rs | 64 ++++++++++++ src/platform/x11/window_shared.rs | 23 ++--- src/wrappers/poller.rs | 41 +++++--- 7 files changed, 307 insertions(+), 65 deletions(-) create mode 100644 src/platform/x11/waker/eventfd.rs diff --git a/Cargo.toml b/Cargo.toml index dcbb44cb..5120c548 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ percent-encoding = "2.3.2" bytemuck = { version = "1.25.2", features = ["extern_crate_alloc"] } libloading = "0.8.9" # Libloading 0.9 is out but its MSRV is 1.88 bitflags = "2.13.2" +rustix = "1.1.4" [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.62.2", features = [ diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 13dfde3e..42136a96 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -37,7 +37,7 @@ pub(crate) struct EventLoop { impl EventLoop { pub fn waker(&self) -> WindowWaker { - todo!() + self.shared.waker_source.create_waker() } pub fn request_poll(&self) -> PlatformResult<()> { @@ -55,13 +55,11 @@ impl EventLoop { pub fn host_timer_callback(&self, timer_id: TimerId) { if let Some(timer) = self.shared.timer_manager.get_handle_if_exists(timer_id) { - self.handler.on_timer(&timer.into()) - } else if let Some(redraw_timer) = - self.shared.redraw_delayed_timers.get_handle_if_exists(timer_id) - { - self.shared.present_state.request_present_notify(); - self.shared.redraw_delayed_timers.destroy_timer(redraw_timer, &self.host); - } else { + self.handler.on_timer(&timer.into()); + return; + } + + if !self.shared.present_state.trigger_if_redraw_timer(timer_id, &self.host) { self.present_state.trigger_possible_fallback_frame_timer(timer_id); } } @@ -95,7 +93,8 @@ impl EventLoop { self.show()?; let connection = Rc::clone(&self.shared.connection); - let mut poller = ConnectionPoller::new(&connection.conn)?; + let waker_fd = self.shared.waker_source.inner_fd(); + let mut poller = ConnectionPoller::new(&connection.conn, &waker_fd)?; self.shared.stop_own_event_loop.set(false); @@ -103,9 +102,16 @@ impl EventLoop { let next_deadline = self.tick_all_timers(); self.handle_idle()?; - if let PollStatus::ReadAvailable = poller.wait(next_deadline)? { - self.flush_drain_xcb_events()?; + match poller.wait(next_deadline)? { + PollStatus::ReadAvailable => { + self.flush_drain_xcb_events()?; + } + PollStatus::WakerTriggered => { + self.shared.waker_source.poll_requests(&self.shared.present_state, &self.host); + } + _ => {} } + /* // Check if the user has requested the window to close if self.window.close_requested.get() { @@ -157,7 +163,7 @@ impl EventLoop { fd_support.register_fd(self.shared.connection.conn.as_fd())?; self.fd_registered.set(true); } else { - todo!() + // TODO } Ok(()) @@ -170,13 +176,7 @@ impl EventLoop { self.handler.on_timer(&triggered_timer.into()); } - let (deadline_2, triggered_timer) = self.shared.redraw_delayed_timers.tick_next_timer(); - - if let Some(h) = triggered_timer { - self.shared.present_state.request_present_notify(); - self.shared.redraw_delayed_timers.destroy_timer(h, &self.host); - } - + let deadline_2 = self.shared.present_state.tick_timers(&self.host); let deadline_3 = self.present_state.tick_fallback_frame_timer(); fn min_deadline(d1: Option, d2: Option) -> Option { @@ -386,7 +386,7 @@ impl Drop for EventLoop { fn drop(&mut self) { // Deinit timers & FD watchers self.shared.timer_manager.destroy_all(&self.host); - self.shared.redraw_delayed_timers.destroy_all(&self.host); + self.shared.present_state.destroy_redraw_timers(&self.host); if self.fd_registered.get() { let Some(fd_support) = self.host.fd_support() else { unreachable!() }; diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index b93e4ca3..76bab946 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -14,30 +14,62 @@ use x11rb::protocol::xproto::ExposeEvent; pub struct PresentStateShared { present_notify_requested: Cell, poll_requested: Cell, + redraw_delayed_timers: TimerManager, } impl PresentStateShared { - pub fn new() -> Self { - Self { present_notify_requested: false.into(), poll_requested: false.into() } + pub fn new(host: &HostHandle) -> Self { + Self { + present_notify_requested: false.into(), + poll_requested: false.into(), + redraw_delayed_timers: TimerManager::new(host), + } } pub fn request_present_notify(&self) { self.present_notify_requested.set(true) } - pub fn request_present_notify_after( - &self, duration: Duration, host: &HostHandle, redraw_timers: &TimerManager, - ) { + pub fn trigger_if_redraw_timer(&self, timer_id: TimerId, host: &HostHandle) -> bool { + if let Some(redraw_timer) = self.redraw_delayed_timers.get_handle_if_exists(timer_id) { + self.request_present_notify(); + self.redraw_delayed_timers.destroy_timer(redraw_timer, host); + true + } else { + false + } + } + + pub fn tick_timers(&self, host: &HostHandle) -> Option { + let (deadline, triggered_timer) = self.redraw_delayed_timers.tick_next_timer(); + + if let Some(h) = triggered_timer { + self.request_present_notify(); + self.redraw_delayed_timers.destroy_timer(h, host); + } + + deadline + } + + pub fn destroy_redraw_timers(&self, host: &HostHandle) { + self.redraw_delayed_timers.destroy_all(host); + } + + pub fn request_present_notify_after(&self, duration: Duration, host: &HostHandle) { if duration.is_zero() || duration.as_millis() < 1 { self.request_present_notify(); return; } - if let Err(e) = redraw_timers.create_timer(duration, host) { + if let Err(e) = self.redraw_delayed_timers.create_timer(duration, host) { warn!("{}", e); self.request_present_notify(); } } + + pub fn request_poll(&self) { + self.poll_requested.set(true); + } } pub struct PresentState { diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index abd677b0..b1f73f5c 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -1,36 +1,171 @@ -use crate::platform::prelude::PlatformResult; use crate::platform::x11::host_handle::HostHandle; -use calloop::LoopSignal; -use std::task::Waker; -use std::time::Duration; +use std::os::fd::{AsFd, BorrowedFd}; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::mpsc::{Receiver, SyncSender, TryRecvError}; +use std::sync::Arc; +use std::time::{Duration, Instant}; -pub(crate) struct WakerSource {} +mod eventfd; +use crate::platform::prelude::PresentStateShared; +use crate::DropData; +use eventfd::*; + +// TODO: only instanciate FDs and stuff when first waker is created +pub(crate) struct WakerSource { + inner: EventFdWakerSource, + flags: Arc, + redraw_channel_send: SyncSender, + redraw_channel_recv: Receiver, +} impl WakerSource { pub fn new(host: &HostHandle) -> Self { - todo!() + if host.fd_support().is_some() { + } else if host.timer_support().is_some() { + todo!() + } else { + } + + let (send, recv) = std::sync::mpsc::sync_channel(100); + + Self { + inner: EventFdWakerSource::new(), + flags: Arc::new(SharedFlags::new()), + redraw_channel_recv: recv, + redraw_channel_send: send, + } + } + + pub fn create_waker(&self) -> WindowWaker { + WindowWaker { + inner: self.inner.create_waker(), + flag: Arc::clone(&self.flags), + redraw_channel_send: self.redraw_channel_send.clone(), + } } - pub fn new_waker(&self) -> Waker { - todo!() + pub fn poll_requests(&self, present_state_shared: &PresentStateShared, host: &HostHandle) { + if !self.inner.poll() { + return; + } + + let flags = self.flags.take(); + let requests = self.get_all_redraw_requests(); + + for instant in requests { + if let Some(duration) = instant.checked_duration_since(Instant::now()) { + present_state_shared.request_present_notify_after(duration, host); + } else { + present_state_shared.request_present_notify() + } + } + + if flags.poll() { + present_state_shared.request_poll(); + } + + if flags.redraw() { + present_state_shared.request_present_notify(); + } + + if flags.dropped() { + // TODO + } + } + + fn get_all_redraw_requests(&self) -> Vec { + let mut requests = vec![]; + + loop { + match self.redraw_channel_recv.try_recv() { + Ok(instant) => requests.push(instant), + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => unreachable!(), // We always hold onto one sender + } + } + + requests + } + + pub fn inner_fd(&self) -> BorrowedFd<'_> { + self.inner.as_fd() } } #[derive(Clone)] pub struct WindowWaker { - pub(crate) loop_signal: LoopSignal, + inner: EventFdWaker, + flag: Arc, + redraw_channel_send: SyncSender, } impl WindowWaker { pub fn request_redraw(&self) { - self.request_redraw_after(Duration::ZERO) + self.flag.set_redraw(); + self.inner.wake(); } pub fn request_redraw_after(&self, duration: Duration) { - todo!() + if duration.is_zero() { + return self.request_redraw(); + } + + self.redraw_channel_send.send(Instant::now() + duration).unwrap(); + self.inner.wake() } pub fn request_poll(&self) { - todo!() + self.flag.set_poll(); + self.inner.wake(); + } +} + +impl Drop for WindowWaker { + fn drop(&mut self) { + self.flag.set_dropped(); + } +} + +struct SharedFlags(AtomicU8); + +impl SharedFlags { + pub fn new() -> Self { + Self(AtomicU8::new(0)) + } + + const POLL: u8 = 1 << 0; + const REDRAW: u8 = 1 << 1; + const DROPPED: u8 = 1 << 2; + + fn set_poll(&self) { + self.0.fetch_or(Self::POLL, Ordering::Release); + } + + fn set_redraw(&self) { + self.0.fetch_or(Self::REDRAW, Ordering::Release); + } + + fn set_dropped(&self) { + self.0.fetch_or(Self::DROPPED, Ordering::Release); + } + + fn take(&self) -> FlagsValues { + FlagsValues(self.0.swap(0, Ordering::Acquire)) + } +} + +pub struct FlagsValues(u8); + +impl FlagsValues { + fn poll(&self) -> bool { + self.0 & SharedFlags::POLL != 0 + } + + fn redraw(&self) -> bool { + self.0 & SharedFlags::REDRAW != 0 + } + + fn dropped(&self) -> bool { + self.0 & SharedFlags::DROPPED != 0 } } diff --git a/src/platform/x11/waker/eventfd.rs b/src/platform/x11/waker/eventfd.rs new file mode 100644 index 00000000..0d5d75d1 --- /dev/null +++ b/src/platform/x11/waker/eventfd.rs @@ -0,0 +1,64 @@ +use rustix::event::{eventfd, EventfdFlags}; +use rustix::io::{read, write, Errno}; +use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; +use std::sync::Arc; + +pub struct EventFdWakerSource { + fd: Arc, +} + +impl EventFdWakerSource { + pub fn new() -> Self { + let fd = eventfd(0, EventfdFlags::CLOEXEC | EventfdFlags::NONBLOCK).unwrap(); + Self { fd: Arc::new(fd) } + } + + pub fn create_waker(&self) -> EventFdWaker { + EventFdWaker(Arc::clone(&self.fd)) + } + + // Returns true if something requested this to be woken up + pub fn poll(&self) -> bool { + // The eventfd counter is effectively a u64. + const BYTES: usize = 8; + let mut buf = [0u8; BYTES]; + + match read(&self.fd, &mut buf) { + // Reading from an eventfd should only ever produce 8 bytes. No looping + // is required. + Ok(BYTES) => u64::from_ne_bytes(buf) > 0, + + Ok(_) => unreachable!(), + + // Any other error can be propagated. + Err(e) => { + crate::warn!("Failed to read from waker's eventfd counter: {}", e); + false + } + } + } +} + +impl AsFd for EventFdWakerSource { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } +} + +#[derive(Clone)] +pub struct EventFdWaker(Arc); + +impl EventFdWaker { + pub fn wake(&self) { + match write(&self.0, &1u64.to_ne_bytes()) { + Ok(_) => {} + // Counter hit capacity. We can ignore it as that means the loop will be already woken up by *something* + Err(Errno::AGAIN) => {} + + Err(e) => { + crate::warn!("Failed to write to waker's eventfd counter: {}", e) + } + } + } +} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 078027e7..b0c038ec 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,5 +1,6 @@ use super::prelude::*; use crate::platform::x11::host_handle::HostHandle; +use crate::platform::x11::waker::WakerSource; use raw_window_handle::{DisplayHandle, XlibWindowHandle}; use std::time::Duration; use x11rb::protocol::xproto; @@ -9,6 +10,7 @@ use x11rb::protocol::xproto::Visualid; pub struct WindowShared { pub xcb_window: XcbWindow, pub connection: Rc, + pub waker_source: WakerSource, host: Rc, visual_id: Visualid, @@ -19,7 +21,6 @@ pub struct WindowShared { pub present_state: PresentStateShared, pub sizing_state: SizingStateShared, pub timer_manager: TimerManager, - pub redraw_delayed_timers: TimerManager, #[cfg(feature = "opengl")] gl_context: Option, @@ -64,10 +65,10 @@ impl WindowShared { stop_own_event_loop: false.into(), cursor_state: CursorStateShared::new(), - present_state: PresentStateShared::new(), + present_state: PresentStateShared::new(&host), sizing_state, timer_manager: TimerManager::new(&host), - redraw_delayed_timers: TimerManager::new(&host), + waker_source: WakerSource::new(&host), host, xcb_window, @@ -89,20 +90,12 @@ impl WindowShared { } pub fn request_redraw_after(&self, duration: Duration) { - self.present_state.request_present_notify_after( - duration, - &self.host, - &self.redraw_delayed_timers, - ); + self.present_state.request_present_notify_after(duration, &self.host); } + #[inline] pub fn waker(&self) -> WindowWaker { - todo!() - /* - WindowWaker { - loop_signal: self.loop_signal.clone(), - shared: Arc::clone(&self.main_thread_shared), - }*/ + self.waker_source.create_waker() } pub fn has_focus(&self) -> bool { @@ -142,7 +135,7 @@ impl WindowShared { #[inline] pub fn create_timer(&self, duration: Duration) -> PlatformResult { - self.timer_manager.create_timer(duration, &self.host).map_err(|e| PlatformError::Host(e)) + self.timer_manager.create_timer(duration, &self.host).map_err(PlatformError::Host) } pub fn scale_factor(&self) -> f64 { diff --git a/src/wrappers/poller.rs b/src/wrappers/poller.rs index b90c30ea..1f16c7e2 100644 --- a/src/wrappers/poller.rs +++ b/src/wrappers/poller.rs @@ -8,17 +8,23 @@ pub struct ConnectionPoller<'a> { poller: Poller, events: Events, fd: BorrowedFd<'a>, + waker_fd: BorrowedFd<'a>, } const CONNECTION_KEY: usize = 42; +const WAKER_KEY: usize = 69; impl<'a> ConnectionPoller<'a> { - pub fn new(source: &'a impl AsFd) -> io::Result { + pub fn new(source: &'a impl AsFd, waker: &'a impl AsFd) -> io::Result { let poller = Poller::new()?; + let fd = source.as_fd(); unsafe { poller.add_with_mode(&fd, Event::readable(CONNECTION_KEY), PollMode::Level)? }; - Ok(Self { poller, fd, events: Events::new() }) + let waker_fd = waker.as_fd(); + unsafe { poller.add_with_mode(&waker_fd, Event::readable(WAKER_KEY), PollMode::Level)? }; + + Ok(Self { poller, fd, events: Events::new(), waker_fd }) } pub fn wait(&mut self, deadline: Option) -> io::Result { @@ -35,31 +41,41 @@ impl<'a> ConnectionPoller<'a> { } for event in self.events.iter() { - if event.key != CONNECTION_KEY { - continue; - } + match event.key { + CONNECTION_KEY => { + if let Some(true) = event.is_err() { + return Err(io::Error::new(ErrorKind::BrokenPipe, "X11 connection closed")); + } - if let Some(true) = event.is_err() { - return Err(io::Error::new(ErrorKind::BrokenPipe, "X11 connection closed")); - } + if event.is_interrupt() { + return Ok(PollStatus::ConnectionClosed); + } - if event.is_interrupt() { - return Ok(PollStatus::ConnectionClosed); - } + return Ok(PollStatus::ReadAvailable); + } + WAKER_KEY => { + if let Some(true) = event.is_err() { + return Err(io::Error::new(ErrorKind::BrokenPipe, "X11 connection closed")); + } - return Ok(PollStatus::ReadAvailable); + return Ok(PollStatus::WakerTriggered); + } + _ => continue, + } } Ok(PollStatus::Nothing) } pub fn delete(self) -> io::Result<()> { + let _ = self.poller.delete(self.waker_fd)?; self.poller.delete(self.fd) } } impl<'a> Drop for ConnectionPoller<'a> { fn drop(&mut self) { + let _ = self.poller.delete(self.waker_fd); let _ = self.poller.delete(self.fd); } } @@ -68,5 +84,6 @@ impl<'a> Drop for ConnectionPoller<'a> { pub enum PollStatus { Nothing, ReadAvailable, + WakerTriggered, ConnectionClosed, } From 9a169ce735406aec599c954a6a0afb37d65537cb Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:20:04 +0200 Subject: [PATCH 13/26] Fixed drag&drop --- src/platform/x11/drag_n_drop.rs | 240 ++++++++++++++++++++++---------- src/platform/x11/event_loop.rs | 10 +- 2 files changed, 167 insertions(+), 83 deletions(-) diff --git a/src/platform/x11/drag_n_drop.rs b/src/platform/x11/drag_n_drop.rs index 6d95d3c7..ecca65c9 100644 --- a/src/platform/x11/drag_n_drop.rs +++ b/src/platform/x11/drag_n_drop.rs @@ -8,6 +8,7 @@ use crate::{DropData, Event, MouseEvent}; use core::result::Result; use keyboard_types::Modifiers; use percent_encoding::percent_decode; +use std::cell::RefCell; use std::error::Error; use std::fmt::{Display, Formatter}; use std::{ @@ -24,13 +25,96 @@ use x11rb::{ protocol::xproto::{self, ConnectionExt}, x11_utils::Serialize, }; -use DragNDropState::*; +use DragNDropStateInner::*; + +pub(crate) struct DragNDropState(RefCell); + +impl DragNDropState { + pub fn new() -> Self { + Self(NoCurrentSession.into()) + } + + pub fn handle_enter_event( + &self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, + ) -> Result<(), ConnectionError> { + let interrupted_active_drag = { + let mut inner = self.0.borrow_mut(); + inner.handle_enter_event(window, event)? + }; + + if interrupted_active_drag { + handler.on_event(Event::Mouse(MouseEvent::DragLeft)); + } + + Ok(()) + } + + pub fn handle_position_event( + &self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, + ) -> Result<(), ConnectionError> { + let event = { + let mut inner = self.0.borrow_mut(); + inner.handle_position_event(window, event)? + }; + + if let Some(event) = event { + handler.on_event(event); + } + + Ok(()) + } + + pub fn handle_leave_event(&self, handler: &Handler, event: &ClientMessageEvent) { + let generate_left_event = { + let mut inner = self.0.borrow_mut(); + inner.handle_leave_event(event) + }; + + if generate_left_event { + handler.on_event(Event::Mouse(MouseEvent::DragLeft)); + } + } + + pub fn handle_drop_event( + &self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, + ) -> Result<(), ConnectionError> { + let event = { + let mut inner = self.0.borrow_mut(); + inner.handle_drop_event(window, event)? + }; + + if let Some(event) = event { + handler.on_event(event); + } + + Ok(()) + } + + pub fn handle_selection_notify_event( + &self, window: &WindowShared, handler: &Handler, event: &SelectionNotifyEvent, + ) -> Result<(), ConnectionError> { + let (e1, e2, r) = { + let mut inner = self.0.borrow_mut(); + inner.handle_selection_notify_event(window, event) + }; + + if let Some(event) = e1 { + handler.on_event(event); + } + + if let Some(event) = e2 { + handler.on_event(event); + } + + r + } +} /// The Drag-N-Drop session state of a `baseview` X11 window, for which it is the target. /// /// For more information about what the heck is going on here, see the /// [XDND (X Drag-n-Drop) specification](https://www.freedesktop.org/wiki/Specifications/XDND/). -pub(crate) enum DragNDropState { +pub(crate) enum DragNDropStateInner { /// There is no active XDND session for this window. NoCurrentSession, /// At some point in this session's lifetime, we have decided we couldn't possibly handle the @@ -93,10 +177,10 @@ pub(crate) enum DragNDropState { // Note: For all X11 operations on this type, only ConnectionErrors are considered fatal. // Other errors (protocol errors, transfer errors) should be dealt with as gracefully as possible. -impl DragNDropState { - pub fn handle_enter_event( - &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, - ) -> Result<(), ConnectionError> { +impl DragNDropStateInner { + fn handle_enter_event( + &mut self, window: &WindowShared, event: &ClientMessageEvent, + ) -> Result { let data = event.data.as_data32(); let source_window = data[0] as xproto::Window; @@ -114,7 +198,7 @@ impl DragNDropState { let result = window.connection.get_property( source_window, window.connection.atoms.XdndTypeList, - xproto::Atom::from(xproto::AtomEnum::ATOM), + Atom::from(xproto::AtomEnum::ATOM), ); match result { @@ -145,17 +229,12 @@ impl DragNDropState { PermanentlyRejected { source_window } }; - // Do this at the end, in case the handler panics, so that it doesn't poison our internal state. - if interrupted_active_drag { - handler.on_event(Event::Mouse(MouseEvent::DragLeft)); - } - - Ok(()) + Ok(interrupted_active_drag) } - pub fn handle_position_event( - &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, - ) -> Result<(), ConnectionError> { + fn handle_position_event( + &mut self, window: &WindowShared, event: &ClientMessageEvent, + ) -> Result, ConnectionError> { let event_data = event.data.as_data32(); let event_source_window = event_data[0] as xproto::Window; @@ -164,7 +243,10 @@ impl DragNDropState { match self { // Someone sent us a position event without first sending an enter event. // Weird, but we'll still politely tell them we reject the drop. - NoCurrentSession => Ok(send_status_rejected(event_source_window, window)?), + NoCurrentSession => { + send_status_rejected(event_source_window, window)?; + Ok(None) + } // The current session's source window does not match the given event. // This means it can either be from a stale session, or a misbehaving app. @@ -175,12 +257,16 @@ impl DragNDropState { | Ready { source_window, .. } if *source_window != event_source_window => { - Ok(send_status_rejected(event_source_window, window)?) + send_status_rejected(event_source_window, window)?; + Ok(None) } // We decided to permanently reject this drop. // This means the WindowHandler can't do anything with the data, so we reject the drop. - PermanentlyRejected { .. } => Ok(send_status_rejected(event_source_window, window)?), + PermanentlyRejected { .. } => { + send_status_rejected(event_source_window, window)?; + Ok(None) + } // This is the position event we were waiting for. Now we can request the selection data. // The code above already checks that source_window == event_source_window. @@ -220,7 +306,7 @@ impl DragNDropState { *position = result; } - Ok(()) + Ok(None) } // We are still waiting for the data. So we'll just update the position in the meantime. @@ -229,7 +315,7 @@ impl DragNDropState { *position = result; } - Ok(()) + Ok(None) } // We have already received the data. We can update the position and notify the handler @@ -248,24 +334,22 @@ impl DragNDropState { DndAction::from_atom(event_data[4] as Atom, &window.connection.atoms) }; - handler.on_event(Event::Mouse(MouseEvent::DragMoved { + Ok(Some(Event::Mouse(MouseEvent::DragMoved { position: position.cast(), data: data.clone(), // We don't get modifiers for drag n drop events. modifiers: Modifiers::empty(), - })); - - Ok(()) + }))) } } } - pub fn handle_leave_event(&mut self, handler: &Handler, event: &ClientMessageEvent) { + pub fn handle_leave_event(&mut self, event: &ClientMessageEvent) -> bool { let data = event.data.as_data32(); let event_source_window = data[0] as xproto::Window; let current_source_window = match self { - NoCurrentSession => return, + NoCurrentSession => return false, WaitingForPosition { source_window, .. } | PermanentlyRejected { source_window, .. } | WaitingForData { source_window, .. } @@ -274,7 +358,7 @@ impl DragNDropState { // Only accept the leave event if it comes from the source window of the current drag session. if event_source_window != current_source_window { - return; + return false; } // If there was an active drag session that we informed the handler about, we need to @@ -285,14 +369,12 @@ impl DragNDropState { *self = NoCurrentSession; // Do this at the end, in case the handler panics, so that it doesn't poison our internal state. - if left_active_drag { - handler.on_event(Event::Mouse(MouseEvent::DragLeft)); - } + left_active_drag } pub fn handle_drop_event( - &mut self, window: &WindowShared, handler: &Handler, event: &ClientMessageEvent, - ) -> Result<(), ConnectionError> { + &mut self, window: &WindowShared, event: &ClientMessageEvent, + ) -> Result, ConnectionError> { let data = event.data.as_data32(); let event_source_window = data[0] as xproto::Window; @@ -339,7 +421,8 @@ impl DragNDropState { *self = PermanentlyRejected { source_window: *source_window }; // Try to inform the source that we ended up rejecting the drop. - return send_finished_rejected(event_source_window, window); + send_finished_rejected(event_source_window, window)?; + return Ok(None); }; *self = WaitingForData { @@ -363,7 +446,7 @@ impl DragNDropState { // Just in case, check if this drop event isn't stale if requested_at > event_timestamp { - return Ok(()); + return Ok(None); } } @@ -382,21 +465,21 @@ impl DragNDropState { send_finished_event(event_source_window, window, requested_action)?.check_warn(); - handler.on_event(Event::Mouse(MouseEvent::DragDropped { + return Ok(Some(Event::Mouse(MouseEvent::DragDropped { position: position.cast(), data, // We don't get modifiers for drag n drop events. modifiers: Modifiers::empty(), - })); + }))); } }; - Ok(()) + Ok(None) } pub fn handle_selection_notify_event( - &mut self, window: &WindowShared, handler: &Handler, event: &SelectionNotifyEvent, - ) -> Result<(), ConnectionError> { + &mut self, window: &WindowShared, event: &SelectionNotifyEvent, + ) -> (Option, Option, Result<(), ConnectionError>) { // Ignore the event if we weren't actually waiting for a selection notify event let WaitingForData { source_window, @@ -407,33 +490,38 @@ impl DragNDropState { requested_action, } = *self else { - return Ok(()); + return (None, None, Ok(())); }; // Ignore if this was meant for another window (?) if event.requestor != window.xcb_window.id().get() { - return Ok(()); + return (None, None, Ok(())); } // Ignore if this is stale selection data. if let Some(requested_at) = requested_at { if requested_at != event.time { - return Ok(()); + return (None, None, Ok(())); } } // The sender should have set the data on our window, let's fetch it. - match fetch_dnd_data(window)? { - None => { + match fetch_dnd_data(window) { + Err(e) => (None, None, Err(e)), + Ok(None) => { *self = PermanentlyRejected { source_window }; - if dropped { - send_finished_rejected(source_window, window)?; - } else { - send_status_rejected(source_window, window)?; - } + ( + None, + None, + if dropped { + send_finished_rejected(source_window, window) + } else { + send_status_rejected(source_window, window) + }, + ) } - Some(data) => { + Ok(Some(data)) => { // Inform the source that we are (still) accepting the drop. // Handle the case where the user already dropped, but we only received the data later. @@ -443,21 +531,21 @@ impl DragNDropState { let reply_result = send_finished_event(source_window, window, requested_action); // Now that we have actual drop data, we can inform the handler about the drag AND drop events. - handler.on_event(Event::Mouse(MouseEvent::DragEntered { - position: position.cast(), - data: data.clone(), - // We don't get modifiers for drag n drop events. - modifiers: Modifiers::empty(), - })); - - handler.on_event(Event::Mouse(MouseEvent::DragDropped { - position: position.cast(), - data: data.clone(), - // We don't get modifiers for drag n drop events. - modifiers: Modifiers::empty(), - })); - - reply_result?.check_warn(); + ( + Some(Event::Mouse(MouseEvent::DragEntered { + position: position.cast(), + data: data.clone(), + // We don't get modifiers for drag n drop events. + modifiers: Modifiers::empty(), + })), + Some(Event::Mouse(MouseEvent::DragDropped { + position: position.cast(), + data: data.clone(), + // We don't get modifiers for drag n drop events. + modifiers: Modifiers::empty(), + })), + reply_result.map(|c| c.check_warn()), + ) } else { // Save the data, now that we finally have it! *self = Ready { @@ -471,19 +559,19 @@ impl DragNDropState { let reply_result = send_status_event(source_window, window, requested_action); // Now that we have actual drop data, we can inform the handler about the drag event. - handler.on_event(Event::Mouse(MouseEvent::DragEntered { - position: position.cast(), - data, - // We don't get modifiers for drag n drop events. - modifiers: Modifiers::empty(), - })); - - reply_result?; + ( + Some(Event::Mouse(MouseEvent::DragEntered { + position: position.cast(), + data, + // We don't get modifiers for drag n drop events. + modifiers: Modifiers::empty(), + })), + None, + reply_result, + ) } } } - - Ok(()) } } diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 42136a96..93f238de 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -144,7 +144,7 @@ impl EventLoop { present_state: PresentState::new(&host), sizing_state: SizingState::new(parent_id), - drag_n_drop: DragNDropState::NoCurrentSession, + drag_n_drop: DragNDropState::new(), xkb_state: XkbcommonState::new(&shared.connection), run_error: None, fd_registered: false.into(), @@ -276,8 +276,6 @@ impl EventLoop { return Ok(()); } - todo!(); - /* 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 { @@ -286,19 +284,17 @@ impl EventLoop { 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) => { - todo!() - /* if event.property == self.shared.connection.atoms.XdndSelection { self.drag_n_drop.handle_selection_notify_event( &self.shared, &self.handler, &event, )?; - }*/ + } } XEvent::Error(e) => { From 7e260d68c03f5fcd285bf12a109b973190cbc5dc Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:22:45 +0200 Subject: [PATCH 14/26] cleanup --- examples/plugin_clack/src/lib.rs | 8 +------- examples/plugin_clack_femtovg/src/lib.rs | 6 +----- host/src/window.rs | 11 ----------- src/platform/x11/event_loop.rs | 19 ------------------- src/window.rs | 19 ------------------- 5 files changed, 2 insertions(+), 61 deletions(-) diff --git a/examples/plugin_clack/src/lib.rs b/examples/plugin_clack/src/lib.rs index 000aff63..a63eeacd 100644 --- a/examples/plugin_clack/src/lib.rs +++ b/examples/plugin_clack/src/lib.rs @@ -78,13 +78,7 @@ impl<'a> ExamplePluginMainThread<'a> { } } -impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread<'a> { - fn on_main_thread(&self) { - if let Some(gui) = self.gui.borrow().as_ref() { - gui.handle.host_main_thread_callback(); - } - } -} +impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread<'a> {} impl PluginStateImpl for ExamplePluginMainThread<'_> { fn save(&self, _output: &mut OutputStream) -> Result<(), PluginError> { diff --git a/examples/plugin_clack_femtovg/src/lib.rs b/examples/plugin_clack_femtovg/src/lib.rs index 3366f88e..d1dd34f0 100644 --- a/examples/plugin_clack_femtovg/src/lib.rs +++ b/examples/plugin_clack_femtovg/src/lib.rs @@ -65,11 +65,7 @@ impl<'a> ExamplePluginMainThread<'a> { } impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread<'a> { - fn on_main_thread(&self) { - if let Some(gui) = self.gui.borrow().as_ref() { - gui.handle.host_main_thread_callback(); - } - } + fn on_main_thread(&self) {} } impl PluginStateImpl for ExamplePluginMainThread<'_> { diff --git a/host/src/window.rs b/host/src/window.rs index 016bb866..67db0bda 100644 --- a/host/src/window.rs +++ b/host/src/window.rs @@ -105,17 +105,6 @@ pub trait HostedWindow { // This is set by the [`WindowSettings::max_size`] field. fn max_size(&self) -> Option; - /// Performs the work the window thread had scheduled for the main thread. - /// - /// This must be called back on the main thread, as a response to [`HostMainThreadCaller::call_main_thread`](crate::host::HostMainThreadCaller::call_main_thread). - /// - /// # Platform compatibility notes - /// - /// Only the X11 platform has a separate window thread, so this is only needed to run host callbacks on X11. - /// - /// On Windows and macOS, this is always a no-op. - fn host_main_thread_callback(&self); - fn host_timer_callback(&self, timer_id: TimerId); #[cfg(unix)] diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 93f238de..e52cad78 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -49,10 +49,6 @@ impl EventLoop { self.shared.sizing_state.strategy() } - pub fn handle_main_thread_callback(&self) { - todo!() - } - pub fn host_timer_callback(&self, timer_id: TimerId) { if let Some(timer) = self.shared.timer_manager.get_handle_if_exists(timer_id) { self.handler.on_timer(&timer.into()); @@ -162,8 +158,6 @@ impl EventLoop { if let Some(fd_support) = self.host.fd_support() { fd_support.register_fd(self.shared.connection.conn.as_fd())?; self.fd_registered.set(true); - } else { - // TODO } Ok(()) @@ -203,19 +197,6 @@ impl EventLoop { Ok(event_received) } - pub fn stop_now(&self) { - todo!(); - //self.loop_signal.stop(); - //self.loop_signal.wakeup(); - } - - pub fn trigger_fatal_error(&self, error: PlatformError) { - if self.run_error.is_none() { - //self.run_error = Some(error); TODO - } - self.stop_now(); - } - pub fn resize(&self, new_size: Size) -> PlatformResult<()> { self.sizing_state.handle_host_resize(new_size, &self.handler, &self.shared) } diff --git a/src/window.rs b/src/window.rs index abe5459d..f2c5a53e 100644 --- a/src/window.rs +++ b/src/window.rs @@ -178,20 +178,6 @@ impl Window { self.inner.max_size() } - /// Performs the work the window thread had scheduled for the main thread. - /// - /// This must be called back on the main thread, as a response to [`HostMainThreadCaller::call_main_thread`](host::HostMainThreadCaller::call_main_thread). - /// - /// # Platform compatibility notes - /// - /// Only the X11 platform has a separate window thread, so this is only needed to run host callbacks on X11. - /// - /// On Windows and macOS, this is always a no-op. - #[inline] - pub fn host_main_thread_callback(&self) { - self.inner.handle_main_thread_callback() - } - #[inline] pub fn host_timer_callback(&self, timer_id: TimerId) { self.inner.host_timer_callback(timer_id) @@ -317,11 +303,6 @@ impl HostedWindow for Window { self.max_size() } - #[inline] - fn host_main_thread_callback(&self) { - self.host_main_thread_callback() - } - #[inline] fn host_timer_callback(&self, timer_id: TimerId) { self.host_timer_callback(timer_id) From 60caba65ea647fa9e2bc84c8cbc8cd3d32cbc0fa Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:23:47 +0200 Subject: [PATCH 15/26] wip --- src/platform/x11/sizing.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/platform/x11/sizing.rs b/src/platform/x11/sizing.rs index bb1ee3c8..6b3d995e 100644 --- a/src/platform/x11/sizing.rs +++ b/src/platform/x11/sizing.rs @@ -36,7 +36,7 @@ impl SizingState { 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()) { + if let Err(e) = shared.xcb_window.resize(new_parent_size.cast())?.check() { 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) @@ -68,8 +68,13 @@ impl SizingState { if !comes_from_parent { if let Err(()) = host_handle.request_resize(new_size) { - let previous = WindowSize::from_physical(previous.cast(), scale_factor); - todo!() // roll back + let previous_size = WindowSize::from_physical(previous.cast(), scale_factor); + if let Err(()) = handler.resize(previous_size) { + // If both host and handler failed, then we're kinda stuck here + return Ok(()); + } + shared.sizing_state.store_size(previous); + shared.xcb_window.resize(previous.cast())?.check_warn(); } } From 1caee1c5ae9efe5cf111aabe0c0e43d802a5f930 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:51:31 +0200 Subject: [PATCH 16/26] cleanup --- Cargo.toml | 2 -- src/platform/x11/error.rs | 16 ---------------- src/platform/x11/event_loop.rs | 19 ++++++------------- 3 files changed, 6 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5120c548..01fb98d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,13 +43,11 @@ tracing = { version = "0.1", optional = true } x11rb = { version = "0.13.2", features = ["cursor", "dri3", "present", "resource_manager", "allow-unsafe-code", "dl-libxcb"], default-features = false } xkbcommon-dl = { version = "0.4.2", features = ["x11"] } x11-dl = { version = "2.21.0" } -calloop = "=0.14.4" # 0.14.5 has MSRV of 1.86 polling = "3.11.0" slotmap = "1.1.1" percent-encoding = "2.3.2" bytemuck = { version = "1.25.2", features = ["extern_crate_alloc"] } libloading = "0.8.9" # Libloading 0.9 is out but its MSRV is 1.88 -bitflags = "2.13.2" rustix = "1.1.4" [target.'cfg(target_os="windows")'.dependencies] diff --git a/src/platform/x11/error.rs b/src/platform/x11/error.rs index 3a24beec..b39ea93d 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -14,7 +14,6 @@ use x11rb::x11_utils::{TryParse, X11Error}; #[derive(Debug)] pub enum FatalError { Connection(ConnectionError), - Calloop(calloop::Error), Redraw(String), Host(Box), } @@ -23,7 +22,6 @@ impl Display for FatalError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { FatalError::Connection(e) => e.fmt(f), - FatalError::Calloop(e) => e.fmt(f), FatalError::Redraw(e) => write!(f, "Fatal error while drawing the window: {}", e), FatalError::Host(e) => e.fmt(f), } @@ -37,11 +35,6 @@ impl From for FatalError { FatalError::Connection(err) } } -impl From for FatalError { - fn from(value: calloop::Error) -> Self { - Self::Calloop(value) - } -} #[derive(Debug)] pub enum PlatformError { @@ -59,7 +52,6 @@ pub enum PlatformError { DisplayOpenFailed(DisplayOpenFailedError), Handler(HandlerError), Host(Box), - Calloop(calloop::Error), Redraw(String), #[cfg(feature = "opengl")] XLib(crate::wrappers::xlib::XLibError), @@ -87,7 +79,6 @@ impl Display for PlatformError { PlatformError::Handler(e) => e.fmt(f), PlatformError::Host(e) => e.fmt(f), PlatformError::Redraw(e) => e.fmt(f), - PlatformError::Calloop(e) => e.fmt(f), #[cfg(feature = "opengl")] PlatformError::XLib(e) => e.fmt(f), #[cfg(feature = "opengl")] @@ -163,17 +154,10 @@ impl From for PlatformError { } } -impl From for PlatformError { - fn from(value: calloop::Error) -> Self { - Self::Calloop(value) - } -} - impl From for PlatformError { fn from(value: FatalError) -> Self { match value { FatalError::Connection(e) => Self::Connection(e), - FatalError::Calloop(e) => Self::Calloop(e), FatalError::Redraw(s) => Self::Redraw(s), FatalError::Host(e) => Self::Handler(HandlerError::from_boxed(e)), } diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index e52cad78..8ee0e9da 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -65,8 +65,7 @@ impl EventLoop { return; }; - self.flush_drain_xcb_events().unwrap(); // TODO: unwraps - self.handle_idle().unwrap(); + self.handle_idle().unwrap(); // TODO: unwraps } pub fn max_size(&self) -> Option { @@ -94,7 +93,7 @@ impl EventLoop { self.shared.stop_own_event_loop.set(false); - while !self.shared.stop_own_event_loop.get() { + loop { let next_deadline = self.tick_all_timers(); self.handle_idle()?; @@ -108,12 +107,10 @@ impl EventLoop { _ => {} } - /* - // Check if the user has requested the window to close - if self.window.close_requested.get() { - self.handle_must_close(); - self.window.close_requested.set(false); - }*/ + if self.shared.stop_own_event_loop.get() { + self.handler.on_event(Event::Window(WindowEvent::WillClose)); + break; + } } poller.delete()?; @@ -241,10 +238,6 @@ impl EventLoop { Ok(()) } - pub fn shared(&self) -> &WindowShared { - &self.shared - } - fn handle_xcb_event(&self, event: XEvent) -> Result<(), FatalError> { match event { XEvent::ClientMessage(event) if event.window == self.shared.raw_id() => { From 087b4dd3c5992bd1709df449004776915b503c82 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 13 Oct 2026 00:26:04 +0200 Subject: [PATCH 17/26] wip --- src/platform/x11/event_loop.rs | 8 ++++++-- src/platform/x11/window_shared.rs | 7 +++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 8ee0e9da..84579d3c 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -66,6 +66,10 @@ impl EventLoop { }; self.handle_idle().unwrap(); // TODO: unwraps + + if self.shared.close_requested.get() { + self.handler.on_event(Event::Window(WindowEvent::WillClose)); + } } pub fn max_size(&self) -> Option { @@ -91,7 +95,7 @@ impl EventLoop { let waker_fd = self.shared.waker_source.inner_fd(); let mut poller = ConnectionPoller::new(&connection.conn, &waker_fd)?; - self.shared.stop_own_event_loop.set(false); + self.shared.close_requested.set(false); loop { let next_deadline = self.tick_all_timers(); @@ -107,7 +111,7 @@ impl EventLoop { _ => {} } - if self.shared.stop_own_event_loop.get() { + if self.shared.close_requested.get() { self.handler.on_event(Event::Window(WindowEvent::WillClose)); break; } diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index b0c038ec..d7643bec 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -15,7 +15,7 @@ pub struct WindowShared { visual_id: Visualid, pub is_focused: Cell, - pub stop_own_event_loop: Cell, + pub close_requested: Cell, pub cursor_state: CursorStateShared, pub present_state: PresentStateShared, @@ -62,7 +62,7 @@ impl WindowShared { gl_context: visual_config.make_gl_context(&xcb_window, &connection)?, is_focused: false.into(), - stop_own_event_loop: false.into(), + close_requested: false.into(), cursor_state: CursorStateShared::new(), present_state: PresentStateShared::new(&host), @@ -81,8 +81,7 @@ impl WindowShared { } pub fn request_close(&self) { - self.stop_own_event_loop.set(true); - // TODO: external event loop + self.close_requested.set(true); } pub fn request_redraw(&self) { From 0cc0f970c2789bdf290cf938bfacd13847e198ba Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Tue, 13 Oct 2026 15:30:25 +0200 Subject: [PATCH 18/26] wip --- src/platform/x11/event_loop.rs | 28 +------ src/platform/x11/mod.rs | 4 +- src/platform/x11/window_handle.rs | 117 ++++++++++++++++++++++++++++++ src/window.rs | 6 +- 4 files changed, 124 insertions(+), 31 deletions(-) create mode 100644 src/platform/x11/window_handle.rs diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 84579d3c..fa80cb70 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -36,17 +36,13 @@ pub(crate) struct EventLoop { } impl EventLoop { - pub fn waker(&self) -> WindowWaker { - self.shared.waker_source.create_waker() - } - pub fn request_poll(&self) -> PlatformResult<()> { self.handler.poll(); Ok(()) } - pub fn sizing_strategy(&self) -> SizingStrategy { - self.shared.sizing_state.strategy() + pub fn shared(&self) -> &Rc { + &self.shared } pub fn host_timer_callback(&self, timer_id: TimerId) { @@ -72,22 +68,6 @@ impl EventLoop { } } - pub fn max_size(&self) -> Option { - self.sizing_strategy().max_size() - } - - pub fn min_size(&self) -> Option { - self.sizing_strategy().min_size() - } - - pub fn is_resizable(&self) -> bool { - self.sizing_strategy().is_resizable() - } - - pub fn is_open(&self) -> bool { - todo!() - } - pub fn run_until_closed(&self) -> PlatformResult<()> { self.show()?; @@ -122,10 +102,6 @@ impl EventLoop { Ok(()) } - - pub fn size(&self) -> WindowSize { - self.shared.sizing_state.window_size() - } } impl EventLoop { diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 562c218d..9b1b24ff 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -13,8 +13,8 @@ mod sizing; mod timer; mod visual_info; mod waker; +mod window_handle; mod window_shared; -// mod window_thread; mod x11_connection; mod xcb_window; @@ -61,7 +61,7 @@ pub use error::PlatformError; pub use present::DamageRect; pub use timer::TimerHandle; pub use waker::WindowWaker; -pub type WindowHandle = EventLoop; +pub use window_handle::WindowHandle; #[derive(Clone)] pub struct PlatformHandle { diff --git a/src/platform/x11/window_handle.rs b/src/platform/x11/window_handle.rs new file mode 100644 index 00000000..c2918d03 --- /dev/null +++ b/src/platform/x11/window_handle.rs @@ -0,0 +1,117 @@ +use super::prelude::*; +use crate::platform::ParentWindowHandle; +use crate::utils::SizingStrategy; +use crate::window::WindowInitializer; +use baseview_host::host::TimerId; +use std::cell::RefCell; +use std::os::fd::RawFd; +use std::rc::Rc; + +pub struct WindowHandle { + ev_loop: OptionRcCell, + shared: Rc, +} + +impl WindowHandle { + pub fn create_window(init: WindowInitializer) -> PlatformResult { + let ev_loop = EventLoop::create_window(init)?; + Ok(Self { shared: Rc::clone(ev_loop.shared()), ev_loop: OptionRcCell::new(ev_loop) }) + } + + pub fn resize(&self, new_size: Size) -> PlatformResult<()> { + self.handle_evloop(|e| e.resize(new_size)) + } + + pub fn suggest_scale_factor(&self, scale: f64) -> PlatformResult<()> { + self.handle_evloop(|e| e.suggest_scale_factor(scale)) + } + + pub fn set_parent(&self, parent: ParentWindowHandle) -> PlatformResult<()> { + self.handle_evloop(|e| e.set_parent(parent)) + } + + pub fn show(&self) -> PlatformResult<()> { + self.handle_evloop(|e| e.show()) + } + + pub fn hide(&self) -> PlatformResult<()> { + self.handle_evloop(|e| e.hide()) + } + + pub fn run_until_closed(&self) -> PlatformResult<()> { + self.handle_evloop(|e| e.run_until_closed()) + } + + pub fn request_poll(&self) -> PlatformResult<()> { + self.handle_evloop(|e| e.request_poll()) + } + + pub fn host_fd_callback(&self, fd: RawFd) { + let Some(event_loop) = self.ev_loop.get() else { return }; + event_loop.host_fd_callback(fd); + + if self.shared.close_requested.get() { + self.ev_loop.delete(); + } + } + + pub fn host_timer_callback(&self, id: TimerId) { + let Some(event_loop) = self.ev_loop.get() else { return }; + event_loop.host_timer_callback(id); + + if self.shared.close_requested.get() { + self.ev_loop.delete(); + } + } + + pub fn sizing_strategy(&self) -> SizingStrategy { + self.shared.sizing_state.strategy() + } + + pub fn size(&self) -> WindowSize { + self.shared.sizing_state.window_size() + } + + pub fn waker(&self) -> WindowWaker { + self.shared.waker() + } + + pub fn is_open(&self) -> bool { + self.ev_loop.is_some() + } +} + +impl WindowHandle { + fn handle_evloop( + &self, handler: impl FnOnce(&EventLoop) -> PlatformResult<()>, + ) -> PlatformResult<()> { + let Some(event_loop) = self.ev_loop.get() else { return Ok(()) }; + let res = handler(&event_loop); + + if self.shared.close_requested.get() { + self.ev_loop.delete(); + } + + res + } +} + +struct OptionRcCell(RefCell>>); + +impl OptionRcCell { + pub fn new(value: T) -> Self { + OptionRcCell(RefCell::new(Some(Rc::new(value)))) + } + + pub fn get(&self) -> Option> { + Some(Rc::clone(self.0.borrow().as_ref()?)) + } + + pub fn is_some(&self) -> bool { + self.0.borrow().is_some() + } + + pub fn delete(&self) { + self.0.borrow_mut().take(); + } +} diff --git a/src/window.rs b/src/window.rs index f2c5a53e..2befe40f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -159,7 +159,7 @@ impl Window { /// This is set by the [`WindowSettings::resizable`] field. #[inline] pub fn is_resizable(&self) -> bool { - self.inner.is_resizable() + self.inner.sizing_strategy().is_resizable() } /// Returns the minimum size of the window, if it has one. @@ -167,7 +167,7 @@ impl Window { /// This is set by the [`WindowSettings::min_size`] field. #[inline] pub fn min_size(&self) -> Option { - self.inner.min_size() + self.inner.sizing_strategy().min_size() } /// Returns the minimum size of the window, if it has one. @@ -175,7 +175,7 @@ impl Window { /// This is set by the [`WindowSettings::max_size`] field. #[inline] pub fn max_size(&self) -> Option { - self.inner.max_size() + self.inner.sizing_strategy().max_size() } #[inline] From 9932adb56d608db09801483b6493d68b930b49e6 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Thu, 15 Oct 2026 19:33:45 +0200 Subject: [PATCH 19/26] Optimize X11 waker --- src/platform/x11/event_loop.rs | 3 +- src/platform/x11/waker.rs | 154 +++++++++++++++++------------- src/platform/x11/waker/eventfd.rs | 38 +++----- src/platform/x11/window_shared.rs | 2 +- src/wrappers/poller.rs | 21 ++-- 5 files changed, 119 insertions(+), 99 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index fa80cb70..36426390 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -73,7 +73,8 @@ impl EventLoop { let connection = Rc::clone(&self.shared.connection); let waker_fd = self.shared.waker_source.inner_fd(); - let mut poller = ConnectionPoller::new(&connection.conn, &waker_fd)?; + let waker_fd = waker_fd.as_ref().and_then(|s| s.fd()); + let mut poller = ConnectionPoller::new(&connection.conn, waker_fd)?; self.shared.close_requested.set(false); diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index b1f73f5c..9aa996a4 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -1,8 +1,9 @@ use crate::platform::x11::host_handle::HostHandle; +use std::cell::RefCell; +use std::mem; use std::os::fd::{AsFd, BorrowedFd}; use std::sync::atomic::{AtomicU8, Ordering}; -use std::sync::mpsc::{Receiver, SyncSender, TryRecvError}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; mod eventfd; @@ -10,47 +11,41 @@ use crate::platform::prelude::PresentStateShared; use crate::DropData; use eventfd::*; -// TODO: only instanciate FDs and stuff when first waker is created pub(crate) struct WakerSource { - inner: EventFdWakerSource, - flags: Arc, - redraw_channel_send: SyncSender, - redraw_channel_recv: Receiver, + shared: RefCell>>, } impl WakerSource { - pub fn new(host: &HostHandle) -> Self { - if host.fd_support().is_some() { - } else if host.timer_support().is_some() { - todo!() - } else { - } - - let (send, recv) = std::sync::mpsc::sync_channel(100); + pub fn new() -> Self { + Self { shared: None.into() } + } - Self { - inner: EventFdWakerSource::new(), - flags: Arc::new(SharedFlags::new()), - redraw_channel_recv: recv, - redraw_channel_send: send, + fn get_shared_or_create(&self) -> Arc { + let mut shared = self.shared.borrow_mut(); + if let Some(shared) = &*shared { + return Arc::clone(shared); } + + let new_shared = Arc::new(Shared::new()); + *shared = Some(Arc::clone(&new_shared)); + new_shared } pub fn create_waker(&self) -> WindowWaker { - WindowWaker { - inner: self.inner.create_waker(), - flag: Arc::clone(&self.flags), - redraw_channel_send: self.redraw_channel_send.clone(), - } + WindowWaker { shared: self.get_shared_or_create() } } pub fn poll_requests(&self, present_state_shared: &PresentStateShared, host: &HostHandle) { - if !self.inner.poll() { - return; + let Some(shared) = &*self.shared.borrow() else { return }; + + if let Some(event_fd) = &shared.event_fd { + if !event_fd.poll() { + return; + } } - let flags = self.flags.take(); - let requests = self.get_all_redraw_requests(); + let flags = shared.take_flags(); + let requests = shared.take_requests(); for instant in requests { if let Some(duration) = instant.checked_duration_since(Instant::now()) { @@ -73,36 +68,27 @@ impl WakerSource { } } - fn get_all_redraw_requests(&self) -> Vec { - let mut requests = vec![]; - - loop { - match self.redraw_channel_recv.try_recv() { - Ok(instant) => requests.push(instant), - Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => unreachable!(), // We always hold onto one sender - } - } - - requests - } - - pub fn inner_fd(&self) -> BorrowedFd<'_> { - self.inner.as_fd() + pub fn inner_fd(&self) -> Option> { + let Some(shared) = &*self.shared.borrow() else { return None }; + Some(Arc::clone(&shared)) } } #[derive(Clone)] pub struct WindowWaker { - inner: EventFdWaker, - flag: Arc, - redraw_channel_send: SyncSender, + shared: Arc, } impl WindowWaker { + fn wake(&self) { + if let Some(event_fd) = &self.shared.event_fd { + event_fd.wake() + } + } + pub fn request_redraw(&self) { - self.flag.set_redraw(); - self.inner.wake(); + self.shared.set_redraw(); + self.wake(); } pub fn request_redraw_after(&self, duration: Duration) { @@ -110,27 +96,48 @@ impl WindowWaker { return self.request_redraw(); } - self.redraw_channel_send.send(Instant::now() + duration).unwrap(); - self.inner.wake() + // If this overflows, then this is so long in the future that it will realistically not occur anyway + let Some(instant) = Instant::now().checked_add(duration) else { + return; + }; + + self.shared.add_request(instant); + self.wake() } pub fn request_poll(&self) { - self.flag.set_poll(); - self.inner.wake(); + self.shared.set_poll(); + self.wake(); } } impl Drop for WindowWaker { fn drop(&mut self) { - self.flag.set_dropped(); + self.shared.set_dropped(); } } -struct SharedFlags(AtomicU8); +pub(crate) struct Shared { + flags: AtomicU8, + requests: Mutex>, + event_fd: Option, +} -impl SharedFlags { +impl Shared { pub fn new() -> Self { - Self(AtomicU8::new(0)) + let event_fd = match EventFd::new() { + Ok(fd) => Some(fd), + Err(e) => { + crate::warn!("Could not create WindowWaker's eventfd: {}", e); + None + } + }; + + Self { flags: AtomicU8::new(0), requests: Mutex::new(vec![]), event_fd } + } + + pub(crate) fn fd(&self) -> Option> { + Some(self.event_fd.as_ref()?.as_fd()) } const POLL: u8 = 1 << 0; @@ -138,19 +145,34 @@ impl SharedFlags { const DROPPED: u8 = 1 << 2; fn set_poll(&self) { - self.0.fetch_or(Self::POLL, Ordering::Release); + self.flags.fetch_or(Self::POLL, Ordering::Release); } fn set_redraw(&self) { - self.0.fetch_or(Self::REDRAW, Ordering::Release); + self.flags.fetch_or(Self::REDRAW, Ordering::Release); } fn set_dropped(&self) { - self.0.fetch_or(Self::DROPPED, Ordering::Release); + self.flags.fetch_or(Self::DROPPED, Ordering::Release); + } + + fn take_flags(&self) -> FlagsValues { + FlagsValues(self.flags.swap(0, Ordering::Acquire)) + } + + fn add_request(&self, instant: Instant) { + const MAX_REQUESTS: usize = 10_000; + let mut requests = self.requests.lock().unwrap_or_else(|e| e.into_inner()); + if requests.len() >= MAX_REQUESTS { + return; + } + + requests.push(instant); } - fn take(&self) -> FlagsValues { - FlagsValues(self.0.swap(0, Ordering::Acquire)) + fn take_requests(&self) -> Vec { + let mut requests = self.requests.lock().unwrap_or_else(|e| e.into_inner()); + mem::take(&mut requests) } } @@ -158,14 +180,14 @@ pub struct FlagsValues(u8); impl FlagsValues { fn poll(&self) -> bool { - self.0 & SharedFlags::POLL != 0 + self.0 & Shared::POLL != 0 } fn redraw(&self) -> bool { - self.0 & SharedFlags::REDRAW != 0 + self.0 & Shared::REDRAW != 0 } fn dropped(&self) -> bool { - self.0 & SharedFlags::DROPPED != 0 + self.0 & Shared::DROPPED != 0 } } diff --git a/src/platform/x11/waker/eventfd.rs b/src/platform/x11/waker/eventfd.rs index 0d5d75d1..96740241 100644 --- a/src/platform/x11/waker/eventfd.rs +++ b/src/platform/x11/waker/eventfd.rs @@ -1,20 +1,13 @@ use rustix::event::{eventfd, EventfdFlags}; use rustix::io::{read, write, Errno}; +use std::io; use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; -use std::sync::Arc; -pub struct EventFdWakerSource { - fd: Arc, -} - -impl EventFdWakerSource { - pub fn new() -> Self { - let fd = eventfd(0, EventfdFlags::CLOEXEC | EventfdFlags::NONBLOCK).unwrap(); - Self { fd: Arc::new(fd) } - } +pub struct EventFd(OwnedFd); - pub fn create_waker(&self) -> EventFdWaker { - EventFdWaker(Arc::clone(&self.fd)) +impl EventFd { + pub fn new() -> io::Result { + Ok(Self(eventfd(0, EventfdFlags::CLOEXEC | EventfdFlags::NONBLOCK)?)) } // Returns true if something requested this to be woken up @@ -23,7 +16,7 @@ impl EventFdWakerSource { const BYTES: usize = 8; let mut buf = [0u8; BYTES]; - match read(&self.fd, &mut buf) { + match read(&self.0, &mut buf) { // Reading from an eventfd should only ever produce 8 bytes. No looping // is required. Ok(BYTES) => u64::from_ne_bytes(buf) > 0, @@ -37,19 +30,7 @@ impl EventFdWakerSource { } } } -} -impl AsFd for EventFdWakerSource { - #[inline] - fn as_fd(&self) -> BorrowedFd<'_> { - self.fd.as_fd() - } -} - -#[derive(Clone)] -pub struct EventFdWaker(Arc); - -impl EventFdWaker { pub fn wake(&self) { match write(&self.0, &1u64.to_ne_bytes()) { Ok(_) => {} @@ -62,3 +43,10 @@ impl EventFdWaker { } } } + +impl AsFd for EventFd { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index d7643bec..eef23f7e 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -68,7 +68,7 @@ impl WindowShared { present_state: PresentStateShared::new(&host), sizing_state, timer_manager: TimerManager::new(&host), - waker_source: WakerSource::new(&host), + waker_source: WakerSource::new(), host, xcb_window, diff --git a/src/wrappers/poller.rs b/src/wrappers/poller.rs index 1f16c7e2..b470e84a 100644 --- a/src/wrappers/poller.rs +++ b/src/wrappers/poller.rs @@ -8,21 +8,24 @@ pub struct ConnectionPoller<'a> { poller: Poller, events: Events, fd: BorrowedFd<'a>, - waker_fd: BorrowedFd<'a>, + waker_fd: Option>, } const CONNECTION_KEY: usize = 42; const WAKER_KEY: usize = 69; impl<'a> ConnectionPoller<'a> { - pub fn new(source: &'a impl AsFd, waker: &'a impl AsFd) -> io::Result { + pub fn new(source: &'a impl AsFd, waker_fd: Option>) -> io::Result { let poller = Poller::new()?; let fd = source.as_fd(); unsafe { poller.add_with_mode(&fd, Event::readable(CONNECTION_KEY), PollMode::Level)? }; - let waker_fd = waker.as_fd(); - unsafe { poller.add_with_mode(&waker_fd, Event::readable(WAKER_KEY), PollMode::Level)? }; + if let Some(waker_fd) = waker_fd { + unsafe { + poller.add_with_mode(&waker_fd, Event::readable(WAKER_KEY), PollMode::Level)? + }; + } Ok(Self { poller, fd, events: Events::new(), waker_fd }) } @@ -68,14 +71,20 @@ impl<'a> ConnectionPoller<'a> { } pub fn delete(self) -> io::Result<()> { - let _ = self.poller.delete(self.waker_fd)?; + if let Some(waker_fd) = self.waker_fd { + let _ = self.poller.delete(waker_fd); + } + self.poller.delete(self.fd) } } impl<'a> Drop for ConnectionPoller<'a> { fn drop(&mut self) { - let _ = self.poller.delete(self.waker_fd); + if let Some(waker_fd) = self.waker_fd { + let _ = self.poller.delete(waker_fd); + } + let _ = self.poller.delete(self.fd); } } From bd268d950bf1e65ff0f7e9327840f15ebd423524 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:04:08 +0200 Subject: [PATCH 20/26] fix external wakeup example --- examples/external-wakeup/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/external-wakeup/src/main.rs b/examples/external-wakeup/src/main.rs index 3d33c394..5cb79484 100644 --- a/examples/external-wakeup/src/main.rs +++ b/examples/external-wakeup/src/main.rs @@ -141,7 +141,9 @@ fn run_thread(sender: Sender, waker: WindowWaker) { loop { let interval: f32 = rand::random_range(0.5..2.5); std::thread::sleep(Duration::from_secs_f32(interval)); - sender.send(Message::Hello).unwrap(); + if sender.send(Message::Hello).is_err() { + break; // Receiver has hung up, we can just shut this down. + } waker.request_poll(); } } From c0d872fa1076b39a4f313e71a4bc3f87bbb5eb94 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:25:25 +0200 Subject: [PATCH 21/26] wip --- src/platform/x11/event_loop.rs | 19 +++++++------------ src/platform/x11/host_handle.rs | 13 +++++++++++++ src/platform/x11/waker.rs | 29 +++++++++++++++++------------ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 36426390..2cf83e56 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -31,7 +31,6 @@ pub(crate) struct EventLoop { host: Rc, - run_error: Option, fd_registered: Cell, } @@ -56,16 +55,14 @@ impl EventLoop { } } - pub fn host_fd_callback(&self, fd: RawFd) { + pub fn host_fd_callback(&self, fd: RawFd) -> Result<(), FatalError> { if fd != self.shared.connection.conn.as_raw_fd() { - return; + return Ok(()); }; - self.handle_idle().unwrap(); // TODO: unwraps + self.handle_idle()?; - if self.shared.close_requested.get() { - self.handler.on_event(Event::Window(WindowEvent::WillClose)); - } + Ok(()) } pub fn run_until_closed(&self) -> PlatformResult<()> { @@ -93,7 +90,6 @@ impl EventLoop { } if self.shared.close_requested.get() { - self.handler.on_event(Event::Window(WindowEvent::WillClose)); break; } } @@ -120,7 +116,6 @@ impl EventLoop { sizing_state: SizingState::new(parent_id), drag_n_drop: DragNDropState::new(), xkb_state: XkbcommonState::new(&shared.connection), - run_error: None, fd_registered: false.into(), shared, @@ -335,14 +330,14 @@ impl EventLoop { impl Drop for EventLoop { fn drop(&mut self) { + self.handler.on_event(Event::Window(WindowEvent::WillClose)); + // Deinit timers & FD watchers self.shared.timer_manager.destroy_all(&self.host); self.shared.present_state.destroy_redraw_timers(&self.host); if self.fd_registered.get() { - let Some(fd_support) = self.host.fd_support() else { unreachable!() }; - fd_support.unregister_fd(self.shared.connection.conn.as_raw_fd()).unwrap(); - // TODO: unwrap + let _ = self.host.unregister_fd(self.shared.connection.conn.as_raw_fd()); } } } diff --git a/src/platform/x11/host_handle.rs b/src/platform/x11/host_handle.rs index 314d3737..11b71fa6 100644 --- a/src/platform/x11/host_handle.rs +++ b/src/platform/x11/host_handle.rs @@ -1,5 +1,6 @@ use baseview_host::dpi::WindowSize; use baseview_host::host::*; +use std::os::fd::RawFd; use std::rc::Rc; pub struct HostHandle { @@ -17,6 +18,7 @@ impl HostHandle { }) } + // TODO: call this pub fn notify_destroyed(&self) { if let Some(callbacks) = &self.callbacks { callbacks.destroyed() @@ -52,4 +54,15 @@ impl HostHandle { Ok(()) } } + + pub fn unregister_fd(&self, fd: RawFd) -> Result<(), ()> { + let Some(callbacks) = &self.fd else { unreachable!() }; + + if let Err(e) = callbacks.unregister_fd(fd) { + crate::warn!("Host failed to unregister fd: {}", e); + Err(()) + } else { + Ok(()) + } + } } diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index 9aa996a4..b97e4028 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -8,7 +8,6 @@ use std::time::{Duration, Instant}; mod eventfd; use crate::platform::prelude::PresentStateShared; -use crate::DropData; use eventfd::*; pub(crate) struct WakerSource { @@ -31,21 +30,27 @@ impl WakerSource { new_shared } + fn delete_shared_if_unused(&self) { + let mut shared = self.shared.borrow_mut(); + let Some(shared_inner) = &mut *shared else { return }; + + if Arc::get_mut(shared_inner).is_some() { + *shared = None; + } + } + pub fn create_waker(&self) -> WindowWaker { WindowWaker { shared: self.get_shared_or_create() } } - pub fn poll_requests(&self, present_state_shared: &PresentStateShared, host: &HostHandle) { - let Some(shared) = &*self.shared.borrow() else { return }; + fn fetch_inner(&self) -> Option<(FlagsValues, Vec)> { + let Some(shared) = &*self.shared.borrow() else { return None }; - if let Some(event_fd) = &shared.event_fd { - if !event_fd.poll() { - return; - } - } + Some((shared.take_flags(), shared.take_requests())) + } - let flags = shared.take_flags(); - let requests = shared.take_requests(); + pub fn poll_requests(&self, present_state_shared: &PresentStateShared, host: &HostHandle) { + let Some((flags, requests)) = self.fetch_inner() else { return }; for instant in requests { if let Some(duration) = instant.checked_duration_since(Instant::now()) { @@ -64,13 +69,13 @@ impl WakerSource { } if flags.dropped() { - // TODO + self.delete_shared_if_unused(); } } pub fn inner_fd(&self) -> Option> { let Some(shared) = &*self.shared.borrow() else { return None }; - Some(Arc::clone(&shared)) + Some(Arc::clone(shared)) } } From 5be74ae62ea33a8722cefd693e2c48c69f6f72aa Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Fri, 16 Oct 2026 22:29:52 +0200 Subject: [PATCH 22/26] fixing stuff --- src/platform/x11/event_loop.rs | 13 +++++++++---- src/platform/x11/window_shared.rs | 6 ++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 2cf83e56..ca9f07ec 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -105,9 +105,15 @@ impl EventLoop { pub fn create_window(init: WindowInitializer) -> PlatformResult { let parent_id = init.settings.parent.as_ref().map(|p| p.inner.window_id); let host = HostHandle::new(init.host); - // TODO: check deinit runs on initialization error + let shared = WindowShared::create(init.settings, Rc::clone(&host))?; - let handler = init.builder.build(WindowContext::new(Rc::clone(&shared)))?; + let handler = match init.builder.build(WindowContext::new(Rc::clone(&shared))) { + Ok(handler) => handler, + Err(e) => { + shared.deinit_all_host(); + return Err(e); + } + }; let this = Self { handler: Handler::new(handler), @@ -333,8 +339,7 @@ impl Drop for EventLoop { self.handler.on_event(Event::Window(WindowEvent::WillClose)); // Deinit timers & FD watchers - self.shared.timer_manager.destroy_all(&self.host); - self.shared.present_state.destroy_redraw_timers(&self.host); + self.shared.deinit_all_host(); if self.fd_registered.get() { let _ = self.host.unregister_fd(self.shared.connection.conn.as_raw_fd()); diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index eef23f7e..554e1d19 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -148,4 +148,10 @@ impl WindowShared { pub fn raw_id(&self) -> xproto::Window { self.xcb_window.id().get() } + + // Deinit host-registered timers and FD watchers + pub fn deinit_all_host(&self) { + self.timer_manager.destroy_all(&self.host); + self.present_state.destroy_redraw_timers(&self.host); + } } From 8566f9076e7b8a385bafab9c654ac79571a2f0e6 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 17 Oct 2026 02:06:22 +0200 Subject: [PATCH 23/26] TimerSlot impl --- src/platform/x11/event_loop.rs | 1 + src/platform/x11/host_handle.rs | 2 + src/platform/x11/present.rs | 38 ++++--------- src/platform/x11/timer.rs | 97 ++++++++++++++++++++++++++++++--- 4 files changed, 101 insertions(+), 37 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index ca9f07ec..967c12c0 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -340,6 +340,7 @@ impl Drop for EventLoop { // Deinit timers & FD watchers self.shared.deinit_all_host(); + self.present_state.destroy_timers(&self.host); if self.fd_registered.get() { let _ = self.host.unregister_fd(self.shared.connection.conn.as_raw_fd()); diff --git a/src/platform/x11/host_handle.rs b/src/platform/x11/host_handle.rs index 11b71fa6..d4c577b1 100644 --- a/src/platform/x11/host_handle.rs +++ b/src/platform/x11/host_handle.rs @@ -1,7 +1,9 @@ use baseview_host::dpi::WindowSize; use baseview_host::host::*; +use std::error::Error; use std::os::fd::RawFd; use std::rc::Rc; +use std::time::Duration; pub struct HostHandle { callbacks: Option>, diff --git a/src/platform/x11/present.rs b/src/platform/x11/present.rs index 76bab946..5b8aeab0 100644 --- a/src/platform/x11/present.rs +++ b/src/platform/x11/present.rs @@ -77,8 +77,7 @@ pub struct PresentState { last_requested_serial: Cell>, last_received_present: Cell>, - fallback_frame_timer_manager: TimerManager, - fallback_frame_timer: Cell>, + fallback_frame_timer: TimerSlot, } impl PresentState { @@ -87,8 +86,7 @@ impl PresentState { draw_now: false.into(), last_requested_serial: None.into(), last_received_present: None.into(), - fallback_frame_timer_manager: TimerManager::new(host), - fallback_frame_timer: None.into(), + fallback_frame_timer: TimerSlot::new(host), } } @@ -174,26 +172,17 @@ impl PresentState { } pub fn tick_fallback_frame_timer(&self) -> Option { - let timer_handle = self.fallback_frame_timer.get()?; + let (deadline, triggered) = self.fallback_frame_timer.tick_next_timer(); - let (deadline, triggered) = self.fallback_frame_timer_manager.tick_next_timer(); - if let Some(triggered) = triggered { - if triggered == timer_handle { - self.draw_now.set(true); - } + if triggered { + self.draw_now.set(true); } deadline } pub fn trigger_possible_fallback_frame_timer(&self, id: TimerId) { - let Some(fallback_timer_handle) = self.fallback_frame_timer.get() else { return }; - - let Some(handle) = self.fallback_frame_timer_manager.get_handle_if_exists(id) else { - return; - }; - - if handle == fallback_timer_handle { + if self.fallback_frame_timer.matches_hosted(id) { self.draw_now.set(true); } } @@ -201,18 +190,11 @@ impl PresentState { fn setup_fallback_frame_timer(&self, host: &HostHandle) -> Result<(), FatalError> { const FRAME_INTERVAL: Duration = Duration::from_millis(15); - if let Some(previous) = self.fallback_frame_timer.take() { - self.fallback_frame_timer_manager.destroy_timer(previous, host); - } - - let handle = self - .fallback_frame_timer_manager - .create_timer(FRAME_INTERVAL, host) - .map_err(FatalError::Host)?; - - self.fallback_frame_timer.set(Some(handle)); + self.fallback_frame_timer.set_timer(FRAME_INTERVAL, host).map_err(FatalError::Host) + } - Ok(()) + pub fn destroy_timers(&self, host: &HostHandle) { + self.fallback_frame_timer.destroy(host) } pub fn handle_present_notify( diff --git a/src/platform/x11/timer.rs b/src/platform/x11/timer.rs index 7b6c9b92..1635075a 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -2,7 +2,7 @@ use crate::platform::prelude::PlatformResult; use crate::platform::x11::host_handle::HostHandle; use baseview_host::host::TimerId; use slotmap::{DefaultKey, DenseSlotMap, Key, KeyData}; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::error::Error; use std::time::{Duration, Instant}; @@ -88,12 +88,6 @@ impl TimerManager { } } -#[derive(Debug)] -struct Timer { - interval: Duration, - next_trigger: Option, -} - pub(crate) struct HostedTimerStore(RefCell>); impl HostedTimerStore { @@ -121,6 +115,18 @@ impl HostedTimerStore { } } +#[derive(Copy, Clone, Debug)] +pub(crate) struct Timer { + interval: Duration, + next_trigger: Option, +} + +impl Timer { + pub fn new(interval: Duration) -> Self { + Self { interval, next_trigger: Instant::now().checked_add(interval) } + } +} + pub(crate) struct StandaloneTimerStore(RefCell>); impl StandaloneTimerStore { @@ -130,9 +136,8 @@ impl StandaloneTimerStore { fn insert_new_timer(&self, interval: Duration) -> TimerHandle { let mut store = self.0.borrow_mut(); - let entry = Timer { interval, next_trigger: Instant::now().checked_add(interval) }; - let key = store.insert(entry); + let key = store.insert(Timer::new(interval)); TimerHandle(key.data().as_ffi()) } @@ -177,3 +182,77 @@ impl StandaloneTimerStore { self.0.borrow_mut().clear(); } } + +pub(crate) enum TimerSlot { + Hosted { id: Cell> }, + Standalone { timer: Cell> }, +} + +impl TimerSlot { + pub fn new(host: &HostHandle) -> Self { + if host.timer_support().is_some() { + Self::Hosted { id: None.into() } + } else { + Self::Standalone { timer: None.into() } + } + } + + pub fn set_timer(&self, interval: Duration, host: &HostHandle) -> Result<(), Box> { + match self { + Self::Standalone { timer } => timer.set(Some(Timer::new(interval))), + Self::Hosted { id: stored_id } => { + if let Some(previous_timer) = stored_id.take() { + let _ = host.unregister_timer(previous_timer); + } + + let Some(timer_support) = host.timer_support() else { unreachable!() }; + let id = timer_support.register_timer(interval)?; + + stored_id.set(Some(id)); + } + } + + Ok(()) + } + + pub fn tick_next_timer(&self) -> (Option, bool) { + match self { + Self::Hosted { .. } => (None, false), // Timers are not manually ticked in hosted mode + Self::Standalone { timer: stored_timer } => { + let Some(mut timer) = stored_timer.get() else { return (None, false) }; + + // Interval was so long it overflowed. This will essentially never trigger, so we just never trigger it. + let Some(next_trigger) = timer.next_trigger else { return (None, false) }; + + let now = Instant::now(); + + if next_trigger <= now { + // Timer triggered! + timer.next_trigger = now.checked_add(timer.interval); + stored_timer.set(Some(timer)); + + (timer.next_trigger, true) + } else { + (timer.next_trigger, false) + } + } + } + } + + pub fn matches_hosted(&self, id: TimerId) -> bool { + match self { + Self::Standalone { .. } => false, + Self::Hosted { id: stored_id } => stored_id.get() == Some(id), + } + } + + pub fn destroy(&self, host: &HostHandle) { + match self { + Self::Standalone { timer } => timer.set(None), + Self::Hosted { id } => { + let Some(id) = id.take() else { return }; + let _ = host.unregister_timer(id); + } + } + } +} From 8a74da03bef835474150883d8f357521578a6efe Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:06:41 +0200 Subject: [PATCH 24/26] Implement hosted waker & destroyed callback --- src/platform/x11/event_loop.rs | 6 +- src/platform/x11/host_handle.rs | 7 ++- src/platform/x11/waker.rs | 95 ++++++++++++++++++++++++------- src/platform/x11/window_handle.rs | 13 ++++- src/platform/x11/window_shared.rs | 5 +- 5 files changed, 101 insertions(+), 25 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index 967c12c0..ecffbb50 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -69,7 +69,7 @@ impl EventLoop { self.show()?; let connection = Rc::clone(&self.shared.connection); - let waker_fd = self.shared.waker_source.inner_fd(); + let waker_fd = self.shared.waker_source.inner_shared(); let waker_fd = waker_fd.as_ref().and_then(|s| s.fd()); let mut poller = ConnectionPoller::new(&connection.conn, waker_fd)?; @@ -345,5 +345,9 @@ impl Drop for EventLoop { if self.fd_registered.get() { let _ = self.host.unregister_fd(self.shared.connection.conn.as_raw_fd()); } + + if !self.shared.close_requested_from_host.get() { + self.host.notify_destroyed(); + } } } diff --git a/src/platform/x11/host_handle.rs b/src/platform/x11/host_handle.rs index d4c577b1..d842dc48 100644 --- a/src/platform/x11/host_handle.rs +++ b/src/platform/x11/host_handle.rs @@ -1,9 +1,7 @@ use baseview_host::dpi::WindowSize; use baseview_host::host::*; -use std::error::Error; use std::os::fd::RawFd; use std::rc::Rc; -use std::time::Duration; pub struct HostHandle { callbacks: Option>, @@ -20,7 +18,10 @@ impl HostHandle { }) } - // TODO: call this + pub fn is_standalone(&self) -> bool { + self.callbacks.is_none() && self.fd.is_none() && self.timer.is_none() + } + pub fn notify_destroyed(&self) { if let Some(callbacks) = &self.callbacks { callbacks.destroyed() diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index b97e4028..756f49ba 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -1,7 +1,9 @@ use crate::platform::x11::host_handle::HostHandle; -use std::cell::RefCell; +use baseview_host::host::TimerId; +use std::cell::{Cell, RefCell}; +use std::error::Error; use std::mem; -use std::os::fd::{AsFd, BorrowedFd}; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd}; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; @@ -12,35 +14,70 @@ use eventfd::*; pub(crate) struct WakerSource { shared: RefCell>>, + host_timer_id: Cell>, } impl WakerSource { pub fn new() -> Self { - Self { shared: None.into() } + Self { shared: None.into(), host_timer_id: None.into() } } - fn get_shared_or_create(&self) -> Arc { + fn get_shared_or_create(&self, host: &HostHandle) -> Arc { let mut shared = self.shared.borrow_mut(); if let Some(shared) = &*shared { return Arc::clone(shared); } - let new_shared = Arc::new(Shared::new()); + let should_create_eventfd = host.fd_support().is_some() || host.is_standalone(); + + let new_shared = Arc::new(Shared::new(should_create_eventfd)); + + if !host.is_standalone() { + self.register_to_host(host, &new_shared); + } + *shared = Some(Arc::clone(&new_shared)); new_shared } - fn delete_shared_if_unused(&self) { + fn register_to_host(&self, host: &HostHandle, shared: &Shared) { + if let Some(fd_support) = host.fd_support() { + if let Some(eventfd) = &shared.event_fd { + match fd_support.register_fd(eventfd.as_fd()) { + Ok(()) => return, + Err(e) => { + crate::warn!("Host failed to register Waker's eventfd: {}.", e); + } + } + } + } + + let Some(timer_support) = host.timer_support() else { return }; + + const WAKE_INTERVAL: Duration = Duration::from_millis(100); + let timer_id = match timer_support.register_timer(WAKE_INTERVAL) { + Ok(id) => id, + Err(e) => { + crate::warn!("Host failed to register Waker's timer: {}.", e); + return; + } + }; + + self.host_timer_id.set(Some(timer_id)); + } + + fn delete_shared_if_unused(&self, host: &HostHandle) { let mut shared = self.shared.borrow_mut(); let Some(shared_inner) = &mut *shared else { return }; - if Arc::get_mut(shared_inner).is_some() { + if let Some(inner) = Arc::get_mut(shared_inner) { + self.deinit_host_inner(host, Some(inner)); *shared = None; } } - pub fn create_waker(&self) -> WindowWaker { - WindowWaker { shared: self.get_shared_or_create() } + pub fn create_waker(&self, host: &HostHandle) -> WindowWaker { + WindowWaker { shared: self.get_shared_or_create(host) } } fn fetch_inner(&self) -> Option<(FlagsValues, Vec)> { @@ -69,14 +106,32 @@ impl WakerSource { } if flags.dropped() { - self.delete_shared_if_unused(); + self.delete_shared_if_unused(host); } } - pub fn inner_fd(&self) -> Option> { + pub fn inner_shared(&self) -> Option> { let Some(shared) = &*self.shared.borrow() else { return None }; Some(Arc::clone(shared)) } + + pub fn deinit_host(&self, host: &HostHandle) { + self.deinit_host_inner(host, self.inner_shared().as_deref()) + } + + pub fn deinit_host_inner(&self, host: &HostHandle, shared: Option<&Shared>) { + if let Some(timer_id) = self.host_timer_id.take() { + let _ = host.unregister_timer(timer_id); + } + + let Some(shared) = shared else { return }; + + if let Some(fd) = &shared.event_fd { + if host.fd_support().is_some() { + let _ = host.unregister_fd(fd.as_fd().as_raw_fd()); + } + } + } } #[derive(Clone)] @@ -129,14 +184,16 @@ pub(crate) struct Shared { } impl Shared { - pub fn new() -> Self { - let event_fd = match EventFd::new() { - Ok(fd) => Some(fd), - Err(e) => { - crate::warn!("Could not create WindowWaker's eventfd: {}", e); - None - } - }; + pub fn new(create_eventfd: bool) -> Self { + let event_fd = create_eventfd + .then(|| match EventFd::new() { + Ok(fd) => Some(fd), + Err(e) => { + crate::warn!("Could not create WindowWaker's eventfd: {}", e); + None + } + }) + .flatten(); Self { flags: AtomicU8::new(0), requests: Mutex::new(vec![]), event_fd } } diff --git a/src/platform/x11/window_handle.rs b/src/platform/x11/window_handle.rs index c2918d03..8cabda34 100644 --- a/src/platform/x11/window_handle.rs +++ b/src/platform/x11/window_handle.rs @@ -48,7 +48,12 @@ impl WindowHandle { pub fn host_fd_callback(&self, fd: RawFd) { let Some(event_loop) = self.ev_loop.get() else { return }; - event_loop.host_fd_callback(fd); + + if let Err(e) = event_loop.host_fd_callback(fd) { + error!("Fatal error: {}", e); + self.ev_loop.delete(); + return; + } if self.shared.close_requested.get() { self.ev_loop.delete(); @@ -96,6 +101,12 @@ impl WindowHandle { } } +impl Drop for WindowHandle { + fn drop(&mut self) { + self.shared.close_requested_from_host.set(true); + } +} + struct OptionRcCell(RefCell>>); impl OptionRcCell { diff --git a/src/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index 554e1d19..246e6fff 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -16,6 +16,7 @@ pub struct WindowShared { pub is_focused: Cell, pub close_requested: Cell, + pub close_requested_from_host: Cell, pub cursor_state: CursorStateShared, pub present_state: PresentStateShared, @@ -63,6 +64,7 @@ impl WindowShared { is_focused: false.into(), close_requested: false.into(), + close_requested_from_host: false.into(), cursor_state: CursorStateShared::new(), present_state: PresentStateShared::new(&host), @@ -94,7 +96,7 @@ impl WindowShared { #[inline] pub fn waker(&self) -> WindowWaker { - self.waker_source.create_waker() + self.waker_source.create_waker(&self.host) } pub fn has_focus(&self) -> bool { @@ -153,5 +155,6 @@ impl WindowShared { pub fn deinit_all_host(&self) { self.timer_manager.destroy_all(&self.host); self.present_state.destroy_redraw_timers(&self.host); + self.waker_source.deinit_host(&self.host); } } From c27abad8aae0320ed29b73eea35103f5db2c53b4 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 17 Oct 2026 04:28:07 +0200 Subject: [PATCH 25/26] cleanup --- src/platform/x11/event_loop.rs | 1 - src/platform/x11/mod.rs | 2 +- src/platform/x11/sizing.rs | 2 -- src/platform/x11/timer.rs | 1 - src/platform/x11/waker.rs | 5 ++++- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/platform/x11/event_loop.rs b/src/platform/x11/event_loop.rs index ecffbb50..81e849c5 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -7,7 +7,6 @@ use crate::platform::x11::host_handle::HostHandle; use crate::platform::x11::present::PresentState; use crate::platform::x11::sizing::SizingState; use crate::platform::ParentWindowHandle; -use crate::utils::SizingStrategy; use crate::window::WindowInitializer; use crate::wrappers::poller::{ConnectionPoller, PollStatus}; use crate::wrappers::xkbcommon::XkbcommonState; diff --git a/src/platform/x11/mod.rs b/src/platform/x11/mod.rs index 9b1b24ff..f7c40ce5 100644 --- a/src/platform/x11/mod.rs +++ b/src/platform/x11/mod.rs @@ -21,7 +21,7 @@ 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(crate) use super::event_loop::*; pub use super::handler::Handler; pub use super::mouse::*; pub use super::present::*; diff --git a/src/platform/x11/sizing.rs b/src/platform/x11/sizing.rs index 6b3d995e..926edab9 100644 --- a/src/platform/x11/sizing.rs +++ b/src/platform/x11/sizing.rs @@ -4,8 +4,6 @@ use crate::utils::SizingStrategy; use crate::{WindowSettings, WindowSize}; 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}; diff --git a/src/platform/x11/timer.rs b/src/platform/x11/timer.rs index 1635075a..e3eb1863 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -1,4 +1,3 @@ -use crate::platform::prelude::PlatformResult; use crate::platform::x11::host_handle::HostHandle; use baseview_host::host::TimerId; use slotmap::{DefaultKey, DenseSlotMap, Key, KeyData}; diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index 756f49ba..c6f3373f 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -1,7 +1,6 @@ use crate::platform::x11::host_handle::HostHandle; use baseview_host::host::TimerId; use std::cell::{Cell, RefCell}; -use std::error::Error; use std::mem; use std::os::fd::{AsFd, AsRawFd, BorrowedFd}; use std::sync::atomic::{AtomicU8, Ordering}; @@ -83,6 +82,10 @@ impl WakerSource { fn fetch_inner(&self) -> Option<(FlagsValues, Vec)> { let Some(shared) = &*self.shared.borrow() else { return None }; + if let Some(eventfd) = &shared.event_fd { + eventfd.poll(); + } + Some((shared.take_flags(), shared.take_requests())) } From 0aa0e5b1ceefeade75ab9e1344ee963a1fddb1f8 Mon Sep 17 00:00:00 2001 From: Adrien Prokopowicz <6529475+prokopyl@users.noreply.github.com> Date: Sat, 17 Oct 2026 11:22:38 +0200 Subject: [PATCH 26/26] fix API changes --- examples/plugin_clack/src/gui.rs | 29 +++++++++++++++++------------ examples/plugin_clack/src/lib.rs | 15 ++++++++------- host/src/window.rs | 3 +-- src/platform/macos/view.rs | 6 ++---- src/platform/macos/window.rs | 23 ++++++----------------- src/platform/win/window.rs | 28 ++++++---------------------- src/window.rs | 7 ++++--- 7 files changed, 44 insertions(+), 67 deletions(-) diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 5085bcd6..501ebc3f 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,18 +1,16 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::*; -use baseview::host::{Host, HostCallbacks, HostFdSupport, HostTimerSupport, TimerId as BvTimerId}; +use baseview::host::{Host, HostCallbacks, HostTimerSupport, TimerId as BvTimerId}; use baseview::{Window, WindowSettings, WindowSize}; use clack_extensions::gui::{ AspectRatioStrategy, GuiApiType, GuiConfiguration, GuiResizeHints, GuiSize, HostGui, PluginGuiImpl, Window as ClapWindow, }; -use clack_extensions::posix_fd::{FdFlags, HostPosixFd, PluginPosixFdImpl}; use clack_extensions::timer::{HostTimer, PluginTimerImpl, TimerId}; use clack_plugin::plugin::PluginError; use clack_plugin::prelude::HostMainThreadHandle; use std::error::Error; -use std::os::fd::{AsRawFd, BorrowedFd, RawFd}; use std::time::Duration; pub struct ExamplePluginGui { @@ -49,6 +47,7 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { host = host.with_callbacks(HostGuiCallbacks { ext: gui, host: host_handle }); } + #[cfg(unix)] if let Some(fd) = self.host_posix_fd { host = host.with_fd(HostFdCallbacks { ext: fd, host: host_handle }); } @@ -200,19 +199,25 @@ impl HostTimerSupport for HostTimerCallbacks { } } +#[cfg(unix)] struct HostFdCallbacks { - ext: HostPosixFd, + ext: clack_extensions::posix_fd::HostPosixFd, host: HostMainThreadHandle<'static>, } -impl HostFdSupport for HostFdCallbacks { - fn register_fd(&self, fd: BorrowedFd) -> Result<(), Box> { - dbg!(fd); - self.ext.register_fd(&self.host, fd.as_raw_fd(), FdFlags::READ)?; +#[cfg(unix)] +impl baseview::host::HostFdSupport for HostFdCallbacks { + fn register_fd(&self, fd: std::os::fd::BorrowedFd) -> Result<(), Box> { + use std::os::fd::AsRawFd; + self.ext.register_fd( + &self.host, + fd.as_raw_fd(), + clack_extensions::posix_fd::FdFlags::READ, + )?; Ok(()) } - fn unregister_fd(&self, fd: RawFd) -> Result<(), Box> { + fn unregister_fd(&self, fd: std::os::fd::RawFd) -> Result<(), Box> { self.ext.unregister_fd(&self.host, fd)?; Ok(()) } @@ -226,9 +231,9 @@ impl PluginTimerImpl for ExamplePluginMainThread<'_> { } } -impl PluginPosixFdImpl for ExamplePluginMainThread<'_> { - fn on_fd(&self, fd: RawFd, _: FdFlags) { - dbg!(fd); +#[cfg(unix)] +impl clack_extensions::posix_fd::PluginPosixFdImpl for ExamplePluginMainThread<'_> { + fn on_fd(&self, fd: std::os::fd::RawFd, _: clack_extensions::posix_fd::FdFlags) { if let Some(gui) = self.gui.borrow().as_ref() { gui.handle.host_fd_callback(fd) } diff --git a/examples/plugin_clack/src/lib.rs b/examples/plugin_clack/src/lib.rs index a63eeacd..1017a009 100644 --- a/examples/plugin_clack/src/lib.rs +++ b/examples/plugin_clack/src/lib.rs @@ -1,7 +1,6 @@ use crate::audio::ExamplePluginAudioProcessor; use crate::gui::ExamplePluginGui; use clack_extensions::gui::{HostGui, PluginGui}; -use clack_extensions::posix_fd::{HostPosixFd, PluginPosixFd}; use clack_extensions::state::{PluginState, PluginStateImpl}; use clack_extensions::timer::{HostTimer, PluginTimer}; use clack_plugin::prelude::*; @@ -23,11 +22,12 @@ impl Plugin for ExamplePlugin { type MainThread<'a> = ExamplePluginMainThread<'a>; fn declare_extensions(builder: &mut PluginExtensions, _shared: Option<&()>) { - builder - .register::() - .register::() - .register::() - .register::(); + builder.register::().register::().register::(); + + #[cfg(unix)] + { + builder.register::(); + } } } @@ -49,6 +49,7 @@ impl DefaultPluginFactory for ExamplePlugin { Ok(Self::MainThread { gui: None.into(), host_gui: host.get_extension(), + #[cfg(unix)] host_posix_fd: host.get_extension(), host_timer: host.get_extension(), host, @@ -64,7 +65,7 @@ pub struct ExamplePluginMainThread<'a> { host_gui: Option, /// The host FD extension handle #[cfg(unix)] - host_posix_fd: Option, + host_posix_fd: Option, /// The host Timer extension handle host_timer: Option, /// The plugin's GUI state and context diff --git a/host/src/window.rs b/host/src/window.rs index 67db0bda..94d665ab 100644 --- a/host/src/window.rs +++ b/host/src/window.rs @@ -3,7 +3,6 @@ use crate::host::TimerId; use dpi::Size; use raw_window_handle::HasWindowHandle; use std::error::Error; -use std::os::fd::RawFd; /// A handle to a hostable Window. /// @@ -108,7 +107,7 @@ pub trait HostedWindow { fn host_timer_callback(&self, timer_id: TimerId); #[cfg(unix)] - fn host_fd_callback(&self, fd: RawFd); + fn host_fd_callback(&self, fd: std::os::fd::RawFd); /// Reparents this window using the given `parent`. /// diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 1efa142b..37d20008 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -78,7 +78,7 @@ pub(crate) struct BaseviewView { display_link: OnceCell>, display_link_started: Cell, - host: Option>>, + host: Option>, pub(crate) cursor_manager: CursorManager, timers: TimerManager, @@ -111,7 +111,7 @@ impl BaseviewView { window_handler: WindowHandlerContainer::new(), notification_center_observer: None.into(), parenting: ViewParentingType::Uninitialized.into(), - host: init.host.take_callbacks().map(|c| c.into()), + host: init.host.take_callbacks(), lifetime_tied_to_app: None.into(), cursor_manager: CursorManager::new(), @@ -329,13 +329,11 @@ impl BaseviewView { fn host_notify_destroyed(&self) { let Some(host) = &self.host else { return }; - let Ok(mut host) = host.try_borrow_mut() else { return }; host.destroyed(); } fn host_request_resize(&self, new_size: WindowSize) -> core::result::Result<(), HandlerError> { let Some(host) = &self.host else { return Ok(()) }; - let Ok(mut host) = host.try_borrow_mut() else { return Ok(()) }; host.request_resize(new_size).map_err(HandlerError::from_boxed)?; Ok(()) } diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index 4a82a6ca..79f447da 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -7,6 +7,7 @@ use objc2_app_kit::{ }; use objc2_foundation::{NSSize, NSString}; use std::cell::Cell; +use std::os::fd::RawFd; use std::rc::Rc; use crate::platform::macos::view::{BaseviewView, ViewParentingType}; @@ -103,23 +104,6 @@ impl WindowHandle { self.state.closed.get() } - pub fn is_resizable(&self) -> bool { - self.state.sizing_strategy.is_resizable() - } - - pub fn min_size(&self) -> Option { - self.state.sizing_strategy.min_size() - } - - pub fn max_size(&self) -> Option { - self.state.sizing_strategy.max_size() - } - - #[inline] - pub fn handle_main_thread_callback(&self) { - // No-op - } - pub fn size(&self) -> WindowSize { WindowSize::from_logical(self.state.size.get(), self.state.scale_factor.get()) } @@ -175,6 +159,11 @@ impl WindowHandle { self.state.sizing_strategy } + #[inline(always)] + pub fn host_timer_callback(&self, _: host::TimerId) {} + #[inline(always)] + pub fn host_fd_callback(&self, _: RawFd) {} + pub fn request_poll(&self) -> Result<()> { let Some(view) = self.view.load() else { return Ok(()) }; let Some(view) = view.inner_ref() else { return Ok(()) }; diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index 3bd90b31..fc9156cc 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -21,7 +21,7 @@ use crate::wrappers::win32::{ use crate::{warn, DamageArea, EventStatus, HandlerError, WindowHandler}; use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSize}; use baseview_host::host::HostCallbacks; -use std::cell::{Cell, OnceCell, RefCell}; +use std::cell::{Cell, OnceCell}; use std::num::NonZeroU32; use windows_sys::Win32::Foundation::POINT; @@ -51,18 +51,6 @@ impl WindowHandle { self.state.is_alive.get() } - pub fn is_resizable(&self) -> bool { - self.state.sizing_strategy.is_resizable() - } - - pub fn min_size(&self) -> Option { - self.state.sizing_strategy.min_size() - } - - pub fn max_size(&self) -> Option { - self.state.sizing_strategy.max_size() - } - pub fn size(&self) -> WindowSize { self.state.size() } @@ -100,6 +88,9 @@ impl WindowHandle { self.state.sizing_strategy } + #[inline(always)] + pub fn host_timer_callback(&self, _: crate::host::TimerId) {} + pub fn suggest_scale_factor(&self, new_host_scale_factor: f64) -> Result<()> { let new_dpi = Dpi::from_scale_factor(new_host_scale_factor) .ok_or(PlatformError::InvalidScaleFactor)?; @@ -167,11 +158,6 @@ impl WindowHandle { Ok(()) } - #[inline] - pub fn handle_main_thread_callback(&self) { - // No-op - } - pub fn show(&self) -> Result<()> { let hwnd = match self.hwnd.get() { Some(hwnd) => hwnd, @@ -232,7 +218,7 @@ pub struct BaseviewWindow { handler_builder: Cell>, handler: OnceCell>, - host: Option>>, + host: Option>, // Workaround some VST3 hosts (e.g. Bitwig) not allowing request_resize before show() occurs pub host_needs_new_size_notified_on_show: Cell>>, @@ -277,7 +263,7 @@ impl BaseviewWindow { handler_builder: Cell::new(Some(init.builder)), handler: OnceCell::new(), shared_state, - host: init.host.take_callbacks().map(|cb| cb.into()), + host: init.host.take_callbacks(), host_needs_new_size_notified_on_show: None.into(), _drop_target: None.into(), @@ -302,7 +288,6 @@ impl BaseviewWindow { }; let Some(host) = &self.host else { return }; - let Ok(mut host) = host.try_borrow_mut() else { return }; host.destroyed(); } @@ -312,7 +297,6 @@ impl BaseviewWindow { }; let Some(host) = &self.host else { return Ok(()) }; - let Ok(mut host) = host.try_borrow_mut() else { return Ok(()) }; host.request_resize(new_size).map_err(HandlerError::from_boxed)?; Ok(()) } diff --git a/src/window.rs b/src/window.rs index 2befe40f..fd733ab0 100644 --- a/src/window.rs +++ b/src/window.rs @@ -6,7 +6,6 @@ use crate::waker::WindowWaker; use crate::*; use raw_window_handle::HasWindowHandle; use std::marker::PhantomData; -use std::os::fd::RawFd; /// A handle to a Window created by baseview. /// @@ -184,7 +183,8 @@ impl Window { } #[inline] - pub fn host_fd_callback(&self, fd: RawFd) { + #[cfg(unix)] + pub fn host_fd_callback(&self, fd: std::os::fd::RawFd) { self.inner.host_fd_callback(fd) } @@ -309,7 +309,8 @@ impl HostedWindow for Window { } #[inline] - fn host_fd_callback(&self, fd: RawFd) { + #[cfg(unix)] + fn host_fd_callback(&self, fd: std::os::fd::RawFd) { self.host_fd_callback(fd) }