aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-21 18:50:22 +0200
committerDanilo M. <danix@danix.xyz>2026-10-21 18:50:22 +0200
commitecf59d1c7c74fe817ee4f9e392e319ad9a8b541a (patch)
tree76e008de23c6adcb2a0a244b195a2eb06dbe8516
parent3fae77d2eaad99bb39c8296e201f9845837dbe02 (diff)
downloadunified-desktop-theme-ecf59d1c7c74fe817ee4f9e392e319ad9a8b541a.tar.gz
unified-desktop-theme-ecf59d1c7c74fe817ee4f9e392e319ad9a8b541a.zip
feat(accent): send the accent to the TV launcher
udt-tv sends the window border's three hues to the tv-debloat launcher on the TV, which draws its focus highlight's edge as their gradient and uses the accent for its pill edges and title glow. One HMAC-SHA256-signed UDP datagram to port 47800; the address comes from ~/.config/udt/tv.json and the secret is the one tv-debloat deploys. Without either it does nothing. udt-accent starts it with the other helpers, install.sh links it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--AGENTS.md9
-rwxr-xr-xbin/udt-accent5
-rwxr-xr-xbin/udt-tv83
-rwxr-xr-xinstall.sh2
4 files changed, 96 insertions, 3 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 5a31dee..a069dd7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -256,6 +256,15 @@ run reads the state first and skips an off bulb; and a request setting colour
and brightness together, or a colour sent while a brightness fade runs, is
partly dropped, so colour goes first and brightness after, separately.
+`udt-tv` sends the same three hues to the TV launcher of the sibling
+tv-debloat repo, which draws its focus highlight's edge as their gradient and
+uses the accent for its pill edges and title glow. One HMAC-SHA256-signed UDP
+datagram to the TV's port 47800 (the protocol is in tv-debloat's README), the
+TV's address in `~/.config/udt/tv.json` and the secret shared with
+tv-debloat's deploy in `~/.config/tv-debloat/accent.secret`; without either it
+does nothing. There is no reply, so a TV in standby simply misses the change
+and keeps the last colour it took.
+
**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
diff --git a/bin/udt-accent b/bin/udt-accent
index 70557d8..1388db1 100755
--- a/bin/udt-accent
+++ b/bin/udt-accent
@@ -626,8 +626,9 @@ def main(image):
# it only rebuilds and notifies, since the copy into /boot needs root.
# The keyboard takes seconds per colour change, and is often not there.
# The LED strip waits up to 20 s for its address broadcast, the bulbs
- # up to 5 s per bulb for the gateway. All three read files written above.
- for helper in ("udt-homepage", "udt-grub", "udt-keyboard", "udt-ledstrip", "udt-bulb"):
+ # up to 5 s per bulb for the gateway. All of them read files written above;
+ # udt-tv is a single UDP datagram to the TV launcher.
+ for helper in ("udt-homepage", "udt-grub", "udt-keyboard", "udt-ledstrip", "udt-bulb", "udt-tv"):
subprocess.Popen([Path(__file__).resolve().parent / helper],
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, start_new_session=True)
diff --git a/bin/udt-tv b/bin/udt-tv
new file mode 100755
index 0000000..4406f7a
--- /dev/null
+++ b/bin/udt-tv
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
+# udt-tv: send the wallpaper accent to the tv-debloat launcher on the TV.
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+# Licensed under the GNU General Public License v2 only.
+"""Tint the TV launcher's focus highlight, pills and title glow in the accent.
+
+Usage: udt-tv send the window border's three hues
+ udt-tv --selftest
+
+The colours are the three udt-accent wrote for the window border, the accent
+between its two hue neighbours: the launcher draws its focus highlight's edge
+as a gradient of all three and uses the middle one everywhere else. One UDP
+datagram to the launcher's port 47800, "<#a,#b,#c> <unix seconds> <hex
+HMAC-SHA256 of the first two fields>", keyed with the secret the launcher was
+deployed with. The launcher drops anything unsigned, more than a minute off
+its clock, or not newer than the last one it took, and sends no reply, so
+this cannot tell whether the TV took it (a TV in standby or a launcher that
+Android killed misses it; the launcher keeps the last colour it got).
+
+The TV's address comes from ~/.config/udt/tv.json ({"host": "<ip>"}), the
+secret from ~/.config/tv-debloat/accent.secret, the file tv-debloat's
+deploy-launcher.sh pushes to the TV. Without either, this does nothing.
+"""
+import hashlib
+import hmac
+import json
+import re
+import socket
+import sys
+import time
+from pathlib import Path
+
+CONFIG = Path.home() / ".config/udt/tv.json"
+SECRET = Path.home() / ".config/tv-debloat/accent.secret"
+BORDER = Path.home() / ".cache/udt/udt-border.lua"
+PORT = 47800
+
+
+def colours(border):
+ """The border's rgb(rrggbb) hues as the launcher's "#a,#b,#c" field."""
+ return ",".join("#" + h for h in re.findall(r"rgb\(([0-9a-fA-F]{6})\)", border))
+
+
+def message(field, secret, now):
+ body = f"{field} {now}"
+ return f"{body} {hmac.new(secret, body.encode(), hashlib.sha256).hexdigest()}"
+
+
+def selftest():
+ border = 'return { "rgb(fcd317)", "rgb(36d190)", "rgb(37daea)" }'
+ assert colours(border) == "#fcd317,#36d190,#37daea"
+ msg = message("#fcd317,#36d190,#37daea", b"k", 1700000000)
+ field, ts, mac = msg.split(" ")
+ assert (field, ts) == ("#fcd317,#36d190,#37daea", "1700000000")
+ assert mac == hmac.new(b"k", f"{field} {ts}".encode(), hashlib.sha256).hexdigest()
+ print("ok")
+
+
+def main():
+ args = sys.argv[1:]
+ if args == ["--selftest"]:
+ return selftest()
+ if args:
+ sys.exit(__doc__.split("\n\n")[1])
+ try:
+ host = json.loads(CONFIG.read_text())["host"]
+ secret = SECRET.read_text().strip().encode()
+ field = colours(BORDER.read_text())
+ except (OSError, ValueError, KeyError):
+ return
+ # The launcher takes one colour or three; anything else it would drop.
+ if field.count(",") not in (0, 2) or not field or not secret:
+ return
+ with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
+ try:
+ s.sendto(message(field, secret, int(time.time())).encode(), (host, PORT))
+ except OSError as e:
+ print(f"udt-tv: {host}: {e}", file=sys.stderr)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/install.sh b/install.sh
index 13f634e..1c202d7 100755
--- a/install.sh
+++ b/install.sh
@@ -126,7 +126,7 @@ ln -sfn "$HOME/.cache/udt/colors.json" "$HOME/.cache/wal/colors.json"
# The rofi menus' scripts live in the sibling rofi-scripts repo, which links
# its own.
-for f in udt-accent udt-grub udt-keyboard udt-ledstrip udt-bulb udt-steps udt-palette udt-machine-name; do
+for f in udt-accent udt-grub udt-keyboard udt-ledstrip udt-bulb udt-tv udt-steps udt-palette udt-machine-name; do
ln -sfn "$repo/bin/$f" "$HOME/bin/$f"
done