Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: use breeze scripts to build xcursors & plasma scalable SVGs #40

Merged
merged 22 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 20 additions & 95 deletions build
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,23 @@

set -eu

INKSCAPE_VERSION=$(inkscape --version 2>/dev/null | awk '/Inkscape[ ]/ {print $2; exit}')
echo -ne "Checking Requirements...\\r"
if ! command -v python > /dev/null ; then
echo -e "\\nFAIL: python must be installed"
exit 1
fi

if ! command -v inkscape > /dev/null ; then
echo -e "\\nFAIL: inkscape must be installed"
exit 1
fi

if ! command -v xcursorgen > /dev/null ; then
echo -e "\\nFAIL: xcursorgen must be installed"
exit 1
fi
echo -e "\033[0KChecking Requirements... DONE"


build_svgs() {
local src_dir="$1"
Expand All @@ -21,79 +37,6 @@ build_svgs() {
done
}

# Generates the commands required for SVG -> PNG conversion
# These commands are to be used within inkscape's "shell" mode which allows for batch processing in a single inkscape process
generate_inkscape_commands() {
local src_dir="$1"
local out_dir="$2"
local file size bitmap_file

[ -d "$src_dir" ] || return 1
[ -d "$out_dir" ] || mkdir -p "$out_dir"

# shellcheck disable=SC2016
for file in "$src_dir"/*.svg; do
[ -f "$file" ] || continue
for size in 24 32 48 64; do
bitmap_file="${out_dir%/}/$(basename "$file" .svg)_${size}.png"
INKSCAPE_COMMANDS+="file-open:$file; export-filename:$bitmap_file; export-height:$size; export-width:$size; export-do; file-close;"
done
done
}

convert_to_x11cursor() {
local src_dir="$1"
local out_dir="$2"
local config base_name

[ -d "$src_dir" ] || return 1

if [ -d "$out_dir" ]; then
rm -rf "$out_dir"
fi

mkdir -p "$out_dir"

for config in "$CONFIG_DIR"/*.cursor; do
[ -f "$config" ] || continue
base_name="$(basename "$config" .cursor)"
xcursorgen -p "$src_dir" "$config" "$out_dir/$base_name"
done
}

write_scalable() {
local src_dir="$1"
local out_dir="$2"
local config base_name

[ -d "$src_dir" ] || return 1

if [ -d "$out_dir" ]; then
rm -rf "$out_dir"
fi

mkdir -p "$out_dir"

for config in "$CONFIG_DIR"/*.json; do
[ -f "$config" ] || continue
base_name="$(basename "$config" .json)"
cursor_out="$out_dir/$base_name"
mkdir "$cursor_out"
find "$src_dir" -type f \( -name "$base_name-??.svg" -o -name "$base_name.svg" \) ! -name "*_24.svg" -exec cp {} "$cursor_out" \;
cp "$config" "$cursor_out/metadata.json"
done
}

create_aliases() {
local out_dir="$1"
local symlink target

while read -r symlink target; do
[ -e "$out_dir/$symlink" ] && continue
ln -sf "$target" "$out_dir/$symlink"
done <"$ALIASES"
}

create_hyprcursors() {
local src_dir="$1"
local out_dir="$2"
Expand Down Expand Up @@ -142,7 +85,6 @@ LICENSE="$SCRIPT_DIR/LICENSE"
ALIASES="$SRC_DIR/cursorList"

ACCENTS="blue dark flamingo green lavender light maroon mauve peach pink red rosewater sapphire sky teal yellow"
INKSCAPE_COMMANDS=""
HYPRCURSOR=0

while getopts "f:a:h" option; do
Expand Down Expand Up @@ -174,34 +116,17 @@ echo "[INFO] - Generating SVGs..."
build_svgs $SRC_DIR $FLAVOR
echo "[INFO] - Generating SVGs complete."

echo "[INFO] - Generating inkscape commands..."
for accent in $ACCENTS; do
theme_name="catppuccin-$FLAVOR-$accent-cursors"
theme_src_dir="$SVGS_DIR/$theme_name"
theme_out_dir="$OUT_DIR/$theme_name"
theme_png_dir="$PNG_DIR/$theme_name"

generate_inkscape_commands "$theme_src_dir" "$theme_png_dir"
done
echo "[INFO] - Generating inkscape commands complete"

echo "[INFO] - Converting SVGs to PNGs..."
inkscape --shell <<<${INKSCAPE_COMMANDS} &>/dev/null
echo "[INFO] - Converting SVGs to PNGs complete"

echo "[INFO] - Converting to x11cursor & creating aliases..."
for accent in $ACCENTS; do
theme_name="catppuccin-$FLAVOR-$accent-cursors"
theme_src_dir="$SVGS_DIR/$theme_name"
theme_out_dir="$OUT_DIR/$theme_name"
theme_png_dir="$PNG_DIR/$theme_name"

convert_to_x11cursor "$theme_png_dir" "$theme_out_dir/cursors"
write_scalable "$theme_src_dir" "$theme_out_dir/cursors_scalable"
create_aliases "$theme_out_dir/cursors"
create_aliases "$theme_out_dir/cursors_scalable"
echo "Generating $FLAVOR - $accent"

scripts/build.sh $theme_src_dir $PNG_DIR/$theme_name $OUT_DIR/$theme_name
Covkie marked this conversation as resolved.
Show resolved Hide resolved

cp -f "$theme_src_dir/index.theme" "$theme_out_dir"
cp -f "$AUTHORS" "$theme_out_dir"
cp -f "$LICENSE" "$theme_out_dir"
done
Expand Down
5 changes: 5 additions & 0 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
hyprcursor,
zip,
catppuccin-whiskers,
python3,
python3Packages,
}:
stdenvNoCC.mkDerivation {
name = "catppuccin-cursors";
Expand All @@ -16,6 +18,7 @@ stdenvNoCC.mkDerivation {
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ./.)) (
lib.fileset.unions [
./src
./scripts
./justfile
./build
./create_zips
Expand All @@ -33,6 +36,8 @@ stdenvNoCC.mkDerivation {
hyprcursor
zip
catppuccin-whiskers
python3
python3Packages.pyside6
];

buildPhase = ''
Expand Down
83 changes: 83 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/bin/bash
Covkie marked this conversation as resolved.
Show resolved Hide resolved
set -euo pipefail

BIN_DIR="$( dirname "${BASH_SOURCE[0]}" )"
SRC_DIR="src"
BUILD_DIR="$2"
RAWSVG_DIR="$1"
INDEX="$1/index.theme"
ALIASES="$SRC_DIR/cursorList"

NOMINAL_SIZE=24
REAL_SIZE=32
FRAME_TIME=30
SCALES="50 75 100 125 150 175 200 225 250 275 300"

echo -ne "Making Folders... \\r"
for scale in $SCALES; do
mkdir -p "$BUILD_DIR/x$scale"
done
mkdir -p "$BUILD_DIR/config"
echo -e "\033[0KMaking Folders... DONE";

echo "Generating pixmaps..."
for RAWSVG in ${RAWSVG_DIR}/*.svg; do
BASENAME=${RAWSVG##*/}
BASENAME=${BASENAME%.*}
genPixmaps="file-open:${RAWSVG};"

#echo -ne " $BASENAME...\\r"

for scale in $SCALES; do
DIR="$BUILD_DIR/x${scale}"
if [[ "${DIR}/${BASENAME}.png" -ot ${RAWSVG} ]]; then
genPixmaps="${genPixmaps} export-width:$((${REAL_SIZE}*scale/100)); export-height:$((${REAL_SIZE}*scale/100)); export-filename:${DIR}/${BASENAME}.png; export-do;"
fi
done
if [ "$genPixmaps" != "file-open:${RAWSVG};" ]; then
inkscape --shell <<<${genPixmaps} &>/dev/null
fi

#echo " $BASENAME... DONE"
done
echo "Generating pixmaps... DONE"

echo "Generating cursor theme..."
OUTPUT=$3
rm -rf "$OUTPUT"
mkdir -p "$OUTPUT/cursors"
mkdir -p "$OUTPUT/cursors_scalable"
$BIN_DIR/generate_cursors ${RAWSVG_DIR} "$BUILD_DIR" "$OUTPUT/cursors" "$OUTPUT/cursors_scalable" ${NOMINAL_SIZE} ${FRAME_TIME} ${SCALES}
echo "Generating cursor theme... DONE"

echo -ne "Generating shortcuts...\\r"
while read ALIAS ; do
FROM=${ALIAS% *}
TO=${ALIAS#* }

if [[ -e "$OUTPUT/cursors/$FROM" ]]; then
continue
fi

ln -s "$TO" "$OUTPUT/cursors/$FROM"
done < $ALIASES

while read ALIAS ; do
FROM=${ALIAS% *}
TO=${ALIAS#* }

if [[ -e "$OUTPUT/cursors_scalable/$FROM" ]]; then
continue
fi

ln -s "$TO" "$OUTPUT/cursors_scalable/$FROM"
done < $ALIASES
echo -e "\033[0KGenerating shortcuts... DONE"

echo -ne "Copying Theme Index...\\r"
if ! [[ -e "$OUTPUT/$INDEX" ]]; then
cp $INDEX "$OUTPUT/index.theme"
fi
echo -e "\033[0KCopying Theme Index... DONE"

echo "COMPLETE!"
97 changes: 97 additions & 0 deletions scripts/generate_cursors
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env python3

import sys
import math
import re
import subprocess
import shutil
import json
from pathlib import Path
from PySide6.QtSvg import QSvgRenderer

# Displace the hotspot to the right and down by 1/100th of a pixel, then
# floor. So if by some float error the hotspot is at 4.995, it will be
# displaced to 5.005, then floored to 5. This is to prevent the hotspot
# from potential off-by-one errors when the cursor is scaled.
HOTSPOT_DISPLACE = 1

if len(sys.argv) <= 7:
print("Usage: " + sys.argv[0] + " <svg cursor dir> <pixmap dir> <xcursor output dir> <svg cursor output dir> <base size> <animation frame time> <scales>")
sys.exit(1)

svg_dir = Path(sys.argv[1])
pixmap_dir = Path(sys.argv[2])
xcursor_output_dir = Path(sys.argv[3])
svg_cursor_output_dir = Path(sys.argv[4])
nominal_size = int(sys.argv[5])
delay = int(sys.argv[6])
scales = [int(i) for i in sys.argv[7:]]

svg_files = list(svg_dir.glob("*.svg"))
svg_files.sort()

processed_svgs = set()

for svg_file in svg_files:
if svg_file in processed_svgs:
continue
processed_svgs.add(svg_file)

basename = svg_file.stem
basename = re.sub(r"-[0-9_]*$", "", basename)
# print(f" {basename}")

# Animated?
frames = list(svg_dir.glob(basename + "-[0-9]*.svg"))
frames.sort()
processed_svgs.update(frames)

svg = QSvgRenderer(str(svg_file))
hotspot = svg.transformForElement("hotspot").map(svg.boundsOnElement("hotspot")).boundingRect().topLeft()

# Generate xcursor
xcursor_config_path = pixmap_dir / "config" / f"{basename}.config"
with open(xcursor_config_path, "w") as config:
for scale in scales:
size = math.floor(nominal_size * scale / 100)
hotspot_x = math.floor((hotspot.x() * scale + HOTSPOT_DISPLACE) / 100)
hotspot_y = math.floor((hotspot.y() * scale + HOTSPOT_DISPLACE) / 100)

if len(frames) == 0:
print(f"{size} {hotspot_x} {hotspot_y} x{scale}/{basename}.png", file=config)
else:
for i in frames:
t = i.stem
print(f"{size} {hotspot_x} {hotspot_y} x{scale}/{t}.png {delay}", file=config)

subprocess.run(["xcursorgen", "-p", pixmap_dir, xcursor_config_path, xcursor_output_dir / basename], check=True)

# Generate SVG cursor
output_dir = svg_cursor_output_dir / basename
output_dir.mkdir(parents=True, exist_ok=True)
hotspot_x = hotspot.x()
hotspot_y = hotspot.y()
with open(output_dir / "metadata.json", "w") as metadata:
if len(frames) == 0:
filename = svg_file.name
shutil.copyfile(svg_file, output_dir / filename)
json.dump([{
"filename": filename,
"hotspot_x": hotspot_x,
"hotspot_y": hotspot_y,
"nominal_size": nominal_size
}],
metadata)
else:
l = []
for i in frames:
filename = i.name
shutil.copyfile(i, output_dir / filename)
l.append({
"filename": filename,
"hotspot_x": hotspot_x,
"hotspot_y": hotspot_y,
"nominal_size": nominal_size,
"delay": delay
})
json.dump(l, metadata)
4 changes: 0 additions & 4 deletions src/config/alias.cursor

This file was deleted.

8 changes: 0 additions & 8 deletions src/config/alias.json

This file was deleted.

4 changes: 0 additions & 4 deletions src/config/all-scroll.cursor

This file was deleted.

8 changes: 0 additions & 8 deletions src/config/all-scroll.json

This file was deleted.

4 changes: 0 additions & 4 deletions src/config/bottom_left_corner.cursor

This file was deleted.

Loading
Loading