use crate::linux::Globals;use crate::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};use anyhow::{Context as _, anyhow};use gpui_util::ResultExt; use wayland_client::Connection;use wayland_client::protocol::wl_surface::WlSurface;use wayland_client::protocol::{wl_pointer::WlPointer, wl_shm::WlShm};use wayland_cursor::{CursorImageBuffer, CursorTheme}; pub(crate) struct Cursor { loaded_theme: Option<LoadedTheme>, size: u32, scaled_size: u32, surface: WlSurface, shm: WlShm, connection: Connection,} pub(crate) struct LoadedTheme { theme: CursorTheme, name: Option<String>, scaled_size: u32,} impl Drop for Cursor { fn drop(&mut self) { self.loaded_theme.take(); self.surface.destroy(); }} impl Cursor { pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self { let mut this = Self { loaded_theme: None, size, scaled_size: size, surface: globals.compositor.create_surface(&globals.qh, ()), shm: globals.shm.clone(), connection: connection.clone(), }; this.set_theme_internal(None); this } fn set_theme_internal(&mut self, theme_name: Option<String>) { if let Some(loaded_theme) = self.loaded_theme.as_ref() && loaded_theme.name == theme_name && loaded_theme.scaled_size == self.scaled_size { return; } let result = if let Some(theme_name) = theme_name.as_ref() { CursorTheme::load_from_name( &self.connection, self.shm.clone(), theme_name, self.scaled_size, ) } else { CursorTheme::load(&self.connection, self.shm.clone(), self.scaled_size) }; if let Some(theme) = result .context("Wayland: Failed to load cursor theme") .log_err() { self.loaded_theme = Some(LoadedTheme { theme, name: theme_name, scaled_size: self.scaled_size, }); } } pub fn set_theme(&mut self, theme_name: String) { self.set_theme_internal(Some(theme_name)); } fn set_scaled_size(&mut self, scaled_size: u32) { self.scaled_size = scaled_size; let theme_name = self .loaded_theme .as_ref() .and_then(|loaded_theme| loaded_theme.name.clone()); self.set_theme_internal(theme_name); } pub fn set_size(&mut self, size: u32) { self.size = size; self.set_scaled_size(size); } pub fn set_icon( &mut self, wl_pointer: &WlPointer, serial_id: u32, cursor_icon_names: &[&str], scale: i32, ) { self.set_scaled_size(self.size * scale as u32); let Some(loaded_theme) = &mut self.loaded_theme else { log::warn!("Wayland: Unable to load cursor themes"); return; }; let theme = &mut loaded_theme.theme; let buffer: &CursorImageBuffer; 'outer: { for cursor_icon_name in cursor_icon_names { if let Some(cursor) = theme.get_cursor(cursor_icon_name) { buffer = &cursor[0]; break 'outer; } } if let Some(cursor) = theme.get_cursor(DEFAULT_CURSOR_ICON_NAME) { buffer = &cursor[0]; log_cursor_icon_warning(anyhow!( "wayland: Unable to get cursor icon {:?}. \ Using default cursor icon: '{}'", cursor_icon_names, DEFAULT_CURSOR_ICON_NAME )); } else { log_cursor_icon_warning(anyhow!( "wayland: Unable to fallback on default cursor icon '{}' for theme '{}'", DEFAULT_CURSOR_ICON_NAME, loaded_theme.name.as_deref().unwrap_or("default") )); return; } } let (width, height) = buffer.dimensions(); let (hot_x, hot_y) = buffer.hotspot(); self.surface.set_buffer_scale(scale); wl_pointer.set_cursor( serial_id, Some(&self.surface), hot_x as i32 / scale, hot_y as i32 / scale, ); self.surface.attach(Some(buffer), 0, 0); self.surface.damage(0, 0, width as i32, height as i32); self.surface.commit(); }}
use crate::linux::Globals;
use crate::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
use anyhow::{Context as _, anyhow};
use gpui_util::ResultExt;
use wayland_client::Connection;
use wayland_client::protocol::wl_surface::WlSurface;
use wayland_client::protocol::{wl_pointer::WlPointer, wl_shm::WlShm};
use wayland_cursor::{CursorImageBuffer, CursorTheme};
pub(crate) struct Cursor {
loaded_theme: Option<LoadedTheme>,
size: u32,
scaled_size: u32,
surface: WlSurface,
shm: WlShm,
connection: Connection,
}
pub(crate) struct LoadedTheme {
theme: CursorTheme,
name: Option<String>,
scaled_size: u32,
}
impl Drop for Cursor {
fn drop(&mut self) {
self.loaded_theme.take();
self.surface.destroy();
}
}
impl Cursor {
pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
let mut this = Self {
loaded_theme: None,
size,
scaled_size: size,
surface: globals.compositor.create_surface(&globals.qh, ()),
shm: globals.shm.clone(),
connection: connection.clone(),
};
this.set_theme_internal(None);
this
}
fn set_theme_internal(&mut self, theme_name: Option<String>) {
if let Some(loaded_theme) = self.loaded_theme.as_ref()
&& loaded_theme.name == theme_name
&& loaded_theme.scaled_size == self.scaled_size
{
return;
}
let result = if let Some(theme_name) = theme_name.as_ref() {
CursorTheme::load_from_name(
&self.connection,
self.shm.clone(),
theme_name,
self.scaled_size,
)
} else {
CursorTheme::load(&self.connection, self.shm.clone(), self.scaled_size)
};
if let Some(theme) = result
.context("Wayland: Failed to load cursor theme")
.log_err()
{
self.loaded_theme = Some(LoadedTheme {
theme,
name: theme_name,
scaled_size: self.scaled_size,
});
}
}
pub fn set_theme(&mut self, theme_name: String) {
self.set_theme_internal(Some(theme_name));
}
fn set_scaled_size(&mut self, scaled_size: u32) {
self.scaled_size = scaled_size;
let theme_name = self
.loaded_theme
.as_ref()
.and_then(|loaded_theme| loaded_theme.name.clone());
self.set_theme_internal(theme_name);
}
pub fn set_size(&mut self, size: u32) {
self.size = size;
self.set_scaled_size(size);
}
pub fn set_icon(
&mut self,
wl_pointer: &WlPointer,
serial_id: u32,
cursor_icon_names: &[&str],
scale: i32,
) {
self.set_scaled_size(self.size * scale as u32);
let Some(loaded_theme) = &mut self.loaded_theme else {
log::warn!("Wayland: Unable to load cursor themes");
return;
};
let theme = &mut loaded_theme.theme;
let buffer: &CursorImageBuffer;
'outer: {
for cursor_icon_name in cursor_icon_names {
if let Some(cursor) = theme.get_cursor(cursor_icon_name) {
buffer = &cursor[0];
break 'outer;
}
}
if let Some(cursor) = theme.get_cursor(DEFAULT_CURSOR_ICON_NAME) {
buffer = &cursor[0];
log_cursor_icon_warning(anyhow!(
"wayland: Unable to get cursor icon {:?}. \
Using default cursor icon: '{}'",
cursor_icon_names,
DEFAULT_CURSOR_ICON_NAME
));
} else {
log_cursor_icon_warning(anyhow!(
"wayland: Unable to fallback on default cursor icon '{}' for theme '{}'",
DEFAULT_CURSOR_ICON_NAME,
loaded_theme.name.as_deref().unwrap_or("default")
));
return;
}
}
let (width, height) = buffer.dimensions();
let (hot_x, hot_y) = buffer.hotspot();
self.surface.set_buffer_scale(scale);
wl_pointer.set_cursor(
serial_id,
Some(&self.surface),
hot_x as i32 / scale,
hot_y as i32 / scale,
);
self.surface.attach(Some(buffer), 0, 0);
self.surface.damage(0, 0, width as i32, height as i32);
self.surface.commit();
}
}