Skip to content

Commit

Permalink
Client side delegate
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyMeilex committed Dec 14, 2024
1 parent cb3ceb6 commit 67e0408
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 39 deletions.
122 changes: 122 additions & 0 deletions wayland-client/examples/delegated.rs
Original file line number Diff line number Diff line change
@@ -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<D: RegistryHandler>(qh: &QueueHandle<D>, 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::<WlRegistry, _, Self>(());
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<D: RegistryHandler> Dispatch<WlRegistry, (), D> for Registry {
/// Called whenever an object created via `make_data<WlRegistry, _, Registry>`
/// receives a server event
fn event(
state: &mut D,
_: &wl_registry::WlRegistry,
event: wl_registry::Event,
_: &(),
_: &Connection,
_: &QueueHandle<D>,
) {
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<Self>,
}

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<WlCompositor, ()> for AppData {
fn event(
_state: &mut Self,
_proxy: &WlCompositor,
_event: wl_compositor::Event,
_data: &(),
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}

fn main() {
let conn = Connection::connect_to_env().unwrap();

let display = conn.display();

let mut event_queue = conn.new_event_queue::<AppData>();
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();
}
66 changes: 45 additions & 21 deletions wayland-client/src/event_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -63,8 +63,6 @@ use crate::{conn::SyncData, Connection, DispatchError, Proxy};
/// // the default State=Self.
/// impl<State> Dispatch<wl_registry::WlRegistry, MyUserData, State> 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<wl_registry::WlRegistry, MyUserData>,
/// // 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
Expand Down Expand Up @@ -96,7 +94,6 @@ pub trait Dispatch<I, UserData, State = Self>
where
Self: Sized,
I: Proxy,
State: Dispatch<I, UserData, State>,
{
/// Called when an event from the server is processed
///
Expand Down Expand Up @@ -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})
},
)*
_ => {
Expand Down Expand Up @@ -328,16 +325,17 @@ pub(crate) struct EventQueueInner<State> {
}

impl<State> EventQueueInner<State> {
pub(crate) fn enqueue_event<I, U>(
pub(crate) fn enqueue_event<I, U, DispatchTo>(
&mut self,
msg: Message<ObjectId, OwnedFd>,
odata: Arc<dyn ObjectData>,
) where
State: Dispatch<I, U> + 'static,
State: 'static,
DispatchTo: Dispatch<I, U, State> + 'static,
U: Send + Sync + 'static,
I: Proxy + 'static,
{
let func = queue_callback::<I, U, State>;
let func = queue_callback::<I, U, State, DispatchTo>;
self.queue.push_back(QueueEvent(func, msg, odata));
if self.freeze_count == 0 {
if let Some(waker) = self.waker.take() {
Expand Down Expand Up @@ -608,14 +606,35 @@ impl<State: 'static> QueueHandle<State> {
/// 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<I: Proxy + 'static, U: Send + Sync + 'static>(
///
/// Events will be dispatched via [`Dispatch`] to a `DelegateTo` generic type.
/// Eg.
/// ```ignore
/// struct OutputDispatcher;
///
/// impl<D> Dispatch<WlOutput, (), D> for OutputDispatcher {
/// // if `DelegateTo` of an object is set to `OutputDispatcher` events will be dispatched
/// // to this impl
/// }
///
/// struct SeatDispatcher;
///
/// impl<D> Dispatch<WlSeat, (), D> for SeatDispatcher {
/// // if `DelegateTo` of an object is set to `SeatDispatcher` events will be dispatched here
/// // to this impl
/// }
///
/// let obj1 = qh.make_data::<WlOutput, (), OutputDispatcher>(());
/// let obj2 = qh.make_data::<WlSeat, (), SeatDispatcher>(());
/// ```
pub fn make_data<I: Proxy + 'static, U: Send + Sync + 'static, DelegateTo>(
&self,
user_data: U,
) -> Arc<dyn ObjectData>
where
State: Dispatch<I, U, State>,
DelegateTo: Dispatch<I, U, State> + 'static,
{
Arc::new(QueueProxyData::<I, U, State> {
Arc::new(QueueProxyData::<I, U, State, DelegateTo> {
handle: self.clone(),
udata: user_data,
_phantom: PhantomData,
Expand Down Expand Up @@ -647,7 +666,8 @@ impl<'a, State> Drop for QueueFreezeGuard<'a, State> {
fn queue_callback<
I: Proxy + 'static,
U: Send + Sync + 'static,
State: Dispatch<I, U, State> + 'static,
State,
DelegateTo: Dispatch<I, U, State> + 'static,
>(
handle: &Connection,
msg: Message<ObjectId, OwnedFd>,
Expand All @@ -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");
<State as Dispatch<I, U, State>>::event(data, &proxy, event, udata, handle, qhandle);
<DelegateTo as Dispatch<I, U, State>>::event(data, &proxy, event, udata, handle, qhandle);
Ok(())
}

/// The [`ObjectData`] implementation used by Wayland proxies, integrating with [`Dispatch`]
pub struct QueueProxyData<I: Proxy, U, State> {
pub struct QueueProxyData<I: Proxy, U, State, DispatchTo = State> {
handle: QueueHandle<State>,
/// The user data associated with this object
pub udata: U,
_phantom: PhantomData<fn(&I)>,
_phantom: PhantomData<fn(&I, &DispatchTo)>,
}

impl<I: Proxy + 'static, U: Send + Sync + 'static, State> ObjectData for QueueProxyData<I, U, State>
impl<I: Proxy + 'static, U: Send + Sync + 'static, State, DispatchTo> ObjectData
for QueueProxyData<I, U, State, DispatchTo>
where
State: Dispatch<I, U, State> + 'static,
State: 'static,
DispatchTo: Dispatch<I, U, State> + 'static,
{
fn event(
self: Arc<Self>,
Expand All @@ -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::<I, U>(msg, self.clone());
self.handle.inner.lock().unwrap().enqueue_event::<I, U, DispatchTo>(msg, self.clone());

new_data
}
Expand All @@ -696,7 +718,9 @@ where
}
}

impl<I: Proxy, U: std::fmt::Debug, State> std::fmt::Debug for QueueProxyData<I, U, State> {
impl<I: Proxy, U: std::fmt::Debug, State, DispatchTo> std::fmt::Debug
for QueueProxyData<I, U, State, DispatchTo>
{
#[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()
Expand Down
59 changes: 53 additions & 6 deletions wayland-client/src/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
use std::{
fmt,
marker::PhantomData,
ops::RangeInclusive,
os::unix::io::OwnedFd,
sync::{
Expand All @@ -80,13 +81,30 @@ pub fn registry_queue_init<State>(
) -> Result<(GlobalList, EventQueue<State>), GlobalError>
where
State: Dispatch<wl_registry::WlRegistry, GlobalListContents> + 'static,
{
registry_queue_init_delegated::<State, State>(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<State, DelegateTo>(
conn: &Connection,
) -> Result<(GlobalList, EventQueue<State>), GlobalError>
where
State: 'static,
DelegateTo: Dispatch<wl_registry::WlRegistry, GlobalListContents, State> + 'static,
{
let event_queue = conn.new_event_queue();
let display = conn.display();
let data = Arc::new(RegistryState {
let data = Arc::new(RegistryState::<State, DelegateTo> {
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
Expand Down Expand Up @@ -139,6 +157,25 @@ impl GlobalList {
I: Proxy + 'static,
State: Dispatch<I, U> + 'static,
U: Send + Sync + 'static,
{
self.bind_delegated::<I, State, U, State>(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<I, State, U, DelegateTo>(
&self,
qh: &QueueHandle<State>,
version: RangeInclusive<u32>,
udata: U,
) -> Result<I, BindError>
where
I: Proxy + 'static,
State: 'static,
U: Send + Sync + 'static,
DelegateTo: Dispatch<I, U, State> + 'static,
{
let version_start = *version.start();
let version_end = *version.end();
Expand Down Expand Up @@ -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::<I, U, DelegateTo>(udata),
)
.unwrap())
}

/// Returns the [`WlRegistry`][wl_registry] protocol object.
Expand Down Expand Up @@ -293,15 +336,16 @@ impl GlobalListContents {
}
}

struct RegistryState<State> {
struct RegistryState<State, DelegateTo> {
globals: GlobalListContents,
handle: QueueHandle<State>,
initial_roundtrip_done: AtomicBool,
_ph: PhantomData<fn() -> DelegateTo>,
}

impl<State: 'static> ObjectData for RegistryState<State>
impl<State: 'static, DelegateTo: 'static> ObjectData for RegistryState<State, DelegateTo>
where
State: Dispatch<wl_registry::WlRegistry, GlobalListContents>,
DelegateTo: Dispatch<wl_registry::WlRegistry, GlobalListContents, State>,
{
fn event(
self: Arc<Self>,
Expand Down Expand Up @@ -344,7 +388,10 @@ where
.inner
.lock()
.unwrap()
.enqueue_event::<wl_registry::WlRegistry, GlobalListContents>(msg, self.clone())
.enqueue_event::<wl_registry::WlRegistry, GlobalListContents, DelegateTo>(
msg,
self.clone(),
)
}

// We do not create any objects in this event handler.
Expand Down
Loading

0 comments on commit 67e0408

Please sign in to comment.