From 6657cd514bf58bf91735d01b83c9e4692e70a636 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 20 Jun 2022 13:15:12 -0400 Subject: [PATCH] wip use calloop event loop --- Cargo.lock | 29 +-- applets/cosmic-applet-workspaces/Cargo.toml | 3 + applets/cosmic-applet-workspaces/src/main.rs | 1 + .../cosmic-applet-workspaces/src/wayland.rs | 12 +- .../src/wayland_source.rs | 215 ++++++++++++++++++ .../src/window/mod.rs | 2 +- 6 files changed, 245 insertions(+), 17 deletions(-) create mode 100644 applets/cosmic-applet-workspaces/src/wayland_source.rs diff --git a/Cargo.lock b/Cargo.lock index c2d87d08..a3f3bc9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,20 +390,23 @@ name = "cosmic-applet-workspaces" version = "0.1.0" dependencies = [ "anyhow", + "calloop", "cascade", "cosmic-panel-config", "gio", "gtk4", "i18n-embed", "i18n-embed-fl", + "log", + "nix 0.22.3", "once_cell", "pretty_env_logger", "rust-embed", "tokio", "wayland-backend", - "wayland-client 0.30.0-beta.4", + "wayland-client 0.30.0-beta.5", "wayland-commons", - "wayland-scanner 0.30.0-beta.4", + "wayland-scanner 0.30.0-beta.5", ] [[package]] @@ -425,7 +428,7 @@ dependencies = [ [[package]] name = "cosmic-panel-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-panel/#231dc1ec0656840458d9f0d3468d9c7ea5c2a98c" +source = "git+https://github.com/pop-os/cosmic-panel#231dc1ec0656840458d9f0d3468d9c7ea5c2a98c" dependencies = [ "anyhow", "gtk4", @@ -2317,8 +2320,8 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wayland-backend" -version = "0.1.0-beta.4" -source = "git+https://github.com/smithay/wayland-rs.git#797f697a66f8541327cbaac319692ec8d2395a38" +version = "0.1.0-beta.5" +source = "git+https://github.com/smithay/wayland-rs.git#70b0389becc49edde19ec16779eec3c5b0ad0a5f" dependencies = [ "cc", "downcast-rs", @@ -2326,7 +2329,7 @@ dependencies = [ "nix 0.24.1", "scoped-tls", "smallvec", - "wayland-sys 0.30.0-beta.4", + "wayland-sys 0.30.0-beta.5", ] [[package]] @@ -2347,8 +2350,8 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.30.0-beta.4" -source = "git+https://github.com/smithay/wayland-rs.git#797f697a66f8541327cbaac319692ec8d2395a38" +version = "0.30.0-beta.5" +source = "git+https://github.com/smithay/wayland-rs.git#70b0389becc49edde19ec16779eec3c5b0ad0a5f" dependencies = [ "bitflags", "futures-channel", @@ -2357,7 +2360,7 @@ dependencies = [ "nix 0.24.1", "thiserror", "wayland-backend", - "wayland-scanner 0.30.0-beta.4", + "wayland-scanner 0.30.0-beta.5", ] [[package]] @@ -2419,8 +2422,8 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.30.0-beta.4" -source = "git+https://github.com/smithay/wayland-rs.git#797f697a66f8541327cbaac319692ec8d2395a38" +version = "0.30.0-beta.5" +source = "git+https://github.com/smithay/wayland-rs.git#70b0389becc49edde19ec16779eec3c5b0ad0a5f" dependencies = [ "proc-macro2", "quote", @@ -2460,8 +2463,8 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.30.0-beta.4" -source = "git+https://github.com/smithay/wayland-rs.git#797f697a66f8541327cbaac319692ec8d2395a38" +version = "0.30.0-beta.5" +source = "git+https://github.com/smithay/wayland-rs.git#70b0389becc49edde19ec16779eec3c5b0ad0a5f" dependencies = [ "dlib", "log", diff --git a/applets/cosmic-applet-workspaces/Cargo.toml b/applets/cosmic-applet-workspaces/Cargo.toml index 9a22dbcb..60030fa2 100644 --- a/applets/cosmic-applet-workspaces/Cargo.toml +++ b/applets/cosmic-applet-workspaces/Cargo.toml @@ -21,6 +21,9 @@ wayland-commons = "0.29.4" wayland-scanner = { git = "https://github.com/smithay/wayland-rs.git", version = "0.30.0-beta.4"} wayland-backend = { version = "0.1.0-beta.4", git = "https://github.com/smithay/wayland-rs.git" } wayland-client = { version = "0.30.0-beta.4", git = "https://github.com/smithay/wayland-rs.git" } +calloop = "*" +nix = "*" +log = "0.4" [build-dependencies] gio = "0.15.10" diff --git a/applets/cosmic-applet-workspaces/src/main.rs b/applets/cosmic-applet-workspaces/src/main.rs index 42e28e01..520673a2 100644 --- a/applets/cosmic-applet-workspaces/src/main.rs +++ b/applets/cosmic-applet-workspaces/src/main.rs @@ -17,6 +17,7 @@ use window::CosmicWorkspacesWindow; mod localize; mod utils; mod wayland; +mod wayland_source; mod window; mod workspace_button; mod workspace_list; diff --git a/applets/cosmic-applet-workspaces/src/wayland.rs b/applets/cosmic-applet-workspaces/src/wayland.rs index ea76a9aa..2fede4a8 100644 --- a/applets/cosmic-applet-workspaces/src/wayland.rs +++ b/applets/cosmic-applet-workspaces/src/wayland.rs @@ -1,4 +1,4 @@ -use crate::{utils::{Activate}, wayland::generated::client::zext_workspace_manager_v1::ZextWorkspaceManagerV1}; +use crate::{utils::{Activate}, wayland::generated::client::zext_workspace_manager_v1::ZextWorkspaceManagerV1, wayland_source::WaylandSource}; use std::{env, os::unix::net::UnixStream, path::PathBuf, sync::Arc, mem, time::Duration}; use gtk4::glib; use tokio::sync::mpsc; @@ -53,9 +53,15 @@ pub fn spawn_workspaces(tx: glib::Sender) -> mpsc::Sender { .and_then(|s| s.map(|s| Connection::from_socket(s).map_err(anyhow::Error::msg))) { std::thread::spawn(move || { - let mut event_queue = conn.new_event_queue::(); + let mut event_loop = calloop::EventLoop::::try_new().unwrap(); + let loop_handle = event_loop.handle(); + let event_queue = conn.new_event_queue::(); let qhandle = event_queue.handle(); + WaylandSource::new(event_queue).expect("Failed to create wayland source") + .insert(loop_handle) + .unwrap(); + let display = conn.display(); display.get_registry(&qhandle, ()).unwrap(); @@ -81,7 +87,7 @@ pub fn spawn_workspaces(tx: glib::Sender) -> mpsc::Sender { if changed { state.workspace_manager.as_ref().unwrap().commit(); } - event_queue.sync_roundtrip(&mut state).unwrap(); + event_loop.dispatch(Duration::from_millis(16), &mut state).unwrap(); std::thread::sleep(Duration::from_millis(16)); } }); diff --git a/applets/cosmic-applet-workspaces/src/wayland_source.rs b/applets/cosmic-applet-workspaces/src/wayland_source.rs new file mode 100644 index 00000000..06389721 --- /dev/null +++ b/applets/cosmic-applet-workspaces/src/wayland_source.rs @@ -0,0 +1,215 @@ +//! 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, os::unix::prelude::RawFd}; + +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 display 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 display. +#[derive(Debug)] +pub struct WaylandSource { + queue: EventQueue, + fd: Generic, + read_guard: Option, +} + +impl WaylandSource { + /// Wrap an [`EventQueue`] as a [`WaylandSource`]. + pub fn new(queue: EventQueue) -> Result, WaylandError> { + let guard = queue.prepare_read()?; + let fd = Generic::new(guard.connection_fd(), Interest::READ, Mode::Level); + drop(guard); + + Ok(WaylandSource { queue, fd, 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 + } + + /// 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 queue = &mut self.queue; + let read_guard = &mut self.read_guard; + + let action = self.fd.process_events(readiness, token, |_, _| { + // 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 = Some(Self::prepare_read(queue)?); + + // 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.fd.register(poll, token_factory) + } + + fn reregister( + &mut self, + poll: &mut Poll, + token_factory: &mut TokenFactory, + ) -> calloop::Result<()> { + self.fd.reregister(poll, token_factory) + } + + fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> { + self.fd.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 = Some(Self::prepare_read(&mut self.queue)?); + + 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 { msg, interface }) => { + log::error!( + "Bad message on interface \"{}\": (opcode: {}, args: {:?})", + interface, + msg.opcode, + msg.args, + ); + + break Err(Errno::EPROTO.into()); + } + } + } + } + + fn prepare_read(queue: &mut EventQueue) -> io::Result { + queue.prepare_read().map_err(|err| match err { + WaylandError::Io(err) => err, + + WaylandError::Protocol(err) => { + log::error!("Protocol error received on display: {}", err); + Errno::EPROTO.into() + } + }) + } +} \ No newline at end of file diff --git a/applets/cosmic-applet-workspaces/src/window/mod.rs b/applets/cosmic-applet-workspaces/src/window/mod.rs index ec3af96e..1ac0b379 100644 --- a/applets/cosmic-applet-workspaces/src/window/mod.rs +++ b/applets/cosmic-applet-workspaces/src/window/mod.rs @@ -35,7 +35,7 @@ impl CosmicWorkspacesWindow { ..set_title(Some(&fl!("cosmic-applet-workspaces"))); ..add_css_class("transparent"); }; - let config = CosmicPanelConfig::load_from_env().unwrap(); + let config = CosmicPanelConfig::load_from_env().unwrap_or_default(); let app_list = WorkspaceList::new(config); self_.set_child(Some(&app_list));