From 67e040853d471fc961c3ed91a0193b0175b0a9b9 Mon Sep 17 00:00:00 2001 From: PolyMeilex Date: Fri, 16 Feb 2024 20:38:03 +0100 Subject: [PATCH] Client side delegate --- wayland-client/examples/delegated.rs | 122 ++++++++++++++++++ wayland-client/src/event_queue.rs | 66 +++++++--- wayland-client/src/globals.rs | 59 ++++++++- wayland-client/src/lib.rs | 10 +- wayland-scanner/src/client_gen.rs | 4 +- .../tests/scanner_assets/test-client-code.rs | 12 +- wayland-tests/tests/client_bad_requests.rs | 2 +- 7 files changed, 236 insertions(+), 39 deletions(-) create mode 100644 wayland-client/examples/delegated.rs diff --git a/wayland-client/examples/delegated.rs b/wayland-client/examples/delegated.rs new file mode 100644 index 00000000000..ebcac9b7bd5 --- /dev/null +++ b/wayland-client/examples/delegated.rs @@ -0,0 +1,122 @@ +#![allow(clippy::single_match)] + +use wayland_client::{ + protocol::{ + wl_compositor::{self, WlCompositor}, + wl_display::{self, WlDisplay}, + wl_registry::{self, WlRegistry}, + }, + Connection, Dispatch, Proxy, QueueHandle, +}; + +/// A demonstration of how delegateing can make implementing protocols an implementation detail +/// +/// Users of this module only need to implement `RegistryHandler` trait on their state, +/// Implementation of `Dispatch` can remain an internal detail of the module. +/// +/// In a way you can pretend that everything inside of this submodule is a library / different crate +mod delegated { + use super::*; + + pub trait RegistryHandler: 'static { + fn state(&mut self) -> &mut Registry; + fn new_global(&mut self, name: u32, interface: &str, version: u32); + } + + pub struct Registry { + wl_registry: WlRegistry, + } + + impl Registry { + /// Create a [`WlRegistry`] object, and handle it's events internally + /// It can use [`RegistryHandler`] trait to callback to your `D` state. + pub fn new(qh: &QueueHandle, display: &WlDisplay) -> Self { + // Let's construct a `WlRegistry` object that dispatches it's events to our + // `Registry::event` rather than to `D`, + // that way it can remain an implementation detail + let data = qh.make_data::(()); + let wl_registry = + display.send_constructor(wl_display::Request::GetRegistry {}, data).unwrap(); + + Self { wl_registry } + } + + pub fn wl_registry(&self) -> WlRegistry { + self.wl_registry.clone() + } + } + + impl Dispatch for Registry { + /// Called whenever an object created via `make_data` + /// receives a server event + fn event( + state: &mut D, + _: &wl_registry::WlRegistry, + event: wl_registry::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + let _state = state.state(); + + if let wl_registry::Event::Global { name, interface, version } = event { + // Let's callback the user of this abstraction, informing them about new global + state.new_global(name, &interface, version); + } + } + } +} + +struct AppData { + registry: delegated::Registry, + qh: QueueHandle, +} + +impl delegated::RegistryHandler for AppData { + fn state(&mut self) -> &mut delegated::Registry { + &mut self.registry + } + + // Even tho we did not implement WlRegistry, `delegated::Registry` implemented it for us, + // and will call this method whenever new globals appear + fn new_global(&mut self, name: u32, interface: &str, version: u32) { + println!("[{}] {} (v{})", name, interface, version); + + match interface { + "wl_compositor" => { + self.registry.wl_registry().bind(name, version, &self.qh, ()); + } + _ => {} + } + } +} + +impl Dispatch for AppData { + fn event( + _state: &mut Self, + _proxy: &WlCompositor, + _event: wl_compositor::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + } +} + +fn main() { + let conn = Connection::connect_to_env().unwrap(); + + let display = conn.display(); + + let mut event_queue = conn.new_event_queue::(); + let qh = event_queue.handle(); + + // Let's ask `delegated::Registry` to implement `WlRegistry` for us, only calling us back whenever + // necessary via `RegistryHandler` trait + let registry = delegated::Registry::new(&qh, &display); + + let mut app = AppData { registry, qh: qh.clone() }; + + println!("Advertized globals:"); + event_queue.roundtrip(&mut app).unwrap(); +} diff --git a/wayland-client/src/event_queue.rs b/wayland-client/src/event_queue.rs index ba46db8e195..1cafd357e5a 100644 --- a/wayland-client/src/event_queue.rs +++ b/wayland-client/src/event_queue.rs @@ -39,9 +39,9 @@ use crate::{conn::SyncData, Connection, DispatchError, Proxy}; /// /// To provide generic handlers for downstream usage, it is possible to make an implementation of the trait /// that is generic over the last type argument, as illustrated below. Users will then be able to -/// automatically delegate their implementation to yours using the [`delegate_dispatch!()`] macro. +/// automatically delegate their implementation to yours using the `DelegateTo` generic in [`QueueHandle::make_data()`]. /// -/// [`delegate_dispatch!()`]: crate::delegate_dispatch!() +/// See `wayland-client` `delegated.rs` example for more info. /// /// As a result, when your implementation is instantiated, the last type parameter `State` will be the state /// struct of the app using your generic implementation. You can put additional trait constraints on it to @@ -63,8 +63,6 @@ use crate::{conn::SyncData, Connection, DispatchError, Proxy}; /// // the default State=Self. /// impl Dispatch for DelegateToMe /// where -/// // State is the type which has delegated to this type, so it needs to have an impl of Dispatch itself -/// State: Dispatch, /// // If your delegate type has some internal state, it'll need to access it, and you can /// // require it by adding custom trait bounds. /// // In this example, we just require an AsMut implementation @@ -96,7 +94,6 @@ pub trait Dispatch where Self: Sized, I: Proxy, - State: Dispatch, { /// Called when an event from the server is processed /// @@ -176,7 +173,7 @@ macro_rules! event_created_child { match opcode { $( $opcode => { - qhandle.make_data::<$child_iface, _>({$child_udata}) + qhandle.make_data::<$child_iface, _, $selftype>({$child_udata}) }, )* _ => { @@ -328,16 +325,17 @@ pub(crate) struct EventQueueInner { } impl EventQueueInner { - pub(crate) fn enqueue_event( + pub(crate) fn enqueue_event( &mut self, msg: Message, odata: Arc, ) where - State: Dispatch + 'static, + State: 'static, + DispatchTo: Dispatch + 'static, U: Send + Sync + 'static, I: Proxy + 'static, { - let func = queue_callback::; + let func = queue_callback::; self.queue.push_back(QueueEvent(func, msg, odata)); if self.freeze_count == 0 { if let Some(waker) = self.waker.take() { @@ -608,14 +606,35 @@ impl QueueHandle { /// This creates an implementation of [`ObjectData`] fitting for direct use with `wayland-backend` APIs /// that forwards all events to the event queue associated with this token, integrating the object into /// the [`Dispatch`]-based logic of `wayland-client`. - pub fn make_data( + /// + /// Events will be dispatched via [`Dispatch`] to a `DelegateTo` generic type. + /// Eg. + /// ```ignore + /// struct OutputDispatcher; + /// + /// impl Dispatch for OutputDispatcher { + /// // if `DelegateTo` of an object is set to `OutputDispatcher` events will be dispatched + /// // to this impl + /// } + /// + /// struct SeatDispatcher; + /// + /// impl Dispatch for SeatDispatcher { + /// // if `DelegateTo` of an object is set to `SeatDispatcher` events will be dispatched here + /// // to this impl + /// } + /// + /// let obj1 = qh.make_data::(()); + /// let obj2 = qh.make_data::(()); + /// ``` + pub fn make_data( &self, user_data: U, ) -> Arc where - State: Dispatch, + DelegateTo: Dispatch + 'static, { - Arc::new(QueueProxyData:: { + Arc::new(QueueProxyData:: { handle: self.clone(), udata: user_data, _phantom: PhantomData, @@ -647,7 +666,8 @@ impl<'a, State> Drop for QueueFreezeGuard<'a, State> { fn queue_callback< I: Proxy + 'static, U: Send + Sync + 'static, - State: Dispatch + 'static, + State, + DelegateTo: Dispatch + 'static, >( handle: &Connection, msg: Message, @@ -657,21 +677,23 @@ fn queue_callback< ) -> Result<(), DispatchError> { let (proxy, event) = I::parse_event(handle, msg)?; let udata = odata.data_as_any().downcast_ref().expect("Wrong user_data value for object"); - >::event(data, &proxy, event, udata, handle, qhandle); + >::event(data, &proxy, event, udata, handle, qhandle); Ok(()) } /// The [`ObjectData`] implementation used by Wayland proxies, integrating with [`Dispatch`] -pub struct QueueProxyData { +pub struct QueueProxyData { handle: QueueHandle, /// The user data associated with this object pub udata: U, - _phantom: PhantomData, + _phantom: PhantomData, } -impl ObjectData for QueueProxyData +impl ObjectData + for QueueProxyData where - State: Dispatch + 'static, + State: 'static, + DispatchTo: Dispatch + 'static, { fn event( self: Arc, @@ -682,9 +704,9 @@ where .args .iter() .any(|arg| matches!(arg, Argument::NewId(id) if !id.is_null())) - .then(|| State::event_created_child(msg.opcode, &self.handle)); + .then(|| DispatchTo::event_created_child(msg.opcode, &self.handle)); - self.handle.inner.lock().unwrap().enqueue_event::(msg, self.clone()); + self.handle.inner.lock().unwrap().enqueue_event::(msg, self.clone()); new_data } @@ -696,7 +718,9 @@ where } } -impl std::fmt::Debug for QueueProxyData { +impl std::fmt::Debug + for QueueProxyData +{ #[cfg_attr(coverage, coverage(off))] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("QueueProxyData").field("udata", &self.udata).finish() diff --git a/wayland-client/src/globals.rs b/wayland-client/src/globals.rs index d422b6962b6..308d07942d2 100644 --- a/wayland-client/src/globals.rs +++ b/wayland-client/src/globals.rs @@ -54,6 +54,7 @@ use std::{ fmt, + marker::PhantomData, ops::RangeInclusive, os::unix::io::OwnedFd, sync::{ @@ -80,13 +81,30 @@ pub fn registry_queue_init( ) -> Result<(GlobalList, EventQueue), GlobalError> where State: Dispatch + 'static, +{ + registry_queue_init_delegated::(conn) +} + +/// Initialize a new event queue with its associated registry and retrieve the initial list of globals +/// +/// This is a delegating variant of [`registry_queue_init`], it dispatches events to `DelegateTo` +/// generic rather than to `State`. +/// +/// See [the module level documentation][self] for more. +pub fn registry_queue_init_delegated( + conn: &Connection, +) -> Result<(GlobalList, EventQueue), GlobalError> +where + State: 'static, + DelegateTo: Dispatch + 'static, { let event_queue = conn.new_event_queue(); let display = conn.display(); - let data = Arc::new(RegistryState { + let data = Arc::new(RegistryState:: { globals: GlobalListContents { contents: Default::default() }, handle: event_queue.handle(), initial_roundtrip_done: AtomicBool::new(false), + _ph: PhantomData, }); let registry = display.send_constructor(wl_display::Request::GetRegistry {}, data.clone())?; // We don't need to dispatch the event queue as for now nothing will be sent to it @@ -139,6 +157,25 @@ impl GlobalList { I: Proxy + 'static, State: Dispatch + 'static, U: Send + Sync + 'static, + { + self.bind_delegated::(qh, version, udata) + } + + /// Binds a global, returning a new protocol object associated with the global. + /// + /// This is a delegating variant of [`Self::bind`], it dispatches events to `DelegateTo` + /// generic rather than to `State`. Read full docs at [`Self::bind`]. + pub fn bind_delegated( + &self, + qh: &QueueHandle, + version: RangeInclusive, + udata: U, + ) -> Result + where + I: Proxy + 'static, + State: 'static, + U: Send + Sync + 'static, + DelegateTo: Dispatch + 'static, { let version_start = *version.start(); let version_end = *version.end(); @@ -175,7 +212,13 @@ impl GlobalList { // requested version. let version = version.min(version_end); - Ok(self.registry.bind(name, version, qh, udata)) + Ok(self + .registry + .send_constructor( + wl_registry::Request::Bind { name, id: (I::interface(), version) }, + qh.make_data::(udata), + ) + .unwrap()) } /// Returns the [`WlRegistry`][wl_registry] protocol object. @@ -293,15 +336,16 @@ impl GlobalListContents { } } -struct RegistryState { +struct RegistryState { globals: GlobalListContents, handle: QueueHandle, initial_roundtrip_done: AtomicBool, + _ph: PhantomData DelegateTo>, } -impl ObjectData for RegistryState +impl ObjectData for RegistryState where - State: Dispatch, + DelegateTo: Dispatch, { fn event( self: Arc, @@ -344,7 +388,10 @@ where .inner .lock() .unwrap() - .enqueue_event::(msg, self.clone()) + .enqueue_event::( + msg, + self.clone(), + ) } // We do not create any objects in this event handler. diff --git a/wayland-client/src/lib.rs b/wayland-client/src/lib.rs index 9500c953ff6..3b9f79c1fb8 100644 --- a/wayland-client/src/lib.rs +++ b/wayland-client/src/lib.rs @@ -41,10 +41,14 @@ //! managing the newly created object. //! //! However, implementing all those traits on your own is a lot of (often uninteresting) work. To make this -//! easier a composition mechanism is provided using the [`delegate_dispatch!`] macro. This way, another -//! library (such as Smithay's Client Toolkit) can provide generic [`Dispatch`] implementations that you +//! easier a composition mechanism is provided. Using the [`QueueHandle::make_data()`] method +//! one is able to create object handlers manually, making it possible to specify `DispatchTo` type different +//! from your `State`. +//! Libraries (such as Smithay's Client Toolkit) can use that mechanism to provide generic [`Dispatch`] implementations that you //! can reuse in your own app by delegating those objects to that provided implementation. See the -//! documentation of those traits and macro for details. +//! documentation of [`QueueHandle::make_data()`] for details. +//! There is also [delegated.rs](https://github.com/Smithay/wayland-rs/blob/master/wayland-client/examples/delegated.rs) example +//! which demonstrates how delegating works. //! //! ## Getting started example //! diff --git a/wayland-scanner/src/client_gen.rs b/wayland-scanner/src/client_gen.rs index b116f55879b..5bc9ce23590 100644 --- a/wayland-scanner/src/client_gen.rs +++ b/wayland-scanner/src/client_gen.rs @@ -269,7 +269,7 @@ fn gen_methods(interface: &Interface) -> TokenStream { Request::#enum_variant { #(#enum_args),* }, - qh.make_data::(udata), + qh.make_data::(udata), ).unwrap_or_else(|_| Proxy::inert(self.backend.clone())) } } @@ -284,7 +284,7 @@ fn gen_methods(interface: &Interface) -> TokenStream { Request::#enum_variant { #(#enum_args),* }, - qh.make_data::(udata), + qh.make_data::(udata), ).unwrap_or_else(|_| Proxy::inert(self.backend.clone())) } } diff --git a/wayland-scanner/tests/scanner_assets/test-client-code.rs b/wayland-scanner/tests/scanner_assets/test-client-code.rs index 6028b8baffd..1f6bd5245a2 100644 --- a/wayland-scanner/tests/scanner_assets/test-client-code.rs +++ b/wayland-scanner/tests/scanner_assets/test-client-code.rs @@ -290,7 +290,7 @@ pub mod wl_display { ) -> super::wl_callback::WlCallback { self.send_constructor( Request::Sync {}, - qh.make_data::(udata), + qh.make_data::(udata), ) .unwrap_or_else(|_| Proxy::inert(self.backend.clone())) } @@ -306,7 +306,7 @@ pub mod wl_display { ) -> super::wl_registry::WlRegistry { self.send_constructor( Request::GetRegistry {}, - qh.make_data::(udata), + qh.make_data::(udata), ) .unwrap_or_else(|_| Proxy::inert(self.backend.clone())) } @@ -560,7 +560,7 @@ pub mod wl_registry { ) -> I { self.send_constructor( Request::Bind { name, id: (I::interface(), version) }, - qh.make_data::(udata), + qh.make_data::(udata), ) .unwrap_or_else(|_| Proxy::inert(self.backend.clone())) } @@ -1236,7 +1236,7 @@ pub mod test_global { ) -> super::secondary::Secondary { self.send_constructor( Request::GetSecondary {}, - qh.make_data::(udata), + qh.make_data::(udata), ) .unwrap_or_else(|_| Proxy::inert(self.backend.clone())) } @@ -1251,7 +1251,7 @@ pub mod test_global { ) -> super::tertiary::Tertiary { self.send_constructor( Request::GetTertiary {}, - qh.make_data::(udata), + qh.make_data::(udata), ) .unwrap_or_else(|_| Proxy::inert(self.backend.clone())) } @@ -1315,7 +1315,7 @@ pub mod test_global { ) -> super::quad::Quad { self.send_constructor( Request::NewidAndAllowNull { sec: sec.cloned(), ter: ter.clone() }, - qh.make_data::(udata), + qh.make_data::(udata), ) .unwrap_or_else(|_| Proxy::inert(self.backend.clone())) } diff --git a/wayland-tests/tests/client_bad_requests.rs b/wayland-tests/tests/client_bad_requests.rs index 8b054525b2b..e16f4bbb77b 100644 --- a/wayland-tests/tests/client_bad_requests.rs +++ b/wayland-tests/tests/client_bad_requests.rs @@ -68,7 +68,7 @@ fn send_constructor_wrong_type() { client .event_queue .handle() - .make_data::(()), + .make_data::(()), ), ) .unwrap();