From dab17811ef518b67db6c2ecd69f6427c7aeab7a9 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Thu, 7 Sep 2023 19:57:33 -0700 Subject: [PATCH] WIP Update wayland-rs and calloop `simple_Window` example here workss. Incorporates the calloop source that was part of wayland-client, updated for current wayland-rs and wayland-client. Unless we want that somewhere else? Updating these crates is a breaking change, so we might as well update APIs to use io safe types. Perhaps rustix could be used in some places. --- Cargo.toml | 23 +-- examples/simple_window.rs | 3 +- src/calloop.rs | 200 ++++++++++++++++++++++++++ src/compositor.rs | 2 +- src/data_device_manager/data_offer.rs | 6 +- src/data_device_manager/read_pipe.rs | 7 +- src/data_device_manager/write_pipe.rs | 7 +- src/lib.rs | 2 + src/primary_selection/offer.rs | 6 +- src/shell/xdg/mod.rs | 2 +- src/shm/multi.rs | 2 +- src/shm/raw.rs | 6 +- src/shm/slot.rs | 2 +- 13 files changed, 237 insertions(+), 31 deletions(-) create mode 100644 src/calloop.rs diff --git a/Cargo.toml b/Cargo.toml index 882a5aa57d..2da61a784e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,20 +23,21 @@ log = "0.4" memmap2 = "0.7.0" nix = { version = "0.26.1", default-features = false, features = ["fs", "mman"] } thiserror = "1.0.30" -wayland-backend = "0.1.0" -wayland-client = "0.30.2" -wayland-cursor = "0.30.0" -wayland-protocols = { version = "0.30.1", features = ["client", "staging", "unstable"] } -wayland-protocols-wlr = { version = "0.1.0", features = ["client"] } -wayland-scanner = "0.30.0" -wayland-csd-frame = { version = "0.2.2", default-features = false, features = ["wayland-backend_0_1"] } +wayland-backend = "0.3.0" +wayland-client = "0.31.0" +wayland-cursor = "0.31.0" +wayland-protocols = { version = "0.31.0", features = ["client", "staging", "unstable"] } +wayland-protocols-wlr = { version = "0.2.0", features = ["client"] } +wayland-scanner = "0.31.0" +# wayland-csd-frame = { version = "0.2.2", default-features = false, features = ["wayland-backend_0_3"] } +wayland-csd-frame = { git = "https://github.com/ids1024/wayland-csd-frame", branch = "wayland_backend_0_3", default-features = false, features = ["wayland-backend_0_3"] } xkbcommon = { version = "0.5", optional = true, features = ["wayland"] } -calloop = { version = "0.10.5", optional = true } +calloop = { version = "0.11.0", optional = true } [features] default = ["calloop", "xkbcommon"] -calloop = ["dep:calloop", "wayland-client/calloop"] +calloop = ["dep:calloop"] xkbcommon = ["dep:xkbcommon", "pkg-config"] [build-dependencies] @@ -55,3 +56,7 @@ pollster = "0.2.5" [[example]] name = "wgpu" required-features = ["wayland-backend/client_system"] + +[patch.crates-io] +wayland-backend = { git = "https://github.com/ids1024/wayland-rs", branch = "client-as-fd" } +wayland-client = { git = "https://github.com/ids1024/wayland-rs", branch = "client-as-fd" } diff --git a/examples/simple_window.rs b/examples/simple_window.rs index 19b52e076c..dfad8c3705 100644 --- a/examples/simple_window.rs +++ b/examples/simple_window.rs @@ -2,6 +2,7 @@ use std::{convert::TryInto, time::Duration}; use calloop::{EventLoop, LoopHandle}; use smithay_client_toolkit::{ + calloop::WaylandSource, compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, @@ -28,7 +29,7 @@ use smithay_client_toolkit::{ use wayland_client::{ globals::registry_queue_init, protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, - Connection, QueueHandle, WaylandSource, + Connection, QueueHandle, }; fn main() { diff --git a/src/calloop.rs b/src/calloop.rs new file mode 100644 index 0000000000..c23b031bf0 --- /dev/null +++ b/src/calloop.rs @@ -0,0 +1,200 @@ +//! Utilities for using an [`EventQueue`] from wayland-client with an event loop that performs polling with +//! [`calloop`](https://crates.io/crates/calloop). + +use std::io; + +use calloop::{ + generic::Generic, EventSource, InsertError, Interest, LoopHandle, Mode, Poll, PostAction, + Readiness, RegistrationToken, Token, TokenFactory, +}; +use nix::errno::Errno; +use wayland_backend::client::{ReadEventsGuard, WaylandError}; +use wayland_client::{DispatchError, EventQueue}; + +/// An adapter to insert an [`EventQueue`] into a calloop [`EventLoop`](calloop::EventLoop). +/// +/// This type implements [`EventSource`] which generates an event whenever events on the event queue need to be +/// dispatched. The event queue available in the callback calloop registers may be used to dispatch pending +/// events using [`EventQueue::dispatch_pending`]. +/// +/// [`WaylandSource::insert`] can be used to insert this source into an event loop and automatically dispatch +/// pending events on the event queue. +#[derive(Debug)] +pub struct WaylandSource { + queue: Generic>, + read_guard: Option, +} + +impl WaylandSource { + /// Wrap an [`EventQueue`] as a [`WaylandSource`]. + pub fn new(queue: EventQueue) -> Result, WaylandError> { + let queue = Generic::new(queue, Interest::READ, Mode::Level); + + Ok(WaylandSource { queue, read_guard: None }) + } + + /// Access the underlying event queue + /// + /// Note that you should be careful when interacting with it if you invoke methods that + /// interact with the wayland socket (such as `dispatch()` or `prepare_read()`). These may + /// interfere with the proper waking up of this event source in the event loop. + pub fn queue(&mut self) -> &mut EventQueue { + &mut self.queue.file + } + + /// Insert this source into the given event loop. + /// + /// This adapter will pass the event loop's shared data as the `D` type for the event loop. + pub fn insert(self, handle: LoopHandle) -> Result> + where + D: 'static, + { + handle.insert_source(self, |_, queue, data| queue.dispatch_pending(data)) + } +} + +impl EventSource for WaylandSource { + type Event = (); + + /// The underlying event queue. + /// + /// You should call [`EventQueue::dispatch_pending`] inside your callback using this queue. + type Metadata = EventQueue; + type Ret = Result; + type Error = calloop::Error; + + fn process_events( + &mut self, + readiness: Readiness, + token: Token, + mut callback: F, + ) -> Result + where + F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, + { + let read_guard = &mut self.read_guard; + + let action = self.queue.process_events(readiness, token, |_, queue| { + // 1. read events from the socket if any are available + if let Some(guard) = read_guard.take() { + // might be None if some other thread read events before us, concurrently + if let Err(WaylandError::Io(err)) = guard.read() { + if err.kind() != io::ErrorKind::WouldBlock { + return Err(err); + } + } + } + + // 2. dispatch any pending events in the queue + // This is done to ensure we are not waiting for messages that are already in the buffer. + Self::loop_callback_pending(queue, &mut callback)?; + *read_guard = queue.prepare_read(); + + // 3. Once dispatching is finished, flush the responses to the compositor + if let Err(WaylandError::Io(e)) = queue.flush() { + if e.kind() != io::ErrorKind::WouldBlock { + // in case of error, forward it and fast-exit + return Err(e); + } + // WouldBlock error means the compositor could not process all our messages + // quickly. Either it is slowed down or we are a spammer. + // Should not really happen, if it does we do nothing and will flush again later + } + + Ok(PostAction::Continue) + })?; + + Ok(action) + } + + fn register( + &mut self, + poll: &mut Poll, + token_factory: &mut TokenFactory, + ) -> calloop::Result<()> { + self.queue.register(poll, token_factory) + } + + fn reregister( + &mut self, + poll: &mut Poll, + token_factory: &mut TokenFactory, + ) -> calloop::Result<()> { + self.queue.reregister(poll, token_factory) + } + + fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> { + self.queue.unregister(poll) + } + + fn pre_run(&mut self, mut callback: F) -> calloop::Result<()> + where + F: FnMut((), &mut Self::Metadata) -> Self::Ret, + { + debug_assert!(self.read_guard.is_none()); + + // flush the display before starting to poll + if let Err(WaylandError::Io(err)) = self.queue().flush() { + if err.kind() != io::ErrorKind::WouldBlock { + // in case of error, don't prepare a read, if the error is persistent, it'll trigger in other + // wayland methods anyway + log::error!("Error trying to flush the wayland display: {}", err); + return Err(err.into()); + } + } + + // ensure we are not waiting for messages that are already in the buffer. + Self::loop_callback_pending(&mut self.queue(), &mut callback)?; + self.read_guard = self.queue().prepare_read(); + + Ok(()) + } + + fn post_run(&mut self, _: F) -> calloop::Result<()> + where + F: FnMut((), &mut Self::Metadata) -> Self::Ret, + { + // Drop implementation of ReadEventsGuard will do cleanup + self.read_guard.take(); + Ok(()) + } +} + +impl WaylandSource { + /// Loop over the callback until all pending messages have been dispatched. + fn loop_callback_pending(queue: &mut EventQueue, callback: &mut F) -> io::Result<()> + where + F: FnMut((), &mut EventQueue) -> Result, + { + // Loop on the callback until no pending events are left. + loop { + match callback((), queue) { + // No more pending events. + Ok(0) => break Ok(()), + + Ok(_) => continue, + + Err(DispatchError::Backend(WaylandError::Io(err))) => { + return Err(err); + } + + Err(DispatchError::Backend(WaylandError::Protocol(err))) => { + log::error!("Protocol error received on display: {}", err); + + break Err(Errno::EPROTO.into()); + } + + Err(DispatchError::BadMessage { interface, sender_id, opcode }) => { + log::error!( + "Bad message on interface \"{}\": (sender_id: {}, opcode: {})", + interface, + sender_id, + opcode, + ); + + break Err(Errno::EPROTO.into()); + } + } + } + } +} diff --git a/src/compositor.rs b/src/compositor.rs index a1428f3eb2..fd1db96288 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -423,7 +423,7 @@ impl wayland_client::backend::ObjectData for RegionData { _: &wayland_client::backend::Backend, _: wayland_client::backend::protocol::Message< wayland_client::backend::ObjectId, - wayland_backend::io_lifetimes::OwnedFd, + std::os::unix::io::OwnedFd, >, ) -> Option> { unreachable!("wl_region has no events"); diff --git a/src/data_device_manager/data_offer.rs b/src/data_device_manager/data_offer.rs index 988746b709..7a3ef4f26f 100644 --- a/src/data_device_manager/data_offer.rs +++ b/src/data_device_manager/data_offer.rs @@ -1,6 +1,6 @@ use std::{ ops::{Deref, DerefMut}, - os::unix::prelude::{FromRawFd, RawFd}, + os::unix::prelude::{BorrowedFd, FromRawFd, RawFd}, sync::{Arc, Mutex}, }; @@ -407,7 +407,7 @@ pub fn receive(offer: &WlDataOffer, mime_type: String) -> std::io::Result std::io::Result wayland_backend::io_lifetimes::BorrowedFd<'_> { + fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> { self.file.file.as_fd() } } @@ -92,7 +91,7 @@ impl AsRawFd for ReadPipe { #[cfg(not(feature = "calloop"))] impl AsFd for ReadPipe { - fn as_fd(&self) -> wayland_backend::io_lifetimes::BorrowedFd<'_> { + fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> { self.file.as_fd() } } diff --git a/src/data_device_manager/write_pipe.rs b/src/data_device_manager/write_pipe.rs index f332213df7..0f9f2664a0 100644 --- a/src/data_device_manager/write_pipe.rs +++ b/src/data_device_manager/write_pipe.rs @@ -1,8 +1,7 @@ use std::{ fs, io, - os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, + os::unix::io::{AsFd, AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}, }; -use wayland_backend::io_lifetimes::{AsFd, OwnedFd}; /// If the `calloop` cargo feature is enabled, this can be used /// as an `EventSource` in a calloop event loop. @@ -86,7 +85,7 @@ impl AsRawFd for WritePipe { #[cfg(feature = "calloop")] impl AsFd for WritePipe { - fn as_fd(&self) -> wayland_backend::io_lifetimes::BorrowedFd<'_> { + fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> { self.file.file.as_fd() } } @@ -100,7 +99,7 @@ impl AsRawFd for WritePipe { #[cfg(not(feature = "calloop"))] impl AsFd for WritePipe { - fn as_fd(&self) -> wayland_backend::io_lifetimes::BorrowedFd<'_> { + fn as_fd(&self) -> std::os::unix::io::BorrowedFd<'_> { self.file.as_fd() } } diff --git a/src/lib.rs b/src/lib.rs index 3818c02d92..b09b66db8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,8 @@ pub mod reexports { pub use wayland_protocols_wlr as protocols_wlr; } +#[cfg(feature = "calloop")] +pub mod calloop; pub mod compositor; pub mod data_device_manager; pub mod error; diff --git a/src/primary_selection/offer.rs b/src/primary_selection/offer.rs index c56dcf3b3b..861625dbd6 100644 --- a/src/primary_selection/offer.rs +++ b/src/primary_selection/offer.rs @@ -1,5 +1,5 @@ use std::{ - os::unix::io::{FromRawFd, RawFd}, + os::unix::io::{BorrowedFd, FromRawFd, RawFd}, sync::Mutex, }; @@ -41,7 +41,7 @@ impl PrimarySelectionOffer { // create a pipe let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?; - self.offer.receive(mime_type, writefd); + self.offer.receive(mime_type, unsafe { BorrowedFd::borrow_raw(writefd) }); if let Err(err) = close(writefd) { log::warn!("Failed to close write pipe: {}", err); @@ -57,7 +57,7 @@ impl PrimarySelectionOffer { pub unsafe fn receive_to_fd(&self, mime_type: String, writefd: RawFd) { use nix::unistd::close; - self.offer.receive(mime_type, writefd); + self.offer.receive(mime_type, unsafe { BorrowedFd::borrow_raw(writefd) }); if let Err(err) = close(writefd) { log::warn!("Failed to close write pipe: {}", err); diff --git a/src/shell/xdg/mod.rs b/src/shell/xdg/mod.rs index 77a2dd015d..c324d76488 100644 --- a/src/shell/xdg/mod.rs +++ b/src/shell/xdg/mod.rs @@ -205,7 +205,7 @@ impl wayland_client::backend::ObjectData for PositionerData { _: &wayland_client::backend::Backend, _: wayland_client::backend::protocol::Message< wayland_client::backend::ObjectId, - wayland_backend::io_lifetimes::OwnedFd, + std::os::unix::io::OwnedFd, >, ) -> Option> { unreachable!("xdg_positioner has no events"); diff --git a/src/shm/multi.rs b/src/shm/multi.rs index c1f49b0546..a55ebb9eb3 100644 --- a/src/shm/multi.rs +++ b/src/shm/multi.rs @@ -406,7 +406,7 @@ impl wayland_client::backend::ObjectData for BufferObjectData { _backend: &wayland_backend::client::Backend, msg: wayland_backend::protocol::Message< wayland_backend::client::ObjectId, - wayland_backend::io_lifetimes::OwnedFd, + std::os::unix::io::OwnedFd, >, ) -> Option> { debug_assert!(wayland_client::backend::protocol::same_interface( diff --git a/src/shm/raw.rs b/src/shm/raw.rs index df6c936be7..486a622ac1 100644 --- a/src/shm/raw.rs +++ b/src/shm/raw.rs @@ -6,7 +6,7 @@ use std::{ fs::File, io, - os::unix::prelude::{FromRawFd, RawFd}, + os::unix::prelude::{AsFd, FromRawFd, RawFd}, sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; @@ -53,7 +53,7 @@ impl RawPool { let pool = shm .send_constructor( - wl_shm::Request::CreatePool { fd: shm_fd, size: len as i32 }, + wl_shm::Request::CreatePool { fd: mem_file.as_fd(), size: len as i32 }, Arc::new(ShmPoolData), ) .unwrap_or_else(|_| Proxy::inert(shm.backend().clone())); @@ -274,7 +274,7 @@ impl ObjectData for ShmPoolData { _: &wayland_client::backend::Backend, _: wayland_client::backend::protocol::Message< wayland_client::backend::ObjectId, - wayland_backend::io_lifetimes::OwnedFd, + std::os::unix::io::OwnedFd, >, ) -> Option> { unreachable!("wl_shm_pool has no events") diff --git a/src/shm/slot.rs b/src/shm/slot.rs index 1bd638e40c..e72b992328 100644 --- a/src/shm/slot.rs +++ b/src/shm/slot.rs @@ -507,7 +507,7 @@ impl wayland_client::backend::ObjectData for BufferData { handle: &wayland_client::backend::Backend, msg: wayland_backend::protocol::Message< wayland_backend::client::ObjectId, - wayland_backend::io_lifetimes::OwnedFd, + std::os::unix::io::OwnedFd, >, ) -> Option> { debug_assert!(wayland_client::backend::protocol::same_interface(