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,
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_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 data = Arc::new(RegistryState {
87        globals: GlobalListContents { contents: Default::default() },
88        handle: event_queue.handle(),
89        initial_roundtrip_done: AtomicBool::new(false),
90    });
91    let registry = display.send_constructor(wl_display::Request::GetRegistry {}, data.clone())?;
92    // We don't need to dispatch the event queue as for now nothing will be sent to it
93    conn.roundtrip()?;
94    data.initial_roundtrip_done.store(true, Ordering::Relaxed);
95    Ok((GlobalList { registry }, event_queue))
96}
97
98/// A helper for global initialization.
99///
100/// See [the module level documentation][self] for more.
101#[derive(Debug)]
102pub struct GlobalList {
103    registry: wl_registry::WlRegistry,
104}
105
106impl GlobalList {
107    /// Access the contents of the list of globals
108    pub fn contents(&self) -> &GlobalListContents {
109        self.registry.data::<GlobalListContents>().unwrap()
110    }
111
112    /// Binds a global, returning a new protocol object associated with the global.
113    ///
114    /// The `version` specifies the range of versions that should be bound. This function will guarantee the
115    /// version of the returned protocol object is the lower of the maximum requested version and the advertised
116    /// version.
117    ///
118    /// If the lower bound of the `version` is less than the version advertised by the server, then
119    /// [`BindError::UnsupportedVersion`] is returned.
120    ///
121    /// ## Multi-instance/Device globals.
122    ///
123    /// This function is not intended to be used with globals that have multiple instances such as `wl_output`
124    /// and `wl_seat`. These types of globals need their own initialization mechanism because these
125    /// multi-instance globals may be removed at runtime. To handle then, you should instead rely on the
126    /// `Dispatch` implementation for `WlRegistry` of your `State`.
127    ///
128    /// # Panics
129    ///
130    /// This function will panic if the maximum requested version is greater than the known maximum version of
131    /// the interface. The known maximum version is determined by the code generated using wayland-scanner.
132    pub fn bind<I, State, U>(
133        &self,
134        qh: &QueueHandle<State>,
135        version: RangeInclusive<u32>,
136        udata: U,
137    ) -> Result<I, BindError>
138    where
139        I: Proxy + 'static,
140        State: Dispatch<I, U> + 'static,
141        U: Send + Sync + 'static,
142    {
143        let version_start = *version.start();
144        let version_end = *version.end();
145        let interface = I::interface();
146
147        if *version.end() > interface.version {
148            // This is a panic because it's a compile-time programmer error, not a runtime error.
149            panic!("Maximum version ({}) of {} was higher than the proxy's maximum version ({}); outdated wayland XML files?",
150                version.end(), interface.name, interface.version);
151        }
152
153        let globals = &self.registry.data::<GlobalListContents>().unwrap().contents;
154        let guard = globals.lock().unwrap();
155        let (name, version) = guard
156            .iter()
157            // Find the with the correct interface
158            .filter_map(|Global { name, interface: interface_name, version }| {
159                // TODO: then_some
160                if interface.name == &interface_name[..] {
161                    Some((*name, *version))
162                } else {
163                    None
164                }
165            })
166            .next()
167            .ok_or(BindError::NotPresent)?;
168
169        // Test version requirements
170        if version < version_start {
171            return Err(BindError::UnsupportedVersion);
172        }
173
174        // To get the version to bind, take the lower of the version advertised by the server and the maximum
175        // requested version.
176        let version = version.min(version_end);
177
178        Ok(self.registry.bind(name, version, qh, udata))
179    }
180
181    /// Returns the [`WlRegistry`][wl_registry] protocol object.
182    ///
183    /// This may be used if more direct control when creating globals is needed.
184    pub fn registry(&self) -> &wl_registry::WlRegistry {
185        &self.registry
186    }
187}
188
189/// An error that may occur when initializing the global list.
190#[derive(Debug)]
191pub enum GlobalError {
192    /// The backend generated an error
193    Backend(WaylandError),
194
195    /// An invalid object id was acted upon.
196    InvalidId(InvalidId),
197}
198
199impl std::error::Error for GlobalError {
200    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
201        match self {
202            GlobalError::Backend(source) => Some(source),
203            GlobalError::InvalidId(source) => std::error::Error::source(source),
204        }
205    }
206}
207
208impl std::fmt::Display for GlobalError {
209    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
210        match self {
211            GlobalError::Backend(source) => {
212                write!(f, "Backend error: {source}")
213            }
214            GlobalError::InvalidId(source) => write!(f, "{source}"),
215        }
216    }
217}
218
219impl From<WaylandError> for GlobalError {
220    fn from(source: WaylandError) -> Self {
221        GlobalError::Backend(source)
222    }
223}
224
225impl From<InvalidId> for GlobalError {
226    fn from(source: InvalidId) -> Self {
227        GlobalError::InvalidId(source)
228    }
229}
230
231/// An error that occurs when a binding a global fails.
232#[derive(Debug)]
233pub enum BindError {
234    /// The requested version of the global is not supported.
235    UnsupportedVersion,
236
237    /// The requested global was not found in the registry.
238    NotPresent,
239}
240
241impl std::error::Error for BindError {}
242
243impl fmt::Display for BindError {
244    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
245        match self {
246            BindError::UnsupportedVersion {} => {
247                write!(f, "the requested version of the global is not supported")
248            }
249            BindError::NotPresent {} => {
250                write!(f, "the requested global was not found in the registry")
251            }
252        }
253    }
254}
255
256/// Description of a global.
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub struct Global {
259    /// The name of the global.
260    ///
261    /// This is an identifier used by the server to reference some specific global.
262    pub name: u32,
263    /// The interface of the global.
264    ///
265    /// This describes what type of protocol object the global is.
266    pub interface: String,
267    /// The advertised version of the global.
268    ///
269    /// This specifies the maximum version of the global that may be bound. This means any lower version of
270    /// the global may be bound.
271    pub version: u32,
272}
273
274/// A container representing the current contents of the list of globals
275#[derive(Debug)]
276pub struct GlobalListContents {
277    contents: Mutex<Vec<Global>>,
278}
279
280impl GlobalListContents {
281    /// Access the list of globals
282    ///
283    /// Your closure is invoked on the global list, and its return value is forwarded to the return value
284    /// of this function. This allows you to process the list without making a copy.
285    pub fn with_list<T, F: FnOnce(&[Global]) -> T>(&self, f: F) -> T {
286        let guard = self.contents.lock().unwrap();
287        f(&guard)
288    }
289
290    /// Get a copy of the contents of the list of globals.
291    pub fn clone_list(&self) -> Vec<Global> {
292        self.contents.lock().unwrap().clone()
293    }
294}
295
296struct RegistryState<State> {
297    globals: GlobalListContents,
298    handle: QueueHandle<State>,
299    initial_roundtrip_done: AtomicBool,
300}
301
302impl<State: 'static> ObjectData for RegistryState<State>
303where
304    State: Dispatch<wl_registry::WlRegistry, GlobalListContents>,
305{
306    fn event(
307        self: Arc<Self>,
308        backend: &Backend,
309        msg: Message<ObjectId, OwnedFd>,
310    ) -> Option<Arc<dyn ObjectData>> {
311        let conn = Connection::from_backend(backend.clone());
312
313        // The registry messages don't contain any fd, so use some type trickery to
314        // clone the message
315        #[derive(Debug, Clone)]
316        enum Void {}
317        let msg: Message<ObjectId, Void> = msg.map_fd(|_| unreachable!());
318        let to_forward = if self.initial_roundtrip_done.load(Ordering::Relaxed) {
319            Some(msg.clone().map_fd(|v| match v {}))
320        } else {
321            None
322        };
323        // and restore the type
324        let msg = msg.map_fd(|v| match v {});
325
326        // Can't do much if the server sends a malformed message
327        if let Ok((_, event)) = wl_registry::WlRegistry::parse_event(&conn, msg) {
328            match event {
329                wl_registry::Event::Global { name, interface, version } => {
330                    let mut guard = self.globals.contents.lock().unwrap();
331                    guard.push(Global { name, interface, version });
332                }
333
334                wl_registry::Event::GlobalRemove { name: remove } => {
335                    let mut guard = self.globals.contents.lock().unwrap();
336                    guard.retain(|Global { name, .. }| name != &remove);
337                }
338            }
339        };
340
341        if let Some(msg) = to_forward {
342            // forward the message to the event queue as normal
343            self.handle
344                .inner
345                .lock()
346                .unwrap()
347                .enqueue_event::<wl_registry::WlRegistry, GlobalListContents>(msg, self.clone())
348        }
349
350        // We do not create any objects in this event handler.
351        None
352    }
353
354    fn destroyed(&self, _id: ObjectId) {
355        // A registry cannot be destroyed unless disconnected.
356    }
357
358    fn data_as_any(&self) -> &dyn std::any::Any {
359        &self.globals
360    }
361}