Skip to main content

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, GlobalListHandler},
17//!     protocol::{wl_registry, wl_compositor},
18//! };
19//! # use std::sync::Mutex;
20//! # struct State;
21//!
22//! // You need to provide a GlobalListHandler impl for your app
23//! impl GlobalListHandler for State {
24//!     /* react to dynamic global events here */
25//! }
26//!
27//! let conn = Connection::connect_to_env().unwrap();
28//! let (globals, queue) = registry_queue_init::<State>(&conn).unwrap();
29//!
30//! # impl wayland_client::Dispatch<wl_compositor::WlCompositor, State> for () {
31//! #     fn event(
32//! #         &self,
33//! #         state: &mut State,
34//! #         proxy: &wl_compositor::WlCompositor,
35//! #         event: wl_compositor::Event,
36//! #         conn: &Connection,
37//! #         qhandle: &QueueHandle<State>,
38//! #     ) {}
39//! # }
40//! // now you can bind the globals you need for your app
41//! let compositor: wl_compositor::WlCompositor = globals.bind_singleton(&queue.handle(), 4..=5, ()).unwrap();
42//! ```
43
44use std::{
45    fmt,
46    ops::RangeInclusive,
47    os::unix::io::OwnedFd,
48    sync::{
49        Arc, Mutex, OnceLock,
50        atomic::{AtomicBool, Ordering},
51    },
52};
53
54use wayland_backend::{
55    client::{Backend, InvalidId, ObjectData, ObjectId, WaylandError},
56    protocol::Message,
57};
58
59use crate::{
60    Connection, Dispatch, EventQueue, Proxy, QueueHandle,
61    protocol::{wl_display, wl_fixes, wl_registry},
62};
63
64/// Initialize a new event queue with its associated registry and retrieve the initial list of globals
65///
66/// See [the module level documentation][self] for more.
67pub fn registry_queue_init<State>(
68    conn: &Connection,
69) -> Result<(GlobalList, EventQueue<State>), GlobalError>
70where
71    State: GlobalListHandler + 'static,
72{
73    let event_queue = conn.new_event_queue();
74    let display = conn.display();
75    let fixes = OnceLock::<wl_fixes::WlFixes>::new();
76
77    let data = Arc::new(RegistryState {
78        globals: GlobalListContents { contents: Default::default(), fixes },
79        handle: event_queue.handle(),
80        initial_roundtrip_done: AtomicBool::new(false),
81    });
82    let registry = display.send_constructor(wl_display::Request::GetRegistry {}, data.clone())?;
83    // We don't need to dispatch the event queue as for now nothing will be sent to it
84    conn.roundtrip()?;
85    data.initial_roundtrip_done.store(true, Ordering::Relaxed);
86    Ok((GlobalList { registry }, event_queue))
87}
88
89/// Handler for runtime global addition/removal in [`GlobalList`] created with
90/// [`registry_queue_init`]
91pub trait GlobalListHandler: Sized {
92    /// A global has been added dynamically after creation of the [`GlobalList`]
93    ///
94    /// By default, does nothing.
95    fn runtime_add_global(
96        &mut self,
97        _globals: &GlobalList,
98        _conn: &Connection,
99        _qh: &QueueHandle<Self>,
100        _global: &Global,
101    ) {
102    }
103
104    /// A global has been removed
105    ///
106    /// By default, does nothing.
107    fn runtime_remove_global(
108        &mut self,
109        _globals: &GlobalList,
110        _conn: &Connection,
111        _qh: &QueueHandle<Self>,
112        _global: &Global,
113    ) {
114    }
115}
116
117/// A helper for global initialization.
118///
119/// See [the module level documentation][self] for more.
120#[derive(Clone, Debug)]
121pub struct GlobalList {
122    registry: wl_registry::WlRegistry,
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 greater 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 call
145    /// [`Self::bind_specific`] in the [`GlobalListHandler`] 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_singleton<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: 'static,
160        U: Dispatch<I, State> + Send + Sync + 'static,
161    {
162        let interface = I::interface();
163
164        if *version.end() > interface.version {
165            // This is a panic because it's a compile-time programmer error, not a runtime error.
166            panic!(
167                "Maximum version ({}) of {} was higher than the proxy's maximum version ({}); outdated wayland XML files?",
168                version.end(),
169                interface.name,
170                interface.version
171            );
172        }
173
174        let globals = &self.registry.data::<GlobalListContents>().unwrap().contents;
175        let guard = globals.lock().unwrap();
176        let global = guard
177            .iter()
178            // Find the global with the correct interface
179            .find(|Global { interface: interface_name, .. }| interface.name == interface_name)
180            .ok_or(BindError::NotPresent(interface.name))?;
181
182        self.bind_inner(qh, global, version, udata)
183    }
184
185    /// Binds a global, returning a new object associated with the global.
186    ///
187    /// This binds a specific object by its name.
188    ///
189    /// Typically, this should be called in [`GlobalListHandler::runtime_add_global`] for dynamically
190    /// added globals.
191    pub fn bind_specific<I, State, U>(
192        &self,
193        qh: &QueueHandle<State>,
194        name: u32,
195        version: std::ops::RangeInclusive<u32>,
196        udata: U,
197    ) -> Result<I, BindError>
198    where
199        I: Proxy + 'static,
200        State: 'static,
201        U: Dispatch<I, State> + Send + Sync + 'static,
202    {
203        let interface = I::interface();
204
205        if *version.end() > interface.version {
206            // This is a panic because it's a compile-time programmer error, not a runtime error.
207            panic!(
208                "Maximum version ({}) of {} was higher than the proxy's maximum version ({}); outdated wayland XML files?",
209                version.end(),
210                interface.name,
211                interface.version
212            );
213        }
214
215        let globals = &self.registry.data::<GlobalListContents>().unwrap().contents;
216        let guard = globals.lock().unwrap();
217        let global = guard
218            .iter()
219            // Find the global with correct name and interface
220            .find(|global| global.name == name && global.interface == interface.name)
221            // TODO Error for not finding name, rather than interface?
222            .ok_or(BindError::NotPresent(interface.name))?;
223
224        self.bind_inner(qh, global, version, udata)
225    }
226
227    fn bind_inner<I, State, U>(
228        &self,
229        qh: &QueueHandle<State>,
230        global: &Global,
231        version: RangeInclusive<u32>,
232        udata: U,
233    ) -> Result<I, BindError>
234    where
235        I: Proxy + 'static,
236        State: 'static,
237        U: Dispatch<I, State> + Send + Sync + 'static,
238    {
239        // Test version requirements
240        if *version.start() > global.version {
241            return Err(BindError::UnsupportedVersion {
242                interface: I::interface().name,
243                requested: *version.start(),
244                available: global.version,
245            });
246        }
247
248        // To get the version to bind, take the lower of the version advertised by the server and the maximum
249        // requested version.
250        let negotiated_version = global.version.min(*version.end());
251
252        Ok(self.registry.bind(global.name, negotiated_version, qh, udata))
253    }
254
255    /// Returns the [`WlRegistry`][wl_registry] protocol object.
256    ///
257    /// This may be used if more direct control when creating globals is needed.
258    pub fn registry(&self) -> &wl_registry::WlRegistry {
259        &self.registry
260    }
261
262    /// Tries to destroy the [`WlRegistry`][wl_registry] protocol object.
263    ///
264    /// If successful no new events will be emitted and the `GlobalListContent`
265    /// will not be updated anymore. Other proocol objects are not affected.
266    ///
267    /// This might end up doing nothing if the compositor doesn't support `wl_fixes`
268    /// in which case the registry cannot be destroyed without closing the connection.
269    pub fn destroy(self) {
270        if let Some(fixes) = self.contents().fixes.get() {
271            let id = self.registry.id();
272            fixes.destroy_registry(&self.registry);
273            if let Some(backend) = fixes.backend().upgrade() {
274                backend.destroy_object(&id).unwrap();
275            }
276            fixes.destroy();
277        }
278    }
279}
280
281/// An error that may occur when initializing the global list.
282#[derive(Debug)]
283pub enum GlobalError {
284    /// The backend generated an error
285    Backend(WaylandError),
286
287    /// An invalid object id was acted upon.
288    InvalidId(InvalidId),
289}
290
291impl std::error::Error for GlobalError {
292    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
293        match self {
294            GlobalError::Backend(source) => Some(source),
295            GlobalError::InvalidId(source) => std::error::Error::source(source),
296        }
297    }
298}
299
300impl std::fmt::Display for GlobalError {
301    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
302        match self {
303            GlobalError::Backend(source) => {
304                write!(f, "Backend error: {source}")
305            }
306            GlobalError::InvalidId(source) => write!(f, "{source}"),
307        }
308    }
309}
310
311impl From<WaylandError> for GlobalError {
312    fn from(source: WaylandError) -> Self {
313        GlobalError::Backend(source)
314    }
315}
316
317impl From<InvalidId> for GlobalError {
318    fn from(source: InvalidId) -> Self {
319        GlobalError::InvalidId(source)
320    }
321}
322
323/// An error that occurs when a binding a global fails.
324#[derive(Debug)]
325pub enum BindError {
326    /// The requested version of the global is not supported.
327    UnsupportedVersion {
328        /// The name of the global for which the server provides a too low value.
329        interface: &'static str,
330        /// The lowest version that was requested by the caller, must be greater than [`Self::UnsupportedVersion::requested`].
331        requested: u32,
332        /// The actual verison that was available on the server, must be less than [`Self::UnsupportedVersion::requested`].
333        available: u32,
334    },
335
336    /// The requested global was not found in the registry.
337    NotPresent(&'static str),
338}
339
340impl std::error::Error for BindError {}
341
342impl fmt::Display for BindError {
343    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
344        match self {
345            BindError::UnsupportedVersion { interface, requested, available } => {
346                write!(
347                    f,
348                    "the requested version `{requested}` of the global `{interface}` is not supported, only `{available}` is available"
349                )
350            }
351            BindError::NotPresent(name) => {
352                write!(f, "the requested global `{name}` was not found in the registry")
353            }
354        }
355    }
356}
357
358/// Description of a global.
359#[derive(Debug, Clone, PartialEq, Eq)]
360pub struct Global {
361    /// The name of the global.
362    ///
363    /// This is an identifier used by the server to reference some specific global.
364    pub name: u32,
365    /// The interface of the global.
366    ///
367    /// This describes what type of protocol object the global is.
368    pub interface: String,
369    /// The advertised version of the global.
370    ///
371    /// This specifies the maximum version of the global that may be bound. This means any lower version of
372    /// the global may be bound.
373    pub version: u32,
374}
375
376/// A container representing the current contents of the list of globals
377#[derive(Debug)]
378pub struct GlobalListContents {
379    contents: Mutex<Vec<Global>>,
380    fixes: OnceLock<wl_fixes::WlFixes>,
381}
382
383impl GlobalListContents {
384    /// Access the list of globals
385    ///
386    /// Your closure is invoked on the global list, and its return value is forwarded to the return value
387    /// of this function. This allows you to process the list without making a copy.
388    pub fn with_list<T, F: FnOnce(&[Global]) -> T>(&self, f: F) -> T {
389        let guard = self.contents.lock().unwrap();
390        f(&guard)
391    }
392
393    /// Get a copy of the contents of the list of globals.
394    pub fn clone_list(&self) -> Vec<Global> {
395        self.contents.lock().unwrap().clone()
396    }
397
398    fn add(&self, global: Global) {
399        self.contents.lock().unwrap().push(global);
400    }
401
402    fn remove(&self, name: u32) -> Option<Global> {
403        let mut guard = self.contents.lock().unwrap();
404        let idx = guard.iter().position(|i| i.name == name)?;
405        Some(guard.remove(idx))
406    }
407}
408
409impl<D> Dispatch<wl_registry::WlRegistry, D> for GlobalListContents
410where
411    D: GlobalListHandler,
412{
413    fn event(
414        &self,
415        state: &mut D,
416        registry: &wl_registry::WlRegistry,
417        event: wl_registry::Event,
418        conn: &Connection,
419        qh: &QueueHandle<D>,
420    ) {
421        let globals = GlobalList { registry: registry.clone() };
422        match event {
423            wl_registry::Event::Global { name, interface, version } => {
424                let global = Global { name, interface, version };
425                self.add(global.clone());
426                state.runtime_add_global(&globals, conn, qh, &global);
427            }
428            wl_registry::Event::GlobalRemove { name } => {
429                if let Some(global) = self.remove(name) {
430                    state.runtime_remove_global(&globals, conn, qh, &global);
431                }
432            }
433        }
434    }
435}
436
437struct RegistryState<State> {
438    globals: GlobalListContents,
439    handle: QueueHandle<State>,
440    initial_roundtrip_done: AtomicBool,
441}
442
443impl<State> ObjectData for RegistryState<State>
444where
445    State: GlobalListHandler + 'static,
446{
447    fn event(
448        self: Arc<Self>,
449        backend: &Backend,
450        msg: Message<ObjectId, OwnedFd>,
451    ) -> Option<Arc<dyn ObjectData>> {
452        // For initial roundtrip, update immediately without waiting for dispatch.
453        // So globals are available after `registry_queue_init` returns.
454        // later, handle in `Dispatch` implementation.
455        if !self.initial_roundtrip_done.load(Ordering::Relaxed) {
456            let conn = Connection::from_backend(backend.clone());
457            // Can't do much if the server sends a malformed message
458            if let Ok((registry, event)) = wl_registry::WlRegistry::parse_event(&conn, msg) {
459                match event {
460                    wl_registry::Event::Global { name, interface, version } => {
461                        let wl_fixes_ver = 1u32..=1;
462                        if interface == "wl_fixes" && version >= *wl_fixes_ver.start() {
463                            let _ = self.globals.fixes.set(registry.bind(
464                                name,
465                                version.min(*wl_fixes_ver.end()),
466                                &self.handle,
467                                crate::Noop,
468                            ));
469                        }
470
471                        self.globals.add(Global { name, interface, version });
472                    }
473
474                    wl_registry::Event::GlobalRemove { name: remove } => {
475                        self.globals.remove(remove);
476                    }
477                }
478            };
479        } else {
480            // forward the message to the event queue as normal
481            self.handle
482                .inner
483                .lock()
484                .unwrap()
485                .enqueue_event::<wl_registry::WlRegistry, GlobalListContents>(msg, self.clone())
486        }
487
488        // We do not create any objects in this event handler.
489        None
490    }
491
492    fn destroyed(&self, _id: ObjectId) {}
493
494    fn data_as_any(&self) -> &dyn std::any::Any {
495        &self.globals
496    }
497}