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}