smithay_client_toolkit/seat/pointer/
mod.rs

1use std::{
2    collections::{hash_map::Entry, HashMap},
3    env, iter, mem,
4    sync::{Arc, Mutex},
5};
6
7use wayland_backend::{client::InvalidId, smallvec::SmallVec};
8use wayland_client::{
9    protocol::{
10        wl_pointer::{self, WlPointer},
11        wl_seat::WlSeat,
12        wl_shm::WlShm,
13        wl_surface::WlSurface,
14    },
15    Connection, Dispatch, Proxy, QueueHandle, WEnum,
16};
17use wayland_cursor::{Cursor, CursorTheme};
18use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1;
19
20use crate::{
21    compositor::{SurfaceData, SurfaceDataExt},
22    error::GlobalError,
23};
24
25use super::SeatState;
26
27#[doc(inline)]
28pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError};
29
30pub mod cursor_shape;
31
32use cursor_shape::cursor_icon_to_shape;
33
34/* From linux/input-event-codes.h - the buttons usually used by mice */
35pub const BTN_LEFT: u32 = 0x110;
36pub const BTN_RIGHT: u32 = 0x111;
37pub const BTN_MIDDLE: u32 = 0x112;
38/// The fourth non-scroll button, which is often used as "back" in web browsers.
39pub const BTN_SIDE: u32 = 0x113;
40/// The fifth non-scroll button, which is often used as "forward" in web browsers.
41pub const BTN_EXTRA: u32 = 0x114;
42
43/// See also [`BTN_EXTRA`].
44pub const BTN_FORWARD: u32 = 0x115;
45/// See also [`BTN_SIDE`].
46pub const BTN_BACK: u32 = 0x116;
47pub const BTN_TASK: u32 = 0x117;
48
49/// Describes a scroll along one axis
50#[derive(Default, Debug, Clone, Copy, PartialEq)]
51pub struct AxisScroll {
52    /// The scroll measured in pixels.
53    pub absolute: f64,
54
55    /// The scroll measured in steps.
56    ///
57    /// Note: this might always be zero if the scrolling is due to a touchpad or other continuous
58    /// source.
59    pub discrete: i32,
60
61    /// The scroll was stopped.
62    ///
63    /// Generally this is encountered when hardware indicates the end of some continuous scrolling.
64    pub stop: bool,
65}
66
67impl AxisScroll {
68    /// Returns true if there was no movement along this axis.
69    pub fn is_none(&self) -> bool {
70        *self == Self::default()
71    }
72
73    fn merge(&mut self, other: &Self) {
74        self.absolute += other.absolute;
75        self.discrete += other.discrete;
76        self.stop |= other.stop;
77    }
78}
79
80/// A single pointer event.
81#[derive(Debug, Clone)]
82pub struct PointerEvent {
83    pub surface: WlSurface,
84    pub position: (f64, f64),
85    pub kind: PointerEventKind,
86}
87
88#[derive(Debug, Clone)]
89pub enum PointerEventKind {
90    Enter {
91        serial: u32,
92    },
93    Leave {
94        serial: u32,
95    },
96    Motion {
97        time: u32,
98    },
99    Press {
100        time: u32,
101        button: u32,
102        serial: u32,
103    },
104    Release {
105        time: u32,
106        button: u32,
107        serial: u32,
108    },
109    Axis {
110        time: u32,
111        horizontal: AxisScroll,
112        vertical: AxisScroll,
113        source: Option<wl_pointer::AxisSource>,
114    },
115}
116
117pub trait PointerHandler: Sized {
118    /// One or more pointer events are available.
119    ///
120    /// Multiple related events may be grouped together in a single frame.  Some examples:
121    ///
122    /// - A drag that terminates outside the surface may send the Release and Leave events as one frame
123    /// - Movement from one surface to another may send the Enter and Leave events in one frame
124    fn pointer_frame(
125        &mut self,
126        conn: &Connection,
127        qh: &QueueHandle<Self>,
128        pointer: &WlPointer,
129        events: &[PointerEvent],
130    );
131}
132
133#[derive(Debug)]
134pub struct PointerData {
135    seat: WlSeat,
136    pub(crate) inner: Mutex<PointerDataInner>,
137}
138
139impl PointerData {
140    pub fn new(seat: WlSeat) -> Self {
141        Self { seat, inner: Default::default() }
142    }
143
144    /// The seat associated with this pointer.
145    pub fn seat(&self) -> &WlSeat {
146        &self.seat
147    }
148
149    /// Serial from the latest [`PointerEventKind::Enter`] event.
150    pub fn latest_enter_serial(&self) -> Option<u32> {
151        self.inner.lock().unwrap().latest_enter
152    }
153
154    /// Serial from the latest button [`PointerEventKind::Press`] and
155    /// [`PointerEventKind::Release`] events.
156    pub fn latest_button_serial(&self) -> Option<u32> {
157        self.inner.lock().unwrap().latest_btn
158    }
159}
160
161pub trait PointerDataExt: Send + Sync {
162    fn pointer_data(&self) -> &PointerData;
163}
164
165impl PointerDataExt for PointerData {
166    fn pointer_data(&self) -> &PointerData {
167        self
168    }
169}
170
171#[macro_export]
172macro_rules! delegate_pointer {
173    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
174        $crate::delegate_pointer!(@{ $(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty }; pointer: []);
175        $crate::delegate_pointer!(@{ $(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty }; pointer-only: $crate::seat::pointer::PointerData);
176    };
177    ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, pointer: [$($pointer_data:ty),* $(,)?]) => {
178        $crate::delegate_pointer!(@{ $(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty }; pointer: [ $($pointer_data),* ]);
179    };
180    (@{$($ty:tt)*}; pointer: []) => {
181        $crate::reexports::client::delegate_dispatch!($($ty)*:
182            [
183                $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1: $crate::globals::GlobalData
184            ] => $crate::seat::pointer::cursor_shape::CursorShapeManager
185        );
186        $crate::reexports::client::delegate_dispatch!($($ty)*:
187            [
188                $crate::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1: $crate::globals::GlobalData
189            ] => $crate::seat::pointer::cursor_shape::CursorShapeManager
190        );
191    };
192    (@{$($ty:tt)*}; pointer-only: $pointer_data:ty) => {
193        $crate::reexports::client::delegate_dispatch!($($ty)*:
194            [
195                $crate::reexports::client::protocol::wl_pointer::WlPointer: $pointer_data
196            ] => $crate::seat::SeatState
197        );
198    };
199    (@$ty:tt; pointer: [$($pointer:ty),*]) => {
200        $crate::delegate_pointer!(@$ty; pointer: []);
201        $( $crate::delegate_pointer!(@$ty; pointer-only: $pointer); )*
202    }
203}
204
205#[derive(Debug, Default)]
206pub(crate) struct PointerDataInner {
207    /// Surface the pointer most recently entered
208    pub(crate) surface: Option<WlSurface>,
209    /// Position relative to the surface
210    pub(crate) position: (f64, f64),
211
212    /// List of pending events.  Only used for version >= 5.
213    pub(crate) pending: SmallVec<[PointerEvent; 3]>,
214
215    /// The serial of the latest enter event for the pointer
216    pub(crate) latest_enter: Option<u32>,
217
218    /// The serial of the latest button event for the pointer
219    pub(crate) latest_btn: Option<u32>,
220}
221
222impl<D, U> Dispatch<WlPointer, U, D> for SeatState
223where
224    D: Dispatch<WlPointer, U> + PointerHandler,
225    U: PointerDataExt,
226{
227    fn event(
228        data: &mut D,
229        pointer: &WlPointer,
230        event: wl_pointer::Event,
231        udata: &U,
232        conn: &Connection,
233        qh: &QueueHandle<D>,
234    ) {
235        let udata = udata.pointer_data();
236        let mut guard = udata.inner.lock().unwrap();
237        let mut leave_surface = None;
238        let kind = match event {
239            wl_pointer::Event::Enter { surface, surface_x, surface_y, serial } => {
240                guard.surface = Some(surface);
241                guard.position = (surface_x, surface_y);
242                guard.latest_enter.replace(serial);
243
244                PointerEventKind::Enter { serial }
245            }
246
247            wl_pointer::Event::Leave { surface, serial } => {
248                if guard.surface.as_ref() == Some(&surface) {
249                    guard.surface = None;
250                }
251                leave_surface = Some(surface);
252
253                PointerEventKind::Leave { serial }
254            }
255
256            wl_pointer::Event::Motion { time, surface_x, surface_y } => {
257                guard.position = (surface_x, surface_y);
258
259                PointerEventKind::Motion { time }
260            }
261
262            wl_pointer::Event::Button { time, button, state, serial } => {
263                guard.latest_btn.replace(serial);
264                match state {
265                    WEnum::Value(wl_pointer::ButtonState::Pressed) => {
266                        PointerEventKind::Press { time, button, serial }
267                    }
268                    WEnum::Value(wl_pointer::ButtonState::Released) => {
269                        PointerEventKind::Release { time, button, serial }
270                    }
271                    WEnum::Unknown(unknown) => {
272                        log::warn!(target: "sctk", "{}: invalid pointer button state: {:x}", pointer.id(), unknown);
273                        return;
274                    }
275                    _ => unreachable!(),
276                }
277            }
278            // Axis logical events.
279            wl_pointer::Event::Axis { time, axis, value } => match axis {
280                WEnum::Value(axis) => {
281                    let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default();
282                    match axis {
283                        wl_pointer::Axis::VerticalScroll => {
284                            vertical.absolute = value;
285                        }
286                        wl_pointer::Axis::HorizontalScroll => {
287                            horizontal.absolute = value;
288                        }
289                        _ => unreachable!(),
290                    };
291
292                    PointerEventKind::Axis { time, horizontal, vertical, source: None }
293                }
294                WEnum::Unknown(unknown) => {
295                    log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown);
296                    return;
297                }
298            },
299
300            wl_pointer::Event::AxisSource { axis_source } => match axis_source {
301                WEnum::Value(source) => PointerEventKind::Axis {
302                    horizontal: AxisScroll::default(),
303                    vertical: AxisScroll::default(),
304                    source: Some(source),
305                    time: 0,
306                },
307                WEnum::Unknown(unknown) => {
308                    log::warn!(target: "sctk", "unknown pointer axis source: {:x}", unknown);
309                    return;
310                }
311            },
312
313            wl_pointer::Event::AxisStop { time, axis } => match axis {
314                WEnum::Value(axis) => {
315                    let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default();
316                    match axis {
317                        wl_pointer::Axis::VerticalScroll => vertical.stop = true,
318                        wl_pointer::Axis::HorizontalScroll => horizontal.stop = true,
319
320                        _ => unreachable!(),
321                    }
322
323                    PointerEventKind::Axis { time, horizontal, vertical, source: None }
324                }
325
326                WEnum::Unknown(unknown) => {
327                    log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown);
328                    return;
329                }
330            },
331
332            wl_pointer::Event::AxisDiscrete { axis, discrete } => match axis {
333                WEnum::Value(axis) => {
334                    let (mut horizontal, mut vertical) = <(AxisScroll, AxisScroll)>::default();
335                    match axis {
336                        wl_pointer::Axis::VerticalScroll => {
337                            vertical.discrete = discrete;
338                        }
339
340                        wl_pointer::Axis::HorizontalScroll => {
341                            horizontal.discrete = discrete;
342                        }
343
344                        _ => unreachable!(),
345                    };
346
347                    PointerEventKind::Axis { time: 0, horizontal, vertical, source: None }
348                }
349
350                WEnum::Unknown(unknown) => {
351                    log::warn!(target: "sctk", "{}: invalid pointer axis: {:x}", pointer.id(), unknown);
352                    return;
353                }
354            },
355
356            wl_pointer::Event::Frame => {
357                let pending = mem::take(&mut guard.pending);
358                drop(guard);
359                if !pending.is_empty() {
360                    data.pointer_frame(conn, qh, pointer, &pending);
361                }
362                return;
363            }
364
365            _ => unreachable!(),
366        };
367
368        let surface = match (leave_surface, &guard.surface) {
369            (Some(surface), _) => surface,
370            (None, Some(surface)) => surface.clone(),
371            (None, None) => {
372                log::warn!(target: "sctk", "{}: got pointer event {:?} without an entered surface", pointer.id(), kind);
373                return;
374            }
375        };
376
377        let event = PointerEvent { surface, position: guard.position, kind };
378
379        if pointer.version() < 5 {
380            drop(guard);
381            // No Frame events, send right away
382            data.pointer_frame(conn, qh, pointer, &[event]);
383        } else {
384            // Merge a new Axis event with the previous event to create an event with more
385            // information and potentially diagonal scrolling.
386            if let (
387                Some(PointerEvent {
388                    kind:
389                        PointerEventKind::Axis { time: ot, horizontal: oh, vertical: ov, source: os },
390                    ..
391                }),
392                PointerEvent {
393                    kind:
394                        PointerEventKind::Axis { time: nt, horizontal: nh, vertical: nv, source: ns },
395                    ..
396                },
397            ) = (guard.pending.last_mut(), &event)
398            {
399                // A time of 0 is "don't know", so avoid using it if possible.
400                if *ot == 0 {
401                    *ot = *nt;
402                }
403                oh.merge(nh);
404                ov.merge(nv);
405                *os = os.or(*ns);
406                return;
407            }
408
409            guard.pending.push(event);
410        }
411    }
412}
413
414/// Pointer themeing
415#[derive(Debug)]
416pub struct ThemedPointer<U = PointerData, S = SurfaceData> {
417    pub(super) themes: Arc<Mutex<Themes>>,
418    /// The underlying wl_pointer.
419    pub(super) pointer: WlPointer,
420    pub(super) shm: WlShm,
421    /// The surface owned by the cursor to present the icon.
422    pub(super) surface: WlSurface,
423    pub(super) shape_device: Option<WpCursorShapeDeviceV1>,
424    pub(super) _marker: std::marker::PhantomData<U>,
425    pub(super) _surface_data: std::marker::PhantomData<S>,
426}
427
428impl<U: PointerDataExt + 'static, S: SurfaceDataExt + 'static> ThemedPointer<U, S> {
429    /// Set the cursor to the given [`CursorIcon`].
430    ///
431    /// The cursor icon should be reloaded on every [`PointerEventKind::Enter`] event.
432    pub fn set_cursor(&self, conn: &Connection, icon: CursorIcon) -> Result<(), PointerThemeError> {
433        let serial = match self
434            .pointer
435            .data::<U>()
436            .and_then(|data| data.pointer_data().latest_enter_serial())
437        {
438            Some(serial) => serial,
439            None => return Err(PointerThemeError::MissingEnterSerial),
440        };
441
442        if let Some(shape_device) = self.shape_device.as_ref() {
443            shape_device.set_shape(serial, cursor_icon_to_shape(icon));
444            Ok(())
445        } else {
446            self.set_cursor_legacy(conn, serial, icon)
447        }
448    }
449
450    /// The legacy method of loading the cursor from the system cursor
451    /// theme instead of relying on compositor to set the cursor.
452    fn set_cursor_legacy(
453        &self,
454        conn: &Connection,
455        serial: u32,
456        icon: CursorIcon,
457    ) -> Result<(), PointerThemeError> {
458        let mut themes = self.themes.lock().unwrap();
459
460        let scale = self.surface.data::<S>().unwrap().surface_data().scale_factor();
461        for cursor_icon_name in iter::once(&icon.name()).chain(icon.alt_names().iter()) {
462            if let Some(cursor) = themes
463                .get_cursor(conn, cursor_icon_name, scale as u32, &self.shm)
464                .map_err(PointerThemeError::InvalidId)?
465            {
466                let image = &cursor[0];
467                let (w, h) = image.dimensions();
468                let (hx, hy) = image.hotspot();
469
470                self.surface.set_buffer_scale(scale);
471                self.surface.attach(Some(image), 0, 0);
472
473                if self.surface.version() >= 4 {
474                    self.surface.damage_buffer(0, 0, w as i32, h as i32);
475                } else {
476                    // Fallback for the old old surface.
477                    self.surface.damage(0, 0, w as i32 / scale, h as i32 / scale);
478                }
479
480                // Commit the surface to place the cursor image in the compositor's memory.
481                self.surface.commit();
482
483                // Set the pointer surface to change the pointer.
484                self.pointer.set_cursor(
485                    serial,
486                    Some(&self.surface),
487                    hx as i32 / scale,
488                    hy as i32 / scale,
489                );
490
491                return Ok(());
492            }
493        }
494
495        Err(PointerThemeError::CursorNotFound)
496    }
497
498    /// Hide the cursor by providing empty surface for it.
499    ///
500    /// The cursor should be hidden on every [`PointerEventKind::Enter`] event.
501    pub fn hide_cursor(&self) -> Result<(), PointerThemeError> {
502        let data = self.pointer.data::<U>();
503        if let Some(serial) = data.and_then(|data| data.pointer_data().latest_enter_serial()) {
504            self.pointer.set_cursor(serial, None, 0, 0);
505            Ok(())
506        } else {
507            Err(PointerThemeError::MissingEnterSerial)
508        }
509    }
510
511    /// The [`WlPointer`] associated with this [`ThemedPointer`].
512    pub fn pointer(&self) -> &WlPointer {
513        &self.pointer
514    }
515
516    /// The associated [`WlSurface`] with this [`ThemedPointer`].
517    pub fn surface(&self) -> &WlSurface {
518        &self.surface
519    }
520}
521
522impl<U, S> Drop for ThemedPointer<U, S> {
523    fn drop(&mut self) {
524        if let Some(shape_device) = self.shape_device.take() {
525            shape_device.destroy();
526        }
527
528        if self.pointer.version() >= 3 {
529            self.pointer.release();
530        }
531        self.surface.destroy();
532    }
533}
534
535/// Specifies which cursor theme should be used by the theme manager.
536#[derive(Debug)]
537pub enum ThemeSpec<'a> {
538    /// Use this specific theme with the given base size.
539    Named {
540        /// Name of the cursor theme.
541        name: &'a str,
542
543        /// Base size of the cursor names.
544        ///
545        /// Note this size assumes a scale factor of 1. Cursor image sizes may be multiplied by the base size
546        /// for HiDPI outputs.
547        size: u32,
548    },
549
550    /// Use the system provided theme
551    ///
552    /// In this case SCTK will read the `XCURSOR_THEME` and
553    /// `XCURSOR_SIZE` environment variables to figure out the
554    /// theme to use.
555    System,
556}
557
558impl<'a> Default for ThemeSpec<'a> {
559    fn default() -> Self {
560        Self::System
561    }
562}
563
564/// An error indicating that the cursor was not found.
565#[derive(Debug, thiserror::Error)]
566pub enum PointerThemeError {
567    /// An invalid ObjectId was used.
568    #[error("Invalid ObjectId")]
569    InvalidId(InvalidId),
570
571    /// A global error occurred.
572    #[error("A Global Error occured")]
573    GlobalError(GlobalError),
574
575    /// The requested cursor was not found.
576    #[error("Cursor not found")]
577    CursorNotFound,
578
579    /// There has been no enter event yet for the pointer.
580    #[error("Missing enter event serial")]
581    MissingEnterSerial,
582}
583
584#[derive(Debug)]
585pub(crate) struct Themes {
586    name: String,
587    size: u32,
588    // Scale -> CursorTheme
589    themes: HashMap<u32, CursorTheme>,
590}
591
592impl Default for Themes {
593    fn default() -> Self {
594        Themes::new(ThemeSpec::default())
595    }
596}
597
598impl Themes {
599    pub(crate) fn new(spec: ThemeSpec) -> Themes {
600        let (name, size) = match spec {
601            ThemeSpec::Named { name, size } => (name.into(), size),
602            ThemeSpec::System => {
603                let name = env::var("XCURSOR_THEME").ok().unwrap_or_else(|| "default".into());
604                let size = env::var("XCURSOR_SIZE").ok().and_then(|s| s.parse().ok()).unwrap_or(24);
605                (name, size)
606            }
607        };
608
609        Themes { name, size, themes: HashMap::new() }
610    }
611
612    fn get_cursor(
613        &mut self,
614        conn: &Connection,
615        name: &str,
616        scale: u32,
617        shm: &WlShm,
618    ) -> Result<Option<&Cursor>, InvalidId> {
619        // Check if the theme has been initialized at the specified scale.
620        if let Entry::Vacant(e) = self.themes.entry(scale) {
621            // Initialize the theme for the specified scale
622            let theme = CursorTheme::load_from_name(
623                conn,
624                shm.clone(), // TODO: Does the cursor theme need to clone wl_shm?
625                &self.name,
626                self.size * scale,
627            )?;
628
629            e.insert(theme);
630        }
631
632        let theme = self.themes.get_mut(&scale).unwrap();
633
634        Ok(theme.get_cursor(name))
635    }
636}