use std::{
cell::RefCell,
ops::Deref,
rc::{Rc, Weak},
};
use wayland_client::{
protocol::{wl_compositor, wl_pointer, wl_seat, wl_shm, wl_surface},
Attached, DispatchData,
};
use wayland_cursor::{Cursor, CursorTheme};
pub enum ThemeSpec<'a> {
Precise {
name: &'a str,
size: u32,
},
System,
}
#[derive(Clone)]
pub struct ThemeManager {
themes: Rc<RefCell<ScaledThemeList>>,
compositor: Attached<wl_compositor::WlCompositor>,
}
impl ThemeManager {
pub fn init(
theme: ThemeSpec,
compositor: Attached<wl_compositor::WlCompositor>,
shm: Attached<wl_shm::WlShm>,
) -> ThemeManager {
ThemeManager { compositor, themes: Rc::new(RefCell::new(ScaledThemeList::new(theme, shm))) }
}
pub fn theme_pointer(&self, pointer: wl_pointer::WlPointer) -> ThemedPointer {
let surface = self.compositor.create_surface();
let inner = Rc::new(RefCell::new(PointerInner {
surface: surface.detach(),
themes: self.themes.clone(),
last_serial: 0,
current_cursor: "left_ptr".into(),
scale_factor: 1,
}));
let my_pointer = pointer.clone();
let winner = Rc::downgrade(&inner);
crate::surface::setup_surface(
surface,
Some(move |scale_factor, _, _: DispatchData| {
if let Some(inner) = Weak::upgrade(&winner) {
let mut inner = inner.borrow_mut();
inner.scale_factor = scale_factor;
let _ = inner.update_cursor(&my_pointer);
}
}),
);
ThemedPointer { pointer, inner }
}
pub fn theme_pointer_with_impl<F>(
&self,
seat: &Attached<wl_seat::WlSeat>,
mut callback: F,
) -> ThemedPointer
where
F: FnMut(wl_pointer::Event, ThemedPointer, DispatchData) + 'static,
{
let surface = self.compositor.create_surface();
let inner = Rc::new(RefCell::new(PointerInner {
surface: surface.detach(),
themes: self.themes.clone(),
last_serial: 0,
current_cursor: "left_ptr".into(),
scale_factor: 1,
}));
let inner2 = inner.clone();
let pointer = seat.get_pointer();
pointer.quick_assign(move |ptr, event, ddata| {
callback(event, ThemedPointer { pointer: ptr.detach(), inner: inner2.clone() }, ddata)
});
let winner = Rc::downgrade(&inner);
let my_pointer = pointer.clone();
crate::surface::setup_surface(
surface,
Some(move |scale_factor, _, _: DispatchData| {
if let Some(inner) = Weak::upgrade(&winner) {
let mut inner = inner.borrow_mut();
inner.scale_factor = scale_factor;
let _ = inner.update_cursor(&my_pointer);
}
}),
);
ThemedPointer { pointer: pointer.detach(), inner }
}
}
struct ScaledThemeList {
shm: Attached<wl_shm::WlShm>,
name: String,
size: u32,
themes: Vec<(u32, CursorTheme)>,
}
impl ScaledThemeList {
fn new(theme: ThemeSpec, shm: Attached<wl_shm::WlShm>) -> ScaledThemeList {
let (name, size) = match theme {
ThemeSpec::Precise { name, size } => (name.into(), size),
ThemeSpec::System => {
let name = std::env::var("XCURSOR_THEME").ok().unwrap_or_else(|| "default".into());
let size =
std::env::var("XCURSOR_SIZE").ok().and_then(|s| s.parse().ok()).unwrap_or(24);
(name, size)
}
};
ScaledThemeList { shm, name, size, themes: vec![] }
}
fn get_cursor(&mut self, name: &str, scale: u32) -> Option<&Cursor> {
let opt_index = self.themes.iter().position(|&(s, _)| s == scale);
if let Some(idx) = opt_index {
self.themes[idx].1.get_cursor(name)
} else {
let new_theme = CursorTheme::load_from_name(&self.name, self.size * scale, &self.shm);
self.themes.push((scale, new_theme));
self.themes.last_mut().unwrap().1.get_cursor(name)
}
}
}
struct PointerInner {
surface: wl_surface::WlSurface,
themes: Rc<RefCell<ScaledThemeList>>,
current_cursor: String,
last_serial: u32,
scale_factor: i32,
}
impl PointerInner {
fn update_cursor(&self, pointer: &wl_pointer::WlPointer) -> Result<(), ()> {
let mut themes = self.themes.borrow_mut();
let scale = self.scale_factor as u32;
let cursor = themes.get_cursor(&self.current_cursor, scale).ok_or(())?;
let image = &cursor[0];
let (w, h) = image.dimensions();
let (hx, hy) = image.hotspot();
self.surface.set_buffer_scale(scale as i32);
self.surface.attach(Some(&image), 0, 0);
if self.surface.as_ref().version() >= 4 {
self.surface.damage_buffer(0, 0, w as i32, h as i32);
} else {
self.surface.damage(0, 0, w as i32 / scale as i32, h as i32 / scale as i32);
}
self.surface.commit();
pointer.set_cursor(
self.last_serial,
Some(&self.surface),
hx as i32 / scale as i32,
hy as i32 / scale as i32,
);
Ok(())
}
}
#[derive(Clone)]
pub struct ThemedPointer {
pointer: wl_pointer::WlPointer,
inner: Rc<RefCell<PointerInner>>,
}
impl ThemedPointer {
pub fn set_cursor(&self, name: &str, serial: Option<u32>) -> Result<(), ()> {
let mut inner = self.inner.borrow_mut();
if let Some(s) = serial {
inner.last_serial = s;
}
inner.current_cursor = name.into();
inner.update_cursor(&self.pointer)
}
}
impl Deref for ThemedPointer {
type Target = wl_pointer::WlPointer;
fn deref(&self) -> &wl_pointer::WlPointer {
&self.pointer
}
}
impl Drop for PointerInner {
fn drop(&mut self) {
self.surface.destroy();
}
}