xcursor-0.3.10/.cargo_vcs_info.json0000644000000001360000000000100126220ustar { "git": { "sha1": "64bd498bc572b5747161e9573781337edacf5ab8" }, "path_in_vcs": "" }xcursor-0.3.10/Cargo.lock0000644000000002300000000000100105700ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "xcursor" version = "0.3.10" xcursor-0.3.10/Cargo.toml0000644000000020720000000000100106210ustar # 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 = "xcursor" version = "0.3.10" authors = ["Samuele Esposito"] build = false include = [ "**/*.rs", "Cargo.toml", "LICENSE", "README.md", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A library for loading XCursor themes" documentation = "https://docs.rs/xcursor" readme = "README.md" keywords = [ "themes", "cursor", "x-cursor", "load", "parser", ] categories = ["gui"] license = "MIT" repository = "https://github.com/esposm03/xcursor-rs" [lib] name = "xcursor" path = "src/lib.rs" xcursor-0.3.10/Cargo.toml.orig000064400000000000000000000006511046102023000143030ustar 00000000000000[package] name = "xcursor" description = "A library for loading XCursor themes" version = "0.3.10" edition = "2018" authors = ["Samuele Esposito"] license = "MIT" repository = "https://github.com/esposm03/xcursor-rs" documentation = "https://docs.rs/xcursor" readme = "README.md" categories = ["gui"] keywords = ["themes", "cursor", "x-cursor", "load", "parser"] include = ["**/*.rs", "Cargo.toml", "LICENSE", "README.md"] xcursor-0.3.10/LICENSE000064400000000000000000000020611046102023000124160ustar 00000000000000MIT License Copyright (c) 2020 Samuele Esposito 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. xcursor-0.3.10/README.md000064400000000000000000000005541046102023000126750ustar 00000000000000# xcursor-rs [![Crates.io](https://img.shields.io/crates/v/xcursor)](https://crates.io/crates/xcursor) [![Docs.rs](https://docs.rs/xcursor/badge.svg)](https://docs.rs/xcursor/) [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) A library to load XCursor themes, and parse XCursor files. # MSRV The minimum supported Rust version is 1.34.0xcursor-0.3.10/src/lib.rs000064400000000000000000000320411046102023000133150ustar 00000000000000//! 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, } 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(SearchPathsEnvironment::get()); 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 { let mut walked_themes = HashSet::new(); self.theme .load_icon_with_depth(icon_name, &self.search_paths, &mut walked_themes) .map(|(pathbuf, _)| pathbuf) } /// Try to load an icon from the theme, returning it with its inheritance /// depth. /// /// If the icon is not found within this theme's directories, then the /// function looks at the theme from which this theme is inherited. The /// second element of the returned tuple indicates how many levels of /// inheritance were traversed before the icon was found. pub fn load_icon_with_depth(&self, icon_name: &str) -> Option<(PathBuf, usize)> { let mut walked_themes = HashSet::new(); self.theme .load_icon_with_depth(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)>, } 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_with_depth( &self, icon_name: &str, search_paths: &[PathBuf], walked_themes: &mut HashSet, ) -> Option<(PathBuf, usize)> { 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, 0)); } } // 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_with_depth(icon_name, search_paths, walked_themes) { Some((icon_path, depth)) => return Some((icon_path, depth + 1)), None => continue, } } None } } #[derive(Default)] struct SearchPathsEnvironment { home: Option, xcursor_path: Option, xdg_data_home: Option, xdg_data_dirs: Option, } impl SearchPathsEnvironment { fn get() -> Self { SearchPathsEnvironment { home: env::var("HOME").ok().filter(|x| !x.is_empty()), xcursor_path: env::var("XCURSOR_PATH").ok().filter(|x| !x.is_empty()), xdg_data_home: env::var("XDG_DATA_HOME").ok().filter(|x| !x.is_empty()), xdg_data_dirs: env::var("XDG_DATA_DIRS").ok().filter(|x| !x.is_empty()), } } } /// Get the list of paths where the themes have to be searched, according to the XDG Icon Theme /// specification. If `XCURSOR_PATH` is set, it will override the default search paths. fn theme_search_paths(environment: SearchPathsEnvironment) -> Vec { let home_dir = environment .home .as_ref() .map(|home| Path::new(home.as_str())); if let Some(xcursor_path) = environment.xcursor_path { return xcursor_path .split(':') .flat_map(|entry| { if entry.is_empty() { return None; } expand_home_dir(PathBuf::from(entry), home_dir) }) .collect(); } // The order is following other XCursor loading libs, like libwayland-cursor. let mut paths = Vec::new(); if let Some(xdg_data_home) = environment.xdg_data_home { paths.extend(expand_home_dir(PathBuf::from(xdg_data_home), home_dir)); } else if let Some(home_dir) = home_dir { paths.push(home_dir.join(".local/share/icons")) } if let Some(home_dir) = home_dir { paths.push(home_dir.join(".icons")); } if let Some(xdg_data_dirs) = environment.xdg_data_dirs { paths.extend(xdg_data_dirs.split(':').flat_map(|entry| { if entry.is_empty() { return None; } let mut entry = expand_home_dir(PathBuf::from(entry), home_dir)?; entry.push("icons"); Some(entry) })) } else { paths.push(PathBuf::from("/usr/local/share/icons")); paths.push(PathBuf::from("/usr/share/icons")); } paths.push(PathBuf::from("/usr/share/pixmaps")); if let Some(home_dir) = home_dir { paths.push(home_dir.join(".cursors")); } paths.push(PathBuf::from("/usr/share/cursors/xorg-x11")); paths } /// If the first component of the path is `~`, replaces it with the home dir. If no home dir is /// present, returns `None`. fn expand_home_dir(path: PathBuf, home_dir: Option<&Path>) -> Option { let mut components = path.iter(); if let Some(first_component) = components.next() { if first_component == "~" { if let Some(home_dir) = home_dir { let mut path = home_dir.to_path_buf(); for component in components { path.push(component); } return Some(path); } else { return None; } } } Some(path) } /// 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 { 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 { 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::*; use std::path::{Path, PathBuf}; #[test] fn test_parse_theme() { 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())); } #[test] fn test_expand_home_dir() { let home = Path::new("/home/user"); let result = expand_home_dir("~".into(), Some(home)); assert_eq!(result, Some("/home/user".into())); let result = expand_home_dir("~/.icons".into(), Some(home)); assert_eq!(result, Some("/home/user/.icons".into())); let result = expand_home_dir("~/.local/share/icons".into(), Some(home)); assert_eq!(result, Some("/home/user/.local/share/icons".into())); let result = expand_home_dir("~/.icons".into(), None); assert_eq!(result, None); let path: PathBuf = "/usr/share/icons".into(); let result = expand_home_dir(path.clone(), Some(home)); assert_eq!(result, Some(path)); let path: PathBuf = "".into(); let result = expand_home_dir(path.clone(), Some(home)); assert_eq!(result, Some(path)); // ~ in the middle of path should not expand let path: PathBuf = "/some/path/~/icons".into(); let result = expand_home_dir(path.clone(), Some(home)); assert_eq!(result, Some(path)); } #[test] fn test_theme_search_paths() { assert_eq!( theme_search_paths(SearchPathsEnvironment { home: Some("/home/user".to_string()), xdg_data_home: Some("/home/user/.data".to_string()), xdg_data_dirs: Some("/opt/share::/usr/local/share:~/custom/share".to_string()), ..Default::default() }), vec![ PathBuf::from("/home/user/.data"), PathBuf::from("/home/user/.icons"), PathBuf::from("/opt/share/icons"), PathBuf::from("/usr/local/share/icons"), PathBuf::from("/home/user/custom/share/icons"), PathBuf::from("/usr/share/pixmaps"), PathBuf::from("/home/user/.cursors"), PathBuf::from("/usr/share/cursors/xorg-x11"), ] ); // XCURSOR_PATH overrides all other paths assert_eq!( theme_search_paths(SearchPathsEnvironment { home: Some("/home/user".to_string()), xcursor_path: Some("~/custom/xcursor/icons:/absolute-path/icons".to_string()), ..Default::default() }), vec![ PathBuf::from("/home/user/custom/xcursor/icons"), PathBuf::from("/absolute-path/icons") ] ); // no home causes tilde paths to be omitted assert_eq!( theme_search_paths(SearchPathsEnvironment { xdg_data_home: Some("~/.data".to_string()), ..Default::default() }), vec![ PathBuf::from("/usr/local/share/icons"), PathBuf::from("/usr/share/icons"), PathBuf::from("/usr/share/pixmaps"), PathBuf::from("/usr/share/cursors/xorg-x11"), ] ); } } xcursor-0.3.10/src/parser.rs000064400000000000000000000236521046102023000140530ustar 00000000000000use std::{ fmt::{self, Debug, Formatter}, io::{Cursor, Error, ErrorKind, Read, Result as IoResult, Seek, SeekFrom}, }; #[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, /// A slice containing the pixels' bytes, in ARGB format. pub pixels_argb: Vec, } 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(i: &mut impl Read) -> IoResult<(u32, u32)> { i.tag(*b"Xcur")?; let header = i.u32_le()?; let _version = i.u32_le()?; let ntoc = i.u32_le()?; Ok((header, ntoc)) } fn parse_toc(i: &mut impl Read) -> IoResult { let toctype = i.u32_le()?; // Type let subtype = i.u32_le()?; // Subtype let pos = i.u32_le()?; // Position Ok(Toc { toctype, subtype, pos, }) } fn parse_img(i: &mut impl Read) -> IoResult { 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()?; // Check image is well-formed. Taken from https://gitlab.freedesktop.org/xorg/lib/libxcursor/-/blob/09617bcc9a0f1b5072212da5f8fede92ab85d157/src/file.c#L456-463 if width > 0x7fff || height > 0x7fff { return Err(Error::new(ErrorKind::Other, "Image too large")); } if width == 0 || height == 0 { return Err(Error::new( ErrorKind::Other, "Image with zero width or height", )); } if xhot > width || yhot > height { return Err(Error::new(ErrorKind::Other, "Hotspot outside image")); } let img_length: usize = (4 * width * height) as usize; let pixels_rgba = i.take_bytes(img_length)?; let pixels_argb = rgba_to_argb(&pixels_rgba); Ok(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 { let mut res = Vec::with_capacity(i.len()); for rgba in i.chunks_exact(4) { 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> { parse_xcursor_stream(&mut Cursor::new(content)).ok() } /// Parse an XCursor file into its images. pub fn parse_xcursor_stream(input: &mut R) -> IoResult> { let (header, ntoc) = parse_header(input)?; input.seek(SeekFrom::Start(header as u64))?; let mut img_indices = Vec::new(); for _ in 0..ntoc { let toc = parse_toc(input)?; if toc.toctype == 0xfffd_0002 { img_indices.push(toc.pos); } } let mut imgs = Vec::with_capacity(ntoc as usize); for index in img_indices { input.seek(SeekFrom::Start(index.into()))?; imgs.push(parse_img(input)?); } Ok(imgs) } trait StreamExt { /// Parse a series of bytes, returning `None` if it doesn't exist. fn tag(&mut self, tag: [u8; 4]) -> IoResult<()>; /// Take a slice of bytes. fn take_bytes(&mut self, len: usize) -> IoResult>; /// Parse a 32-bit little endian number. fn u32_le(&mut self) -> IoResult; } impl StreamExt for R { fn tag(&mut self, tag: [u8; 4]) -> IoResult<()> { let mut data = [0u8; 4]; self.read_exact(&mut data)?; if data != tag { Err(Error::new(ErrorKind::Other, "Tag mismatch")) } else { Ok(()) } } fn take_bytes(&mut self, len: usize) -> IoResult> { let mut data = vec![0; len]; self.read_exact(&mut data)?; Ok(data) } fn u32_le(&mut self) -> IoResult { let mut data = [0u8; 4]; self.read_exact(&mut data)?; Ok(u32::from_le_bytes(data)) } } #[cfg(test)] mod tests { use super::{parse_header, parse_toc, parse_xcursor, rgba_to_argb, Image, Toc}; use std::io::Cursor; // 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() { let mut cursor = Cursor::new(&FILE_CONTENTS[..]); assert_eq!(parse_header(&mut cursor).unwrap(), (16, 1)); assert_eq!(cursor.position(), 16); } #[test] fn test_parse_toc() { let toc = Toc { toctype: 0xfffd0002, subtype: 4, pos: 0x1c, }; let mut cursor = Cursor::new(&FILE_CONTENTS[16..]); assert_eq!(parse_toc(&mut cursor).unwrap(), toc); assert_eq!(cursor.position(), 28 - 16); } #[test] fn test_parse_image() { // The image always repeats the same pixels across its 4 x 4 pixels let make_pixels = |pixel: [u8; 4]| { // This is just "pixels.repeat(4 * 4)", but working in Rust 1.34 std::iter::repeat(pixel) .take(4 * 4) .flat_map(|p| p.iter().cloned().collect::>()) .collect() }; let expected = Image { size: 4, width: 4, height: 4, xhot: 1, yhot: 1, delay: 1, pixels_rgba: make_pixels([0, 0, 0, 128]), pixels_argb: make_pixels([128, 0, 0, 0]), }; assert_eq!(Some(vec![expected]), parse_xcursor(&FILE_CONTENTS)); } #[test] fn test_one_image_three_times() { let data = [ b'X', b'c', b'u', b'r', // magic 0x10, 0x00, 0x00, 0x00, // header file offset (16) 0x00, 0x00, 0x00, 0x00, // version 0x03, 0x00, 0x00, 0x00, // num TOC entries, 3 // TOC 0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE 0x04, 0x00, 0x00, 0x00, // size 4 0x34, 0x00, 0x00, 0x00, // image offset (52) 0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE 0x03, 0x00, 0x00, 0x00, // size 3 0x34, 0x00, 0x00, 0x00, // image offset (52) 0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE 0x04, 0x00, 0x00, 0x00, // size 4 0x34, 0x00, 0x00, 0x00, // image offset (52) // image 0x24, 0x00, 0x00, 0x00, // header 0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE 0x04, 0x00, 0x00, 0x00, // size 4 0x01, 0x00, 0x00, 0x00, // version 0x01, 0x00, 0x00, 0x00, // width 1 0x01, 0x00, 0x00, 0x00, // height 1 0x00, 0x00, 0x00, 0x00, // x_hot 0 0x00, 0x00, 0x00, 0x00, // y_hot 0 0x00, 0x00, 0x00, 0x00, // delay 0 0x12, 0x34, 0x56, 0x78, // pixel ]; let expected = Image { size: 4, width: 1, height: 1, xhot: 0, yhot: 0, delay: 0, pixels_rgba: vec![0x12, 0x34, 0x56, 0x78], pixels_argb: vec![0x78, 0x12, 0x34, 0x56], }; assert_eq!( Some(vec![expected.clone(), expected.clone(), expected.clone()]), parse_xcursor(&data) ); } #[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)[..]); } }