1 | use std::{ |
2 | collections::{hash_map::Entry, HashMap}, |
3 | env, iter, mem, |
4 | sync::{Arc, Mutex}, |
5 | }; |
6 | |
7 | use wayland_backend::{client::InvalidId, smallvec::SmallVec}; |
8 | use 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 | }; |
17 | use wayland_cursor::{Cursor, CursorTheme}; |
18 | use wayland_protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; |
19 | |
20 | use crate::{ |
21 | compositor::{SurfaceData, SurfaceDataExt}, |
22 | error::GlobalError, |
23 | }; |
24 | |
25 | use super::SeatState; |
26 | |
27 | #[doc (inline)] |
28 | pub use cursor_icon::{CursorIcon, ParseError as CursorIconParseError}; |
29 | |
30 | pub mod cursor_shape; |
31 | |
32 | use cursor_shape::cursor_icon_to_shape; |
33 | |
34 | /* From linux/input-event-codes.h - the buttons usually used by mice */ |
35 | pub const BTN_LEFT: u32 = 0x110; |
36 | pub const BTN_RIGHT: u32 = 0x111; |
37 | pub const BTN_MIDDLE: u32 = 0x112; |
38 | /// The fourth non-scroll button, which is often used as "back" in web browsers. |
39 | pub const BTN_SIDE: u32 = 0x113; |
40 | /// The fifth non-scroll button, which is often used as "forward" in web browsers. |
41 | pub const BTN_EXTRA: u32 = 0x114; |
42 | |
43 | /// See also [`BTN_EXTRA`]. |
44 | pub const BTN_FORWARD: u32 = 0x115; |
45 | /// See also [`BTN_SIDE`]. |
46 | pub const BTN_BACK: u32 = 0x116; |
47 | pub const BTN_TASK: u32 = 0x117; |
48 | |
49 | /// Describes a scroll along one axis |
50 | #[derive (Default, Debug, Clone, Copy, PartialEq)] |
51 | pub 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 | |
67 | impl 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)] |
82 | pub struct PointerEvent { |
83 | pub surface: WlSurface, |
84 | pub position: (f64, f64), |
85 | pub kind: PointerEventKind, |
86 | } |
87 | |
88 | #[derive (Debug, Clone)] |
89 | pub 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 | |
117 | pub 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)] |
134 | pub struct PointerData { |
135 | seat: WlSeat, |
136 | pub(crate) inner: Mutex<PointerDataInner>, |
137 | } |
138 | |
139 | impl 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 | |
161 | pub trait PointerDataExt: Send + Sync { |
162 | fn pointer_data(&self) -> &PointerData; |
163 | } |
164 | |
165 | impl PointerDataExt for PointerData { |
166 | fn pointer_data(&self) -> &PointerData { |
167 | self |
168 | } |
169 | } |
170 | |
171 | #[macro_export ] |
172 | macro_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)] |
206 | pub(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 enter event for the pointer |
219 | pub(crate) latest_btn: Option<u32>, |
220 | } |
221 | |
222 | impl<D, U> Dispatch<WlPointer, U, D> for SeatState |
223 | where |
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)] |
416 | pub 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 | |
428 | impl<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 | |
522 | impl<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)] |
537 | pub 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 | |
558 | impl<'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)] |
566 | pub 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)] |
585 | pub(crate) struct Themes { |
586 | name: String, |
587 | size: u32, |
588 | // Scale -> CursorTheme |
589 | themes: HashMap<u32, CursorTheme>, |
590 | } |
591 | |
592 | impl Default for Themes { |
593 | fn default() -> Self { |
594 | Themes::new(spec:ThemeSpec::default()) |
595 | } |
596 | } |
597 | |
598 | impl 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 | } |
637 | |