diff --git a/Cargo.toml b/Cargo.toml index f3aa6f04..01fb98d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,10 +43,12 @@ 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 +rustix = "1.1.4" [target.'cfg(target_os="windows")'.dependencies] windows = { version = "0.62.2", features = [ 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(); } } 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/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..501ebc3f 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -1,15 +1,17 @@ use crate::window_handler::OpenWindowExample; use crate::ExamplePluginMainThread; use baseview::dpi::*; -use baseview::host::{Host, HostCallbacks, HostMainThreadCaller}; +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::timer::{HostTimer, PluginTimerImpl, TimerId}; use clack_plugin::plugin::PluginError; -use clack_plugin::prelude::{HostMainThreadHandle, HostSharedHandle}; +use clack_plugin::prelude::HostMainThreadHandle; use std::error::Error; +use std::time::Duration; pub struct ExamplePluginGui { pub handle: Window, @@ -35,14 +37,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(); 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 }); + } + + #[cfg(unix)] + 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)?; @@ -153,29 +164,78 @@ 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); } } + +struct HostTimerCallbacks { + ext: HostTimer, + host: HostMainThreadHandle<'static>, +} + +impl HostTimerSupport for HostTimerCallbacks { + 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(BvTimerId(timer.0)) + } + + fn unregister_timer(&self, timer: BvTimerId) -> Result<(), Box> { + self.ext.unregister_timer(&self.host, TimerId(timer.0))?; + Ok(()) + } +} + +#[cfg(unix)] +struct HostFdCallbacks { + ext: clack_extensions::posix_fd::HostPosixFd, + host: HostMainThreadHandle<'static>, +} + +#[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: std::os::fd::RawFd) -> Result<(), Box> { + self.ext.unregister_fd(&self.host, fd)?; + 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)); + } + } +} + +#[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 ba52d508..1017a009 100644 --- a/examples/plugin_clack/src/lib.rs +++ b/examples/plugin_clack/src/lib.rs @@ -2,6 +2,7 @@ use crate::audio::ExamplePluginAudioProcessor; use crate::gui::ExamplePluginGui; use clack_extensions::gui::{HostGui, PluginGui}; use clack_extensions::state::{PluginState, PluginStateImpl}; +use clack_extensions::timer::{HostTimer, PluginTimer}; use clack_plugin::prelude::*; use clack_plugin::stream::{InputStream, OutputStream}; use std::cell::{Ref, RefCell}; @@ -21,7 +22,12 @@ impl Plugin for ExamplePlugin { type MainThread<'a> = ExamplePluginMainThread<'a>; fn declare_extensions(builder: &mut PluginExtensions, _shared: Option<&()>) { - builder.register::().register::(); + builder.register::().register::().register::(); + + #[cfg(unix)] + { + builder.register::(); + } } } @@ -40,7 +46,14 @@ 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(), + #[cfg(unix)] + host_posix_fd: host.get_extension(), + host_timer: host.get_extension(), + host, + }) } } @@ -48,8 +61,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>, } @@ -61,13 +79,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/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/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/host.rs b/host/src/host.rs index db5602b1..9dfdf807 100644 --- a/host/src/host.rs +++ b/host/src/host.rs @@ -1,24 +1,22 @@ 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 TimerId(pub u32); + +pub trait HostTimerSupport: 'static { + fn register_timer(&self, period: Duration) -> Result>; + fn unregister_timer(&self, timer: TimerId) -> Result<(), Box>; +} + +#[cfg(unix)] +use std::os::fd::*; +pub trait HostFdSupport: 'static { + #[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. @@ -31,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. /// @@ -39,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. @@ -56,7 +54,9 @@ pub trait HostCallbacks: 'static { pub struct Host { callbacks: Option>, #[cfg(target_os = "linux")] - main_thread: Option>, + timer: Option>, + #[cfg(target_os = "linux")] + fd: Option>, } impl Default for Host { @@ -70,30 +70,40 @@ impl Host { #[inline] pub fn new() -> Self { Self { - #[cfg(target_os = "linux")] - main_thread: None, callbacks: 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 { + pub fn with_fd(self, fd: impl HostFdSupport) -> Self { #[cfg(target_os = "linux")] { let mut this = self; - this.main_thread = Some(Box::new(main_thread)); + this.fd = Some(Box::new(fd)); this } #[cfg(not(target_os = "linux"))] { - let _ = main_thread; + 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 } } @@ -113,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/host/src/window.rs b/host/src/window.rs index 9fd9a480..94d665ab 100644 --- a/host/src/window.rs +++ b/host/src/window.rs @@ -1,4 +1,5 @@ use crate::dpi::{NativeSize, WindowSize}; +use crate::host::TimerId; use dpi::Size; use raw_window_handle::HasWindowHandle; use std::error::Error; @@ -103,16 +104,10 @@ 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)] + 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/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/error.rs b/src/platform/x11/error.rs index 83bba98e..b39ea93d 100644 --- a/src/platform/x11/error.rs +++ b/src/platform/x11/error.rs @@ -1,9 +1,9 @@ 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; +use std::error::Error; use std::fmt::{Display, Formatter}; use x11_dl::error::OpenError; use x11rb::connection::RequestConnection; @@ -14,36 +14,27 @@ use x11rb::x11_utils::{TryParse, X11Error}; #[derive(Debug)] pub enum FatalError { Connection(ConnectionError), - Calloop(calloop::Error), - SendMainThread, Redraw(String), + Host(Box), } 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::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), + 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 { FatalError::Connection(err) } } -impl From for FatalError { - fn from(value: calloop::Error) -> Self { - Self::Calloop(value) - } -} #[derive(Debug)] pub enum PlatformError { @@ -60,21 +51,18 @@ pub enum PlatformError { Connect(ConnectError), DisplayOpenFailed(DisplayOpenFailedError), Handler(HandlerError), - MainThreadRecvResult, - Calloop(calloop::Error), - RequestFromMainThreadFailed(RequestFailed), - SendMainThread, + Host(Box), Redraw(String), #[cfg(feature = "opengl")] XLib(crate::wrappers::xlib::XLibError), #[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"), @@ -89,13 +77,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::Host(e) => e.fmt(f), 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")] @@ -106,13 +89,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")] @@ -128,6 +112,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) @@ -164,25 +154,12 @@ impl From for PlatformError { } } -impl From for PlatformError { - fn from(value: calloop::Error) -> Self { - Self::Calloop(value) - } -} - -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), + FatalError::Host(e) => Self::Handler(HandlerError::from_boxed(e)), } } } @@ -234,8 +211,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 122dc66f..81e849c5 100644 --- a/src/platform/x11/event_loop.rs +++ b/src/platform/x11/event_loop.rs @@ -1,241 +1,225 @@ 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::window::WindowInitializer; +use crate::wrappers::poller::{ConnectionPoller, PollStatus}; 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 baseview_host::host::TimerId; +use std::os::fd::{AsFd, AsRawFd, RawFd}; use std::result::Result; -use std::sync::mpsc; -use std::sync::mpsc::Receiver; +use std::time::Instant; use x11rb::connection::Connection; use x11rb::protocol::Event as XEvent; -pub struct MainThreadCaller { - sender: mpsc::Sender, - caller: Box, +pub(crate) struct EventLoop { + handler: Handler, + shared: Rc, + + drag_n_drop: DragNDropState, + sizing_state: SizingState, + pub present_state: PresentState, + xkb_state: Option, + + host: Rc, + + fd_registered: Cell, } -impl MainThreadCaller { - pub(crate) fn new( - main_thread: Option>, - ) -> (Option, Option>) { - let Some(main_thread) = main_thread else { - return (None, None); - }; +impl EventLoop { + pub fn request_poll(&self) -> PlatformResult<()> { + self.handler.poll(); + Ok(()) + } - let (sender, receiver) = mpsc::channel(); - (Some(Self { sender, caller: main_thread }), Some(receiver)) + pub fn shared(&self) -> &Rc { + &self.shared } - pub fn send(&mut self, msg: HostCallback) -> Result<(), FatalError> { - self.sender.send(msg).map_err(|_| FatalError::SendMainThread)?; - self.caller.call_main_thread(); + 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()); + return; + } + + if !self.shared.present_state.trigger_if_redraw_timer(timer_id, &self.host) { + self.present_state.trigger_possible_fallback_frame_timer(timer_id); + } + } + + pub fn host_fd_callback(&self, fd: RawFd) -> Result<(), FatalError> { + if fd != self.shared.connection.conn.as_raw_fd() { + return Ok(()); + }; + + self.handle_idle()?; + Ok(()) } -} -pub(crate) struct EventLoop { - response_sender: mpsc::Sender, - main_thread: Option, + pub fn run_until_closed(&self) -> PlatformResult<()> { + self.show()?; - handler: Handler, - shared: Rc, + let connection = Rc::clone(&self.shared.connection); + 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)?; - drag_n_drop: DragNDropState, - sizing_state: SizingState, - pub present_state: PresentState, - xkb_state: Option, + self.shared.close_requested.set(false); + + loop { + let next_deadline = self.tick_all_timers(); + self.handle_idle()?; + + 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); + } + _ => {} + } + + if self.shared.close_requested.get() { + break; + } + } - loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, Self>, + poller.delete()?; + self.hide()?; - run_error: Option, + Ok(()) + } } 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)?; - - Ok(Self { - loop_signal: inner.get_signal(), - loop_handle, + 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); + + let shared = WindowShared::create(init.settings, Rc::clone(&host))?; + 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), - present_state: PresentState::new(), + + present_state: PresentState::new(&host), sizing_state: SizingState::new(parent_id), + drag_n_drop: DragNDropState::new(), + xkb_state: XkbcommonState::new(&shared.connection), + fd_registered: false.into(), - drag_n_drop: DragNDropState::NoCurrentSession, - xkb_state: XkbcommonState::new(&window.connection), - run_error: None, - main_thread, + shared, + host, + }; - shared: window, - response_sender, - }) - } + this.register_fd()?; - pub(crate) fn handle_timer(&mut self, handle: &TimerHandle) { - self.handler.on_timer(handle.into()) + Ok(this) } - #[inline] - fn drain_xcb_events(&mut self) -> Result { - let mut event_received = false; - while let Some(event) = self.shared.connection.conn.poll_for_event()? { - event_received = true; - self.handle_xcb_event(event)?; + 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); } - Ok(event_received) + Ok(()) } - fn handle_main_thread_request(&mut self, event: calloop::channel::Event) { - match event { - calloop::channel::Event::Closed => { - // Closed channel means the sender, i.e. the Window Handle has been dropped. - // It should already stop this event loop on drop, but we'll take the hint. - self.stop_now(); + 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 = 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 { + match (d1, d2) { + (Some(d1), Some(d2)) => Some(d2.min(d1)), + (None, Some(d)) | (Some(d), None) => Some(d), + (None, None) => None, } - calloop::channel::Event::Msg(req) => match self.handle_request(req) { - Ok(()) => self.send_response(Ok(())), - Err(e) => self.send_response(Err(e.to_string())), - }, } + + min_deadline(min_deadline(deadline_1, deadline_2), deadline_3) } - 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) - } + #[inline] + fn flush_drain_xcb_events(&self) -> Result { + self.shared.connection.conn.flush()?; - self.stop_now(); + let mut event_received = false; + while let Some(event) = self.shared.connection.conn.poll_for_event()? { + event_received = true; + self.handle_xcb_event(event)?; } - } - pub fn stop_now(&self) { - self.loop_signal.stop(); - self.loop_signal.wakeup(); + Ok(event_received) } - pub fn trigger_fatal_error(&mut self, error: PlatformError) { - if self.run_error.is_none() { - self.run_error = Some(error); - } - self.stop_now(); + pub fn resize(&self, new_size: Size) -> PlatformResult<()> { + self.sizing_state.handle_host_resize(new_size, &self.handler, &self.shared) } - 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 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(()) } - fn handle_connection_event_ready(&mut self) -> Result { - self.drain_xcb_events()?; - - Ok(PostAction::Continue) + pub fn show(&self) -> PlatformResult<()> { + self.shared.xcb_window.map_window()?.check()?; + Ok(()) } - fn handle_idle(&mut self) { - if let Err(e) = self.try_handle_idle() { - self.trigger_fatal_error(e.into()); - } + pub fn hide(&self) -> PlatformResult<()> { + self.shared.xcb_window.unmap_window()?.check()?; + Ok(()) } - fn try_handle_idle(&mut 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( &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()? { + if !self.flush_drain_xcb_events()? { break; } } - self.shared.connection.conn.flush()?; - - 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 { @@ -299,17 +283,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 +310,7 @@ impl EventLoop { self.present_state.handle_window_mapped( &self.shared.present_state, &self.shared.xcb_window, - &self.loop_handle, + &self.host, )?; } @@ -352,3 +332,21 @@ impl EventLoop { Ok(()) } } + +impl Drop for EventLoop { + fn drop(&mut self) { + self.handler.on_event(Event::Window(WindowEvent::WillClose)); + + // 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()); + } + + 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 new file mode 100644 index 00000000..d842dc48 --- /dev/null +++ b/src/platform/x11/host_handle.rs @@ -0,0 +1,71 @@ +use baseview_host::dpi::WindowSize; +use baseview_host::host::*; +use std::os::fd::RawFd; +use std::rc::Rc; + +pub struct HostHandle { + callbacks: Option>, + fd: Option>, + timer: Option>, +} + +impl HostHandle { + pub(crate) fn new(mut host: Host) -> Rc { + Rc::new(Self { + callbacks: host.take_callbacks(), + fd: host.take_fd(), + timer: host.take_timer(), + }) + } + + 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() + } + } + + pub fn request_resize(&self, new_size: WindowSize) -> Result<(), ()> { + 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); + Err(()) + } else { + Ok(()) + } + } + + pub fn timer_support(&self) -> Option<&dyn HostTimerSupport> { + 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!() }; + + if let Err(e) = callbacks.unregister_timer(id) { + crate::warn!("Host failed to unregister timer: {}", e); + Err(()) + } else { + 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/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..f7c40ce5 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,15 +13,15 @@ mod sizing; mod timer; mod visual_info; mod waker; +mod window_handle; mod window_shared; -mod window_thread; mod x11_connection; mod xcb_window; pub(crate) mod prelude { pub use super::cursor::*; pub use super::error::{CookieExt as _, FatalError, PlatformError, ReplyExt as _}; - pub use super::event_loop::*; + pub(crate) use super::event_loop::*; pub use super::handler::Handler; pub use super::mouse::*; pub use super::present::*; @@ -29,7 +30,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 +61,7 @@ pub use error::PlatformError; pub use present::DamageRect; pub use timer::TimerHandle; pub use waker::WindowWaker; +pub use window_handle::WindowHandle; #[derive(Clone)] pub struct PlatformHandle { @@ -151,8 +152,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..5b8aeab0 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 baseview_host::host::TimerId; 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}; @@ -17,81 +14,107 @@ use x11rb::protocol::xproto::ExposeEvent; pub struct PresentStateShared { present_notify_requested: Cell, poll_requested: Cell, + redraw_delayed_timers: TimerManager, } impl PresentStateShared { - pub(crate) fn request_present_notify_after( - &self, duration: Duration, loop_handle: &LoopHandle, - ) { - if duration.is_zero() || duration.as_millis() < 1 { + 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 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(); - return; + self.redraw_delayed_timers.destroy_timer(redraw_timer, host); + true + } else { + false } + } - let result = loop_handle.insert_source(Timer::from_duration(duration), |_, _, e| { - e.shared().present_state.request_present_notify(); - TimeoutAction::Drop - }); + pub fn tick_timers(&self, host: &HostHandle) -> Option { + let (deadline, triggered_timer) = self.redraw_delayed_timers.tick_next_timer(); - if let Err(e) = result { - warn!("{}", e); + if let Some(h) = triggered_timer { self.request_present_notify(); + self.redraw_delayed_timers.destroy_timer(h, host); } + + deadline } -} -impl PresentStateShared { - pub fn new() -> Self { - Self { present_notify_requested: false.into(), poll_requested: false.into() } + pub fn destroy_redraw_timers(&self, host: &HostHandle) { + self.redraw_delayed_timers.destroy_all(host); } - pub fn request_present_notify(&self) { - self.present_notify_requested.set(true) + 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) = 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 { - draw_now: bool, - last_requested_serial: Option, - last_received_present: Option<(u32, u64)>, + draw_now: Cell, + last_requested_serial: Cell>, + last_received_present: Cell>, + + fallback_frame_timer: TimerSlot, } impl PresentState { - pub(crate) fn handle_requests( - &mut self, shared: &WindowShared, handler: &Handler, loop_handle: &LoopHandle, - ) -> Result<(), FatalError> { - let shared_state = &shared.present_state; - let thread_state = &shared.main_thread_shared.present; - - // Consume all requests from above poll - if let Some(redraw_after) = thread_state.take_redraw_request() { - shared.request_redraw_after(redraw_after) + pub fn new(host: &HostHandle) -> Self { + Self { + draw_now: false.into(), + last_requested_serial: None.into(), + last_received_present: None.into(), + fallback_frame_timer: TimerSlot::new(host), } + } - if self.draw_now { - let _ = thread_state.take_poll_request(); + pub fn handle_requests( + &self, shared: &WindowShared, handler: &Handler, host: &HostHandle, + ) -> Result<(), FatalError> { + let shared_state = &shared.present_state; + 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(); } - 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 { @@ -109,88 +132,73 @@ impl PresentState { handler.damage(area); } -} -impl PresentState { pub fn handle_window_mapped( - &self, shared: &PresentStateShared, window: &XcbWindow, loop_handle: &LoopHandle, + &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, last_requested_serial: None, last_received_present: None } - } - 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> { - const FRAME_INTERVAL: Duration = Duration::from_millis(15); - - fn handle_frame(evloop: &mut EventLoop, previous_deadline: Instant) -> TimeoutAction { - evloop.present_state.draw_now = true; - - // We'll try to keep a consistent frame pace. If the last frame couldn't be processed in - // the expected frame time, this will throttle down to prevent multiple frames from - // being queued up. + pub fn tick_fallback_frame_timer(&self) -> Option { + let (deadline, triggered) = self.fallback_frame_timer.tick_next_timer(); - let now = Instant::now(); - - let Some(next_deadline) = previous_deadline.checked_add(FRAME_INTERVAL) else { - return TimeoutAction::ToDuration(FRAME_INTERVAL); - }; + if triggered { + self.draw_now.set(true); + } - if next_deadline >= now { - return TimeoutAction::ToDuration(FRAME_INTERVAL); - } + deadline + } - TimeoutAction::ToInstant(next_deadline) + pub fn trigger_possible_fallback_frame_timer(&self, id: TimerId) { + if self.fallback_frame_timer.matches_hosted(id) { + self.draw_now.set(true); } + } - loop_handle - .insert_source(Timer::from_duration(FRAME_INTERVAL), |i, _, e| handle_frame(e, i)) - .map_err(|e| e.error)?; + fn setup_fallback_frame_timer(&self, host: &HostHandle) -> Result<(), FatalError> { + const FRAME_INTERVAL: Duration = Duration::from_millis(15); - Ok(()) + self.fallback_frame_timer.set_timer(FRAME_INTERVAL, host).map_err(FatalError::Host) + } + + pub fn destroy_timers(&self, host: &HostHandle) { + self.fallback_frame_timer.destroy(host) } pub fn handle_present_notify( - &mut self, shared: &PresentStateShared, window: &XcbWindow, - loop_handle: &LoopHandle, + &self, shared: &PresentStateShared, window: &XcbWindow, host: &HostHandle, ) -> Result<(), FatalError> { if !shared.present_notify_requested.get() { return Ok(()); @@ -202,7 +210,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,10 +228,10 @@ 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::setup_fallback_frame_timer(loop_handle)?; + self.last_requested_serial.set(None); + self.setup_fallback_frame_timer(host)?; } shared.present_notify_requested.set(false); @@ -231,36 +239,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..926edab9 100644 --- a/src/platform/x11/sizing.rs +++ b/src/platform/x11/sizing.rs @@ -1,54 +1,52 @@ use super::prelude::*; +use crate::platform::x11::host_handle::HostHandle; 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}; 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; 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) // 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 +56,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 +65,14 @@ 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_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(); } } @@ -81,25 +82,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 +107,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 +132,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 +171,12 @@ 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); - } + pub fn strategy(&self) -> SizingStrategy { + self.sizing_strategy + } - previous + 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 +199,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 +211,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 +227,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..e3eb1863 100644 --- a/src/platform/x11/timer.rs +++ b/src/platform/x11/timer.rs @@ -1,49 +1,257 @@ -use crate::platform::x11::event_loop::EventLoop; -use crate::platform::PlatformError; -use calloop::timer::{TimeoutAction, Timer}; -use calloop::{LoopHandle, RegistrationToken}; -use std::rc::{Rc, Weak}; -use std::time::Duration; +use crate::platform::x11::host_handle::HostHandle; +use baseview_host::host::TimerId; +use slotmap::{DefaultKey, DenseSlotMap, Key, KeyData}; +use std::cell::{Cell, RefCell}; +use std::error::Error; +use std::time::{Duration, Instant}; -#[derive(Clone, Eq)] -pub struct TimerHandle(Rc); +// Compatible with both TimerHandles from host (u32) and slotmap keys (u64) +#[derive(PartialEq, Eq, Copy, Clone)] +pub struct TimerHandle(u64); -impl PartialEq for TimerHandle { - #[inline] - fn eq(&self, other: &Self) -> bool { - Rc::ptr_eq(&self.0, &other.0) +impl TimerHandle { + pub fn to_id(self) -> Option { + Some(TimerId(self.0.try_into().ok()?)) } } -#[derive(PartialEq, Eq)] -pub struct TimerHandleInner { - token: RegistrationToken, +pub enum TimerManager { + Hosted(HostedTimerStore), + Standalone(StandaloneTimerStore), } -impl TimerHandleInner {} +impl TimerManager { + pub fn new(host: &HostHandle) -> Self { + if host.timer_support().is_some() { + Self::Hosted(HostedTimerStore::new()) + } else { + Self::Standalone(StandaloneTimerStore::new()) + } + } + + pub fn create_timer( + &self, duration: Duration, host: &HostHandle, + ) -> Result> { + match self { + 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(_) => (None, None), // Timers are not manually ticked in hosted mode + TimerManager::Standalone(store) => store.tick_next_timer(), + } + } + + pub fn destroy_timer(&self, handle: TimerHandle, host: &HostHandle) { + match self { + TimerManager::Standalone(s) => s.remove(handle), + TimerManager::Hosted(s) => { + let Some(id) = handle.to_id() else { return }; + let _ = host.unregister_timer(id); + s.remove(id) + } + } + } + + pub fn get_handle_if_exists(&self, id: TimerId) -> Option { + let TimerManager::Hosted(store) = self else { return None }; // Only hosted mode needs this + + if store.exists(id) { + Some(TimerHandle(id.0.into())) + } else { + None + } + } + + pub fn destroy_all(&self, host: &HostHandle) { + match self { + 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); + } + } + } + } + } +} + +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()) + } + + 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); + } +} + +#[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) fn insert_timer( - loop_handle: &LoopHandle, duration: Duration, -) -> Result { - let timer = Timer::from_duration(duration); +pub(crate) struct StandaloneTimerStore(RefCell>); - let handle = Rc::::new_cyclic(move |this| { - let this = Weak::clone(this); +impl StandaloneTimerStore { + fn new() -> Self { + Self(DenseSlotMap::new().into()) + } + + fn insert_new_timer(&self, interval: Duration) -> TimerHandle { + let mut store = self.0.borrow_mut(); + + let key = store.insert(Timer::new(interval)); + TimerHandle(key.data().as_ffi()) + } + + 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; - let result = loop_handle.insert_source(timer, move |_, _, e| { - if let Some(this) = this.upgrade() { - e.handle_timer(&TimerHandle(this)); + 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 }; + + if triggered_key.is_none() && next_trigger <= now { + // Timer triggered! + triggered_key = Some(TimerHandle(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 }, } - }); - Ok(TimerHandle(handle)) + (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(); + } +} + +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); + } + } + } } diff --git a/src/platform/x11/waker.rs b/src/platform/x11/waker.rs index 109f0a4c..c6f3373f 100644 --- a/src/platform/x11/waker.rs +++ b/src/platform/x11/waker.rs @@ -1,26 +1,258 @@ -use crate::platform::x11::window_thread::WindowThreadShared; -use calloop::LoopSignal; -use std::sync::Arc; -use std::time::Duration; +use crate::platform::x11::host_handle::HostHandle; +use baseview_host::host::TimerId; +use std::cell::{Cell, RefCell}; +use std::mem; +use std::os::fd::{AsFd, AsRawFd, BorrowedFd}; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +mod eventfd; +use crate::platform::prelude::PresentStateShared; +use eventfd::*; + +pub(crate) struct WakerSource { + shared: RefCell>>, + host_timer_id: Cell>, +} + +impl WakerSource { + pub fn new() -> Self { + Self { shared: None.into(), host_timer_id: None.into() } + } + + 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 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 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 let Some(inner) = Arc::get_mut(shared_inner) { + self.deinit_host_inner(host, Some(inner)); + *shared = None; + } + } + + pub fn create_waker(&self, host: &HostHandle) -> WindowWaker { + WindowWaker { shared: self.get_shared_or_create(host) } + } + + 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())) + } + + 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()) { + 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() { + self.delete_shared_if_unused(host); + } + } + + 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)] pub struct WindowWaker { - pub(crate) loop_signal: LoopSignal, - pub(crate) shared: Arc, + 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.request_redraw_after(Duration::ZERO) + self.shared.set_redraw(); + self.wake(); } pub fn request_redraw_after(&self, duration: Duration) { - self.shared.present.request_redraw_after(duration); - self.loop_signal.wakeup(); + if duration.is_zero() { + return self.request_redraw(); + } + + // 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.shared.present.request_poll(); - self.loop_signal.wakeup(); + self.shared.set_poll(); + self.wake(); + } +} + +impl Drop for WindowWaker { + fn drop(&mut self) { + self.shared.set_dropped(); + } +} + +pub(crate) struct Shared { + flags: AtomicU8, + requests: Mutex>, + event_fd: Option, +} + +impl Shared { + 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 } + } + + pub(crate) fn fd(&self) -> Option> { + Some(self.event_fd.as_ref()?.as_fd()) + } + + const POLL: u8 = 1 << 0; + const REDRAW: u8 = 1 << 1; + const DROPPED: u8 = 1 << 2; + + fn set_poll(&self) { + self.flags.fetch_or(Self::POLL, Ordering::Release); + } + + fn set_redraw(&self) { + self.flags.fetch_or(Self::REDRAW, Ordering::Release); + } + + fn set_dropped(&self) { + 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_requests(&self) -> Vec { + let mut requests = self.requests.lock().unwrap_or_else(|e| e.into_inner()); + mem::take(&mut requests) + } +} + +pub struct FlagsValues(u8); + +impl FlagsValues { + fn poll(&self) -> bool { + self.0 & Shared::POLL != 0 + } + + fn redraw(&self) -> bool { + self.0 & Shared::REDRAW != 0 + } + + fn dropped(&self) -> bool { + self.0 & Shared::DROPPED != 0 } } diff --git a/src/platform/x11/waker/eventfd.rs b/src/platform/x11/waker/eventfd.rs new file mode 100644 index 00000000..96740241 --- /dev/null +++ b/src/platform/x11/waker/eventfd.rs @@ -0,0 +1,52 @@ +use rustix::event::{eventfd, EventfdFlags}; +use rustix::io::{read, write, Errno}; +use std::io; +use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; + +pub struct EventFd(OwnedFd); + +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 + pub fn poll(&self) -> bool { + // The eventfd counter is effectively a u64. + const BYTES: usize = 8; + let mut buf = [0u8; BYTES]; + + 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, + + Ok(_) => unreachable!(), + + // Any other error can be propagated. + Err(e) => { + crate::warn!("Failed to read from waker's eventfd counter: {}", e); + false + } + } + } + + 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) + } + } + } +} + +impl AsFd for EventFd { + #[inline] + fn as_fd(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } +} diff --git a/src/platform/x11/window_handle.rs b/src/platform/x11/window_handle.rs new file mode 100644 index 00000000..8cabda34 --- /dev/null +++ b/src/platform/x11/window_handle.rs @@ -0,0 +1,128 @@ +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 }; + + 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(); + } + } + + 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 + } +} + +impl Drop for WindowHandle { + fn drop(&mut self) { + self.shared.close_requested_from_host.set(true); + } +} + +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/platform/x11/window_shared.rs b/src/platform/x11/window_shared.rs index b2273f06..246e6fff 100644 --- a/src/platform/x11/window_shared.rs +++ b/src/platform/x11/window_shared.rs @@ -1,5 +1,6 @@ use super::prelude::*; -use calloop::{LoopHandle, LoopSignal}; +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,18 +10,18 @@ use x11rb::protocol::xproto::Visualid; pub struct WindowShared { pub xcb_window: XcbWindow, pub connection: Rc, + pub waker_source: WakerSource, + host: Rc, visual_id: Visualid, pub is_focused: Cell, + pub close_requested: Cell, + pub close_requested_from_host: Cell, pub cursor_state: CursorStateShared, pub present_state: PresentStateShared, pub sizing_state: SizingStateShared, - - loop_signal: LoopSignal, - loop_handle: LoopHandle<'static, EventLoop>, - - pub main_thread_shared: Arc, + pub timer_manager: TimerManager, #[cfg(feature = "opengl")] gl_context: Option, @@ -28,12 +29,11 @@ pub struct WindowShared { 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 +62,16 @@ 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(), + close_requested: false.into(), + close_requested_from_host: false.into(), cursor_state: CursorStateShared::new(), - present_state: PresentStateShared::new(), + present_state: PresentStateShared::new(&host), sizing_state, - main_thread_shared: thread_shared, + timer_manager: TimerManager::new(&host), + waker_source: WakerSource::new(), + host, xcb_window, connection, @@ -82,8 +83,7 @@ impl WindowShared { } pub fn request_close(&self) { - self.loop_signal.stop(); - self.loop_signal.wakeup(); + self.close_requested.set(true); } pub fn request_redraw(&self) { @@ -91,14 +91,12 @@ 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); } + #[inline] pub fn waker(&self) -> WindowWaker { - WindowWaker { - loop_signal: self.loop_signal.clone(), - shared: Arc::clone(&self.main_thread_shared), - } + self.waker_source.create_waker(&self.host) } pub fn has_focus(&self) -> bool { @@ -138,7 +136,7 @@ impl WindowShared { #[inline] pub fn create_timer(&self, duration: Duration) -> PlatformResult { - insert_timer(&self.loop_handle, duration) + self.timer_manager.create_timer(duration, &self.host).map_err(PlatformError::Host) } pub fn scale_factor(&self) -> f64 { @@ -152,4 +150,11 @@ 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); + self.waker_source.deinit_host(&self.host); + } } diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs deleted file mode 100644 index 48f7b823..00000000 --- a/src/platform/x11/window_thread.rs +++ /dev/null @@ -1,388 +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, - receiver, - sender, - main_thread_caller, - &mut ev_loop, - )?; - - 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), - } - } -} 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/window.rs b/src/window.rs index 0b62b5a8..fd733ab0 100644 --- a/src/window.rs +++ b/src/window.rs @@ -158,7 +158,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. @@ -166,7 +166,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. @@ -174,21 +174,18 @@ 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] + pub fn host_timer_callback(&self, timer_id: TimerId) { + self.inner.host_timer_callback(timer_id) } - /// 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() + #[cfg(unix)] + pub fn host_fd_callback(&self, fd: std::os::fd::RawFd) { + self.inner.host_fd_callback(fd) } /// Reparents this window using the given `parent`. @@ -248,6 +245,7 @@ pub(crate) struct WindowInitializer { } pub use baseview_host::dpi::WindowSize; +use baseview_host::host::TimerId; use baseview_host::HostedWindow; impl HostedWindow for Window { @@ -306,8 +304,14 @@ impl HostedWindow for Window { } #[inline] - fn host_main_thread_callback(&self) { - self.host_main_thread_callback() + fn host_timer_callback(&self, timer_id: TimerId) { + self.host_timer_callback(timer_id) + } + + #[inline] + #[cfg(unix)] + fn host_fd_callback(&self, fd: std::os::fd::RawFd) { + self.host_fd_callback(fd) } #[inline] 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..b470e84a --- /dev/null +++ b/src/wrappers/poller.rs @@ -0,0 +1,98 @@ +use polling::{Event, Events, PollMode, Poller}; +use std::io; +use std::io::ErrorKind; +use std::os::fd::{AsFd, BorrowedFd}; +use std::time::Instant; + +pub struct ConnectionPoller<'a> { + poller: Poller, + events: Events, + 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_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)? }; + + 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 }) + } + + pub fn wait(&mut self, deadline: Option) -> io::Result { + self.events.clear(); + + // NOTE: polling crate already handles retrying on EINTR + 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); + } + + for event in self.events.iter() { + match event.key { + CONNECTION_KEY => { + 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); + } + + 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::WakerTriggered); + } + _ => continue, + } + } + + Ok(PollStatus::Nothing) + } + + pub fn delete(self) -> io::Result<()> { + 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) { + if let Some(waker_fd) = self.waker_fd { + let _ = self.poller.delete(waker_fd); + } + + let _ = self.poller.delete(self.fd); + } +} + +#[derive(Debug)] +pub enum PollStatus { + Nothing, + ReadAvailable, + WakerTriggered, + ConnectionClosed, +} 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) } }