diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-11 18:45:15 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-11 18:45:15 +0200 |
| commit | ee39bd927e056c2dae9d61230a95a05715b3a8c6 (patch) | |
| tree | 5539cb2dba13acb2f2b8f607d691f9b9a1dbd858 /bin | |
| parent | a77f24d657a6091022754adb1b30750748132216 (diff) | |
| download | unified-desktop-theme-ee39bd927e056c2dae9d61230a95a05715b3a8c6.tar.gz unified-desktop-theme-ee39bd927e056c2dae9d61230a95a05715b3a8c6.zip | |
feat(palette): add five more schemes, and parse-check them all
Adds Catppuccin Frappe and Mocha, which share Macchiato's colour names so
their role maps are the same mapping against different values, and Material
Ocean, Palenight and Darker, which bring their own names and their own maps.
Nine schemes now, all dark.
Material ships one accent per hue and three greys where the roles want five
depths, so each Material palette carries four interpolated shades, marked as
not upstream, and its bright_* terminal slots name the same colours as the
normal ones.
The selftest now renders every scheme to a temp directory and has rofi parse
every theme, not just check that roles resolve. Resolving was never the
property that mattered: the Tokyo Night breakage resolved fine and still
emitted a palette the themes could not use. Verified the check fails when a
name is removed.
Catppuccin Latte is deliberately absent. It is light, and that is not a role
remap: homepage is pinned theme: dark and the generated card overrides target
its dark-mode class.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gbjA2bmswN8jyPDKzrvqe
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/udt-palette | 46 |
1 files changed, 43 insertions, 3 deletions
diff --git a/bin/udt-palette b/bin/udt-palette index 1df0c4f..18dc0b1 100755 --- a/bin/udt-palette +++ b/bin/udt-palette @@ -417,8 +417,8 @@ TARGETS = [ ] -def generate(roles_path, out_dir): - scheme, palette, res, snap_names = load(roles_path) +def generate(roles_path, out_dir, scheme=None): + scheme, palette, res, snap_names = load(roles_path, scheme=scheme) written = [] for target, fn, template in TARGETS: dest = out_dir / target @@ -437,6 +437,44 @@ def generate(roles_path, out_dir): return scheme, written +def check_rofi_parses(scheme): + """Render `scheme` to a temp dir and have rofi parse every theme. + + The role checks below prove a scheme resolves, not that what it emits is + valid. A palette missing a name the themes reference parses as a broken + theme and every launcher stops opening, which is how exactly that bug + shipped once. rofi needs no display for -dump-theme. + """ + import shutil + import subprocess + import tempfile + + if not shutil.which("rofi"): + return None + + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) + generate(None, out, scheme=scheme) + udt = out / "rofi" / "udt" + # The themes @import siblings, so they need the whole directory. + for extra in (REPO / "rofi" / "udt").glob("*.rasi"): + if not (udt / extra.name).exists(): + shutil.copy(extra, udt / extra.name) + # accent.rasi is generated per wallpaper; seed it so themes resolve. + (udt / "accent.rasi").write_text("* { accent: #ffffffff; }\n") + + for theme in sorted(udt.glob("*.rasi")): + if theme.name in ("palette.rasi", "accent.rasi", "common.rasi"): + continue + proc = subprocess.run( + ["rofi", "-no-config", "-theme", str(theme), "-dump-theme"], + capture_output=True, text=True) + if "Failed to parse" in proc.stderr: + raise AssertionError( + f"{scheme}: rofi cannot parse {theme.name}\n{proc.stderr.strip()}") + return True + + def selftest(): # Every shipped scheme must satisfy every role, or switching to it breaks # at install time. load() raises on any role the palette cannot supply. @@ -449,7 +487,9 @@ def selftest(): assert res["bg"] != res["fg"], f"{scheme}: bg and fg are the same colour" assert len(snap) >= 5, f"{scheme}: only {len(snap)} snap candidates" seen[scheme] = set(res) - print(f" {scheme}: {len(res)} roles, {len(snap)} accents") + parsed = check_rofi_parses(scheme) + note = "" if parsed else " (rofi not installed, parse unchecked)" + print(f" {scheme}: {len(res)} roles, {len(snap)} accents{note}") # Every scheme must define the SAME roles: a generator asks for a role by # name, so one scheme missing it would fail only once that scheme was |
