Skip to main content

smithay_client_toolkit/seat/
mod.rs

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