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: EventQueue = conn.new_event_queue();
85 let display = conn.display();
86 let data: Arc> = Arc::new(data: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(val:true, order: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: &WaylandError) => Some(source),
203 GlobalError::InvalidId(source: &InvalidId) => std::error::Error::source(self: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: &WaylandError) => {
212 write!(f, "Backend error: {source}")
213 }
214 GlobalError::InvalidId(source: &InvalidId) => 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: MutexGuard<'_, Vec> = 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}
362