aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/udt-accent58
1 files changed, 58 insertions, 0 deletions
diff --git a/bin/udt-accent b/bin/udt-accent
index e459a35..a47460e 100755
--- a/bin/udt-accent
+++ b/bin/udt-accent
@@ -13,7 +13,10 @@ including the terminal's ANSI colors. See the design spec for why that matters.
"""
import math
+import os
import sys
+import tempfile
+from pathlib import Path
# The nine candidate accents. rosewater and flamingo are excluded as
# near-neutral tints that capture saturated inputs; maroon, sapphire and
@@ -33,6 +36,8 @@ ACCENTS = {
FALLBACK = "mauve"
MIN_CHROMA = 10.0
+OUTPUT = Path.home() / ".cache" / "wal" / "udt-accent.rasi"
+
def _to_lab(hexval):
"""Convert #rrggbb to CIELAB. sRGB D65, the standard conversion."""
@@ -80,6 +85,54 @@ def snap(hexval):
return min(ACCENTS, key=distance)
+def signature_color(image):
+ """Extract the image's most chromatic mid-tone color.
+
+ Calls the colorz backend directly. It returns a list and writes nothing,
+ which is what keeps the pywal cache (and so the terminal) untouched.
+ """
+ from pywal.backends import colorz
+
+ colors = colorz.get(str(image), 16)
+ # Slot 0 trends near-black and the upper slots near-white; the signature
+ # color of an image lives in the middle.
+ return max(colors[1:7], key=_chroma)
+
+
+def write_accent(name):
+ """Write the accent rasi file atomically."""
+ hexval = ACCENTS[name]
+ content = (
+ "/* Generated by udt-accent. Do not edit. */\n"
+ f"* {{ accent: {hexval}ff; }}\n"
+ )
+
+ OUTPUT.parent.mkdir(parents=True, exist_ok=True)
+ # Write-then-rename: a rofi launch during a wallpaper change must never
+ # read a half-written file.
+ fd, tmp = tempfile.mkstemp(dir=str(OUTPUT.parent), suffix=".tmp")
+ try:
+ with os.fdopen(fd, "w") as handle:
+ handle.write(content)
+ os.replace(tmp, OUTPUT)
+ except BaseException:
+ if os.path.exists(tmp):
+ os.unlink(tmp)
+ raise
+
+
+def main(image):
+ try:
+ name = snap(signature_color(image))
+ except Exception as exc:
+ # A broken image must still leave a working theme.
+ print(f"udt-accent: {exc}, falling back to {FALLBACK}", file=sys.stderr)
+ name = FALLBACK
+
+ write_accent(name)
+ print(f"{name} {ACCENTS[name]}")
+
+
def selftest():
# Every accent must snap to itself, or the metric is not self-consistent.
for name, hexval in ACCENTS.items():
@@ -99,3 +152,8 @@ def selftest():
if __name__ == "__main__":
if len(sys.argv) == 2 and sys.argv[1] == "--selftest":
selftest()
+ elif len(sys.argv) == 2:
+ main(sys.argv[1])
+ else:
+ print(__doc__, file=sys.stderr)
+ sys.exit(2)