use std::{fs, io::Result, os::unix}; use serde::{Deserialize, Serialize}; use tiny_skia::Pixmap; use xcursor::{ImageInfo, XCursorEncoder}; mod xcursor; #[derive(Serialize, Deserialize, Debug, Clone, Copy)] struct Xy { x: u32, y: u32, } #[derive(Serialize, Deserialize, Debug, Clone)] struct BibataPlatformNames { windows: Option, xorg: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] struct BibataFrame { idx: i32, id: String, url: String, delay: u32, sha512: String, svg: String, } #[derive(Serialize, Deserialize, Debug, Clone)] struct BibataCursor { animated: bool, id: String, name: String, aliases: Vec, hot: Xy, frames: Vec, frame_count: i32, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BibataCursorStyle { kind: String, cursors: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BibataColourScheme<'a> { pub base: &'a str, pub outline: &'a str, pub watch_background: &'a str, pub watch_color: Vec<&'a str>, /** Value between 0 and 1 - Bibata defaults to 0.8 */ pub watch_opacity: f32, } fn vector_to_pixmap(svg_data: &[u8]) -> Pixmap { let tree = { let mut opt = usvg::Options::default(); // Get file's absolute directory. // opt.resources_dir = std::fs::canonicalize(&args[1]) // .ok() // .and_then(|p| p.parent().map(|p| p.to_path_buf())); opt.fontdb_mut().load_system_fonts(); usvg::Tree::from_data(svg_data, &opt).unwrap() }; let pixmap_size = tree.size().to_int_size(); let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap(); resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut()); pixmap } fn resize_cursvg(svg: &str, new_size: &u16) -> String { svg.to_string().replace( "width=\"256\" height=\"256\"", format!("width=\"{new_size}\" height=\"{new_size}\"").as_str(), ) } fn apply_colours(unpatched_svg: String, colours: &BibataColourScheme) -> String { if colours.watch_color.len() != 4 { panic!("Must specify 4 watch colours!"); }; unpatched_svg .replace("#00FF00", colours.base) .replace("#0000FF", colours.outline) .replace("#FF0000", colours.watch_background) .replace("#F05024", colours.watch_color[0]) .replace("#FCB813", colours.watch_color[1]) .replace("#7EBA41", colours.watch_color[2]) .replace("#32A0DA", colours.watch_color[3]) .replace( "fill-opacity=\"0.8\"", format!("fill-opacity=\"{}\"", colours.watch_opacity).as_str(), ) } fn process_xcursor( cursor: BibataCursor, desired_colours: BibataColourScheme, desired_sizes: Vec, time_scale: u32, ) -> Vec { let mut image_info_vec: Vec = Vec::new(); for cursor_frame in cursor.frames { for desired_size in &desired_sizes { let svg_data = apply_colours( resize_cursvg(&cursor_frame.svg, desired_size), &desired_colours, ); let pixmap = vector_to_pixmap(svg_data.as_bytes()); let pixels = { let pixels = pixmap.pixels(); let mut otherpixels: Vec = Vec::new(); for elem in pixels.iter() { otherpixels.push(elem.red()); otherpixels.push(elem.green()); otherpixels.push(elem.blue()); otherpixels.push(elem.alpha()); } otherpixels }; let image_info = ImageInfo { r#type: *desired_size as u32, subtype: *desired_size as u32, width: pixmap.width().try_into().unwrap(), height: pixmap.height().try_into().unwrap(), xhot: (cursor.hot.x * pixmap.width() / 256).try_into().unwrap(), yhot: (cursor.hot.y * pixmap.height() / 256).try_into().unwrap(), data: pixels.try_into().unwrap(), delay: (time_scale / cursor_frame.delay).try_into().unwrap(), }; image_info_vec.push(image_info); } } let mut encoder = XCursorEncoder::new(image_info_vec); encoder.pack() } fn main() -> Result<()> { let cursor_style: Vec = serde_json::from_str(include_str!("../.cursors.min")).unwrap(); // TODO: proper arg parsing, handling shit cutely, not hardcoding everything, etc if fs::exists("xcursor-out")? { fs::remove_dir_all("xcursor-out")?; } fs::create_dir("xcursor-out")?; for style in cursor_style { fs::create_dir(format!("xcursor-out/{}", style.kind))?; for cursor in style.cursors { println!("xcursor-out/{}/{}", style.kind, cursor.name.clone()); // shove these files in a dir named `cursors` inside the theme dir // the theme dir should have these 2 files in it: // // cursor.theme: // [Icon Theme] // Name=Bibata // Inherits="Bibata" // // index.theme: // [Icon Theme] // Name=Bibata // Comment=Generated Bibata Theme // Inherits="hicolor" // // the theme dir should be ~/.local/share/icons/Bibata // // to apply on gnome: gsettings set org.gnome.desktop.interface cursor-theme Bibata fs::write( format!("xcursor-out/{}/{}", style.kind, cursor.name.clone()), process_xcursor( cursor.clone(), BibataColourScheme { base: "#ffffff", outline: "#000000", watch_background: "#ffffff", watch_color: vec!["#ff5e9c", "#65c7e0", "#f69a59", "#6789f0"], watch_opacity: 1.0, }, vec![ 4, 6, 8, 10, 12, 16, 18, 24, 30, 32, 36, 42, 48, 54, 60, 66, 72, 128, ], 750, ), )?; for alias in cursor.aliases { unix::fs::symlink( cursor.name.clone(), format!("xcursor-out/{}/{}", style.kind, alias), )?; } } } Ok(()) }