diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-29 12:29:38 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-29 12:29:38 +0200 |
| commit | 0ca89376ea0a04ad88ae80b9b602033f3beb8eb6 (patch) | |
| tree | 1c20a959647fb31689e97b343f1cabd11084c3ce /bin/udt-palette | |
| parent | 5b133dbd86aaf54b0c728128f304c8dd8ebea9d1 (diff) | |
| download | unified-desktop-theme-0ca89376ea0a04ad88ae80b9b602033f3beb8eb6.tar.gz unified-desktop-theme-0ca89376ea0a04ad88ae80b9b602033f3beb8eb6.zip | |
feat(palette): theme btop, Joplin and Feishin
btop gets a generated udt.theme and only the color_theme line of btop.conf
is rewritten. Joplin gets userchrome.css overriding the --joplin-* custom
properties most of its UI is built on, with !important since Joplin's own
:root block is injected later; a userchrome.css udt did not write is left
alone. Feishin 1.17 watches a Themes folder for JSON themes, so UDT.json
extends its built-in Catppuccin Mocha with the scheme's roles and reaches
a running Feishin live once selected.
All three use existing roles, so no scheme file changes. udt-appthemes
installs them next to Obsidian and Typora; the recap hint names btop and
Joplin among the apps that need a restart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'bin/udt-palette')
| -rwxr-xr-x | bin/udt-palette | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/bin/udt-palette b/bin/udt-palette index 00e3092..cdeb571 100755 --- a/bin/udt-palette +++ b/bin/udt-palette @@ -13,6 +13,7 @@ what each is for, and everything else here is generated. Editing a generated file is pointless: the next install.sh overwrites it. """ +import json import re import sys from pathlib import Path @@ -795,6 +796,153 @@ def load(selector_path, scheme=None): # What gets written where. Templates are read from the repo, everything else is # generated whole. +def gen_btop(res, scheme, palette): + """btop: a .theme file, installed as ~/.config/btop/themes/udt.theme. + + btop has no include or variables, so every key is written. The graphs are + three-stop gradients; they run through the state colours so a meter reads + the same way a waybar pill does, calm to critical. + """ + def c(role): + return hex6(res[role]) + + def grad(name, start, mid, end): + return (f'theme[{name}_start]="{c(start)}"\n' + f'theme[{name}_mid]="{c(mid)}"\n' + f'theme[{name}_end]="{c(end)}"\n') + + return (f"# {BANNER.format(scheme=scheme)}\n\n" + f'theme[main_bg]="{c("bg")}"\n' + f'theme[main_fg]="{c("fg")}"\n' + f'theme[title]="{c("fg")}"\n' + f'theme[hi_fg]="{c("accent")}"\n' + f'theme[selected_bg]="{c("surface_alt")}"\n' + f'theme[selected_fg]="{c("fg")}"\n' + f'theme[inactive_fg]="{c("fg_faint")}"\n' + f'theme[graph_text]="{c("fg_dim")}"\n' + f'theme[meter_bg]="{c("surface")}"\n' + f'theme[proc_misc]="{c("info")}"\n' + f'theme[cpu_box]="{c("accent")}"\n' + f'theme[mem_box]="{c("success")}"\n' + f'theme[net_box]="{c("peach")}"\n' + f'theme[proc_box]="{c("info")}"\n' + f'theme[div_line]="{c("border")}"\n' + + grad("temp", "success", "warning", "critical") + + grad("cpu", "success", "warning", "critical") + + grad("free", "surface_alt", "success", "success") + + grad("cached", "surface_alt", "info", "info") + + grad("available", "surface_alt", "peach", "peach") + + grad("used", "surface_alt", "critical", "critical") + + grad("download", "surface_alt", "info", "accent") + + grad("upload", "surface_alt", "peach", "warning") + + grad("process", "info", "accent", "critical")) + + +def gen_joplin(res, scheme, palette): + """Joplin: userchrome.css, overriding the --joplin-* theme variables. + + Joplin turns its theme object into :root custom properties and most of the + desktop UI reads those, so overriding them recolours it without restyling + a single component. !important because Joplin's own :root block is + injected after this file. Parts still styled from the JS theme object keep + the base theme's colours, so the base theme should stay a dark one. + """ + def c(role): + return hex6(res[role]) + + pairs = [ + ("background-color", c("bg")), + ("background-color-transparent", hex8((*res["bg"][:3], 90))), + ("odd-background-color", c("bg_alt")), + ("color", c("fg")), + ("color-error", c("critical")), + ("color-correct", c("success")), + ("color-warn", c("warning")), + ("color-warn-url", c("warning")), + ("color-faded", c("fg_faint")), + ("divider-color", c("border")), + ("selected-color", c("surface_alt")), + ("url-color", c("info")), + ("color-error-selected", c("critical")), + ("background-color2", c("bg_deep")), + ("color2", c("fg")), + ("selected-color2", c("surface_alt")), + ("color-error2", c("critical")), + ("color-warn2", c("warning")), + ("color-warn3", c("warning")), + ("background-color3", c("bg_alt")), + ("background-color-hover3", c("surface")), + ("background-color-active3", c("surface_alt")), + ("color3", c("fg")), + ("background-color4", c("bg")), + ("color4", c("accent")), + ("background-color5", c("surface")), + ("color5", c("fg")), + ("background-color-hover2", c("surface")), + ("color-hover2", c("fg")), + ("color-active2", c("fg")), + ("color-faded2", c("fg_dim")), + ("raised-background-color", c("surface")), + ("raised-color", c("fg")), + ("warning-background-color", c("surface_alt")), + ("destructive-color", c("critical")), + ("table-background-color", c("bg_alt")), + ("code-background-color", c("bg_deep")), + ("code-border-color", c("border")), + ("code-color", c("fg")), + ("header-background-color", c("bg_alt")), + ("text-selection-color", c("accent")), + ("search-marker-background-color", c("warning")), + ("search-marker-color", c("bg")), + ("focus-outline-color", c("accent")), + ("focus-outline-color-dimmed", hex8((*res["accent"][:3], 50))), + ("border-color4", c("border")), + ("scrollbar-thumb-color", c("fg_faint")), + ("scrollbar-thumb-color-hover", c("fg_dim")), + ("scrollbar-thumb-color2", c("fg_faint")), + ("scrollbar-thumb-color-hover2", c("fg_dim")), + ] + body = "\n".join(f" --joplin-{k}: {v} !important;" for k, v in pairs) + return f"/* {BANNER.format(scheme=scheme)} */\n\n:root {{\n{body}\n}}\n" + + +def gen_feishin(res, scheme, palette): + """Feishin: a custom theme JSON for its watched Themes folder. + + It extends the built-in Catppuccin Mocha, so everything not restated here + (spacing, radii, the Mantine mapping) comes from a dark theme Feishin + already ships. Feishin validates every colour and drops what it cannot + parse, so plain hex only. + """ + def c(role): + return hex6(res[role]) + + r, g, b, _ = res["bg"] + theme = { + "extends": "catppuccinMocha", + "mode": "dark", + "app": { + "overlay-header": f"linear-gradient(transparent 0%, rgb({r} {g} {b} / 85%) 100%), var(--theme-background-noise)", + "overlay-subheader": f"linear-gradient(180deg, rgb({r} {g} {b} / 5%) 0%, var(--theme-colors-background) 100%), var(--theme-background-noise)", + }, + "colors": { + "background": c("bg"), + "background-alternate": c("bg_deep"), + "foreground": c("fg"), + "foreground-muted": c("fg_dim"), + "primary": c("accent"), + "state-error": c("critical"), + "state-info": c("info"), + "state-success": c("success"), + "state-warning": c("warning"), + "surface": c("bg_alt"), + "surface-foreground": c("fg"), + }, + } + # JSON has no comments, so the banner goes in a key Feishin ignores. + return json.dumps({"_comment": BANNER.format(scheme=scheme), **theme}, indent=4) + "\n" + + TARGETS = [ ("rofi/udt/palette.rasi", gen_rofi, None), ("templates/waybar/theme.css", gen_waybar, None), @@ -817,6 +965,9 @@ TARGETS = [ ("templates/gtk/gtk3.css", gen_gtk, "templates/gtk/gtk3.css.in"), ("templates/gtk/gtk4.css", gen_gtk, "templates/gtk/gtk4.css.in"), ("templates/gtk/index.theme", gen_gtk, "templates/gtk/index.theme.in"), + ("templates/btop/udt.theme", gen_btop, None), + ("templates/joplin/userchrome.css", gen_joplin, None), + ("templates/feishin/UDT.json", gen_feishin, None), ] # The GTK widget assets, one target each. Discovered rather than listed: there |
