#!/usr/bin/env python3 """Generate the Tanin- icon themes. Each theme is a THIN overlay that `Inherits=Adwaita` and recolours only the accent-bearing place/folder icons to a Fuji hue — everything else resolves straight from Adwaita, so app-icon coverage stays exactly Adwaita's. Recolour = HSL hue/saturation swap that KEEPS each source stop's lightness: the Adwaita blue ramp (#62a0ea / #438de6 / #a4caee / …) is shifted to the muted Fuji hue while the gradient depth (light front / dark back) is preserved. White emblems (music note, download arrow) are untouched — they are desaturated, so the saturation gate skips them. Runs at package build time and reads the installed Adwaita theme. Override the input/output dirs via ADWAITA_DIR / OUT_DIR. """ from __future__ import annotations import colorsys import os import re import shutil import subprocess import sys from pathlib import Path # Fuji accents — keep in sync with taninux.core.theme / taninux.gui.accents. ACCENTS = { "ume": "#8f8aac", # violet "shinkai": "#8a98ac", # blue "take": "#8aac8b", # green "chikyu": "#aca98a", # yellow "beni": "#ac8a8c", # red } SRC = Path(os.environ.get("ADWAITA_DIR", "/usr/share/icons/Adwaita")) OUT = Path(os.environ.get("OUT_DIR", "out")) # Icons that semantically carry the accent (containers / locations). Anything # not listed here is left to Adwaita via Inherits. PLACES = [ "folder", "folder-documents", "folder-download", "folder-drag-accept", "folder-music", "folder-pictures", "folder-publicshare", "folder-remote", "folder-templates", "folder-videos", "user-home", "user-desktop", "user-bookmarks", "network-workgroup", ] MIMETYPES = ["inode-directory"] # the folder mimetype — same look as a folder HEX = re.compile(r"#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b") INDEX = """[Icon Theme] Name=Tanin-{key} Comment=TANINUX Fuji accent ({key}) over Adwaita Inherits=Adwaita,hicolor Directories=scalable/places,scalable/mimetypes,16x16/places [scalable/places] Context=Places Size=128 MinSize=8 MaxSize=512 Type=Scalable [scalable/mimetypes] Context=MimeTypes Size=128 MinSize=8 MaxSize=512 Type=Scalable [16x16/places] Context=Places Size=16 Type=Fixed """ def _parse(h: str) -> tuple[float, float, float]: h = h.lstrip("#") if len(h) == 3: h = "".join(c * 2 for c in h) return tuple(int(h[i:i + 2], 16) / 255 for i in (0, 2, 4)) # type: ignore[return-value] def _fmt(rgb: tuple[float, float, float]) -> str: return "#" + "".join(f"{round(max(0.0, min(1.0, c)) * 255):02x}" for c in rgb) def _recolour(accent_hex: str): ar, ag, ab = _parse(accent_hex) ah, _al, a_s = colorsys.rgb_to_hls(ar, ag, ab) def repl(m: re.Match) -> str: r, g, b = _parse(m.group(0)) h, l, s = colorsys.rgb_to_hls(r, g, b) # Only the Adwaita blue family: saturated + blue hue band (~194-230°). if s > 0.12 and 0.54 <= h <= 0.64: return _fmt(colorsys.hls_to_rgb(ah, l, a_s)) return m.group(0) return repl def build_theme(key: str, accent_hex: str, rsvg: str) -> int: repl = _recolour(accent_hex) root = OUT / f"Tanin-{key}" root.mkdir(parents=True, exist_ok=True) (root / "index.theme").write_text(INDEX.format(key=key)) n = 0 for ctx, names in (("places", PLACES), ("mimetypes", MIMETYPES)): sdir = root / "scalable" / ctx sdir.mkdir(parents=True, exist_ok=True) for name in names: src = SRC / "scalable" / ctx / f"{name}.svg" if not src.exists(): print(f" ! missing in Adwaita: scalable/{ctx}/{name}.svg", file=sys.stderr) continue (sdir / f"{name}.svg").write_text(HEX.sub(repl, src.read_text())) n += 1 # 16x16 raster: size-exact, else the inherited Adwaita blue PNG wins at 16px. pdir = root / "16x16" / "places" pdir.mkdir(parents=True, exist_ok=True) for name in PLACES: svg = root / "scalable" / "places" / f"{name}.svg" if not svg.exists(): continue subprocess.run([rsvg, "-w", "16", "-h", "16", "-o", str(pdir / f"{name}.png"), str(svg)], check=True) return n def main() -> int: if not SRC.is_dir(): sys.exit(f"Adwaita theme not found at {SRC} (install adwaita-icon-theme)") rsvg = shutil.which("rsvg-convert") if not rsvg: sys.exit("rsvg-convert not found (install librsvg) — needed for 16px folders") # Only ever touch our own Tanin- dirs — OUT may be a shared icon dir # (e.g. ~/.local/share/icons), never rmtree the whole thing. for key, hexv in ACCENTS.items(): old = OUT / f"Tanin-{key}" if old.exists(): shutil.rmtree(old) n = build_theme(key, hexv, rsvg) print(f"==> Tanin-{key:<8} {hexv} ({n} svg recoloured + 16px raster)") print(f"== {len(ACCENTS)} themes written to {OUT} ==") return 0 if __name__ == "__main__": raise SystemExit(main())