cursor.rs

  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            && loaded_theme.name == theme_name
 50            && loaded_theme.scaled_size == self.scaled_size
 51        {
 52            return;
 53        }
 54        let result = if let Some(theme_name) = theme_name.as_ref() {
 55            CursorTheme::load_from_name(
 56                &self.connection,
 57                self.shm.clone(),
 58                theme_name,
 59                self.scaled_size,
 60            )
 61        } else {
 62            CursorTheme::load(&self.connection, self.shm.clone(), self.scaled_size)
 63        };
 64        if let Some(theme) = result
 65            .context("Wayland: Failed to load cursor theme")
 66            .log_err()
 67        {
 68            self.loaded_theme = Some(LoadedTheme {
 69                theme,
 70                name: theme_name,
 71                scaled_size: self.scaled_size,
 72            });
 73        }
 74    }
 75
 76    pub fn set_theme(&mut self, theme_name: String) {
 77        self.set_theme_internal(Some(theme_name));
 78    }
 79
 80    fn set_scaled_size(&mut self, scaled_size: u32) {
 81        self.scaled_size = scaled_size;
 82        let theme_name = self
 83            .loaded_theme
 84            .as_ref()
 85            .and_then(|loaded_theme| loaded_theme.name.clone());
 86        self.set_theme_internal(theme_name);
 87    }
 88
 89    pub fn set_size(&mut self, size: u32) {
 90        self.size = size;
 91        self.set_scaled_size(size);
 92    }
 93
 94    pub fn set_icon(
 95        &mut self,
 96        wl_pointer: &WlPointer,
 97        serial_id: u32,
 98        mut cursor_icon_names: &[&str],
 99        scale: i32,
100    ) {
101        self.set_scaled_size(self.size * scale as u32);
102
103        let Some(loaded_theme) = &mut self.loaded_theme else {
104            log::warn!("Wayland: Unable to load cursor themes");
105            return;
106        };
107        let mut theme = &mut loaded_theme.theme;
108
109        let mut buffer: &CursorImageBuffer;
110        'outer: {
111            for cursor_icon_name in cursor_icon_names {
112                if let Some(cursor) = theme.get_cursor(cursor_icon_name) {
113                    buffer = &cursor[0];
114                    break 'outer;
115                }
116            }
117
118            if let Some(cursor) = theme.get_cursor(DEFAULT_CURSOR_ICON_NAME) {
119                buffer = &cursor[0];
120                log_cursor_icon_warning(anyhow!(
121                    "wayland: Unable to get cursor icon {:?}. \
122                    Using default cursor icon: '{}'",
123                    cursor_icon_names,
124                    DEFAULT_CURSOR_ICON_NAME
125                ));
126            } else {
127                log_cursor_icon_warning(anyhow!(
128                    "wayland: Unable to fallback on default cursor icon '{}' for theme '{}'",
129                    DEFAULT_CURSOR_ICON_NAME,
130                    loaded_theme.name.as_deref().unwrap_or("default")
131                ));
132                return;
133            }
134        }
135
136        let (width, height) = buffer.dimensions();
137        let (hot_x, hot_y) = buffer.hotspot();
138
139        self.surface.set_buffer_scale(scale);
140
141        wl_pointer.set_cursor(
142            serial_id,
143            Some(&self.surface),
144            hot_x as i32 / scale,
145            hot_y as i32 / scale,
146        );
147
148        self.surface.attach(Some(buffer), 0, 0);
149        self.surface.damage(0, 0, width as i32, height as i32);
150        self.surface.commit();
151    }
152}