1 | use std::{ |
2 | fmt::{self, Display, Formatter}, |
3 | sync::{ |
4 | atomic::{AtomicBool, Ordering}, |
5 | Arc, Mutex, |
6 | }, |
7 | }; |
8 | |
9 | use crate::reexports::client::{ |
10 | globals::{Global, GlobalList}, |
11 | protocol::{wl_pointer, wl_registry::WlRegistry, wl_seat, wl_shm, wl_surface, wl_touch}, |
12 | Connection, Dispatch, Proxy, QueueHandle, |
13 | }; |
14 | use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; |
15 | use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; |
16 | use crate::{ |
17 | compositor::SurfaceDataExt, |
18 | globals::GlobalData, |
19 | registry::{ProvidesRegistryState, RegistryHandler}, |
20 | }; |
21 | |
22 | #[cfg (feature = "xkbcommon" )] |
23 | pub mod keyboard; |
24 | pub mod pointer; |
25 | pub mod pointer_constraints; |
26 | pub mod relative_pointer; |
27 | pub mod touch; |
28 | |
29 | use pointer::cursor_shape::CursorShapeManager; |
30 | use pointer::{PointerData, PointerDataExt, PointerHandler, ThemeSpec, ThemedPointer, Themes}; |
31 | use touch::{TouchData, TouchDataExt, TouchHandler}; |
32 | |
33 | #[non_exhaustive ] |
34 | #[derive (Debug, Clone, Copy, PartialEq, Eq)] |
35 | pub enum Capability { |
36 | Keyboard, |
37 | |
38 | Pointer, |
39 | |
40 | Touch, |
41 | } |
42 | |
43 | impl Display for Capability { |
44 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
45 | match self { |
46 | Capability::Keyboard => write!(f, "keyboard" ), |
47 | Capability::Pointer => write!(f, "pointer" ), |
48 | Capability::Touch => write!(f, "touch" ), |
49 | } |
50 | } |
51 | } |
52 | |
53 | #[derive (Debug, thiserror::Error)] |
54 | pub enum SeatError { |
55 | #[error("the capability \"{0} \" is not supported" )] |
56 | /// The capability is not supported. |
57 | UnsupportedCapability(Capability), |
58 | |
59 | /// The seat is dead. |
60 | #[error("the seat is dead" )] |
61 | DeadObject, |
62 | } |
63 | |
64 | #[derive (Debug)] |
65 | pub struct SeatState { |
66 | // (name, seat) |
67 | seats: Vec<SeatInner>, |
68 | cursor_shape_manager_state: CursorShapeManagerState, |
69 | } |
70 | |
71 | #[derive (Debug)] |
72 | enum CursorShapeManagerState { |
73 | NotPresent, |
74 | Pending { registry: WlRegistry, global: Global }, |
75 | Bound(CursorShapeManager), |
76 | } |
77 | |
78 | impl SeatState { |
79 | pub fn new<D: Dispatch<wl_seat::WlSeat, SeatData> + 'static>( |
80 | global_list: &GlobalList, |
81 | qh: &QueueHandle<D>, |
82 | ) -> SeatState { |
83 | let (seats, cursor_shape_manager) = global_list.contents().with_list(|globals| { |
84 | let global = globals |
85 | .iter() |
86 | .find(|global| global.interface == WpCursorShapeManagerV1::interface().name) |
87 | .map(|global| CursorShapeManagerState::Pending { |
88 | registry: global_list.registry().clone(), |
89 | global: global.clone(), |
90 | }) |
91 | .unwrap_or(CursorShapeManagerState::NotPresent); |
92 | |
93 | ( |
94 | crate::registry::bind_all(global_list.registry(), globals, qh, 1..=7, |id| { |
95 | SeatData { |
96 | has_keyboard: Arc::new(AtomicBool::new(false)), |
97 | has_pointer: Arc::new(AtomicBool::new(false)), |
98 | has_touch: Arc::new(AtomicBool::new(false)), |
99 | name: Arc::new(Mutex::new(None)), |
100 | id, |
101 | } |
102 | }) |
103 | .expect("failed to bind global" ), |
104 | global, |
105 | ) |
106 | }); |
107 | |
108 | let mut state = |
109 | SeatState { seats: vec![], cursor_shape_manager_state: cursor_shape_manager }; |
110 | |
111 | for seat in seats { |
112 | let data = seat.data::<SeatData>().unwrap().clone(); |
113 | |
114 | state.seats.push(SeatInner { seat: seat.clone(), data }); |
115 | } |
116 | state |
117 | } |
118 | |
119 | /// Returns an iterator over all the seats. |
120 | pub fn seats(&self) -> impl Iterator<Item = wl_seat::WlSeat> { |
121 | self.seats.iter().map(|inner| inner.seat.clone()).collect::<Vec<_>>().into_iter() |
122 | } |
123 | |
124 | /// Returns information about a seat. |
125 | /// |
126 | /// This will return [`None`] if the seat is dead. |
127 | pub fn info(&self, seat: &wl_seat::WlSeat) -> Option<SeatInfo> { |
128 | self.seats.iter().find(|inner| &inner.seat == seat).map(|inner| { |
129 | let name = inner.data.name.lock().unwrap().clone(); |
130 | |
131 | SeatInfo { |
132 | name, |
133 | has_keyboard: inner.data.has_keyboard.load(Ordering::SeqCst), |
134 | has_pointer: inner.data.has_pointer.load(Ordering::SeqCst), |
135 | has_touch: inner.data.has_touch.load(Ordering::SeqCst), |
136 | } |
137 | }) |
138 | } |
139 | |
140 | /// Creates a pointer from a seat. |
141 | /// |
142 | /// ## Errors |
143 | /// |
144 | /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. |
145 | pub fn get_pointer<D>( |
146 | &mut self, |
147 | qh: &QueueHandle<D>, |
148 | seat: &wl_seat::WlSeat, |
149 | ) -> Result<wl_pointer::WlPointer, SeatError> |
150 | where |
151 | D: Dispatch<wl_pointer::WlPointer, PointerData> + PointerHandler + 'static, |
152 | { |
153 | self.get_pointer_with_data(qh, seat, PointerData::new(seat.clone())) |
154 | } |
155 | |
156 | /// Creates a pointer from a seat with the provided theme. |
157 | /// |
158 | /// This will use [`CursorShapeManager`] under the hood when it's available. |
159 | /// |
160 | /// ## Errors |
161 | /// |
162 | /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. |
163 | pub fn get_pointer_with_theme<D, S>( |
164 | &mut self, |
165 | qh: &QueueHandle<D>, |
166 | seat: &wl_seat::WlSeat, |
167 | shm: &wl_shm::WlShm, |
168 | surface: wl_surface::WlSurface, |
169 | theme: ThemeSpec, |
170 | ) -> Result<ThemedPointer<PointerData>, SeatError> |
171 | where |
172 | D: Dispatch<wl_pointer::WlPointer, PointerData> |
173 | + Dispatch<wl_surface::WlSurface, S> |
174 | + Dispatch<WpCursorShapeManagerV1, GlobalData> |
175 | + Dispatch<WpCursorShapeDeviceV1, GlobalData> |
176 | + PointerHandler |
177 | + 'static, |
178 | S: SurfaceDataExt + 'static, |
179 | { |
180 | self.get_pointer_with_theme_and_data( |
181 | qh, |
182 | seat, |
183 | shm, |
184 | surface, |
185 | theme, |
186 | PointerData::new(seat.clone()), |
187 | ) |
188 | } |
189 | |
190 | /// Creates a pointer from a seat. |
191 | /// |
192 | /// ## Errors |
193 | /// |
194 | /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. |
195 | pub fn get_pointer_with_data<D, U>( |
196 | &mut self, |
197 | qh: &QueueHandle<D>, |
198 | seat: &wl_seat::WlSeat, |
199 | pointer_data: U, |
200 | ) -> Result<wl_pointer::WlPointer, SeatError> |
201 | where |
202 | D: Dispatch<wl_pointer::WlPointer, U> + PointerHandler + 'static, |
203 | U: PointerDataExt + 'static, |
204 | { |
205 | let inner = |
206 | self.seats.iter().find(|inner| &inner.seat == seat).ok_or(SeatError::DeadObject)?; |
207 | |
208 | if !inner.data.has_pointer.load(Ordering::SeqCst) { |
209 | return Err(SeatError::UnsupportedCapability(Capability::Pointer)); |
210 | } |
211 | |
212 | Ok(seat.get_pointer(qh, pointer_data)) |
213 | } |
214 | |
215 | /// Creates a pointer from a seat with the provided theme and data. |
216 | /// |
217 | /// ## Errors |
218 | /// |
219 | /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support a pointer. |
220 | pub fn get_pointer_with_theme_and_data<D, S, U>( |
221 | &mut self, |
222 | qh: &QueueHandle<D>, |
223 | seat: &wl_seat::WlSeat, |
224 | shm: &wl_shm::WlShm, |
225 | surface: wl_surface::WlSurface, |
226 | theme: ThemeSpec, |
227 | pointer_data: U, |
228 | ) -> Result<ThemedPointer<U>, SeatError> |
229 | where |
230 | D: Dispatch<wl_pointer::WlPointer, U> |
231 | + Dispatch<wl_surface::WlSurface, S> |
232 | + Dispatch<WpCursorShapeManagerV1, GlobalData> |
233 | + Dispatch<WpCursorShapeDeviceV1, GlobalData> |
234 | + PointerHandler |
235 | + 'static, |
236 | S: SurfaceDataExt + 'static, |
237 | U: PointerDataExt + 'static, |
238 | { |
239 | let inner = |
240 | self.seats.iter().find(|inner| &inner.seat == seat).ok_or(SeatError::DeadObject)?; |
241 | |
242 | if !inner.data.has_pointer.load(Ordering::SeqCst) { |
243 | return Err(SeatError::UnsupportedCapability(Capability::Pointer)); |
244 | } |
245 | |
246 | let wl_ptr = seat.get_pointer(qh, pointer_data); |
247 | |
248 | if let CursorShapeManagerState::Pending { registry, global } = |
249 | &self.cursor_shape_manager_state |
250 | { |
251 | self.cursor_shape_manager_state = |
252 | match crate::registry::bind_one(registry, &[global.clone()], qh, 1..=1, GlobalData) |
253 | { |
254 | Ok(bound) => { |
255 | CursorShapeManagerState::Bound(CursorShapeManager::from_existing(bound)) |
256 | } |
257 | Err(_) => CursorShapeManagerState::NotPresent, |
258 | } |
259 | } |
260 | |
261 | let shape_device = |
262 | if let CursorShapeManagerState::Bound(ref bound) = self.cursor_shape_manager_state { |
263 | Some(bound.get_shape_device(&wl_ptr, qh)) |
264 | } else { |
265 | None |
266 | }; |
267 | |
268 | Ok(ThemedPointer { |
269 | themes: Arc::new(Mutex::new(Themes::new(theme))), |
270 | pointer: wl_ptr, |
271 | shm: shm.clone(), |
272 | surface, |
273 | shape_device, |
274 | _marker: std::marker::PhantomData, |
275 | _surface_data: std::marker::PhantomData, |
276 | }) |
277 | } |
278 | |
279 | /// Creates a touch handle from a seat. |
280 | /// |
281 | /// ## Errors |
282 | /// |
283 | /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support touch. |
284 | pub fn get_touch<D>( |
285 | &mut self, |
286 | qh: &QueueHandle<D>, |
287 | seat: &wl_seat::WlSeat, |
288 | ) -> Result<wl_touch::WlTouch, SeatError> |
289 | where |
290 | D: Dispatch<wl_touch::WlTouch, TouchData> + TouchHandler + 'static, |
291 | { |
292 | self.get_touch_with_data(qh, seat, TouchData::new(seat.clone())) |
293 | } |
294 | |
295 | /// Creates a touch handle from a seat. |
296 | /// |
297 | /// ## Errors |
298 | /// |
299 | /// This will return [`SeatError::UnsupportedCapability`] if the seat does not support touch. |
300 | pub fn get_touch_with_data<D, U>( |
301 | &mut self, |
302 | qh: &QueueHandle<D>, |
303 | seat: &wl_seat::WlSeat, |
304 | udata: U, |
305 | ) -> Result<wl_touch::WlTouch, SeatError> |
306 | where |
307 | D: Dispatch<wl_touch::WlTouch, U> + TouchHandler + 'static, |
308 | U: TouchDataExt + 'static, |
309 | { |
310 | let inner = |
311 | self.seats.iter().find(|inner| &inner.seat == seat).ok_or(SeatError::DeadObject)?; |
312 | |
313 | if !inner.data.has_touch.load(Ordering::SeqCst) { |
314 | return Err(SeatError::UnsupportedCapability(Capability::Touch)); |
315 | } |
316 | |
317 | Ok(seat.get_touch(qh, udata)) |
318 | } |
319 | } |
320 | |
321 | pub trait SeatHandler: Sized { |
322 | fn seat_state(&mut self) -> &mut SeatState; |
323 | |
324 | /// A new seat has been created. |
325 | /// |
326 | /// This function only indicates that a seat has been created, you will need to wait for [`new_capability`](SeatHandler::new_capability) |
327 | /// to be called before creating any keyboards, |
328 | fn new_seat(&mut self, conn: &Connection, qh: &QueueHandle<Self>, seat: wl_seat::WlSeat); |
329 | |
330 | /// A new capability is available on the seat. |
331 | /// |
332 | /// This allows you to create the corresponding object related to the capability. |
333 | fn new_capability( |
334 | &mut self, |
335 | conn: &Connection, |
336 | qh: &QueueHandle<Self>, |
337 | seat: wl_seat::WlSeat, |
338 | capability: Capability, |
339 | ); |
340 | |
341 | /// A capability has been removed from the seat. |
342 | /// |
343 | /// If an object has been created from the capability, it should be destroyed. |
344 | fn remove_capability( |
345 | &mut self, |
346 | conn: &Connection, |
347 | qh: &QueueHandle<Self>, |
348 | seat: wl_seat::WlSeat, |
349 | capability: Capability, |
350 | ); |
351 | |
352 | /// A seat has been removed. |
353 | /// |
354 | /// The seat is destroyed and all capability objects created from it are invalid. |
355 | fn remove_seat(&mut self, conn: &Connection, qh: &QueueHandle<Self>, seat: wl_seat::WlSeat); |
356 | } |
357 | |
358 | /// Description of a seat. |
359 | #[non_exhaustive ] |
360 | #[derive (Debug, Clone)] |
361 | pub struct SeatInfo { |
362 | /// The name of the seat. |
363 | pub name: Option<String>, |
364 | |
365 | /// Does the seat support a keyboard. |
366 | pub has_keyboard: bool, |
367 | |
368 | /// Does the seat support a pointer. |
369 | pub has_pointer: bool, |
370 | |
371 | /// Does the seat support touch input. |
372 | pub has_touch: bool, |
373 | } |
374 | |
375 | impl Display for SeatInfo { |
376 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
377 | if let Some(ref name) = self.name { |
378 | write!(f, "name: \"{name}\" " )?; |
379 | } |
380 | |
381 | write!(f, "capabilities: (" )?; |
382 | |
383 | if !self.has_keyboard && !self.has_pointer && !self.has_touch { |
384 | write!(f, "none" )?; |
385 | } else { |
386 | if self.has_keyboard { |
387 | write!(f, "keyboard" )?; |
388 | |
389 | if self.has_pointer || self.has_touch { |
390 | write!(f, ", " )?; |
391 | } |
392 | } |
393 | |
394 | if self.has_pointer { |
395 | write!(f, "pointer" )?; |
396 | |
397 | if self.has_touch { |
398 | write!(f, ", " )?; |
399 | } |
400 | } |
401 | |
402 | if self.has_touch { |
403 | write!(f, "touch" )?; |
404 | } |
405 | } |
406 | |
407 | write!(f, ")" ) |
408 | } |
409 | } |
410 | |
411 | #[derive (Debug, Clone)] |
412 | pub struct SeatData { |
413 | has_keyboard: Arc<AtomicBool>, |
414 | has_pointer: Arc<AtomicBool>, |
415 | has_touch: Arc<AtomicBool>, |
416 | name: Arc<Mutex<Option<String>>>, |
417 | id: u32, |
418 | } |
419 | |
420 | #[macro_export ] |
421 | macro_rules! delegate_seat { |
422 | ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { |
423 | $crate::reexports::client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: |
424 | [ |
425 | $crate::reexports::client::protocol::wl_seat::WlSeat: $crate::seat::SeatData |
426 | ] => $crate::seat::SeatState |
427 | ); |
428 | }; |
429 | } |
430 | |
431 | #[derive (Debug)] |
432 | struct SeatInner { |
433 | seat: wl_seat::WlSeat, |
434 | data: SeatData, |
435 | } |
436 | |
437 | impl<D> Dispatch<wl_seat::WlSeat, SeatData, D> for SeatState |
438 | where |
439 | D: Dispatch<wl_seat::WlSeat, SeatData> + SeatHandler, |
440 | { |
441 | fn event( |
442 | state: &mut D, |
443 | seat: &wl_seat::WlSeat, |
444 | event: wl_seat::Event, |
445 | data: &SeatData, |
446 | conn: &Connection, |
447 | qh: &QueueHandle<D>, |
448 | ) { |
449 | match event { |
450 | wl_seat::Event::Capabilities { capabilities } => { |
451 | let capabilities = wl_seat::Capability::from_bits_truncate(capabilities.into()); |
452 | |
453 | let keyboard = capabilities.contains(wl_seat::Capability::Keyboard); |
454 | let has_keyboard = data.has_keyboard.load(Ordering::SeqCst); |
455 | let pointer = capabilities.contains(wl_seat::Capability::Pointer); |
456 | let has_pointer = data.has_pointer.load(Ordering::SeqCst); |
457 | let touch = capabilities.contains(wl_seat::Capability::Touch); |
458 | let has_touch = data.has_touch.load(Ordering::SeqCst); |
459 | |
460 | // Update capabilities as necessary |
461 | if keyboard != has_keyboard { |
462 | data.has_keyboard.store(keyboard, Ordering::SeqCst); |
463 | |
464 | match keyboard { |
465 | true => state.new_capability(conn, qh, seat.clone(), Capability::Keyboard), |
466 | false => { |
467 | state.remove_capability(conn, qh, seat.clone(), Capability::Keyboard) |
468 | } |
469 | } |
470 | } |
471 | |
472 | if pointer != has_pointer { |
473 | data.has_pointer.store(pointer, Ordering::SeqCst); |
474 | |
475 | match pointer { |
476 | true => state.new_capability(conn, qh, seat.clone(), Capability::Pointer), |
477 | false => { |
478 | state.remove_capability(conn, qh, seat.clone(), Capability::Pointer) |
479 | } |
480 | } |
481 | } |
482 | |
483 | if touch != has_touch { |
484 | data.has_touch.store(touch, Ordering::SeqCst); |
485 | |
486 | match touch { |
487 | true => state.new_capability(conn, qh, seat.clone(), Capability::Touch), |
488 | false => state.remove_capability(conn, qh, seat.clone(), Capability::Touch), |
489 | } |
490 | } |
491 | } |
492 | |
493 | wl_seat::Event::Name { name } => { |
494 | *data.name.lock().unwrap() = Some(name); |
495 | } |
496 | |
497 | _ => unreachable!(), |
498 | } |
499 | } |
500 | } |
501 | |
502 | impl<D> RegistryHandler<D> for SeatState |
503 | where |
504 | D: Dispatch<wl_seat::WlSeat, SeatData> + SeatHandler + ProvidesRegistryState + 'static, |
505 | { |
506 | fn new_global( |
507 | state: &mut D, |
508 | conn: &Connection, |
509 | qh: &QueueHandle<D>, |
510 | name: u32, |
511 | interface: &str, |
512 | _: u32, |
513 | ) { |
514 | if interface == wl_seat::WlSeat::interface().name { |
515 | let seat = state |
516 | .registry() |
517 | .bind_specific( |
518 | qh, |
519 | name, |
520 | 1..=7, |
521 | SeatData { |
522 | has_keyboard: Arc::new(AtomicBool::new(false)), |
523 | has_pointer: Arc::new(AtomicBool::new(false)), |
524 | has_touch: Arc::new(AtomicBool::new(false)), |
525 | name: Arc::new(Mutex::new(None)), |
526 | id: name, |
527 | }, |
528 | ) |
529 | .expect("failed to bind global" ); |
530 | |
531 | let data = seat.data::<SeatData>().unwrap().clone(); |
532 | |
533 | state.seat_state().seats.push(SeatInner { seat: seat.clone(), data }); |
534 | state.new_seat(conn, qh, seat); |
535 | } |
536 | } |
537 | |
538 | fn remove_global( |
539 | state: &mut D, |
540 | conn: &Connection, |
541 | qh: &QueueHandle<D>, |
542 | name: u32, |
543 | interface: &str, |
544 | ) { |
545 | if interface == wl_seat::WlSeat::interface().name { |
546 | if let Some(seat) = state.seat_state().seats.iter().find(|inner| inner.data.id == name) |
547 | { |
548 | let seat = seat.seat.clone(); |
549 | |
550 | state.remove_seat(conn, qh, seat); |
551 | state.seat_state().seats.retain(|inner| inner.data.id != name); |
552 | } |
553 | } |
554 | } |
555 | } |
556 | |