Skip to main content

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