1use crate::Globals;
2use crate::platform::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
3use anyhow::{Context as _, anyhow};
4use util::ResultExt;
5
6use wayland_client::Connection;
7use wayland_client::protocol::wl_surface::WlSurface;
8use wayland_client::protocol::{wl_pointer::WlPointer, wl_shm::WlShm};
9use wayland_cursor::{CursorImageBuffer, CursorTheme};
10
11pub(crate) struct Cursor {
12 loaded_theme: Option<LoadedTheme>,
13 size: u32,
14 scaled_size: u32,
15 surface: WlSurface,
16 shm: WlShm,
17 connection: Connection,
18}
19
20pub(crate) struct LoadedTheme {
21 theme: CursorTheme,
22 name: Option<String>,
23 scaled_size: u32,
24}
25
26impl Drop for Cursor {
27 fn drop(&mut self) {
28 self.loaded_theme.take();
29 self.surface.destroy();
30 }
31}
32
33impl Cursor {
34 pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
35 let mut this = Self {
36 loaded_theme: None,
37 size,
38 scaled_size: size,
39 surface: globals.compositor.create_surface(&globals.qh, ()),
40 shm: globals.shm.clone(),
41 connection: connection.clone(),
42 };
43 this.set_theme_internal(None);
44 this
45 }
46
47 fn set_theme_internal(&mut self, theme_name: Option<String>) {
48 if let Some(loaded_theme) = self.loaded_theme.as_ref() {
49 if loaded_theme.name == theme_name && loaded_theme.scaled_size == self.scaled_size {
50 return;
51 }
52 }
53 let result = if let Some(theme_name) = theme_name.as_ref() {
54 CursorTheme::load_from_name(
55 &self.connection,
56 self.shm.clone(),
57 theme_name,
58 self.scaled_size,
59 )
60 } else {
61 CursorTheme::load(&self.connection, self.shm.clone(), self.scaled_size)
62 };
63 if let Some(theme) = result
64 .context("Wayland: Failed to load cursor theme")
65 .log_err()
66 {
67 self.loaded_theme = Some(LoadedTheme {
68 theme,
69 name: theme_name.map(|name| name.to_string()),
70 scaled_size: self.scaled_size,
71 });
72 }
73 }
74
75 pub fn set_theme(&mut self, theme_name: String) {
76 self.set_theme_internal(Some(theme_name));
77 }
78
79 fn set_scaled_size(&mut self, scaled_size: u32) {
80 self.scaled_size = scaled_size;
81 let theme_name = self
82 .loaded_theme
83 .as_ref()
84 .and_then(|loaded_theme| loaded_theme.name.clone());
85 self.set_theme_internal(theme_name);
86 }
87
88 pub fn set_size(&mut self, size: u32) {
89 self.size = size;
90 self.set_scaled_size(size);
91 }
92
93 pub fn set_icon(
94 &mut self,
95 wl_pointer: &WlPointer,
96 serial_id: u32,
97 mut cursor_icon_names: &[&str],
98 scale: i32,
99 ) {
100 self.set_scaled_size(self.size * scale as u32);
101
102 let Some(loaded_theme) = &mut self.loaded_theme else {
103 log::warn!("Wayland: Unable to load cursor themes");
104 return;
105 };
106 let mut theme = &mut loaded_theme.theme;
107
108 let mut buffer: &CursorImageBuffer;
109 'outer: {
110 for cursor_icon_name in cursor_icon_names {
111 if let Some(cursor) = theme.get_cursor(cursor_icon_name) {
112 buffer = &cursor[0];
113 break 'outer;
114 }
115 }
116
117 if let Some(cursor) = theme.get_cursor(DEFAULT_CURSOR_ICON_NAME) {
118 buffer = &cursor[0];
119 log_cursor_icon_warning(anyhow!(
120 "wayland: Unable to get cursor icon {:?}. \
121 Using default cursor icon: '{}'",
122 cursor_icon_names,
123 DEFAULT_CURSOR_ICON_NAME
124 ));
125 } else {
126 log_cursor_icon_warning(anyhow!(
127 "wayland: Unable to fallback on default cursor icon '{}' for theme '{}'",
128 DEFAULT_CURSOR_ICON_NAME,
129 loaded_theme.name.as_deref().unwrap_or("default")
130 ));
131 return;
132 }
133 }
134
135 let (width, height) = buffer.dimensions();
136 let (hot_x, hot_y) = buffer.hotspot();
137
138 self.surface.set_buffer_scale(scale);
139
140 wl_pointer.set_cursor(
141 serial_id,
142 Some(&self.surface),
143 hot_x as i32 / scale,
144 hot_y as i32 / scale,
145 );
146
147 self.surface.attach(Some(&buffer), 0, 0);
148 self.surface.damage(0, 0, width as i32, height as i32);
149 self.surface.commit();
150 }
151}