tanin-icons: accent-reactive folder icon theme (replaces Papirus)

New package packaging/tanin-icons/ generating 5 thin themes Tanin-<accent>
that inherit Adwaita and recolour only the folder/place ramp to the muted
Fuji accent (HSL shift keeping per-stop lightness; 16px rasterised so the
size-exact inherited blue PNG can't win).

- style.py: _set_gsettings now also sets icon-theme=Tanin-<accent_key> so
  folders recolour live with the accent.
- desktop.py: hide Tanin-* from the manual icon picker (accent-driven, a
  manual pick would be overridden on the next accent change).
- panel.py: pin-add icon hint Papirus -> Adwaita.
- build/finish-tanin-repo.sh: tanin-icons in OWN_SIMPLE; AUR rebuilds add
  librewolf-bin, arch-update, timeshift-autosnap, xdg-terminal-exec, paru.
- camel.toml: icon_theme=Tanin-<accent>, icons=[adwaita], tanin-icons own
  package + the new rebuilt_aur entries.

Deliberately excludes unrelated in-flight work (calendar/gtklock/
online-accounts/overview-backdrop) still uncommitted in the tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 20:39:27 +02:00
parent ca94a1828a
commit c5b0848a50
11 changed files with 364 additions and 10 deletions
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""Generate the Tanin-<accent> 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-<key> 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())