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 | |
55 | use std::{ |
56 | fmt, |
57 | ops::RangeInclusive, |
58 | os::unix::io::OwnedFd, |
59 | sync::{ |
60 | atomic::{AtomicBool, Ordering}, |
61 | Arc, Mutex, |
62 | }, |
63 | }; |
64 | |
65 | use wayland_backend::{ |
66 | client::{Backend, InvalidId, ObjectData, ObjectId, WaylandError}, |
67 | protocol::Message, |
68 | }; |
69 | |
70 | use 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. |
78 | pub fn registry_queue_init<State>( |
79 | conn: &Connection, |
80 | ) -> Result<(GlobalList, EventQueue<State>), GlobalError> |
81 | where |
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)] |
102 | pub struct GlobalList { |
103 | registry: wl_registry::WlRegistry, |
104 | } |
105 | |
106 | impl 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)] |
191 | pub 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 | |
199 | impl 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 | |
208 | impl 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 | |
219 | impl From<WaylandError> for GlobalError { |
220 | fn from(source: WaylandError) -> Self { |
221 | GlobalError::Backend(source) |
222 | } |
223 | } |
224 | |
225 | impl 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)] |
233 | pub 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 | |
241 | impl std::error::Error for BindError {} |
242 | |
243 | impl 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)] |
258 | pub 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)] |
276 | pub struct GlobalListContents { |
277 | contents: Mutex<Vec<Global>>, |
278 | } |
279 | |
280 | impl 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 | |
296 | struct RegistryState<State> { |
297 | globals: GlobalListContents, |
298 | handle: QueueHandle<State>, |
299 | initial_roundtrip_done: AtomicBool, |
300 | } |
301 | |
302 | impl<State: 'static> ObjectData for RegistryState<State> |
303 | where |
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 | |