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