remoku
A small native desktop remote for Roku TVs and players, built directly on Roku's public External Control Protocol (ECP).
The UI is NiceGUI running in a native window
(pywebview): remoku opens a window, full stop — no browser tab, no server
to manage, no browser mode.
Every Roku runs a tiny REST server on TCP port 8060. This app talks to it directly — no accounts, no cloud, no companion service.
Install
uv tool install remoku
or with pipx:
pipx install remoku
That gives you the remoku command (plus a rokuremote alias) in an
isolated environment. To also get a desktop entry and icons, run the
installer:
curl -fsSL https://raw.githubusercontent.com/slug-enjoyer/remoku/main/install.sh | sh
It installs with uv (or pipx) under ~/.local — no root, nothing
system-wide — and drops a launcher into your app menu. If PyPI is
unreachable, the installer falls back to the GitHub repository.
# install somewhere else
curl -fsSL https://raw.githubusercontent.com/slug-enjoyer/remoku/main/install.sh | sh -s -- --prefix /opt/remoku
# a specific branch or tag (also used for the desktop entry and icons)
curl -fsSL https://raw.githubusercontent.com/slug-enjoyer/remoku/main/install.sh | sh -s -- --ref v2.0.0
# just the command, no desktop entry
curl -fsSL https://raw.githubusercontent.com/slug-enjoyer/remoku/main/install.sh | sh -s -- --no-desktop
# uninstall (settings and icon cache are kept)
curl -fsSL https://raw.githubusercontent.com/slug-enjoyer/remoku/main/install.sh | sh -s -- --uninstall
Features
- Purple Roku-style remote in a native window: power, back, home, info, instant replay, D-pad with OK, playback, volume
- App shortcuts grid, loaded from the device (
/query/apps) with real icons; click a tile to launch - TV inputs in their own row, including custom names (e.g. "Nintendo Switch", "Soundbar") configured on the TV
- A "Type to the Roku" box: each keystroke streams to the device, and deleting a character sends Backspace, so you can edit as you type
- Device discovery: SSDP, plus an ARP-assisted scan of the local /24 that stays gentle on consumer routers (broad high-concurrency scans can take the whole Wi-Fi network down for ~10 seconds)
- Wake button: Roku TVs in standby only answer
device-infoand reply 403 to everything else; the button sends Wake-on-LAN and waits for the TV to come up - Typing: letters, digits and punctuation go straight to the Roku whenever its on-screen keyboard is up, with no mode to toggle
- Keyboard control: the arrow keys behave like the D-pad, so the whole remote is usable without a mouse
Requirements
- Python 3.10+
- A graphical session (X11 or Wayland)
That's all: the Qt backend ships as wheels, so there is nothing to install for GTK or WebKit. On Linux the window uses Qt (PyQt6) and falls back to GTK/WebKit2 when PyGObject is available.
Usage
remoku # start the native window
remoku --ip 192.0.2.50
remoku --list # print Roku devices found on the network
remoku --apps # print apps/inputs of the last used device
remoku --key Home # send a single keypress
rokuremote --help # rokuremote is an alias for remoku
The last used device is remembered in ~/.config/remoku/devices.json;
app icons are cached in ~/.cache/remoku/icons/.
Keyboard shortcuts
| Key | Action |
|---|---|
| Arrows | Up, Down, Left, Right |
| Enter | Select (OK), or press the focused on-screen button |
| Esc | Home |
| Backspace | Backspace (deletes while typing) |
| Letters / digits / punctuation / space | Typed to the Roku |
| Alt + W / A / S / D | Up / Left / Down / Right |
| Alt + B | Back |
| Alt + H | Home |
| Alt + I | Info |
| Alt + O | Select (OK) |
| Alt + R, F or P | Rewind, Fast forward, Play/pause |
Alt + , . or / |
Rewind, Fast forward, Play/pause |
Alt + [ or - |
Volume down |
Alt + ], + or = |
Volume up |
Alt + \ or M |
Mute |
| Ctrl + Up / Down | Volume up / down |
| Tab / Shift+Tab | Move focus through the window |
Printable keys are always forwarded to the Roku as Lit_ characters.
Roku ignores those unless a text field is focused, which means typing
simply works whenever the TV's on-screen keyboard is up. Roku's API has no
way to ask whether a keyboard is open (verified against
/query/active-app, /query/device-info and friends), which is why the
remote keys that used to live on letters moved to Alt combinations. When a
button in the window has focus, Enter presses that button instead of
reaching the Roku.
Development
git clone https://github.com/slug-enjoyer/remoku
cd remoku
just install # uv sync --all-extras
just run # launch the native window from source
just lint # ruff format + check, pyrefly
just test # pytest with coverage (gate: 80%)
just security # bandit
just build # uv build
just version bump patch # commitizen: bump, changelog, tag
Every push and pull request runs lint, the test matrix (Python 3.10–3.14),
bandit and a build check; tagging v* runs the tests again and publishes
to PyPI with uv (trusted publishing).
The test suite is hermetic by construction: an autouse guard raises on any
non-loopback network access, and XDG paths are redirected into a temp
sandbox, so tests never touch a real device, the network or your real
config — tests/test_hermeticity.py proves it.
Secret scanning
A pre-commit hook blocks commits that contain secrets and warns (without blocking) when staged lines look like local/private values such as LAN addresses, MAC addresses or home directory paths.
just hooks # enable the hook for this clone (per-clone git config)
just audit # scan the whole repo, history included, any time
make hooks # same thing, if you prefer make
make audit
It runs gitleaks when installed
(sudo pacman -S gitleaks) and falls back to a small built-in check
otherwise. A deliberate example can be marked on its line with the comment
sensitive-example; git commit --no-verify bypasses the hook entirely.
Notes and troubleshooting
- Since Roku OS 14.1, remote commands require Settings → System → Advanced system settings → Control by mobile apps → Enabled on the device. If a command is refused, the app says so in the status bar.
- A Roku TV in standby (
power-mode: Ready) answersdevice-infobut refuses every other command with HTTP 403, which looks like the setting above being off even when it is on. In that case the app shows a Wake TV button, which sends a Wake-on-LAN magic packet and waits for the TV to come up (this is what Roku's own mobile app does). If waking does not work, enable Settings → System → Power → Fast TV start on the TV. - SSDP discovery is unreliable on many Wi-Fi networks because access points drop multicast between clients. When that happens the app falls back to scanning: it pokes the kernel into resolving every address in the local /24 via ARP (a single UDP datagram each), then TCP-probes only the hosts that actually exist. If the ARP table cannot be read it falls back to a low-concurrency TCP scan, and it never scans wider than a /24.
- Connecting to a device is retried up to three times if the network is briefly busy, and the startup scan runs only after the first connection attempt finishes.
- Power/volume buttons only appear for devices that report support for them (Roku TVs, or players using TV controls over HDMI-CEC).
License
GPL-3.0. See LICENSE.
Metadata
Release files for remoku 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| remoku-2.0.0.tar.gz | 40.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| remoku-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 81.4 kB
Release files / remoku-2.0.0.tar.gz
| Download URL | remoku-2.0.0.tar.gz |
|---|---|
| Size | 40.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
157229fdd98bc5e9d445ad679666c5ed941d28dcf528f07a059414f5cae69730
|
|
BLAKE2b-256 checksum How to use checksums |
0bc0bb3411ca127c429ef705f53b9720109869f20690cdda3f6d5d35824fea78
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / remoku-2.0.0-py3-none-any.whl
| Download URL | remoku-2.0.0-py3-none-any.whl |
|---|---|
| Size | 40.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5fa3879192a29d931e470ac6ec8f79b612917e501ee6b2b30c3ad4a2343ee0c6
|
|
BLAKE2b-256 checksum How to use checksums |
36d2972785bedc4c6a9e53407ad9c5f17c0e3eb4c24e4b143305bb0453aa10f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.18 {"installer":{"name":"uv","version":"0.12.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|