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