wayland-cursor-0.29.5/.cargo_vcs_info.json0000644000000001540000000000100141030ustar { "git": { "sha1": "8f4127a21d65cf10189ff0e3b78983e9686dd288" }, "path_in_vcs": "wayland-cursor" }wayland-cursor-0.29.5/Cargo.toml0000644000000020720000000000100121020ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "wayland-cursor" version = "0.29.5" authors = ["Victor Berger "] description = "Bindings to libwayland-cursor." documentation = "https://smithay.github.io/wayland-rs/wayland_client/" readme = "README.md" keywords = [ "wayland", "client", ] categories = [ "gui", "api-bindings", ] license = "MIT" repository = "https://github.com/smithay/wayland-rs" [dependencies.nix] version = "0.24.1" features = [ "fs", "mman", ] default-features = false [dependencies.wayland-client] version = "0.29.5" [dependencies.xcursor] version = "0.3.1" wayland-cursor-0.29.5/Cargo.toml.orig000064400000000000000000000011101046102023000155530ustar 00000000000000[package] name = "wayland-cursor" version = "0.29.5" documentation = "https://smithay.github.io/wayland-rs/wayland_client/" repository = "https://github.com/smithay/wayland-rs" authors = ["Victor Berger "] license = "MIT" edition = "2018" categories = ["gui", "api-bindings"] keywords = ["wayland", "client"] description = "Bindings to libwayland-cursor." readme = "README.md" [dependencies] wayland-client = { version = "0.29.5", path = "../wayland-client" } xcursor = "0.3.1" nix = { version = "0.24.1", default-features = false, features = ["fs", "mman"] } wayland-cursor-0.29.5/LICENSE.txt000064400000000000000000000020401046102023000145120ustar 00000000000000Copyright (c) 2015 Victor Berger Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.wayland-cursor-0.29.5/README.md000064400000000000000000000014111046102023000141470ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/wayland-cursor.svg)](https://crates.io/crates/wayland-cursor) [![docs.rs](https://docs.rs/wayland-cursor/badge.svg)](https://docs.rs/wayland-cursor) [![Continuous Integration](https://github.com/Smithay/wayland-rs/workflows/Continuous%20Integration/badge.svg)](https://github.com/Smithay/wayland-rs/actions?query=workflow%3A%22Continuous+Integration%22) [![codecov](https://codecov.io/gh/Smithay/wayland-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/Smithay/wayland-rs) # wayland-cursor Loading of XCursor images for Wayland client apps. This crate provides helpers to load the system provided cursor images and load them into `WlBuffer`s as well as obtain the necessary metadata to properly display animated cursors. wayland-cursor-0.29.5/src/lib.rs000064400000000000000000000306641046102023000146070ustar 00000000000000#![warn(missing_docs, missing_debug_implementations)] //! Wayland cursor utilities //! //! This crate aims to reimplement the functionality of the `libwayland-cursor` library in Rust. //! //! It allows you to load cursors from the system and display them correctly. //! //! First of all, you need to create a `CursorTheme`, //! which represents the full cursor theme. //! //! From this theme, using the `get_cursor` method, you can load a specific `Cursor`, //! which can contain several images if the cursor is animated. It also provides you with the //! means of querying which frame of the animation should be displayed at //! what time, as well as handles to the buffers containing these frames, to //! attach them to a wayland surface. //! //! # Example //! //! ```ignore //! use wayland_cursor::CursorTheme; //! # use std::thread::sleep; //! # use std::time::{Instant, Duration}; //! //! let cursor_theme = CursorTheme::load(32, wl_shm); //! let cursor = cursor_theme.get_cursor("wait").expect("Cursor not provided by theme"); //! //! let start_time = Instant::now(); //! loop { //! // Obtain which frame we should show, and for how long. //! let millis = start_time.elapsed().as_millis(); //! let fr_info = cursor.frame_and_duration(millis as u32); //! //! // Here, we obtain the right cursor frame... //! let buffer = cursor[fr_info.frame_index]; //! // and attach it to a wl_surface. //! cursor_surface.attach(Some(&buffer), 0, 0); //! cursor_surface.commit(); //! //! sleep(fr_info.frame_duration); //! } //! ``` use std::env; use std::fs::File; use std::io::{Error as IoError, Read, Result as IoResult, Seek, SeekFrom, Write}; use std::ops::{Deref, Index}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::time::{SystemTime, UNIX_EPOCH}; use nix::errno::Errno; use nix::fcntl; use nix::sys::{mman, stat}; use nix::unistd; #[cfg(target_os = "linux")] use {nix::sys::memfd, std::ffi::CStr}; use wayland_client::protocol::wl_buffer::WlBuffer; use wayland_client::protocol::wl_shm::{Format, WlShm}; use wayland_client::protocol::wl_shm_pool::WlShmPool; use wayland_client::{Attached, Main}; use xcursor::parser as xparser; use xcursor::CursorTheme as XCursorTheme; use xparser::Image as XCursorImage; /// Represents a cursor theme loaded from the system. #[derive(Debug)] pub struct CursorTheme { name: String, cursors: Vec, size: u32, pool: Main, pool_size: i32, file: File, } impl CursorTheme { /// Load a cursor theme from system defaults. /// /// Same as calling `load_or("default", size, shm)` pub fn load(size: u32, shm: &Attached) -> Self { CursorTheme::load_or("default", size, shm) } /// Load a cursor theme, using `name` as fallback. /// /// The theme name and cursor size are read from the `XCURSOR_THEME` and /// `XCURSOR_SIZE` environment variables, respectively, or from the provided variables /// if those are invalid. pub fn load_or(name: &str, mut size: u32, shm: &Attached) -> 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) } /// Create a new cursor theme, ignoring the system defaults. pub fn load_from_name(name: &str, size: u32, shm: &Attached) -> Self { // Set some minimal cursor size to hold it. We're not using `size` argument for that, // because the actual size that we'll use depends on theme sizes available on a system. // The minimal size covers most common minimal theme size, which is 16. const INITIAL_POOL_SIZE: i32 = 16 * 16 * 4; // Create shm. let mem_fd = create_shm_fd().expect("Shm fd allocation failed"); let mut file = unsafe { File::from_raw_fd(mem_fd) }; file.set_len(INITIAL_POOL_SIZE as u64).expect("Failed to set buffer length"); // Ensure that we have the same we requested. file.write_all(&[0; INITIAL_POOL_SIZE as usize]).expect("Write to shm fd failed"); // Flush to ensure the compositor has access to the buffer when it tries to map it. file.flush().expect("Flush on shm fd failed"); let pool = shm.create_pool(file.as_raw_fd(), INITIAL_POOL_SIZE); let name = String::from(name); CursorTheme { name, file, size, pool, pool_size: INITIAL_POOL_SIZE, cursors: Vec::new() } } /// Retrieve a cursor from the theme. /// /// This method returns `None` if this cursor is not provided /// either by the theme, or by one of its parents. pub fn get_cursor(&mut self, name: &str) -> Option<&Cursor> { match self.cursors.iter().position(|cursor| cursor.name == name) { Some(i) => Some(&self.cursors[i]), None => { let cursor = self.load_cursor(name, self.size)?; self.cursors.push(cursor); self.cursors.iter().last() } } } /// This function loads a cursor, parses it and /// pushes the images onto the shm pool. /// Keep in mind that if the cursor is already loaded, /// the function will make a duplicate. fn load_cursor(&mut self, name: &str, size: u32) -> Option { let icon_path = XCursorTheme::load(&self.name).load_icon(name)?; let mut icon_file = File::open(icon_path).ok()?; let mut buf = Vec::new(); let images = { icon_file.read_to_end(&mut buf).ok()?; xparser::parse_xcursor(&buf)? }; Some(Cursor::new(name, self, &images, size)) } /// Grow the wl_shm_pool this theme is stored on. /// This method does nothing if the provided size is /// smaller or equal to the pool's current size. fn grow(&mut self, size: i32) { if size > self.pool_size { self.file.set_len(size as u64).expect("Failed to set new buffer length"); self.pool.resize(size); self.pool_size = size; } } } /// A cursor from a theme. Can contain several images if animated. #[derive(Debug, Clone)] pub struct Cursor { name: String, images: Vec, total_duration: u32, } impl Cursor { /// Construct a new Cursor. /// /// Each of the provided images will be written into `theme`. /// This will also grow `theme.pool` if necessary. fn new(name: &str, theme: &mut CursorTheme, images: &[XCursorImage], size: u32) -> Self { let mut total_duration = 0; let images: Vec = Cursor::nearest_images(size, images) .map(|image| { let buffer = CursorImageBuffer::new(theme, image); total_duration += buffer.delay; buffer }) .collect(); Cursor { total_duration, name: String::from(name), images } } fn nearest_images(size: u32, images: &[XCursorImage]) -> impl Iterator { // Follow the nominal size of the cursor to choose the nearest 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 }) } /// Given a time, calculate which frame to show, and how much time remains until the next frame. /// /// Time will wrap, so if for instance the cursor has an animation during 100ms, /// then calling this function with 5ms and 105ms as input gives the same output. 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 } } /// Total number of images forming this cursor animation pub fn image_count(&self) -> usize { self.images.len() } } impl Index for Cursor { type Output = CursorImageBuffer; fn index(&self, index: usize) -> &Self::Output { &self.images[index] } } /// A buffer containing a cursor image. /// /// You can access the `WlBuffer` via `Deref`. /// /// Note that this proxy will be considered as "unmanaged" by the crate, as such you should /// not try to act on it beyond assigning it to `wl_surface`s. #[derive(Debug, Clone)] pub struct CursorImageBuffer { buffer: WlBuffer, delay: u32, xhot: u32, yhot: u32, width: u32, height: u32, } impl CursorImageBuffer { /// Construct a new CursorImageBuffer /// /// This function appends the pixels of the image to the provided file, /// and constructs a wl_buffer on that data. fn new(theme: &mut CursorTheme, image: &XCursorImage) -> Self { let buf = &image.pixels_rgba; let offset = theme.file.seek(SeekFrom::End(0)).unwrap(); // Resize memory before writing to it to handle shm correctly. let new_size = offset + buf.len() as u64; theme.grow(new_size as i32); theme.file.write_all(buf).unwrap(); let buffer = theme.pool.create_buffer( offset as i32, image.width as i32, image.height as i32, (image.width * 4) as i32, Format::Argb8888, ); buffer.quick_assign(|_, _, _| {}); CursorImageBuffer { buffer: buffer.detach(), delay: image.delay, xhot: image.xhot, yhot: image.yhot, width: image.width, height: image.height, } } /// Dimensions of this image pub fn dimensions(&self) -> (u32, u32) { (self.width, self.height) } /// Location of the pointer hotspot in this image pub fn hotspot(&self) -> (u32, u32) { (self.xhot, self.yhot) } /// Time (in milliseconds) for which this image should be displayed pub fn delay(&self) -> u32 { self.delay } } impl Deref for CursorImageBuffer { type Target = WlBuffer; fn deref(&self) -> &WlBuffer { &self.buffer } } /// Which frame to show, and for how long. /// /// This struct is output by `Cursor::frame_and_duration` #[derive(Debug, Clone, Eq, PartialEq)] pub struct FrameAndDuration { /// The index of the frame which should be shown. pub frame_index: usize, /// The duration that the frame should be shown for (in milliseconds). pub frame_duration: u32, } /// Create a shared file descriptor in memory. fn create_shm_fd() -> IoResult { // Only try memfd on linux. #[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(Errno::EINTR) => continue, Err(Errno::ENOSYS) => break, Err(errno) => return Err(errno.into()), } } // Fallback to using shm_open. 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(errno) => match unistd::close(fd) { Ok(_) => return Err(IoError::from(errno)), Err(errno) => return Err(IoError::from(errno)), }, }, Err(Errno::EEXIST) => { // If a file with that handle exists then change the handle mem_file_handle = format!( "/wayland-cursor-rs-{}", sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos() ); continue; } Err(Errno::EINTR) => continue, Err(errno) => return Err(IoError::from(errno)), } } }