summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs199
-rw-r--r--src/xcursor.rs101
2 files changed, 300 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..fed8090
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,199 @@
+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<String>,
+ xorg: Option<String>,
+}
+
+#[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<String>,
+ hot: Xy,
+ frames: Vec<BibataFrame>,
+ frame_count: i32,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct BibataCursorStyle {
+ kind: String,
+ cursors: Vec<BibataCursor>,
+}
+
+#[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<u16>,
+ time_scale: u32,
+) -> Vec<u8> {
+ let mut image_info_vec: Vec<ImageInfo> = 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<u8> = 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<BibataCursorStyle> =
+ 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(())
+}
diff --git a/src/xcursor.rs b/src/xcursor.rs
new file mode 100644
index 0000000..ff6c3d6
--- /dev/null
+++ b/src/xcursor.rs
@@ -0,0 +1,101 @@
+#[derive(Debug, Clone)]
+pub struct ImageInfo {
+ pub r#type: u32,
+ pub subtype: u32,
+ pub width: u32,
+ pub height: u32,
+ pub xhot: u32,
+ pub yhot: u32,
+ pub delay: u32,
+ pub data: Vec<u8>,
+}
+
+pub struct XCursorEncoder {
+ pub images: Vec<ImageInfo>,
+}
+
+const MAGIC: [u8; 4] = [0x58, 0x63, 0x75, 0x72];
+const IMAGE_HEADER: [u8; 4] = [0x02, 0x00, 0xfd, 0xff];
+
+impl XCursorEncoder {
+ pub fn new(images: Vec<ImageInfo>) -> Self {
+ XCursorEncoder { images }
+ }
+
+ fn image_pos(&self, position: usize) -> usize {
+ let mut val = 12 * self.images.len() + 16;
+ for n in 0..position {
+ val += 36 + ((self.images[n].width as usize) * (self.images[n].height as usize) * 4)
+ }
+ val
+ }
+
+ pub fn pack(&mut self) -> Vec<u8> {
+ fn insert_bytes(data: &mut Vec<u8>, new_data: impl IntoIterator<Item = u8>) {
+ for int in new_data {
+ data.push(int);
+ }
+ }
+ fn insert_int(data: &mut Vec<u8>, int: u32) {
+ data.push((int & 0xff) as u8);
+ data.push(((int >> 8) & 0xff) as u8);
+ data.push(((int >> 16) & 0xff) as u8);
+ data.push(((int >> 24) & 0xff) as u8);
+ }
+
+ let mut data: Vec<u8> = Vec::new();
+
+ // File Header
+ {
+ // MAGIC string ("Xcur")
+ insert_bytes(&mut data, MAGIC);
+ // CARD32 bytes in this header
+ insert_int(&mut data, 16);
+ // CARD32 file version
+ insert_int(&mut data, 1);
+ // CARD32 number of toc entries
+ insert_int(&mut data, self.images.len().try_into().unwrap());
+ }
+
+ // ntoc entries
+ {
+ let mut img_idx: usize = 0;
+ for img in self.images.clone().into_iter() {
+ // Some header
+ insert_bytes(&mut data, IMAGE_HEADER);
+ // CARD32 type-specific label - size for images
+ insert_int(&mut data, img.r#type);
+ // CARD32 absolute byte position of table in file
+ insert_int(&mut data, self.image_pos(img_idx).try_into().unwrap());
+
+ img_idx += 1;
+ }
+ }
+
+ // images
+ {
+ for img in self.images.clone().into_iter() {
+ // Header Size (36)
+ insert_int(&mut data, 36);
+ // Image Type
+ insert_bytes(&mut data, IMAGE_HEADER);
+ // Subtype, for nominal size
+ insert_int(&mut data, img.subtype);
+ // Version
+ insert_int(&mut data, 1);
+ // Image dimensions
+ insert_int(&mut data, img.width);
+ insert_int(&mut data, img.height);
+ // Cursor positioning
+ insert_int(&mut data, img.xhot);
+ insert_int(&mut data, img.yhot);
+ // Milliseconds till next frame
+ insert_int(&mut data, img.delay);
+ // Raw image data
+ insert_bytes(&mut data, img.data);
+ }
+ }
+
+ data
+ }
+}