1use std::{
2 fmt::{self, Display, Formatter},
3 sync::{
4 atomic::{AtomicBool, Ordering},
5 Arc, Mutex,
6 },
7};
8
9use 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};
14use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
15use crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1;
16use crate::{
17 compositor::SurfaceDataExt,
18 globals::GlobalData,
19 registry::{ProvidesRegistryState, RegistryHandler},
20};
21
22#[cfg(feature = "xkbcommon")]
23pub mod keyboard;
24pub mod pointer;
25pub mod pointer_constraints;
26pub mod relative_pointer;
27pub mod touch;
28
29use pointer::cursor_shape::CursorShapeManager;
30use pointer::{PointerData, PointerDataExt, PointerHandler, ThemeSpec, ThemedPointer, Themes};
31use touch::{TouchData, TouchDataExt, TouchHandler};
32
33#[non_exhaustive]
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum Capability {
36 Keyboard,
37
38 Pointer,
39
40 Touch,
41}
42
43impl 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)]
54pub 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)]
65pub struct SeatState {
66 // (name, seat)
67 seats: Vec<SeatInner>,
68 cursor_shape_manager_state: CursorShapeManagerState,
69}
70
71#[derive(Debug)]
72enum CursorShapeManagerState {
73 NotPresent,
74 Pending { registry: WlRegistry, global: Global },
75 Bound(CursorShapeManager),
76}
77
78impl 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
321pub 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)]
361pub 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
375impl 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)]
412pub 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]
421macro_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)]
432struct SeatInner {
433 seat: wl_seat::WlSeat,
434 data: SeatData,
435}
436
437impl<D> Dispatch<wl_seat::WlSeat, SeatData, D> for SeatState
438where
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
502impl<D> RegistryHandler<D> for SeatState
503where
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