Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1 @@
{"files":{"Cargo.toml":"0a4fbbc7acfce4c65c4f3621643570a9ddeb24208e5803c96e129431883b0ad8","LICENSE.txt":"10e5daea68c3ab7da6468ea51860e9d4ac87ea6a9a2d34127beff17b7193b618","README.md":"3cc39e7b4050fe6f666ef172a98238f162211028672aff5bcdc4ff8a86a4fe9f","src/lib.rs":"0b9d8be0222647705316c563c3df23f44a23d28ba5093e4114512ced028ad083"},"package":"6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"}

View file

@ -0,0 +1,43 @@
# 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 <victor.berger@m4x.org>"]
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"

View file

@ -0,0 +1,19 @@
Copyright (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.

View file

@ -0,0 +1,10 @@
[![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.

View file

@ -0,0 +1,377 @@
#![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<Cursor>,
size: u32,
pool: Main<WlShmPool>,
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<WlShm>) -> 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<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)
}
/// Create a new cursor theme, ignoring the system defaults.
pub fn load_from_name(name: &str, size: u32, shm: &Attached<WlShm>) -> 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<Cursor> {
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<CursorImageBuffer>,
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<CursorImageBuffer> = 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<Item = &XCursorImage> {
// 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<usize> 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<RawFd> {
// 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)),
}
}
}