Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
283
third-party/vendor/xcursor/src/lib.rs
vendored
Normal file
283
third-party/vendor/xcursor/src/lib.rs
vendored
Normal 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
251
third-party/vendor/xcursor/src/parser.rs
vendored
Normal 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)[..]);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue