wayland_client/
globals.rs

1//! Helpers for handling the initialization of an app
2//!
3//! At the startup of your Wayland app, the initial step is generally to retrieve the list of globals
4//! advertized by the compositor from the registry. Using the [`Dispatch`] mechanism for this task can be
5//! very unpractical, this is why this module provides a special helper for handling the registry.
6//!
7//! The entry point of this helper is the [`registry_queue_init`] function. Given a reference to your
8//! [`Connection`] it will create an [`EventQueue`], retrieve the initial list of globals, and register a
9//! handler using your provided `Dispatch<WlRegistry,_>` implementation for handling dynamic registry events.
10//!
11//! ## Example
12//!
13//! ```no_run
14//! use wayland_client::{
15//!     Connection, Dispatch, QueueHandle,
16//!     globals::{registry_queue_init, Global, GlobalListContents},
17//!     protocol::{wl_registry, wl_compositor},
18//! };
19//! # use std::sync::Mutex;
20//! # struct State;
21//!
22//! // You need to provide a Dispatch<WlRegistry, GlobalListContents> impl for your app
23//! impl wayland_client::Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
24//!     fn event(
25//!         state: &mut State,
26//!         proxy: &wl_registry::WlRegistry,
27//!         event: wl_registry::Event,
28//!         // This mutex contains an up-to-date list of the currently known globals
29//!         // including the one that was just added or destroyed
30//!         data: &GlobalListContents,
31//!         conn: &Connection,
32//!         qhandle: &QueueHandle<State>,
33//!     ) {
34//!         /* react to dynamic global events here */
35//!     }
36//! }
37//!
38//! let conn = Connection::connect_to_env().unwrap();
39//! let (globals, queue) = registry_queue_init::<State>(&conn).unwrap();
40//!
41//! # impl wayland_client::Dispatch<wl_compositor::WlCompositor, ()> for State {
42//! #     fn event(
43//! #         state: &mut State,
44//! #         proxy: &wl_compositor::WlCompositor,
45//! #         event: wl_compositor::Event,
46//! #         data: &(),
47//! #         conn: &Connection,
48//! #         qhandle: &QueueHandle<State>,
49//! #     ) {}
50//! # }
51//! // now you can bind the globals you need for your app
52//! let compositor: wl_compositor::WlCompositor = globals.bind(&queue.handle(), 4..=5, ()).unwrap();
53//! ```
54
55use std::{
56    fmt,
57    ops::RangeInclusive,
58    os::unix::io::OwnedFd,
59    sync::{
60        atomic::{AtomicBool, Ordering},
61        Arc, Mutex, OnceLock,
62    },
63};
64
65use wayland_backend::{
66    client::{Backend, InvalidId, ObjectData, ObjectId, WaylandError},
67    protocol::Message,
68};
69
70use crate::{
71    protocol::{wl_display, wl_fixes, wl_registry},
72    Connection, Dispatch, EventQueue, Proxy, QueueHandle,
73};
74
75/// Initialize a new event queue with its associated registry and retrieve the initial list of globals
76///
77/// See [the module level documentation][self] for more.
78pub fn registry_queue_init<State>(
79    conn: &Connection,
80) -> Result<(GlobalList, EventQueue<State>), GlobalError>
81where
82    State: Dispatch<wl_registry::WlRegistry, GlobalListContents> + 'static,
83{
84    let event_queue = conn.new_event_queue();
85    let display = conn.display();
86    let fixes = Arc::new(OnceLock::<wl_fixes::WlFixes>::new());
87
88    let data = Arc::new(RegistryState {
89        globals: GlobalListContents { contents: Default::default() },
90        handle: event_queue.handle(),
91        fixes: fixes.clone(),
92        initial_roundtrip_done: AtomicBool::new(false),
93    });
94    let registry = display.send_constructor(wl_display::Request::GetRegistry {}, data.clone())?;
95    // We don't need to dispatch the event queue as for now nothing will be sent to it
96    conn.roundtrip()?;
97    data.initial_roundtrip_done.store(true, Ordering::Relaxed);
98    Ok((GlobalList { registry, fixes }, event_queue))
99}
100
101/// A helper for global initialization.
102///
103/// See [the module level documentation][self] for more.
104#[derive(Debug)]
105pub struct GlobalList {
106    registry: wl_registry::WlRegistry,
107    fixes: Arc<OnceLock<wl_fixes::WlFixes>>,
108}
109
110struct Fixes;
111
112impl ObjectData for Fixes {
113    fn event(
114        self: Arc<Self>,
115        _backend: &Backend,
116        _msg: Message<ObjectId, OwnedFd>,
117    ) -> Option<Arc<dyn ObjectData>> {
118        // wl_fixes has no events
119        None
120    }
121
122    fn destroyed(&self, _object_id: ObjectId) {}
123}
124
125impl GlobalList {
126    /// Access the contents of the list of globals
127    pub fn contents(&self) -> &GlobalListContents {
128        self.registry.data::<GlobalListContents>().unwrap()
129    }
130
131    /// Binds a global, returning a new protocol object associated with the global.
132    ///
133    /// The `version` specifies the range of versions that should be bound. This function will guarantee the
134    /// version of the returned protocol object is the lower of the maximum requested version and the advertised
135    /// version.
136    ///
137    /// If the lower bound of the `version` is less than the version advertised by the server, then
138    /// [`BindError::UnsupportedVersion`] is returned.
139    ///
140    /// ## Multi-instance/Device globals.
141    ///
142    /// This function is not intended to be used with globals that have multiple instances such as `wl_output`
143    /// and `wl_seat`. These types of globals need their own initialization mechanism because these
144    /// multi-instance globals may be removed at runtime. To handle then, you should instead rely on the
145    /// `Dispatch` implementation for `WlRegistry` of your `State`.
146    ///
147    /// # Panics
148    ///
149    /// This function will panic if the maximum requested version is greater than the known maximum version of
150    /// the interface. The known maximum version is determined by the code generated using wayland-scanner.
151    pub fn bind<I, State, U>(
152        &self,
153        qh: &QueueHandle<State>,
154        version: RangeInclusive<u32>,
155        udata: U,
156    ) -> Result<I, BindError>
157    where
158        I: Proxy + 'static,
159        State: Dispatch<I, U> + 'static,
160        U: Send + Sync + 'static,
161    {
162        let version_start = *version.start();
163        let version_end = *version.end();
164        let interface = I::interface();
165
166        if *version.end() > interface.version {
167            // This is a panic because it's a compile-time programmer error, not a runtime error.
168            panic!("Maximum version ({}) of {} was higher than the proxy's maximum version ({}); outdated wayland XML files?",
169                version.end(), interface.name, interface.version);
170        }
171
172        let globals = &self.registry.data::<GlobalListContents>().unwrap().contents;
173        let guard = globals.lock().unwrap();
174        let (name, version) = guard
175            .iter()
176            // Find the with the correct interface
177            .filter_map(|Global { name, interface: interface_name, version }| {
178                // TODO: then_some
179                if interface.name == &interface_name[..] {
180                    Some((*name, *version))
181                } else {
182                    None
183                }
184            })
185            .next()
186            .ok_or(BindError::NotPresent)?;
187
188        // Test version requirements
189        if version < version_start {
190            return Err(BindError::UnsupportedVersion);
191        }
192
193        // To get the version to bind, take the lower of the version advertised by the server and the maximum
194        // requested version.
195        let version = version.min(version_end);
196
197        Ok(self.registry.bind(name, version, qh, udata))
198    }
199
200    /// Returns the [`WlRegistry`][wl_registry] protocol object.
201    ///
202    /// This may be used if more direct control when creating globals is needed.
203    pub fn registry(&self) -> &wl_registry::WlRegistry {
204        &self.registry
205    }
206
207    /// Tries to destroy the [`WlRegistry`][wl_registry] protocol object.
208    ///
209    /// If successful no new events will be emitted and the `GlobalListContent`
210    /// will not be updated anymore. Other proocol objects are not affected.
211    ///
212    /// This might end up doing nothing if the compositor doesn't support `wl_fixes`
213    /// in which case the registry cannot be destroyed without closing the connection.
214    pub fn destroy(self) {
215        if let Some(fixes) = self.fixes.get() {
216            let id = self.registry.id();
217            fixes.destroy_registry(&self.registry);
218            if let Some(backend) = fixes.backend().upgrade() {
219                backend.destroy_object(&id).unwrap();
220            }
221            fixes.destroy();
222        }
223    }
224}
225
226/// An error that may occur when initializing the global list.
227#[derive(Debug)]
228pub enum GlobalError {
229    /// The backend generated an error
230    Backend(WaylandError),
231
232    /// An invalid object id was acted upon.
233    InvalidId(InvalidId),
234}
235
236impl std::error::Error for GlobalError {
237    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
238        match self {
239            GlobalError::Backend(source) => Some(source),
240            GlobalError::InvalidId(source) => std::error::Error::source(source),
241        }
242    }
243}
244
245impl std::fmt::Display for GlobalError {
246    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
247        match self {
248            GlobalError::Backend(source) => {
249                write!(f, "Backend error: {source}")
250            }
251            GlobalError::InvalidId(source) => write!(f, "{source}"),
252        }
253    }
254}
255
256impl From<WaylandError> for GlobalError {
257    fn from(source: WaylandError) -> Self {
258        GlobalError::Backend(source)
259    }
260}
261
262impl From<InvalidId> for GlobalError {
263    fn from(source: InvalidId) -> Self {
264        GlobalError::InvalidId(source)
265    }
266}
267
268/// An error that occurs when a binding a global fails.
269#[derive(Debug)]
270pub enum BindError {
271    /// The requested version of the global is not supported.
272    UnsupportedVersion,
273
274    /// The requested global was not found in the registry.
275    NotPresent,
276}
277
278impl std::error::Error for BindError {}
279
280impl fmt::Display for BindError {
281    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
282        match self {
283            BindError::UnsupportedVersion => {
284                write!(f, "the requested version of the global is not supported")
285            }
286            BindError::NotPresent => {
287                write!(f, "the requested global was not found in the registry")
288            }
289        }
290    }
291}
292
293/// Description of a global.
294#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct Global {
296    /// The name of the global.
297    ///
298    /// This is an identifier used by the server to reference some specific global.
299    pub name: u32,
300    /// The interface of the global.
301    ///
302    /// This describes what type of protocol object the global is.
303    pub interface: String,
304    /// The advertised version of the global.
305    ///
306    /// This specifies the maximum version of the global that may be bound. This means any lower version of
307    /// the global may be bound.
308    pub version: u32,
309}
310
311/// A container representing the current contents of the list of globals
312#[derive(Debug)]
313pub struct GlobalListContents {
314    contents: Mutex<Vec<Global>>,
315}
316
317impl GlobalListContents {
318    /// Access the list of globals
319    ///
320    /// Your closure is invoked on the global list, and its return value is forwarded to the return value
321    /// of this function. This allows you to process the list without making a copy.
322    pub fn with_list<T, F: FnOnce(&[Global]) -> T>(&self, f: F) -> T {
323        let guard = self.contents.lock().unwrap();
324        f(&guard)
325    }
326
327    /// Get a copy of the contents of the list of globals.
328    pub fn clone_list(&self) -> Vec<Global> {
329        self.contents.lock().unwrap().clone()
330    }
331}
332
333struct RegistryState<State> {
334    globals: GlobalListContents,
335    handle: QueueHandle<State>,
336    fixes: Arc<OnceLock<wl_fixes::WlFixes>>,
337    initial_roundtrip_done: AtomicBool,
338}
339
340impl<State: 'static> ObjectData for RegistryState<State>
341where
342    State: Dispatch<wl_registry::WlRegistry, GlobalListContents>,
343{
344    fn event(
345        self: Arc<Self>,
346        backend: &Backend,
347        msg: Message<ObjectId, OwnedFd>,
348    ) -> Option<Arc<dyn ObjectData>> {
349        let conn = Connection::from_backend(backend.clone());
350
351        // The registry messages don't contain any fd, so use some type trickery to
352        // clone the message
353        #[derive(Debug, Clone)]
354        enum Void {}
355        let msg: Message<ObjectId, Void> = msg.map_fd(|_| unreachable!());
356        let to_forward = if self.initial_roundtrip_done.load(Ordering::Relaxed) {
357            Some(msg.clone().map_fd(|v| match v {}))
358        } else {
359            None
360        };
361        // and restore the type
362        let msg = msg.map_fd(|v| match v {});
363
364        // Can't do much if the server sends a malformed message
365        if let Ok((registry, event)) = wl_registry::WlRegistry::parse_event(&conn, msg) {
366            match event {
367                wl_registry::Event::Global { name, interface, version } => {
368                    let wl_fixes_ver = 1u32..=1;
369                    if interface == "wl_fixes" && version >= *wl_fixes_ver.start() {
370                        let _ = self.fixes.set(
371                            registry
372                                .send_constructor(
373                                    wl_registry::Request::Bind {
374                                        name,
375                                        id: (
376                                            wl_fixes::WlFixes::interface(),
377                                            version.min(*wl_fixes_ver.end()),
378                                        ),
379                                    },
380                                    Arc::new(Fixes),
381                                )
382                                .expect("We just created this registry"),
383                        );
384                    }
385
386                    let mut guard = self.globals.contents.lock().unwrap();
387                    guard.push(Global { name, interface, version });
388                }
389
390                wl_registry::Event::GlobalRemove { name: remove } => {
391                    let mut guard = self.globals.contents.lock().unwrap();
392                    guard.retain(|Global { name, .. }| name != &remove);
393                }
394            }
395        };
396
397        if let Some(msg) = to_forward {
398            // forward the message to the event queue as normal
399            self.handle
400                .inner
401                .lock()
402                .unwrap()
403                .enqueue_event::<wl_registry::WlRegistry, GlobalListContents>(msg, self.clone())
404        }
405
406        // We do not create any objects in this event handler.
407        None
408    }
409
410    fn destroyed(&self, _id: ObjectId) {}
411
412    fn data_as_any(&self) -> &dyn std::any::Any {
413        &self.globals
414    }
415}