diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-15 11:16:09 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-15 11:16:09 +0200 |
| commit | a9f3e65355b64577044467ca70ca16d00fba016b (patch) | |
| tree | e23ba75ade9be67f194a6535d351f6a8fd9909ae /bin | |
| parent | c90c72ed7d5bb5337319a053bf89d73fcb16c604 (diff) | |
| download | unified-desktop-theme-a9f3e65355b64577044467ca70ca16d00fba016b.tar.gz unified-desktop-theme-a9f3e65355b64577044467ca70ca16d00fba016b.zip | |
feat(palette): add Sublime Text as a consumer
Two outputs, not one. The colour scheme themes the editor pane and tracks
the wallpaper accent; the theme themes the UI chrome and sits on the
scheme's fixed accent, because Sublime reads a theme once per window and a
moving accent would leave open windows disagreeing with new ones.
The colour scheme costs no new roles. Its scopes map onto the sixteen ANSI
slots every scheme already declares, so all nine satisfy it and the
identical-role-set selftest passes untouched.
The theme is an override layered over Material Behave rather than a theme
of its own: that theme hardcodes 419 rules of [r,g,b] literals with no
variables, so the chrome surfaces are restated and its geometry, icons and
animations are inherited. It installs under the name of the theme it
overrides, because Sublime merges .sublime-theme files by filename with
Packages/User last; under its own name it would never apply. It also has
to follow Material Behave's material_theme_contrast_mode block and carry
no settings gate, or contrast mode wins.
The accent is replaced by matching the variable's current value rather
than a placeholder: install.sh copies the file out of the repo, so after
the first wallpaper change there is no placeholder left to substitute.
udt-accent --selftest now covers that it stays idempotent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'bin')
| -rwxr-xr-x | bin/udt-accent | 38 | ||||
| -rwxr-xr-x | bin/udt-palette | 52 |
2 files changed, 90 insertions, 0 deletions
diff --git a/bin/udt-accent b/bin/udt-accent index 75058df..1a542a6 100755 --- a/bin/udt-accent +++ b/bin/udt-accent @@ -32,6 +32,8 @@ MIN_CHROMA = 10.0 OUTPUT = Path.home() / ".cache" / "wal" / "udt-accent.rasi" BORDER_OUTPUT = Path.home() / ".cache" / "wal" / "udt-border.lua" DUNSTRC = Path.home() / ".cache" / "wal" / "dunstrc" +SUBLIME = (Path.home() / ".config" / "sublime-text" / "Packages" / "User" + / "udt.sublime-color-scheme") QML_OUTPUT = Path.home() / ".cache" / "wal" / "udt-palette.qml" # The installed rofi palette, itself generated by udt-palette. Read rather than @@ -280,6 +282,29 @@ def write_dunst(name): stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) +def write_sublime(name): + """Substitute the accent into the Sublime colour scheme install.sh copied. + + In place, like dunst: 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. + + Unlike dunst, 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. + """ + try: + text = SUBLIME.read_text() + except FileNotFoundError: + return + + 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 main(image): try: name = snap(signature_color(image)) @@ -292,6 +317,7 @@ def main(image): write_border(name) write_qml(name) write_dunst(name) + write_sublime(name) write_colors_json(name, image) # Hyprland only rereads its config on request, and may not be running. @@ -316,6 +342,18 @@ def selftest(): delta = min(delta, 2 * math.pi - delta) assert delta < 0.6, f"{probe} snapped to {got}, {delta:.2f} rad away" + # The Sublime accent is replaced by matching its current value, not a + # placeholder, so it has to survive being run twice: once over the freshly + # installed @ACCENT@, and again over the accent it wrote last time. + probe = '{\n "variables":\n {\n "accent": "@ACCENT@",\n' + first, n1 = re.subn(r'("accent":\s*)"[^"]*"', r'\1"#f5a97f"', probe, count=1) + second, n2 = re.subn(r'("accent":\s*)"[^"]*"', r'\1"#8bd5ca"', first, count=1) + third, n3 = re.subn(r'("accent":\s*)"[^"]*"', r'\1"#8bd5ca"', second, count=1) + assert (n1, n2, n3) == (1, 1, 1), (n1, n2, n3) + assert "@ACCENT@" not in first and '"#f5a97f"' in first + assert '"#f5a97f"' not in second and '"#8bd5ca"' in second + assert third == second, "accent substitution is not idempotent" + # A near-grey has an unstable hue angle and must take the fallback. assert snap("#888888") == FALLBACK, snap("#888888") assert FALLBACK in ACCENTS, FALLBACK diff --git a/bin/udt-palette b/bin/udt-palette index a746766..ceed64d 100755 --- a/bin/udt-palette +++ b/bin/udt-palette @@ -106,6 +106,12 @@ def hex8(c): return f"#{r:02x}{g:02x}{b:02x}{round(a * 255 / 100):02x}" +def rgb_array(c): + """[r, g, b], which is how a .sublime-theme writes a colour.""" + r, g, b, _ = c + return f"[{r}, {g}, {b}]" + + def rgba(c): r, g, b, a = c return f"rgb({r},{g},{b})" if a == 100 else f"rgba({r},{g},{b},{a / 100:.2f})" @@ -422,6 +428,48 @@ def gen_dunst(res, scheme, palette, template): return out +def gen_sublime(res, scheme, palette, template): + """Sublime Text: substitute the palette into the colour scheme. + + Scopes map onto the terminal roles rather than onto syntax roles of their + own. A scheme already declares all sixteen ANSI slots, so a syntax theme + costs no new roles and every shipped scheme satisfies it without touching + nine role maps. + + @ACCENT@ is left alone for udt-accent, exactly as in the dunst template: + Sublime colour schemes do not cascade, so the accent cannot arrive as a + second file and has to be substituted into this one. + """ + out = template.replace("@SCHEME@", scheme) + for role in ("bg", "bg_alt", "bg_deep", "surface", "surface_alt", + "fg", "fg_dim", "fg_faint", + "red", "green", "yellow", "blue", "magenta", "cyan", + "critical", "warning", "success", "info"): + out = out.replace(f"@{role.upper()}@", hex6(res[role])) + # The selection has to be translucent or it hides the text under it, and + # selection_bg resolves opaque because the terminal wants it that way. + out = out.replace("@SELECTION_BG@", hex8((*res["selection_bg"][:3], 35))) + return out + + +def gen_sublime_theme(res, scheme, palette, template): + """Sublime Text: the UI chrome, as an override layered over Material Behave. + + A .sublime-theme writes colours as [r, g, b] arrays rather than hex, so this + substitutes a different syntax from the colour scheme next door out of the + same roles. + + The accent is the scheme's fixed one, not the wallpaper's: Sublime reads a + theme once per window, so a moving accent would leave open windows + disagreeing with new ones. Same reasoning as conky, Qt, GTK and waybar. + """ + out = template.replace("@SCHEME@", scheme) + for role in ("bg", "bg_alt", "bg_deep", "fg", "fg_dim", "fg_faint", + "accent"): + out = out.replace(f"@{role.upper()}@", rgb_array(res[role])) + return out + + def gen_conky(res, scheme, palette, template): """conky: substitute into the shipped template. @@ -539,6 +587,10 @@ TARGETS = [ ("templates/typora/udt.css", gen_typora, "templates/typora/udt.css.in"), ("templates/kvantum/theme.kvconfig", gen_kvantum, "templates/kvantum/theme.kvconfig.in"), ("templates/kvantum/theme.svg", gen_kvantum, "templates/kvantum/theme.svg.in"), + ("templates/sublime/udt.sublime-color-scheme", gen_sublime, + "templates/sublime/udt.sublime-color-scheme.in"), + ("templates/sublime/udt.sublime-theme", gen_sublime_theme, + "templates/sublime/udt.sublime-theme.in"), ] |
