diff options
| -rw-r--r-- | AGENTS.md | 14 | ||||
| -rwxr-xr-x | bin/udt-accent | 72 | ||||
| -rwxr-xr-x | bin/udt-palette | 18 |
3 files changed, 78 insertions, 26 deletions
@@ -199,14 +199,16 @@ One colour moves: `@accent`. `bin/udt-accent` reads the current wallpaper, extracts its signature colour, and snaps it to the nearest of ten Catppuccin accents by perceptual hue in CIELAB. `wallp` calls it on every wallpaper change. -It writes seven things: `~/.cache/udt/udt-accent.rasi` (rofi), +It writes nine things: `~/.cache/udt/udt-accent.rasi` (rofi), `~/.cache/udt/udt-border.lua` (Hyprland), `~/.cache/udt/hyprlock-border.conf` (hyprlock, the same three hues as the window border, as a hyprlang gradient), `~/.cache/wal/colors.json` (Firefox, via pywalfox), `~/.cache/udt/udt-palette.qml` (quickshell), `~/.local/share/udt/sddm-theme.conf` (the SDDM greeter, palette plus wallpaper, read through a root-made `theme.conf.user` symlink), and the accent -substitution in Sublime's installed `Packages/User/udt.sublime-color-scheme`. +substitution in three generated files: Sublime's installed +`Packages/User/udt.sublime-color-scheme`, grub-theme-udt's `theme.txt` (its +`# udt:` line) and `templates/homepage/custom.css` (its `--udt-accent` line). It then starts `udt-homepage` and `udt-grub` detached, for the two outputs that need a root step on another filesystem or host, and `udt-keyboard`, which lights an Ajazz AK820 Pro in the accent through `ak820-ctl` (a no-op without @@ -215,7 +217,11 @@ started from hypr-theme's autostart, reapplies the accent on every plug-in. Its firmware ignores a mode change unless the lights go off first and settle for a few seconds, hence the off, sleep 3, mode sequence; one second was not enough. -**Sublime's accent is replaced, not substituted into a placeholder.** Its +**Sublime's, GRUB's and the homepage's accents are replaced, not substituted +into a placeholder.** GRUB and the homepage are generated by udt-palette with +the scheme's accent, then rewritten in place, before `udt-grub` and +`udt-homepage` start. Both already refresh on every wallpaper change for the +wallpaper itself, so following the accent adds no root step. Sublime's colour scheme cannot cascade, so the accent has to live in the one generated file rather than arriving as a second one, and `install.sh` copies that file out of the repo. `write_sublime` therefore rewrites the `"accent"` variable @@ -266,7 +272,7 @@ those two and pywal's wallpaper-derived colours come back. Not everything tracks the wallpaper. rofi, quickshell, Hyprland borders, hyprlock's border (through `~/.cache/udt/hyprlock-border.conf`), Sublime's -colour scheme, Firefox and the AK820 Pro keyboard do; conky, Qt, GTK, waybar, hyprlock's panel palette +colour scheme, Firefox, GRUB, the homepage and the AK820 Pro keyboard do; conky, Qt, GTK, waybar, hyprlock's panel palette (from `templates/hyprlock/hyprlock-palette.conf`) and Sublime's UI chrome sit on the scheme's accent, lavender by default, which is also the fallback when a wallpaper is too grey to snap. Qt and GTK apps only reread a theme on restart, diff --git a/bin/udt-accent b/bin/udt-accent index d758ea6..69698d1 100755 --- a/bin/udt-accent +++ b/bin/udt-accent @@ -47,6 +47,12 @@ HYPRLOCK_BORDER_OUTPUT = CACHE / "hyprlock-border.conf" SUBLIME = (Path.home() / ".config" / "sublime-text" / "Packages" / "User" / "udt.sublime-color-scheme") QML_OUTPUT = CACHE / "udt-palette.qml" +# Generated by udt-palette with the scheme's accent and rewritten here with the +# wallpaper's. Both already go stale on a wallpaper change, GRUB's background +# and homepage's staged wallpaper, so following the accent costs no extra step. +REPO = Path(__file__).resolve().parent.parent +GRUB_THEME = REPO.parent / "grub-theme-udt" / "theme.txt" +HOMEPAGE_CSS = REPO / "templates" / "homepage" / "custom.css" # The installed rofi palette, itself generated by udt-palette. Read rather than # duplicated so the QML singleton carries exactly the colours rofi uses, under @@ -381,27 +387,42 @@ def write_colors_json(name, image): subprocess.run(["pywalfox", "update"], capture_output=True, check=False) +def rewrite(path, pattern, value): + """Replace group 1's tail in the first line matching pattern with value. + + For generated files whose accent is replaced by matching the line that + carries it, not a placeholder, so a second run rewrites the first run's + accent. A missing file is not an error: its consumer may not be here. + """ + try: + text = path.read_text() + except FileNotFoundError: + return + new, count = re.subn(pattern, lambda m: m.group(1) + value, text, + count=1, flags=re.M) + if count and new != text: + write_atomic(path, new, mode=path.stat().st_mode & 0o777) + + def write_sublime(name): """Substitute the accent into the Sublime colour scheme install.sh copied. In place: Sublime colour schemes do not cascade, so the accent cannot arrive as a second file. Sublime watches Packages/User and reloads a - scheme when its file changes, so no signal is needed. A missing file is not - an error, Sublime simply may not be installed here. - - This reads the installed copy and has nowhere to recover @ACCENT@ from - once substituted, so the previous accent is replaced by matching the - variable line rather than a placeholder. + scheme when its file changes, so no signal is needed. """ - try: - text = SUBLIME.read_text() - except FileNotFoundError: - return + rewrite(SUBLIME, r'("accent":\s*")[^"]*', ACCENTS[name]) - new, count = re.subn(r'("accent":\s*)"[^"]*"', - lambda m: f'{m.group(1)}"{ACCENTS[name]}"', text, count=1) - if count and new != text: - write_atomic(SUBLIME, new) + +def write_grub(name): + """The accent on theme.txt's '# udt:' line, which GRUB's build reads.""" + rewrite(GRUB_THEME, r"^(# udt: .*\baccent=)#[0-9a-fA-F]{6}", ACCENTS[name]) + + +def write_homepage(name): + """The --udt-accent triple every accent in homepage's custom.css reads.""" + r, g, b = bytes.fromhex(ACCENTS[name].lstrip("#")) + rewrite(HOMEPAGE_CSS, r"^(\s*--udt-accent:\s*)[^;]*", f"{r} {g} {b}") def icon_theme(): @@ -520,6 +541,9 @@ def main(image): write_sublime(name) write_sddm(name, image) write_colors_json(name, image) + # Before the helpers below start, which build and stage these two. + write_grub(name) + write_homepage(name) # Hyprland only rereads its config on request, and may not be running. subprocess.run(["hyprctl", "reload"], capture_output=True, check=False) @@ -565,6 +589,26 @@ def selftest(): assert '"#f5a97f"' not in second and '"#8bd5ca"' in second assert third == second, "accent substitution is not idempotent" + # GRUB and homepage go through the same match-the-line rewrite, so the + # same twice-over test: the scheme's accent first, then the last run's. + with tempfile.TemporaryDirectory() as tmpdir: + f = Path(tmpdir) / "f" + cases = [ + (write_grub, "# udt: bg_alt=#1e2030 accent=#b7bdf8\nx = \"#b7bdf8\"\n", + lambda h: f"# udt: bg_alt=#1e2030 accent={h}\nx = \"#b7bdf8\"\n"), + (write_homepage, ".t {\n --udt-accent: 183 189 248;\n}\n", + lambda h: ".t {\n --udt-accent: %d %d %d;\n}\n" + % tuple(bytes.fromhex(h[1:]))), + ] + for write, start, want in cases: + globals()["GRUB_THEME"] = globals()["HOMEPAGE_CSS"] = f + f.write_text(start) + f.chmod(0o644) + for name in list(ACCENTS)[:2]: + write(name) + assert f.read_text() == want(ACCENTS[name]), f.read_text() + assert f.stat().st_mode & 0o777 == 0o644, "rewrite lost the mode" + # A near-grey has an unstable hue angle and must take the fallback. assert snap("#888888") == FALLBACK, snap("#888888") diff --git a/bin/udt-palette b/bin/udt-palette index cdeb571..86c53d6 100755 --- a/bin/udt-palette +++ b/bin/udt-palette @@ -592,7 +592,6 @@ def gen_homepage(res, scheme, palette): sr, sg, sb, _ = res["surface"] ar, ag, ab, _ = res["surface_alt"] br, bg_, bb, _ = res["bg"] - accent = hex6(res["accent"]) return f"""/* {BANNER.format(scheme=scheme)} * @@ -603,7 +602,10 @@ def gen_homepage(res, scheme, palette): .theme-gray {{ {rows} - --color-logo-start: {triple('accent')}; + /* The scheme's accent until udt-accent rewrites this line with the + * wallpaper's. Every accent below reads it, so that is one line to match. */ + --udt-accent: {triple('accent')}; + --color-logo-start: var(--udt-accent); --color-logo-stop: {triple('info')}; }} @@ -622,10 +624,9 @@ def gen_homepage(res, scheme, palette): background-color: rgb({br} {bg_} {bb} / 50%); }} -/* Accent on secondary labels. Fixed, not wallpaper-tracking: this runs on a - * server with no wallpaper to read. */ +/* Accent on secondary labels. */ .theme-gray .text-theme-500 {{ - color: {accent}; + color: rgb(var(--udt-accent)); }} """ @@ -693,9 +694,10 @@ def gen_conky(res, scheme, palette, template): def gen_grub(res, scheme, palette, template): """GRUB: substitute into the theme template in the grub-theme-udt repo. - theme.txt has no include, so the colours are baked in. The accent is the - scheme's fixed one: nothing at boot can read the wallpaper. The PNG slices - that carry colour are drawn by that repo's build script from this output. + theme.txt has no include, so the colours are baked in. The accent lands only + on the '# udt:' line, where the build script reads it to draw the PNG + slices that carry colour. udt-accent rewrites it there with the wallpaper's, + which is no less stale than the background snapshot the build already takes. """ out = template.replace("@SCHEME@", scheme) for role in ("bg", "bg_alt", "bg_deep", "surface", "fg", "fg_dim", |
