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}