use waybackend::{Waybackend, types::ObjectId};
#[derive(Debug)]
pub enum CursorLoadError {
Io(rustix::io::Errno),
EnvVar(std::env::VarError),
}
impl core::error::Error for CursorLoadError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
CursorLoadError::Io(errno) => errno.source(),
CursorLoadError::EnvVar(var_error) => var_error.source(),
}
}
fn cause(&self) -> Option<&dyn core::error::Error> {
self.source()
}
}
impl core::fmt::Display for CursorLoadError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "CursorLoadError: ")?;
match self {
CursorLoadError::Io(errno) => errno.fmt(f),
CursorLoadError::EnvVar(var_error) => var_error.fmt(f),
}
}
}
pub struct CursorTheme {
name: String,
cursors: Vec<Cursor>,
size: u32,
wl_shm_pool: ObjectId,
pool_size: u32,
pool_len: u32,
shm_fd: rustix::fd::OwnedFd,
}
impl CursorTheme {
const INITIAL_POOL_SIZE: usize = 16 * 16 * 4;
#[inline]
pub fn load_from_env<F>(mut size: u32, create_pool_fn: F) -> Result<Self, CursorLoadError>
where
F: FnOnce(&rustix::fd::OwnedFd, u32) -> ObjectId,
{
let name = &std::env::var("XCURSOR_THEME").map_err(CursorLoadError::EnvVar)?;
if let Ok(var) = std::env::var("XCURSOR_SIZE")
&& let Ok(int) = var.parse()
{
size = int;
}
Self::load_from_name(name, size, create_pool_fn)
}
#[inline]
pub fn load_from_name<F>(
name: &str,
size: u32,
create_pool_fn: F,
) -> Result<Self, CursorLoadError>
where
F: FnOnce(&rustix::fd::OwnedFd, u32) -> ObjectId,
{
let shm_fd = waybackend::shm::create().map_err(CursorLoadError::Io)?;
rustix::fs::ftruncate(&shm_fd, Self::INITIAL_POOL_SIZE as u64)
.map_err(CursorLoadError::Io)?;
let wl_shm_pool = create_pool_fn(&shm_fd, size);
let name = String::from(name);
Ok(Self {
name,
size,
pool_size: Self::INITIAL_POOL_SIZE as u32,
pool_len: 0,
cursors: Vec::new(),
wl_shm_pool,
shm_fd,
})
}
#[inline]
pub fn get_cursor<F1, F2>(
&mut self,
name: &str,
backend: &mut Waybackend,
create_buffer_fn: F1,
resize_fn: F2,
) -> Option<&Cursor>
where
F1: FnMut(&mut Waybackend, ObjectId, i32, i32, i32, i32) -> ObjectId,
F2: FnMut(&mut Waybackend, ObjectId, u32),
{
match self.cursors.iter().position(|cursor| cursor.name == name) {
Some(i) => Some(&self.cursors[i]),
None => {
let cursor =
self.load_cursor(name, self.size, backend, create_buffer_fn, resize_fn)?;
self.cursors.push(cursor);
self.cursors.last()
}
}
}
#[inline]
fn load_cursor<F1, F2>(
&mut self,
name: &str,
size: u32,
backend: &mut Waybackend,
create_buffer_fn: F1,
resize_fn: F2,
) -> Option<Cursor>
where
F1: FnMut(&mut Waybackend, ObjectId, i32, i32, i32, i32) -> ObjectId,
F2: FnMut(&mut Waybackend, ObjectId, u32),
{
use rustix::{buffer, fs, io};
let icon_path = xcursor::CursorTheme::load(&self.name).load_icon(name)?;
let icon_file = fs::open(icon_path, fs::OFlags::RDONLY, fs::Mode::RUSR).ok()?;
let mut buf = Vec::with_capacity(4096);
loop {
match io::retry_on_intr(|| io::read(&icon_file, buffer::spare_capacity(&mut buf))) {
Ok(0) => break,
Err(_) => return None,
Ok(_) => (),
}
if buf.capacity() == buf.len() {
buf.reserve(buf.capacity() * 2);
}
}
let images = xcursor::parser::parse_xcursor(&buf)?;
Some(Cursor::new(
name,
self,
&images,
size,
backend,
create_buffer_fn,
resize_fn,
))
}
#[inline]
fn grow<F>(&mut self, backend: &mut Waybackend, size: u32, resize_fn: F)
where
F: FnOnce(&mut Waybackend, ObjectId, u32),
{
if size > self.pool_size {
rustix::fs::ftruncate(&self.shm_fd, size as u64)
.expect("failed to new cursor buffer length");
resize_fn(backend, self.wl_shm_pool, size);
self.pool_size = size;
}
}
}
pub struct Cursor {
name: String,
images: Vec<CursorImageBuffer>,
total_duration: u32,
}
impl Cursor {
#[inline]
fn new<F1, F2>(
name: &str,
theme: &mut CursorTheme,
images: &[xcursor::parser::Image],
size: u32,
backend: &mut Waybackend,
mut create_buffer_fn: F1,
mut resize_fn: F2,
) -> Self
where
F1: FnMut(&mut Waybackend, ObjectId, i32, i32, i32, i32) -> ObjectId,
F2: FnMut(&mut Waybackend, ObjectId, u32),
{
let mut total_duration = 0;
let f1 = &mut create_buffer_fn;
let f2 = &mut resize_fn;
let images: Vec<CursorImageBuffer> = Self::nearest_images(size, images)
.map(|image| {
let buffer =
CursorImageBuffer::new::<&mut F1, &mut F2>(theme, image, backend, f1, f2);
total_duration += buffer.delay;
buffer
})
.collect();
Self {
total_duration,
name: String::from(name),
images,
}
}
#[inline]
fn nearest_images(
size: u32,
images: &[xcursor::parser::Image],
) -> impl Iterator<Item = &xcursor::parser::Image> {
let nearest_image = images
.iter()
.min_by_key(|image| (size as i32 - image.size as i32).abs())
.unwrap();
images.iter().filter(move |image| {
image.width == nearest_image.width && image.height == nearest_image.height
})
}
#[inline]
pub fn frame_and_duration(&self, mut millis: u32) -> (usize, u32) {
millis %= self.total_duration;
let mut res = 0;
for (i, img) in self.images.iter().enumerate() {
if millis < img.delay {
res = i;
break;
}
millis -= img.delay;
}
(res, millis)
}
#[inline]
pub fn images(&self) -> &[CursorImageBuffer] {
&self.images
}
}
pub struct CursorImageBuffer {
wl_buffer: ObjectId,
delay: u32,
xhot: u32,
yhot: u32,
width: u32,
height: u32,
}
impl CursorImageBuffer {
#[inline]
fn new<F1, F2>(
theme: &mut CursorTheme,
image: &xcursor::parser::Image,
backend: &mut Waybackend,
create_buffer_fn: F1,
resize_fn: F2,
) -> Self
where
F1: FnOnce(&mut Waybackend, ObjectId, i32, i32, i32, i32) -> ObjectId,
F2: FnOnce(&mut Waybackend, ObjectId, u32),
{
use rustix::mm::{MapFlags, ProtFlags, mmap, munmap};
let buf = &image.pixels_rgba;
let offset = theme.pool_len as u64;
let new_size = offset + buf.len() as u64;
theme.grow(backend, new_size as u32, resize_fn);
let mmap = unsafe {
mmap(
core::ptr::null_mut(),
new_size as usize,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::SHARED,
&theme.shm_fd,
0,
)
}
.expect("failed to mmap cursor shared memory");
{
let slice = unsafe {
core::slice::from_raw_parts_mut(mmap.cast::<u8>().add(offset as usize), buf.len())
};
slice.copy_from_slice(buf);
}
theme.pool_len += buf.len() as u32;
let wl_buffer = create_buffer_fn(
backend,
theme.wl_shm_pool,
offset as i32,
image.width as i32,
image.height as i32,
(image.width * 4) as i32,
);
unsafe { munmap(mmap, new_size as usize).expect("failed to munmap cursor shared memory") };
Self {
wl_buffer,
delay: image.delay,
xhot: image.xhot,
yhot: image.yhot,
width: image.width,
height: image.height,
}
}
#[inline]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
#[inline]
pub fn hotspot(&self) -> (u32, u32) {
(self.xhot, self.yhot)
}
#[inline]
pub fn delay(&self) -> u32 {
self.delay
}
#[inline]
pub fn wl_buffer(&self) -> ObjectId {
self.wl_buffer
}
}