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            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}