A Decky Loader plugin that automatically wakes your TV and switches to the correct HDMI input when your Steam Deck, steamMachine or any device running steamos resumes from sleep or when you press the gamepad Guide button.
Works with any TV that supports Wake-on-LAN (WOL) and the LG webOS SSAP protocol for HDMI switching (LG TVs from 2018+).
- Wake-on-LAN - Send magic packets to wake your TV
- Auto HDMI switch - Automatically switch to the correct HDMI input after waking
- Gamepad Guide button wake - Press the Guide/Home button on your gamepad to wake the TV
- Sleep/resume wake - Automatically wake the TV when your device resumes from sleep
- Auto-disable built-in controller - Automatically disables the built-in gamepad (e.g. ROG Ally controller) when an external controller is connected, and re-enables it when disconnected — eliminates the duplicate controller problem. External controllers are emulated as Xbox Elite for back paddle support via InputPlumber.
- External gamepad fix - Automatically rebinds external USB gamepads after resume when InputPlumber is not present (defers to InputPlumber's suspend service when available to avoid duplicate notifications)
- Bluetooth controller wake - Enables the internal Bluetooth adapter's USB wakeup flag so Bluetooth gamepads (e.g. DualSense) can wake the device from sleep. Required on non-Steam Deck hardware (ROG Ally, Legion Go) where SteamOS doesn't enable this by default.
- USB wakeup chain repair - Re-enables BT adapter and USB3 hub wakeup flags after each resume so Bluetooth gamepads and Wake-on-LAN always work from the next sleep
- Pair & forget - One-time pairing with your LG TV, credentials persist across reboots
- Install Decky Loader on your device
- Copy the plugin to
~/homebrew/plugins/waketv/ - Restart Decky Loader:
sudo systemctl restart plugin_loader
- Open the Decky sidebar (... button) and find Wake TV
- Enter your TV's IP address
- Click Pair with TV - accept the pairing prompt on your TV screen
- Select the correct HDMI input
- Click Save
The MAC address is auto-discovered during pairing.
The following system-level configuration enables advanced features like gamepad wake-from-sleep, external gamepad fix, and Wake-on-LAN to the device itself. These commands require SSH access to your device.
SteamOS uses an A/B rootfs with an /etc overlay. Updates reset this overlay, but SteamOS 3.6+ provides an official whitelist mechanism: files listed in /etc/atomic-update.conf.d/*.conf are preserved across updates. The keeplist directory itself is preserved by the system keeplist at /usr/lib/rauc/atomic-update-keep.conf, so custom entries survive too.
The setup below registers all WakeTV config files with this mechanism. Zero runtime overhead — the OS preserves them natively during the update process. The only boot-time service is for volatile USB/ACPI wakeup flags (sysfs entries that the kernel resets on every reboot).
All config files are installed once and persist across SteamOS updates via the atomic update keeplist.
ssh -t deck@<DEVICE_IP> "sudo /bin/sh -c '
# Register all WakeTV files with the SteamOS atomic update keeplist
mkdir -p /etc/atomic-update.conf.d
cat > /etc/atomic-update.conf.d/waketv.conf << KEEPLIST
/etc/sudoers.d/zz-waketv-usb
/etc/sudoers.d/zz-waketv-suspend
/etc/udev/rules.d/99-waketv-usb-wake.rules
/etc/polkit-1/rules.d/zz-waketv-inputplumber.rules
/etc/inputplumber/devices.d/50-external-gamepad.yaml
KEEPLIST
# Sudoers: USB unbind/bind for external gamepad fix + wakeup sysfs writes
echo \"deck ALL=(ALL) NOPASSWD: /usr/bin/tee /sys/bus/usb/drivers/usb/unbind, /usr/bin/tee /sys/bus/usb/drivers/usb/bind, /usr/bin/tee /sys/bus/usb/devices/*/power/wakeup\" > /etc/sudoers.d/zz-waketv-usb
chmod 440 /etc/sudoers.d/zz-waketv-usb
# Sudoers: remote suspend (needed for Homebridge/Apple Home)
echo \"deck ALL=(ALL) NOPASSWD: /usr/bin/systemctl suspend\" > /etc/sudoers.d/zz-waketv-suspend
chmod 440 /etc/sudoers.d/zz-waketv-suspend
# Udev: enable wakeup on dock USB3 hub and BT adapter on hot-plug
# Note: USB2 hub (05e3:0610) is intentionally excluded — its 2.4GHz gamepad
# dongle causes spurious wakes. BT controllers are the preferred wake source.
cat > /etc/udev/rules.d/99-waketv-usb-wake.rules << UDEV
ACTION==\"add\", SUBSYSTEM==\"usb\", ATTR{idVendor}==\"05e3\", ATTR{idProduct}==\"0626\", RUN+=\"/bin/sh -c '\\''echo enabled > /sys%p/power/wakeup'\\''\"
ACTION==\"add\", SUBSYSTEM==\"usb\", ATTR{idVendor}==\"0489\", ATTR{idProduct}==\"e0f5\", RUN+=\"/bin/sh -c '\\''echo enabled > /sys%p/power/wakeup'\\''\"
UDEV
udevadm control --reload-rules
# Polkit: allow plugin to toggle InputPlumber D-Bus API
mkdir -p /etc/polkit-1/rules.d
cat > /etc/polkit-1/rules.d/zz-waketv-inputplumber.rules << POLKIT
polkit.addRule(function(action, subject) {
if (action.id === \"org.shadowblip.Input.CompositeDevice.SetTargetDevices\" &&
subject.user === \"deck\") {
return polkit.Result.YES;
}
});
POLKIT
chmod 644 /etc/polkit-1/rules.d/zz-waketv-inputplumber.rules
'"If you use an external gamepad (e.g. GameSir via 2.4GHz dongle), this InputPlumber device config makes it appear as an Xbox Elite controller. Adjust vendor_id, product_id, and name to match your controller (run lsusb and check /sys/class/input/event*/device/name).
ssh -t deck@<DEVICE_IP> "sudo /bin/sh -c '
mkdir -p /etc/inputplumber/devices.d
cat > /etc/inputplumber/devices.d/50-external-gamepad.yaml << EOF
version: 1
kind: CompositeDevice
name: External Controller
single_source: true
matches: []
source_devices:
- group: gamepad
evdev:
name: Generic X-Box pad
vendor_id: 3537
product_id: 1098
handler: event*
options:
auto_manage: true
target_devices:
- xbox-elite
EOF
systemctl restart inputplumber
'"Note: The plugin will automatically correct the target to xbox-elite if steamos-manager overrides it on startup.
The boot service enables wakeup on the internal Bluetooth adapter, dock USB3 hub (for Ethernet WOL), root hubs, and ACPI wakeup sources. These are volatile kernel flags that reset every reboot — the service is the only runtime component.
Note: Port
1-4is the internal MediaTek Bluetooth adapter — enabling its wakeup allows Bluetooth controllers (DualSense, DualShock 4, Switch Pro, etc.) to wake the device from s2idle sleep. The USB3 hub (05e3:0626at6-1) is needed for Wake-on-LAN via the dock Ethernet adapter. The USB2 hub (05e3:0610at5-1) is intentionally excluded — 2.4GHz gamepad dongles on this hub cause spurious wakes because the dongle firmware cannot distinguish a button press from a gamepad power-off event. The ACPI wakeup sources (XHC0-XHC4) and root hubs (usb5,usb6) are for the ROG Ally. Adjust for your hardware if needed — runlsusband check/proc/acpi/wakeup.
ssh -t deck@<DEVICE_IP> "sudo /bin/sh -c '
cat > /etc/systemd/system/usb-wake.service << SVC
[Unit]
Description=Enable USB and Bluetooth wake for gamepad and Ethernet WOL
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c \"\
for hub in usb5 usb6; do \
[ -f /sys/bus/usb/devices/\\\$hub/power/wakeup ] && echo enabled > /sys/bus/usb/devices/\\\$hub/power/wakeup; \
done; \
for port in 1-4 6-1; do \
[ -f /sys/bus/usb/devices/\\\$port/power/wakeup ] && echo enabled > /sys/bus/usb/devices/\\\$port/power/wakeup; \
done; \
for xhc in XHC0 XHC1 XHC2 XHC3 XHC4; do \
grep -q \\\"^\\\$xhc.*disabled\\\" /proc/acpi/wakeup 2>/dev/null && echo \\\$xhc > /proc/acpi/wakeup; \
done; \
true\"
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
SVC
systemctl daemon-reload
systemctl enable --now usb-wake.service
'"Enables proper input device cleanup/restoration on sleep/resume. When this service is enabled, the plugin automatically skips its own USB rebind (InputPlumber handles it), which avoids duplicate controller notifications on wake.
ssh -t deck@<DEVICE_IP> "sudo systemctl enable inputplumber-suspend.service"If your device is connected to both WiFi and a wired dock/hub, NetworkManager may prefer WiFi. These commands set the wired connection to higher priority so traffic always routes through LAN. Changes persist across reboots and SteamOS updates.
ssh -t deck@<DEVICE_IP> "\
sudo nmcli connection modify 'LAN' ipv4.route-metric 100 ipv6.route-metric 100 && \
sudo nmcli connection modify 'LAN' connection.autoconnect-priority 100"Then lower the priority on each WiFi network you use:
ssh -t deck@<DEVICE_IP> "\
sudo nmcli connection modify '<WIFI_NAME>' ipv4.route-metric 700 ipv6.route-metric 700"Note: Replace
LANwith your wired connection name and<WIFI_NAME>with each WiFi network name. Runnmcli connection showon the device to see all connection names. Lower metric = higher priority.
- System wakes up (power button, gamepad disconnect, or WOL packet)
- Plugin detects the D-Bus
PrepareForSleep(false)signal - If
inputplumber-suspend.serviceis not enabled, external USB gamepads are rebound (unbind + bind) to fix the xpad driver resume bug. Otherwise InputPlumber handles device restoration. - BT adapter and USB3 hub wakeup flags are re-enabled (some drivers reset them on suspend)
- Plugin waits for the network to come up (polls every 1s)
- WOL magic packet is sent to the TV
- Plugin connects to the TV via WebSocket and switches to the configured HDMI input
When enabled in the plugin settings, the plugin continuously monitors connected gamepads:
- Polls
/dev/input/event*every 2 seconds for gamepad-capable devices - Classifies each device as built-in or external by resolving its sysfs path to a USB bus port
- When an external gamepad is detected, the built-in controller's virtual gamepad is removed via InputPlumber's D-Bus API (
SetTargetDevicesempty on the built-in CompositeDevice) - If an InputPlumber device config exists for the external controller (section 2 of System Setup), it is emulated as Xbox Elite. The plugin corrects the target if steamos-manager overrides it.
- When all external gamepads are disconnected, the built-in controller is restored
- On plugin startup/shutdown, the built-in controller is always re-enabled as a safety net
Requires the polkit rule from section 1. Works with USB dongles, wired controllers, and Bluetooth gamepads.
- Plugin watches
/dev/input/event*devices forBTN_MODE(code 316) events - On press, sends WOL + HDMI switch to the TV
- 5-second cooldown prevents duplicate triggers
If you have a Homebridge setup, you can control the entire gaming setup from Apple Home or Siri.
In the Homebridge UI, install Homebridge WoL (homebridge-wol).
| Field | Value |
|---|---|
| Name | Gaming Setup (or whatever you like) |
| MAC address | Your device's Ethernet MAC (not WiFi) |
| Broadcast address | 192.168.1.255 (adjust for your subnet) |
| Ping command | ping -c 1 -W 2 <DEVICE_IP> |
| Wake grace time | 30 |
| Shutdown command | see below |
Find your Ethernet MAC:
ssh deck@<DEVICE_IP> "cat /sys/class/net/enp*/address"To suspend with the proper Steam sleep animation, the shutdown command injects a fake power button event:
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 deck@<DEVICE_IP> "python3 -c \"import struct,time;fd=open('/dev/input/event0','wb');t=time.time();s=int(t);u=int((t-s)*1e6);p=struct.pack;fd.write(p('llHHi',s,u,1,116,1)+p('llHHi',s,u,0,0,0));fd.flush();time.sleep(0.1);t=time.time();s=int(t);u=int((t-s)*1e6);fd.write(p('llHHi',s,u,1,116,0)+p('llHHi',s,u,0,0,0));fd.close()\""
Note: Homebridge must have SSH key-based auth to the device. Run
ssh-keygen && ssh-copy-id deck@<DEVICE_IP>from the Homebridge terminal.
- "Hey Siri, turn on Gaming Setup" - Sends WOL to device, device wakes, plugin wakes TV + switches HDMI
- "Hey Siri, turn off Gaming Setup" - SSHs to device, triggers Steam sleep animation
- Works with HomeKit scenes and automations
| Method | How | Animation |
|---|---|---|
| Power button | Press the device's power button | Yes |
| Bluetooth controller | Press PS/Guide button on paired BT gamepad (e.g. DualSense, GameSir) — requires 1-4 wakeup enabled |
Yes |
| Wake-on-LAN | Send WOL packet to device's Ethernet MAC from any device on the network | Yes |
| Apple Home / Siri | "Hey Siri, turn on Gaming Setup" via Homebridge WOL plugin | Yes |
Note: USB 2.4GHz gamepad dongles (on dock hub
5-1) are intentionally excluded from wake sources. The dongle firmware sends identical USB wakeup signals for button presses and gamepad power-off events, causing spurious wakes. Use Bluetooth controllers for gamepad wake instead.
All methods trigger the plugin's auto-wake flow: WOL to TV + HDMI switch.
- Node.js 18+
- pnpm
pnpm install
pnpm run build# Configure .env.deck with your device's IP
./deploy.shwake-tv/
├── main.py # Backend: lifecycle, watchers, settings, TV control
├── py_modules/
│ ├── tv_client.py # LG webOS SSAP protocol (WebSocket, WOL, MAC discovery)
│ ├── input_watcher.py # Gamepad Guide button detection via /dev/input
│ ├── sleep_watcher.py # D-Bus PrepareForSleep signal monitoring
│ ├── usb_rebind.py # USB rebind + wakeup chain repair after resume
│ └── controller_manager.py # Auto-disable built-in controller on external connect
├── src/
│ ├── index.tsx # Frontend plugin entry point
│ └── components/
│ └── WakeTVPanel.tsx # Settings UI (IP, HDMI, toggles, pairing)
├── plugin.json # Plugin metadata
├── deploy.sh # Build & deploy script
└── .env.deck # Device SSH config (not committed)
- Ensure WOL is enabled in your TV's settings (Network > Wake on LAN)
- Verify the MAC address is correct in the plugin settings
- Check that the TV and device are on the same network/subnet
- Make sure you've paired with the TV (accept the prompt on the TV screen)
- Check the HDMI input number matches the port your device is connected to
- Gamepad wake uses Bluetooth only (not USB 2.4GHz dongles — see note below)
- Verify the BT adapter and USB3 hub wakeup flags:
# BT adapter (1-4) must be enabled for controller wake # USB2 dock hub (5-1) should be DISABLED (causes spurious wakes) for d in 1-4 5-1 6-1 usb5 usb6; do echo "$d: $(cat /sys/bus/usb/devices/$d/power/wakeup 2>/dev/null || echo N/A)" done # Check ACPI wakeup sources (XHC controllers should show *enabled) grep XHC /proc/acpi/wakeup
- Ensure
usb-wake.serviceis running:systemctl status usb-wake.service - Ensure the udev rule is installed:
cat /etc/udev/rules.d/99-waketv-usb-wake.rules - The
inputplumber-suspend.serviceshould be enabled - Verify the controller has
WakeAllowed: yesinbluetoothctl info <MAC>and that the BT adapter wakeup is enabled (cat /sys/bus/usb/devices/1-4/power/wakeupshould showenabled). On non-Steam Deck hardware, SteamOS does not enable BT adapter wakeup automatically — theusb-wake.servicehandles this.
- USB 2.4GHz gamepad dongles cause spurious wakes because the dongle firmware cannot distinguish between a button press and a gamepad power-off event. Ensure dock USB2 hub wakeup is disabled:
cat /sys/bus/usb/devices/5-1/power/wakeup # should show "disabled" - For Bluetooth spurious wakes, ensure only game controllers have
WakeAllowed: yes(not headphones or other peripherals):bluetoothctl info <MAC> | grep WakeAllowed
- If
inputplumber-suspend.serviceis enabled, InputPlumber handles device restoration. Check its logs:journalctl -u inputplumber -n 50 - If InputPlumber is not installed, verify the sudoers rule is in place:
ls -la /etc/sudoers.d/zz-waketv-usb - Check plugin logs for rebind results (look for "Post-resume:" and "inputplumber-suspend")
Config files should persist natively via the atomic update keeplist. Verify:
cat /etc/atomic-update.conf.d/waketv.conf # should list 5 paths
ls /etc/sudoers.d/zz-waketv-usb /etc/udev/rules.d/99-waketv-usb-wake.rules /etc/polkit-1/rules.d/zz-waketv-inputplumber.rules 2>&1If the keeplist file itself was removed (edge case in major OS restructuring), re-run section 1 of System Setup.
# View latest log file
ls -t ~/homebrew/logs/waketv/*.log | head -1 | xargs tail -100
# Follow logs in real-time (via deploy script)
./deploy.sh --logs