smithay_client_toolkit/seat/
mod.rs

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}