use std::{
env,
fs::File,
io::{Read, Seek, SeekFrom, Write},
ops::{Deref, Index},
os::unix::io::{AsRawFd, FromRawFd},
};
use wayland_client::{
protocol::{
wl_buffer::WlBuffer,
wl_shm::{Format, WlShm},
wl_shm_pool::WlShmPool,
},
Attached, Main,
};
use xcur::parser::File as XCurFile;
use xcursor::{theme_search_paths, XCursorTheme};
pub struct CursorTheme {
name: String,
cursors: Vec<Cursor>,
size: u32,
pool: Main<WlShmPool>,
pool_size: i32,
file: File,
}
impl CursorTheme {
pub fn load(size: u32, shm: &Attached<WlShm>) -> Self {
CursorTheme::load_or("default", size, shm)
}
pub fn load_or(name: &str, mut size: u32, shm: &Attached<WlShm>) -> Self {
let name_string = String::from(name);
let name = &env::var("XCURSOR_THEME").unwrap_or(name_string);
if let Ok(var) = env::var("XCURSOR_SIZE") {
if let Ok(int) = var.parse() {
size = int;
}
}
CursorTheme::load_from_name(name, size, shm)
}
pub fn load_from_name(name: &str, size: u32, shm: &Attached<WlShm>) -> Self {
let name = String::from(name);
let pool_size = (size * size * 4) as i32;
let mem_fd = create_shm_fd().unwrap();
let file = unsafe { File::from_raw_fd(mem_fd) };
let pool = shm.create_pool(file.as_raw_fd(), pool_size);
CursorTheme {
name,
file,
size,
pool,
pool_size,
cursors: Vec::new(),
}
}
pub fn get_cursor(&mut self, name: &str) -> Option<&Cursor> {
let cur = self.cursors.iter().position(|i| i.name == name);
match cur {
Some(i) => Some(&self.cursors[i]),
None => {
let cur = self.load_cursor(name, self.size).unwrap();
self.cursors.push(cur);
self.cursors.iter().last()
}
}
}
fn load_cursor(&mut self, name: &str, size: u32) -> Option<Cursor> {
let icon_path = XCursorTheme::load(&self.name, &theme_search_paths()).load_icon(name)?;
let mut icon_file = File::open(icon_path).ok()?;
let mut buf = Vec::new();
let xcur = {
icon_file.read_to_end(&mut buf).ok()?;
XCurFile::parse(&buf)
};
if !xcur.is_done() {
return None;
}
let file_images = xcur.unwrap().1.images;
let cursor = Cursor::new(name, self, &file_images, size);
Some(cursor)
}
fn grow(&mut self, size: i32) {
if size > self.pool_size {
self.pool.resize(size);
self.pool_size = size;
}
}
}
#[derive(Clone)]
pub struct Cursor {
name: String,
images: Vec<CursorImageBuffer>,
total_duration: u32,
}
impl Cursor {
fn new(name: &str, theme: &mut CursorTheme, images: &[xcur::parser::Image], size: u32) -> Self {
let mut buffers = Vec::with_capacity(images.len());
let size = Cursor::nearest_size(size, images);
let iter = images.iter().filter(|el| el.width == size && el.height == size);
for img in iter {
buffers.push(CursorImageBuffer::new(theme, img));
}
let total_duration = buffers.iter().map(|el| el.delay).sum();
Cursor {
total_duration,
name: String::from(name),
images: buffers,
}
}
fn nearest_size(size: u32, images: &[xcur::parser::Image]) -> u32 {
let size = size as i32;
let mut all_sizes = Vec::new();
for img in images {
if !all_sizes.contains(&(img.width as i32)) {
all_sizes.push(img.width as i32);
}
}
let mut min = 0;
for (i, width) in all_sizes.iter().enumerate() {
if (width - size).abs() < (all_sizes[min] - size).abs() {
min = i;
}
}
all_sizes[min] as u32
}
pub fn frame_and_duration(&self, mut millis: u32) -> FrameAndDuration {
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;
}
FrameAndDuration {
frame_index: res,
frame_duration: millis,
}
}
pub fn image_count(&self) -> usize {
self.images.len()
}
}
impl Index<usize> for Cursor {
type Output = CursorImageBuffer;
fn index(&self, index: usize) -> &Self::Output {
&self.images[index]
}
}
#[derive(Clone)]
pub struct CursorImageBuffer {
buffer: WlBuffer,
delay: u32,
xhot: u32,
yhot: u32,
width: u32,
height: u32,
}
impl CursorImageBuffer {
fn new(theme: &mut CursorTheme, image: &xcur::parser::Image) -> Self {
let buf = CursorImageBuffer::convert_pixels(&image.pixels);
let offset = theme.file.seek(SeekFrom::End(0)).unwrap();
theme.file.write_all(&buf).unwrap();
let new_size = theme.file.seek(SeekFrom::End(0)).unwrap();
theme.grow(new_size as i32);
let buffer = theme
.pool
.create_buffer(
offset as i32,
image.width as i32,
image.height as i32,
(image.width * 4) as i32,
Format::Argb8888,
)
.detach();
CursorImageBuffer {
buffer,
delay: image.delay,
xhot: image.xhot,
yhot: image.yhot,
width: image.width,
height: image.height,
}
}
fn convert_pixels(pixels: &[u32]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::with_capacity(pixels.len() * 4);
for pixel in pixels {
buf.extend_from_slice(&pixel.to_be_bytes());
}
buf
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn hotspot(&self) -> (u32, u32) {
(self.xhot, self.yhot)
}
pub fn delay(&self) -> u32 {
self.delay
}
}
impl Deref for CursorImageBuffer {
type Target = WlBuffer;
fn deref(&self) -> &WlBuffer {
&self.buffer
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FrameAndDuration {
pub frame_index: usize,
pub frame_duration: u32,
}
use {
nix::{
errno::Errno,
fcntl,
sys::{memfd, mman, stat},
unistd,
},
std::{
ffi::CStr,
io,
os::unix::io::RawFd,
time::{SystemTime, UNIX_EPOCH},
},
};
fn create_shm_fd() -> io::Result<RawFd> {
#[cfg(target_os = "linux")]
loop {
match memfd::memfd_create(
CStr::from_bytes_with_nul(b"wayland-cursor-rs\0").unwrap(),
memfd::MemFdCreateFlag::MFD_CLOEXEC,
) {
Ok(fd) => return Ok(fd),
Err(nix::Error::Sys(Errno::EINTR)) => continue,
Err(nix::Error::Sys(Errno::ENOSYS)) => break,
Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
Err(err) => unreachable!(err),
}
}
let sys_time = SystemTime::now();
let mut mem_file_handle = format!(
"/wayland-cursor-rs-{}",
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
);
loop {
match mman::shm_open(
mem_file_handle.as_str(),
fcntl::OFlag::O_CREAT | fcntl::OFlag::O_EXCL | fcntl::OFlag::O_RDWR | fcntl::OFlag::O_CLOEXEC,
stat::Mode::S_IRUSR | stat::Mode::S_IWUSR,
) {
Ok(fd) => match mman::shm_unlink(mem_file_handle.as_str()) {
Ok(_) => return Ok(fd),
Err(nix::Error::Sys(errno)) => match unistd::close(fd) {
Ok(_) => return Err(io::Error::from(errno)),
Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
Err(err) => panic!(err),
},
Err(err) => panic!(err),
},
Err(nix::Error::Sys(Errno::EEXIST)) => {
mem_file_handle = format!(
"/wayland-cursor-rs-{}",
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
);
continue;
}
Err(nix::Error::Sys(Errno::EINTR)) => continue,
Err(nix::Error::Sys(errno)) => return Err(io::Error::from(errno)),
Err(err) => unreachable!(err),
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_convert_pixels() {
let pixels: &[u32] = &[0x12345678, 0x87654321];
let parsed_pixels: &[u8] = &[0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21];
assert_eq!(
super::CursorImageBuffer::convert_pixels(&pixels),
Vec::from(parsed_pixels)
);
}
}