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 enter 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(spec: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}
637