1 | //! Utilities for binding globals with [`wl_registry`] in delegates. |
2 | //! |
3 | //! This module is based around the [`RegistryHandler`] trait and [`RegistryState`]. |
4 | //! |
5 | //! [`RegistryState`] provides an interface to bind globals regularly, creating an object with each new |
6 | //! instantiation or caching bound globals to prevent duplicate object instances from being created. Binding |
7 | //! a global regularly is accomplished through [`RegistryState::bind_one`]. |
8 | //! |
9 | //! The [`delegate_registry`](crate::delegate_registry) macro is used to implement handling for [`wl_registry`]. |
10 | //! |
11 | //! ## Sample implementation of [`RegistryHandler`] |
12 | //! |
13 | //! ``` |
14 | //! use smithay_client_toolkit::reexports::client::{ |
15 | //! Connection, Dispatch, QueueHandle, |
16 | //! delegate_dispatch, |
17 | //! globals::GlobalList, |
18 | //! protocol::wl_output, |
19 | //! }; |
20 | //! |
21 | //! use smithay_client_toolkit::registry::{ |
22 | //! GlobalProxy, ProvidesRegistryState, RegistryHandler, RegistryState, |
23 | //! }; |
24 | //! |
25 | //! struct ExampleApp { |
26 | //! /// The registry state is needed to use the global abstractions. |
27 | //! registry_state: RegistryState, |
28 | //! /// This is a type we want to delegate global handling to. |
29 | //! delegate_that_wants_registry: Delegate, |
30 | //! } |
31 | //! |
32 | //! /// The delegate a global should be provided to. |
33 | //! struct Delegate { |
34 | //! outputs: Vec<wl_output::WlOutput>, |
35 | //! } |
36 | //! |
37 | //! // When implementing RegistryHandler, you must be able to dispatch any type you could bind using the registry state. |
38 | //! impl<D> RegistryHandler<D> for Delegate |
39 | //! where |
40 | //! // In order to bind a global, you must statically assert the global may be handled with the data type. |
41 | //! D: Dispatch<wl_output::WlOutput, ()> |
42 | //! // ProvidesRegistryState provides a function to access the RegistryState within the impl. |
43 | //! + ProvidesRegistryState |
44 | //! // We need some way to access our part of the application's state. This uses AsMut, |
45 | //! // but you may prefer to create your own trait to avoid making .as_mut() ambiguous. |
46 | //! + AsMut<Delegate> |
47 | //! + 'static, |
48 | //! { |
49 | //! /// New global added after initial enumeration. |
50 | //! fn new_global( |
51 | //! data: &mut D, |
52 | //! conn: &Connection, |
53 | //! qh: &QueueHandle<D>, |
54 | //! name: u32, |
55 | //! interface: &str, |
56 | //! version: u32, |
57 | //! ) { |
58 | //! if interface == "wl_output" { |
59 | //! // Bind `wl_output` with newest version from 1 to 4 the compositor supports |
60 | //! let output = data.registry().bind_specific(qh, name, 1..=4, ()).unwrap(); |
61 | //! data.as_mut().outputs.push(output); |
62 | //! } |
63 | //! |
64 | //! // You could either handle errors here or when attempting to use the interface. Most |
65 | //! // Wayland protocols are optional, so if your application can function without a |
66 | //! // protocol it should try to do so; the From impl of GlobalProxy is written to make |
67 | //! // this straightforward. |
68 | //! } |
69 | //! } |
70 | //! ``` |
71 | |
72 | use crate::{error::GlobalError, globals::ProvidesBoundGlobal}; |
73 | use wayland_client::{ |
74 | globals::{BindError, Global, GlobalList, GlobalListContents}, |
75 | protocol::wl_registry, |
76 | Connection, Dispatch, Proxy, QueueHandle, |
77 | }; |
78 | |
79 | /// A trait implemented by modular parts of a smithay's client toolkit and protocol delegates that may be used |
80 | /// to receive notification of a global being created or destroyed. |
81 | /// |
82 | /// Delegates that choose to implement this trait may be used in [`registry_handlers`] which |
83 | /// automatically notifies delegates about the creation and destruction of globals. |
84 | /// |
85 | /// [`registry_handlers`]: crate::registry_handlers |
86 | /// |
87 | /// Note that in order to delegate registry handling to a type which implements this trait, your `D` data type |
88 | /// must implement [`ProvidesRegistryState`]. |
89 | pub trait RegistryHandler<D> |
90 | where |
91 | D: ProvidesRegistryState, |
92 | { |
93 | /// Called when a new global has been advertised by the compositor. |
94 | /// |
95 | /// The provided registry handle may be used to bind the global. This is not called during |
96 | /// initial enumeration of globals. It is primarily useful for multi-instance globals such as |
97 | /// `wl_output` and `wl_seat`. |
98 | /// |
99 | /// The default implementation does nothing. |
100 | fn new_global( |
101 | data: &mut D, |
102 | conn: &Connection, |
103 | qh: &QueueHandle<D>, |
104 | name: u32, |
105 | interface: &str, |
106 | version: u32, |
107 | ) { |
108 | let _ = (data, conn, qh, name, interface, version); |
109 | } |
110 | |
111 | /// Called when a global has been destroyed by the compositor. |
112 | /// |
113 | /// The default implementation does nothing. |
114 | fn remove_global( |
115 | data: &mut D, |
116 | conn: &Connection, |
117 | qh: &QueueHandle<D>, |
118 | name: u32, |
119 | interface: &str, |
120 | ) { |
121 | let _ = (data, conn, qh, name, interface); |
122 | } |
123 | } |
124 | |
125 | /// Trait which asserts a data type may provide a mutable reference to the registry state. |
126 | /// |
127 | /// Typically this trait will be required by delegates or [`RegistryHandler`] implementations which need |
128 | /// to access the registry utilities provided by Smithay's client toolkit. |
129 | pub trait ProvidesRegistryState: Sized { |
130 | /// Returns a mutable reference to the registry state. |
131 | fn registry(&mut self) -> &mut RegistryState; |
132 | |
133 | /// Called when a new global has been advertised by the compositor. |
134 | /// |
135 | /// This is not called during initial global enumeration. |
136 | fn runtime_add_global( |
137 | &mut self, |
138 | conn: &Connection, |
139 | qh: &QueueHandle<Self>, |
140 | name: u32, |
141 | interface: &str, |
142 | version: u32, |
143 | ); |
144 | |
145 | /// Called when a global has been destroyed by the compositor. |
146 | fn runtime_remove_global( |
147 | &mut self, |
148 | conn: &Connection, |
149 | qh: &QueueHandle<Self>, |
150 | name: u32, |
151 | interface: &str, |
152 | ); |
153 | } |
154 | |
155 | /// State object associated with the registry handling for smithay's client toolkit. |
156 | /// |
157 | /// This object provides utilities to cache bound globals that are needed by multiple modules. |
158 | #[derive (Debug)] |
159 | pub struct RegistryState { |
160 | registry: wl_registry::WlRegistry, |
161 | globals: Vec<Global>, |
162 | } |
163 | |
164 | impl RegistryState { |
165 | /// Creates a new registry handle. |
166 | /// |
167 | /// This type may be used to bind globals as they are advertised. |
168 | pub fn new(global_list: &GlobalList) -> Self { |
169 | let registry = global_list.registry().clone(); |
170 | let globals = global_list.contents().clone_list(); |
171 | |
172 | RegistryState { registry, globals } |
173 | } |
174 | |
175 | pub fn registry(&self) -> &wl_registry::WlRegistry { |
176 | &self.registry |
177 | } |
178 | |
179 | /// Returns an iterator over all globals. |
180 | /// |
181 | /// This list may change if the compositor adds or removes globals after initial |
182 | /// enumeration. |
183 | /// |
184 | /// No guarantees are provided about the ordering of the globals in this iterator. |
185 | pub fn globals(&self) -> impl Iterator<Item = &Global> + '_ { |
186 | self.globals.iter() |
187 | } |
188 | |
189 | /// Returns an iterator over all globals implementing the given interface. |
190 | /// |
191 | /// This may be more efficient than searching [Self::globals]. |
192 | pub fn globals_by_interface<'a>( |
193 | &'a self, |
194 | interface: &'a str, |
195 | ) -> impl Iterator<Item = &Global> + 'a { |
196 | self.globals.iter().filter(move |g| g.interface == interface) |
197 | } |
198 | |
199 | /// Binds a global, returning a new object associated with the global. |
200 | /// |
201 | /// This should not be used to bind globals that have multiple instances such as `wl_output`; |
202 | /// use [Self::bind_all] instead. |
203 | pub fn bind_one<I, D, U>( |
204 | &self, |
205 | qh: &QueueHandle<D>, |
206 | version: std::ops::RangeInclusive<u32>, |
207 | udata: U, |
208 | ) -> Result<I, BindError> |
209 | where |
210 | D: Dispatch<I, U> + 'static, |
211 | I: Proxy + 'static, |
212 | U: Send + Sync + 'static, |
213 | { |
214 | bind_one(&self.registry, &self.globals, qh, version, udata) |
215 | } |
216 | |
217 | /// Binds a global, returning a new object associated with the global. |
218 | /// |
219 | /// This binds a specific object by its name as provided by the [RegistryHandler::new_global] |
220 | /// callback. |
221 | pub fn bind_specific<I, D, U>( |
222 | &self, |
223 | qh: &QueueHandle<D>, |
224 | name: u32, |
225 | version: std::ops::RangeInclusive<u32>, |
226 | udata: U, |
227 | ) -> Result<I, BindError> |
228 | where |
229 | D: Dispatch<I, U> + 'static, |
230 | I: Proxy + 'static, |
231 | U: Send + Sync + 'static, |
232 | { |
233 | let iface = I::interface(); |
234 | if *version.end() > iface.version { |
235 | // This is a panic because it's a compile-time programmer error, not a runtime error. |
236 | panic!("Maximum version ( {}) was higher than the proxy's maximum version ( {}); outdated wayland XML files?" , |
237 | version.end(), iface.version); |
238 | } |
239 | // Optimize for runtime_add_global which will use the last entry |
240 | for global in self.globals.iter().rev() { |
241 | if global.name != name || global.interface != iface.name { |
242 | continue; |
243 | } |
244 | if global.version < *version.start() { |
245 | return Err(BindError::UnsupportedVersion); |
246 | } |
247 | let version = global.version.min(*version.end()); |
248 | let proxy = self.registry.bind(global.name, version, qh, udata); |
249 | log::debug!(target: "sctk" , "Bound new global [ {}] {} v {}" , global.name, iface.name, version); |
250 | |
251 | return Ok(proxy); |
252 | } |
253 | Err(BindError::NotPresent) |
254 | } |
255 | |
256 | /// Binds all globals with a given interface. |
257 | pub fn bind_all<I, D, U, F>( |
258 | &self, |
259 | qh: &QueueHandle<D>, |
260 | version: std::ops::RangeInclusive<u32>, |
261 | make_udata: F, |
262 | ) -> Result<Vec<I>, BindError> |
263 | where |
264 | D: Dispatch<I, U> + 'static, |
265 | I: Proxy + 'static, |
266 | F: FnMut(u32) -> U, |
267 | U: Send + Sync + 'static, |
268 | { |
269 | bind_all(&self.registry, &self.globals, qh, version, make_udata) |
270 | } |
271 | } |
272 | |
273 | /// Delegates the handling of [`wl_registry`]. |
274 | /// |
275 | /// Anything which implements [`RegistryHandler`] may be used in the delegate. |
276 | /// |
277 | /// ## Usage |
278 | /// |
279 | /// ``` |
280 | /// use smithay_client_toolkit::{ |
281 | /// delegate_registry, delegate_shm, registry_handlers, |
282 | /// shm::{ShmHandler, Shm}, |
283 | /// }; |
284 | /// |
285 | /// struct ExampleApp { |
286 | /// shm_state: Shm, |
287 | /// } |
288 | /// |
289 | /// // Here is the implementation of wl_shm to compile: |
290 | /// delegate_shm!(ExampleApp); |
291 | /// |
292 | /// impl ShmHandler for ExampleApp { |
293 | /// fn shm_state(&mut self) -> &mut Shm { |
294 | /// &mut self.shm_state |
295 | /// } |
296 | /// } |
297 | /// ``` |
298 | #[macro_export ] |
299 | macro_rules! delegate_registry { |
300 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { |
301 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: |
302 | [ |
303 | $crate::reexports::client::protocol::wl_registry::WlRegistry: $crate::reexports::client::globals::GlobalListContents |
304 | ] => $crate::registry::RegistryState |
305 | ); |
306 | }; |
307 | } |
308 | |
309 | impl<D> Dispatch<wl_registry::WlRegistry, GlobalListContents, D> for RegistryState |
310 | where |
311 | D: Dispatch<wl_registry::WlRegistry, GlobalListContents> + ProvidesRegistryState, |
312 | { |
313 | fn event( |
314 | state: &mut D, |
315 | _: &wl_registry::WlRegistry, |
316 | event: wl_registry::Event, |
317 | _: &GlobalListContents, |
318 | conn: &Connection, |
319 | qh: &QueueHandle<D>, |
320 | ) { |
321 | match event { |
322 | wl_registry::Event::Global { name, interface, version } => { |
323 | let iface = interface.clone(); |
324 | state.registry().globals.push(Global { name, interface, version }); |
325 | state.runtime_add_global(conn, qh, name, &iface, version); |
326 | } |
327 | |
328 | wl_registry::Event::GlobalRemove { name } => { |
329 | if let Some(i) = state.registry().globals.iter().position(|g| g.name == name) { |
330 | let global = state.registry().globals.swap_remove(i); |
331 | state.runtime_remove_global(conn, qh, name, &global.interface); |
332 | } |
333 | } |
334 | |
335 | _ => unreachable!("wl_registry is frozen" ), |
336 | } |
337 | } |
338 | } |
339 | |
340 | /// A helper for storing a bound global. |
341 | /// |
342 | /// This helper is intended to simplify the implementation of [RegistryHandler] for state objects |
343 | /// that cache a bound global. |
344 | #[derive (Debug)] |
345 | pub enum GlobalProxy<I> { |
346 | /// The requested global was not present after a complete enumeration. |
347 | NotPresent, |
348 | /// The cached global. |
349 | Bound(I), |
350 | } |
351 | |
352 | impl<I> From<Result<I, BindError>> for GlobalProxy<I> { |
353 | fn from(r: Result<I, BindError>) -> Self { |
354 | match r { |
355 | Ok(proxy: I) => GlobalProxy::Bound(proxy), |
356 | Err(_) => GlobalProxy::NotPresent, |
357 | } |
358 | } |
359 | } |
360 | |
361 | impl<I: Proxy> GlobalProxy<I> { |
362 | pub fn get(&self) -> Result<&I, GlobalError> { |
363 | self.with_min_version(0) |
364 | } |
365 | |
366 | pub fn with_min_version(&self, min_version: u32) -> Result<&I, GlobalError> { |
367 | match self { |
368 | GlobalProxy::Bound(proxy: &I) => { |
369 | if proxy.version() < min_version { |
370 | Err(GlobalError::InvalidVersion { |
371 | name: I::interface().name, |
372 | required: min_version, |
373 | available: proxy.version(), |
374 | }) |
375 | } else { |
376 | Ok(proxy) |
377 | } |
378 | } |
379 | GlobalProxy::NotPresent => Err(GlobalError::MissingGlobal(I::interface().name)), |
380 | } |
381 | } |
382 | } |
383 | |
384 | #[derive (Debug)] |
385 | pub struct SimpleGlobal<I, const MAX_VERSION: u32> { |
386 | proxy: GlobalProxy<I>, |
387 | } |
388 | |
389 | impl<I: Proxy + 'static, const MAX_VERSION: u32> SimpleGlobal<I, MAX_VERSION> { |
390 | pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError> |
391 | where |
392 | State: Dispatch<I, (), State> + 'static, |
393 | { |
394 | let proxy: I = globals.bind(qh, version:0..=MAX_VERSION, ())?; |
395 | Ok(Self { proxy: GlobalProxy::Bound(proxy) }) |
396 | } |
397 | |
398 | pub fn get(&self) -> Result<&I, GlobalError> { |
399 | self.proxy.get() |
400 | } |
401 | |
402 | pub fn with_min_version(&self, min_version: u32) -> Result<&I, GlobalError> { |
403 | self.proxy.with_min_version(min_version) |
404 | } |
405 | } |
406 | |
407 | impl<I: Proxy + Clone, const MAX_VERSION: u32> ProvidesBoundGlobal<I, MAX_VERSION> |
408 | for SimpleGlobal<I, MAX_VERSION> |
409 | { |
410 | fn bound_global(&self) -> Result<I, GlobalError> { |
411 | self.proxy.get().cloned() |
412 | } |
413 | } |
414 | |
415 | impl<D, I, const MAX_VERSION: u32> Dispatch<I, (), D> for SimpleGlobal<I, MAX_VERSION> |
416 | where |
417 | D: Dispatch<I, ()>, |
418 | I: Proxy, |
419 | { |
420 | fn event(_: &mut D, _: &I, _: <I as Proxy>::Event, _: &(), _: &Connection, _: &QueueHandle<D>) { |
421 | unreachable!("SimpleGlobal is not suitable for {} which has events" , I::interface().name); |
422 | } |
423 | } |
424 | |
425 | /// Binds all globals with a given interface. |
426 | pub(crate) fn bind_all<I, D, U, F>( |
427 | registry: &wl_registry::WlRegistry, |
428 | globals: &[Global], |
429 | qh: &QueueHandle<D>, |
430 | version: std::ops::RangeInclusive<u32>, |
431 | mut make_udata: F, |
432 | ) -> Result<Vec<I>, BindError> |
433 | where |
434 | D: Dispatch<I, U> + 'static, |
435 | I: Proxy + 'static, |
436 | F: FnMut(u32) -> U, |
437 | U: Send + Sync + 'static, |
438 | { |
439 | let iface: &Interface = I::interface(); |
440 | if *version.end() > iface.version { |
441 | // This is a panic because it's a compile-time programmer error, not a runtime error. |
442 | panic!("Maximum version ( {}) was higher than the proxy's maximum version ( {}); outdated wayland XML files?" , |
443 | version.end(), iface.version); |
444 | } |
445 | let mut rv: Vec = Vec::new(); |
446 | for global: &Global in globals { |
447 | if global.interface != iface.name { |
448 | continue; |
449 | } |
450 | if global.version < *version.start() { |
451 | return Err(BindError::UnsupportedVersion); |
452 | } |
453 | let version: u32 = global.version.min(*version.end()); |
454 | let udata: U = make_udata(global.name); |
455 | let proxy: I = registry.bind(global.name, version, qh, udata); |
456 | log::debug!(target: "sctk" , "Bound new global [ {}] {} v {}" , global.name, iface.name, version); |
457 | |
458 | rv.push(proxy); |
459 | } |
460 | Ok(rv) |
461 | } |
462 | |
463 | /// Binds a global, returning a new object associated with the global. |
464 | pub(crate) fn bind_one<I, D, U>( |
465 | registry: &wl_registry::WlRegistry, |
466 | globals: &[Global], |
467 | qh: &QueueHandle<D>, |
468 | version: std::ops::RangeInclusive<u32>, |
469 | udata: U, |
470 | ) -> Result<I, BindError> |
471 | where |
472 | D: Dispatch<I, U> + 'static, |
473 | I: Proxy + 'static, |
474 | U: Send + Sync + 'static, |
475 | { |
476 | let iface = I::interface(); |
477 | if *version.end() > iface.version { |
478 | // This is a panic because it's a compile-time programmer error, not a runtime error. |
479 | panic!("Maximum version ( {}) of {} was higher than the proxy's maximum version ( {}); outdated wayland XML files?" , |
480 | version.end(), iface.name, iface.version); |
481 | } |
482 | if *version.end() < iface.version { |
483 | // This is a reminder to evaluate the new API and bump the maximum in order to be able |
484 | // to use new APIs. Actual use of new APIs still needs runtime version checks. |
485 | log::trace!(target: "sctk" , "Version {} of {} is available; binding is currently limited to {}" , iface.version, iface.name, version.end()); |
486 | } |
487 | for global in globals { |
488 | if global.interface != iface.name { |
489 | continue; |
490 | } |
491 | if global.version < *version.start() { |
492 | return Err(BindError::UnsupportedVersion); |
493 | } |
494 | let version = global.version.min(*version.end()); |
495 | let proxy = registry.bind(global.name, version, qh, udata); |
496 | log::debug!(target: "sctk" , "Bound new global [ {}] {} v {}" , global.name, iface.name, version); |
497 | |
498 | return Ok(proxy); |
499 | } |
500 | Err(BindError::NotPresent) |
501 | } |
502 | |
503 | #[macro_export ] |
504 | macro_rules! delegate_simple { |
505 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty:ty, $iface:ty, $max:expr) => { |
506 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ $iface: () ] |
507 | => $crate::registry::SimpleGlobal<$iface, $max> |
508 | ); |
509 | }; |
510 | } |
511 | |
512 | /// A helper macro for implementing [`ProvidesRegistryState`]. |
513 | /// |
514 | /// See [`delegate_registry`] for an example. |
515 | #[macro_export ] |
516 | macro_rules! registry_handlers { |
517 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $($ty:ty),* $(,)?) => { |
518 | fn runtime_add_global( |
519 | &mut self, |
520 | conn: &$crate::reexports::client::Connection, |
521 | qh: &$crate::reexports::client::QueueHandle<Self>, |
522 | name: u32, |
523 | interface: &str, |
524 | version: u32, |
525 | ) { |
526 | $( |
527 | <$ty as $crate::registry::RegistryHandler<Self>>::new_global(self, conn, qh, name, interface, version); |
528 | )* |
529 | } |
530 | |
531 | fn runtime_remove_global( |
532 | &mut self, |
533 | conn: &$crate::reexports::client::Connection, |
534 | qh: &$crate::reexports::client::QueueHandle<Self>, |
535 | name: u32, |
536 | interface: &str, |
537 | ) { |
538 | $( |
539 | <$ty as $crate::registry::RegistryHandler<Self>>::remove_global(self, conn, qh, name, interface); |
540 | )* |
541 | } |
542 | } |
543 | } |
544 | |