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

283
third-party/vendor/xcursor/src/lib.rs vendored Normal file
View file

@ -0,0 +1,283 @@
//! A crate to load cursor themes, and parse XCursor files.
use std::collections::HashSet;
use std::env;
use std::path::{Path, PathBuf};
/// A module implementing XCursor file parsing.
pub mod parser;
/// A cursor theme.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CursorTheme {
theme: CursorThemeIml,
/// Global search path for themes.
search_paths: Vec<PathBuf>,
}
impl CursorTheme {
/// Search for a theme with the given name in the given search paths,
/// and returns an XCursorTheme which represents it. If no inheritance
/// can be determined, then the themes inherits from the "default" theme.
pub fn load(name: &str) -> Self {
let search_paths = theme_search_paths();
let theme = CursorThemeIml::load(name, &search_paths);
CursorTheme {
theme,
search_paths,
}
}
/// Try to load an icon from the theme.
/// If the icon is not found within this theme's
/// directories, then the function looks at the
/// theme from which this theme is inherited.
pub fn load_icon(&self, icon_name: &str) -> Option<PathBuf> {
let mut walked_themes = HashSet::new();
self.theme
.load_icon(icon_name, &self.search_paths, &mut walked_themes)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct CursorThemeIml {
/// Theme name.
name: String,
/// Directories where the theme is presented and corresponding names of inherited themes.
/// `None` if theme inherits nothing.
data: Vec<(PathBuf, Option<String>)>,
}
impl CursorThemeIml {
/// The implementation of cursor theme loading.
fn load(name: &str, search_paths: &[PathBuf]) -> Self {
let mut data = Vec::new();
// Find directories where this theme is presented.
for mut path in search_paths.iter().cloned() {
path.push(name);
if path.is_dir() {
let data_dir = path.clone();
path.push("index.theme");
let inherits = if let Some(inherits) = theme_inherits(&path) {
Some(inherits)
} else if name != "default" {
Some(String::from("default"))
} else {
None
};
data.push((data_dir, inherits));
}
}
CursorThemeIml {
name: name.to_owned(),
data,
}
}
/// The implementation of cursor icon loading.
fn load_icon(
&self,
icon_name: &str,
search_paths: &[PathBuf],
walked_themes: &mut HashSet<String>,
) -> Option<PathBuf> {
for data in &self.data {
let mut icon_path = data.0.clone();
icon_path.push("cursors");
icon_path.push(icon_name);
if icon_path.is_file() {
return Some(icon_path);
}
}
// We've processed all based theme files. Traverse inherited themes, marking this theme
// as already visited to avoid infinite recursion.
walked_themes.insert(self.name.clone());
for data in &self.data {
// Get inherited theme name, if any.
let inherits = match data.1.as_ref() {
Some(inherits) => inherits,
None => continue,
};
// We've walked this theme, avoid rebuilding.
if walked_themes.contains(inherits) {
continue;
}
let inherited_theme = CursorThemeIml::load(inherits, search_paths);
match inherited_theme.load_icon(icon_name, search_paths, walked_themes) {
Some(icon_path) => return Some(icon_path),
None => continue,
}
}
None
}
}
/// Get the list of paths where the themes have to be searched,
/// according to the XDG Icon Theme specification, respecting `XCURSOR_PATH` env
/// variable, in case it was set.
fn theme_search_paths() -> Vec<PathBuf> {
// Handle the `XCURSOR_PATH` env variable, which takes over default search paths for cursor
// theme. Some systems rely are using non standard directory layout and primary using this
// env variable to perform cursor loading from a right places.
let xcursor_path = match env::var("XCURSOR_PATH") {
Ok(xcursor_path) => xcursor_path.split(':').map(PathBuf::from).collect(),
Err(_) => {
// Get icons locations from XDG data directories.
let get_icon_dirs = |xdg_path: String| -> Vec<PathBuf> {
xdg_path
.split(':')
.map(|entry| {
let mut entry = PathBuf::from(entry);
entry.push("icons");
entry
})
.collect()
};
let mut xdg_data_home = get_icon_dirs(
env::var("XDG_DATA_HOME").unwrap_or_else(|_| String::from("~/.local/share")),
);
let mut xdg_data_dirs = get_icon_dirs(
env::var("XDG_DATA_DIRS")
.unwrap_or_else(|_| String::from("/usr/local/share:/usr/share")),
);
let mut xcursor_path =
Vec::with_capacity(xdg_data_dirs.len() + xdg_data_home.len() + 4);
// The order is following other XCursor loading libs, like libwayland-cursor.
xcursor_path.append(&mut xdg_data_home);
xcursor_path.push(PathBuf::from("~/.icons"));
xcursor_path.append(&mut xdg_data_dirs);
xcursor_path.push(PathBuf::from("/usr/share/pixmaps"));
xcursor_path.push(PathBuf::from("~/.cursors"));
xcursor_path.push(PathBuf::from("/usr/share/cursors/xorg-x11"));
xcursor_path
}
};
let homedir = env::var("HOME");
xcursor_path
.into_iter()
.filter_map(|dir| {
// Replace `~` in a path with `$HOME` for compatibility with other libs.
let mut expaned_dir = PathBuf::new();
for component in dir.iter() {
if component == "~" {
let homedir = match homedir.as_ref() {
Ok(homedir) => homedir.clone(),
Err(_) => return None,
};
expaned_dir.push(homedir);
} else {
expaned_dir.push(component);
}
}
Some(expaned_dir)
})
.collect()
}
/// Load the specified index.theme file, and returns a `Some` with
/// the value of the `Inherits` key in it.
/// Returns `None` if the file cannot be read for any reason,
/// if the file cannot be parsed, or if the `Inherits` key is omitted.
fn theme_inherits(file_path: &Path) -> Option<String> {
let content = std::fs::read_to_string(file_path).ok()?;
parse_theme(&content)
}
/// Parse the content of the `index.theme` and return the `Inherits` value.
fn parse_theme(content: &str) -> Option<String> {
const PATTERN: &str = "Inherits";
let is_xcursor_space_or_separator =
|&ch: &char| -> bool { ch.is_whitespace() || ch == ';' || ch == ',' };
for line in content.lines() {
// Line should start with `Inherits`, otherwise go to the next line.
if !line.starts_with(PATTERN) {
continue;
}
// Skip the `Inherits` part and trim the leading white spaces.
let mut chars = line.get(PATTERN.len()..).unwrap().trim_start().chars();
// If the next character after leading white spaces isn't `=` go the next line.
if Some('=') != chars.next() {
continue;
}
// Skip XCursor spaces/separators.
let result: String = chars
.skip_while(is_xcursor_space_or_separator)
.take_while(|ch| !is_xcursor_space_or_separator(ch))
.collect();
if !result.is_empty() {
return Some(result);
}
}
None
}
#[cfg(test)]
mod tests {
use super::parse_theme;
#[test]
fn parse_inherits() {
let theme_name = String::from("XCURSOR_RS");
let theme = format!("Inherits={}", theme_name.clone());
assert_eq!(parse_theme(&theme), Some(theme_name.clone()));
let theme = format!(" Inherits={}", theme_name.clone());
assert_eq!(parse_theme(&theme), None);
let theme = format!(
"[THEME name]\nInherits = ,;\t\t{};;;;Tail\n\n",
theme_name.clone()
);
assert_eq!(parse_theme(&theme), Some(theme_name.clone()));
let theme = format!("Inherits;=;{}", theme_name.clone());
assert_eq!(parse_theme(&theme), None);
let theme = format!("Inherits = {}\n\nInherits=OtherTheme", theme_name.clone());
assert_eq!(parse_theme(&theme), Some(theme_name.clone()));
let theme = format!(
"Inherits = ;;\nSome\tgarbage\nInherits={}",
theme_name.clone()
);
assert_eq!(parse_theme(&theme), Some(theme_name.clone()));
}
}

251
third-party/vendor/xcursor/src/parser.rs vendored Normal file
View file

@ -0,0 +1,251 @@
use std::{
convert::TryInto,
fmt,
fmt::{Debug, Formatter},
mem::size_of,
};
#[derive(Debug, Clone, Eq, PartialEq)]
struct Toc {
toctype: u32,
subtype: u32,
pos: u32,
}
/// A struct representing an image.
/// Pixels are in ARGB format, with each byte representing a single channel.
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Image {
/// The nominal size of the image.
pub size: u32,
/// The actual width of the image. Doesn't need to match `size`.
pub width: u32,
/// The actual height of the image. Doesn't need to match `size`.
pub height: u32,
/// The X coordinate of the hotspot pixel (the pixel where the tip of the arrow is situated)
pub xhot: u32,
/// The Y coordinate of the hotspot pixel (the pixel where the tip of the arrow is situated)
pub yhot: u32,
/// The amount of time (in milliseconds) that this image should be shown for, before switching to the next.
pub delay: u32,
/// A slice containing the pixels' bytes, in RGBA format (or, in the order of the file).
pub pixels_rgba: Vec<u8>,
/// A slice containing the pixels' bytes, in ARGB format.
pub pixels_argb: Vec<u8>,
}
impl std::fmt::Display for Image {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Image")
.field("size", &self.size)
.field("width", &self.width)
.field("height", &self.height)
.field("xhot", &self.xhot)
.field("yhot", &self.yhot)
.field("delay", &self.delay)
.field("pixels", &"/* omitted */")
.finish()
}
}
fn parse_header(mut i: Stream<'_>) -> Option<(Stream<'_>, u32)> {
i.tag(b"Xcur")?;
i.u32_le()?;
i.u32_le()?;
let ntoc = i.u32_le()?;
Some((i, ntoc))
}
fn parse_toc(mut i: Stream<'_>) -> Option<(Stream<'_>, Toc)> {
let toctype = i.u32_le()?; // Type
let subtype = i.u32_le()?; // Subtype
let pos = i.u32_le()?; // Position
Some((
i,
Toc {
toctype,
subtype,
pos,
},
))
}
fn parse_img(mut i: Stream<'_>) -> Option<(Stream<'_>, Image)> {
i.tag(&[0x24, 0x00, 0x00, 0x00])?; // Header size
i.tag(&[0x02, 0x00, 0xfd, 0xff])?; // Type
let size = i.u32_le()?;
i.tag(&[0x01, 0x00, 0x00, 0x00])?; // Image version (1)
let width = i.u32_le()?;
let height = i.u32_le()?;
let xhot = i.u32_le()?;
let yhot = i.u32_le()?;
let delay = i.u32_le()?;
let img_length: usize = (4 * width * height) as usize;
let pixels_slice = i.take_bytes(img_length)?;
let pixels_argb = rgba_to_argb(pixels_slice);
let pixels_rgba = Vec::from(pixels_slice);
Some((
i,
Image {
size,
width,
height,
xhot,
yhot,
delay,
pixels_argb,
pixels_rgba,
},
))
}
/// Converts a RGBA slice into an ARGB vec
///
/// Note that, if the input length is not
/// a multiple of 4, the extra elements are ignored.
fn rgba_to_argb(i: &[u8]) -> Vec<u8> {
let mut res = Vec::with_capacity(i.len());
for rgba in i.chunks(4) {
if rgba.len() < 4 {
break;
}
res.push(rgba[3]);
res.push(rgba[0]);
res.push(rgba[1]);
res.push(rgba[2]);
}
res
}
/// Parse an XCursor file into its images.
pub fn parse_xcursor(content: &[u8]) -> Option<Vec<Image>> {
let (mut i, ntoc) = parse_header(content)?;
let mut imgs = Vec::with_capacity(ntoc as usize);
for _ in 0..ntoc {
let (j, toc) = parse_toc(i)?;
i = j;
if toc.toctype == 0xfffd_0002 {
let index = toc.pos as usize..;
let (_, img) = parse_img(&content[index])?;
imgs.push(img);
}
}
Some(imgs)
}
type Stream<'a> = &'a [u8];
trait StreamExt<'a>: 'a {
/// Parse a series of bytes, returning `None` if it doesn't exist.
fn tag(&mut self, tag: &[u8]) -> Option<()>;
/// Take a slice of bytes.
fn take_bytes(&mut self, len: usize) -> Option<&'a [u8]>;
/// Parse a 32-bit little endian number.
fn u32_le(&mut self) -> Option<u32>;
}
impl<'a> StreamExt<'a> for Stream<'a> {
fn tag(&mut self, tag: &[u8]) -> Option<()> {
if self.len() < tag.len() || self[..tag.len()] != *tag {
None
} else {
*self = &self[tag.len()..];
Some(())
}
}
fn take_bytes(&mut self, len: usize) -> Option<&'a [u8]> {
if self.len() < len {
None
} else {
let (value, tail) = self.split_at(len);
*self = tail;
Some(value)
}
}
fn u32_le(&mut self) -> Option<u32> {
self.take_bytes(size_of::<u32>())
.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
}
}
#[cfg(test)]
mod tests {
use super::{parse_header, parse_toc, rgba_to_argb, Toc};
// A sample (and simple) XCursor file generated with xcursorgen.
// Contains a single 4x4 image.
const FILE_CONTENTS: [u8; 128] = [
0x58, 0x63, 0x75, 0x72, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
0x00, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00,
0x00, 0x00, 0x02, 0x00, 0xFD, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00,
0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80,
];
#[test]
fn test_parse_header() {
assert_eq!(
parse_header(&FILE_CONTENTS).unwrap(),
(&FILE_CONTENTS[16..], 1)
)
}
#[test]
fn test_parse_toc() {
let toc = Toc {
toctype: 0xfffd0002,
subtype: 4,
pos: 0x1c,
};
assert_eq!(
parse_toc(&FILE_CONTENTS[16..]).unwrap(),
(&FILE_CONTENTS[28..], toc)
)
}
#[test]
fn test_rgba_to_argb() {
let initial: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
assert_eq!(rgba_to_argb(&initial), [3u8, 0, 1, 2, 7, 4, 5, 6])
}
#[test]
fn test_rgba_to_argb_extra_items() {
let initial: [u8; 9] = [0, 1, 2, 3, 4, 5, 6, 7, 8];
assert_eq!(rgba_to_argb(&initial), &[3u8, 0, 1, 2, 7, 4, 5, 6]);
}
#[test]
fn test_rgba_to_argb_no_items() {
let initial: &[u8] = &[];
assert_eq!(initial, &rgba_to_argb(initial)[..]);
}
}