A Decky Loader plugin for SteamOS that pairs with network TVs, switches their HDMI input from the Quick Access menu, and automatically wakes a TV and switches it to the right input when SteamOS connects to that screen — like HDMI-CEC, but over the network.
The core is brand-agnostic. Drivers ship for LG (webOS) and TCL (Google TV) (including the 75QM6K Pro); other brands plug in without touching the rest of the code.
- Pair one or more TVs (pick the brand, enter the IP).
- Manually switch any paired TV's input.
- Per-screen auto-switch rules: each connected display maps to a TV + input. When that screen appears — at boot, on resume, or when the TV reconnects — the TV is woken over the network (Wake-on-LAN) and switched to the chosen input automatically.
main.py composition root: builds the driver registry, wires the plugin
backend/ Python source (our code)
tv_core/ brand-agnostic core (knows no driver)
driver.py TvDriver contract + registry (build/list/select)
edid.py display detection
store.py paired TVs + rules; TV = {host,name,brand,creds}
tests/ core unit tests (excluded from the release)
tv_driver_lg/ LG driver package (depends on tv_core)
__init__.py LgDriver
webos.py LG SSAP WebSocket client
tv_driver_tcl/ TCL Google TV driver (Android TV Remote v2)
__init__.py TclDriver
atv.py pairing + app-link input switching
discover.py mDNS browse for `_androidtvremote2._tcp`
src/ React UI (generic; brand is just a dropdown)
scripts/ build, deploy, and vendor tooling
py_modules/ vendored pure-Python deps (gitignored; built in CI)
Single repository, single release. Dependencies point one way:
tv_driver_* → tv_core, and main.py → drivers. The core never imports a
driver; it only calls pair, list_inputs, and set_input on the TvDriver
contract. Displays are identified by EDID, so a rule follows the physical TV
rather than a port. A 5-second poll detects when a screen appears and applies
its rule.
- Create
backend/tv_driver_<brand>/with aTvDriversubclass implementingpair,list_inputs,set_input, plus a uniquenameandlabel. Brands that show an on-screen PIN raiseNeedsPinfrompairwhen called without one (the UI already handles the follow-up PIN entry). - Inject it in
main.py:build_registry([LgDriver(), TclDriver(), …]).
Nothing in tv_core changes for prompt-only brands; PIN brands only need the
existing pair_tv(..., pin) path.
Requires Decky Loader with Developer mode enabled — the plugin needs _root
to read display info.
-
In Decky, go to Settings → Developer mode and choose Install Plugin from URL.
-
Paste this URL — it always points to the latest release:
https://github.com/hunguy/DeckaTV/releases/latest/download/deckatv.zip(This is the release zip URL, not the repository URL.)
-
Restart Decky (or reboot) — plugins installed from a URL only appear after a restart. It shows up as "DeckaTV" in the plugin list.
Download the release .zip from the Releases
page, extract it into ~/homebrew/plugins/, and restart Decky.
- Turn the TV on and connect it to your network.
- Open the plugin, pick the brand, enter the IP, press Pair.
- LG: accept the prompt on the TV.
- TCL (Google TV): enter the 6-character code shown on the TV.
- Use Switch input to change inputs manually.
- Under Auto-switch rules, pick the TV and input for each screen and enable the toggle.
For TCL wake-from-standby (PC resume / docking while the TV sleeps), enable on the TV:
- Settings → System → Power & energy → Standby → Network standby (On)
- Quick Start (On), if your firmware shows it
Without Network Standby the control ports go fully offline and Wake-on-LAN often fails on Wi‑Fi.
pnpm install
pnpm run build # produces dist/index.jsPure-Python dependencies (websockets, protobuf) are vendored under
py_modules/ by scripts/vendor_python.sh.