diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-21 18:50:22 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-21 18:50:22 +0200 |
| commit | ecf59d1c7c74fe817ee4f9e392e319ad9a8b541a (patch) | |
| tree | 76e008de23c6adcb2a0a244b195a2eb06dbe8516 /bin/udt-tv | |
| parent | 3fae77d2eaad99bb39c8296e201f9845837dbe02 (diff) | |
| download | unified-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>
Diffstat (limited to 'bin/udt-tv')
| -rwxr-xr-x | bin/udt-tv | 83 |
1 files changed, 83 insertions, 0 deletions
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() |
