A Roku remote for Linux. A small native GNOME app (Rust + GTK4 + libadwaita) that talks the plain-HTTP Roku ECP API on your LAN. Nothing leaves the network, no account, no cloud.
Not affiliated with or endorsed by Roku, Inc. "Roku" is a trademark of Roku, Inc. Built with Claude as co-pilot (like most things these days); I reviewed it, made sure the test suite is solid, and did manual testing against my own Roku TV.
Features:
- The full remote: D-pad, Back, Home, Replay, Options, Search, playback and volume. Press and hold repeats like the physical remote.
- Type into TV search boxes from your keyboard.
- Channel grid built from what your TV reports, icons cached per device.
- Keyboard control while the window is focused (Ctrl+? lists the keys).
- Finds the TV by itself (SSDP), falling back to a unicast sweep of your /24 when a strict firewall zone drops the multicast replies. Several TVs: pick one under "TVs" in the header menu.
- Layout modelled on Roam by Scott Driggers (macOS/iOS; none of its code or assets are used).
Download remote.flatpak from the latest release, then:
flatpak install --user remote.flatpak # pulls the GNOME 50 runtime from Flathub if neededgit clone https://github.com/NelsonScott/remote && cd remote
scripts/install.sh # builds the Flatpak, installs it for your user
flatpak run xyz.scottnelson.Remote # or launch "Remote" from the app grid
scripts/install.sh --uninstallinstall.sh needs only flatpak. On first run it installs, per user from
Flathub, the GNOME 50 SDK, the Rust SDK extension and org.flatpak.Builder
(unless a host flatpak-builder exists). Flatpak permissions: network (SSDP
and ECP on your LAN), the Wayland and fallback X11 display sockets with the
shared memory they need, and GPU access for GTK4 rendering. No filesystem
access; config and icon cache live in ~/.var/app/xyz.scottnelson.Remote/.
sudo dnf copr enable scottnelson/remote
sudo dnf install remoteThe usual password prompt is dnf's. The RPM links the system GTK4 and
libadwaita, which every GNOME desktop already has; the -devel packages are
only ever needed to build from source, never to run.
Building from source does not need gtk4-devel, libadwaita-devel or a
host Rust toolchain: scripts/dev.sh runs cargo inside the same GNOME SDK
the Flatpak uses.
scripts/dev.sh build
scripts/dev.sh test # unit + fake-ECP-server tests
scripts/dev.sh clippy --all-targets -- -D warnings
scripts/dev.sh fmt --check
ROKU_MOCK=1 scripts/dev.sh run # in-process fake Roku
ROKU_HOST=192.168.1.221 scripts/dev.sh run # pin a device, skip discovery
RUST_LOG=remote=debug scripts/dev.sh run # log every command sentPlain host cargo works too if you prefer it, after
sudo dnf install gtk4-devel libadwaita-devel (Debian/Ubuntu:
libgtk-4-dev libadwaita-1-dev).
Headless UI check (Xvfb + xdotool + ImageMagick):
Xvfb :97 -screen 0 1000x900x24 & export DISPLAY=:97; unset WAYLAND_DISPLAY
GDK_BACKEND=x11 GSK_RENDERER=cairo ROKU_MOCK=1 ROKU_MOCK_LOG=target/reqs.log scripts/dev.sh run &
sleep 6; import -window root target/remote.png; xdotool key h; cat target/reqs.logADW_DEBUG_COLOR_SCHEME=prefer-dark forces dark mode. The SDK sandbox has
its own /tmp, so keep logs under target/.
After changing dependencies run scripts/update-cargo-sources.sh (refreshes
the vendored crate list the offline Flatpak build uses) and commit
packaging/flatpak/cargo-sources.json with Cargo.lock.
| Path | What |
|---|---|
packaging/flatpak/xyz.scottnelson.Remote.json |
Flatpak manifest (GNOME 50 runtime, rust-stable extension, offline cargo build) |
packaging/flatpak/cargo-sources.json |
generated by scripts/update-cargo-sources.sh |
packaging/rpm/remote.spec |
RPM spec, vendored crates, cargo-rpm-macros |
packaging/rpm/make-srpm.sh |
source + vendor tarballs from HEAD, then the SRPM |
data/ |
desktop file, hicolor PNG app icons + symbolic SVG, AppStream metainfo |
Publish a new RPM: bump Version in Cargo.toml and the spec, commit, then
packaging/rpm/make-srpm.sh && copr-cli build remote packaging/rpm/out/*.src.rpm.
| Variable | Effect |
|---|---|
ROKU_HOST |
ip[:port] of the Roku. Skips discovery and never writes the config. |
ROKU_MOCK |
1 starts an in-process fake Roku and points the app at it. |
ROKU_MOCK_LOG |
File that receives every request the fake Roku saw (one per line). |
ROKU_CONFIG_DIR |
Overrides ~/.config/remote. |
ROKU_CACHE_DIR |
Overrides ~/.cache/remote. |
RUST_LOG |
env_logger filter, default remote=info. |
src/ecp.rs ECP client (ureq), key enum, XML parsing
src/discovery.rs SSDP M-SEARCH (multicast, then unicast /24 sweep), dedupe by USN
src/config.rs ~/.config/remote/config.toml
src/icons.rs icon cache
src/fake_roku.rs fake ECP server (tests + ROKU_MOCK)
src/ui/mod.rs window, header title, command queue, keyboard, app grid
src/ui/keys.rs keyboard map + shortcuts dialog rows
src/ui/glyph.rs cairo line glyphs for the remote tiles
src/bin/fake_roku.rs `cargo run --bin fake-roku [port]` standalone fake
tests/fake_server.rs client vs fake server, end to end
data/ .desktop, icons (hicolor PNG set + symbolic), metainfo
packaging/ flatpak/ and rpm/
scripts/ install.sh, dev.sh, update-cargo-sources.sh
docs/PHASE2.md notes for the next milestone
.github/ CI: fmt, clippy, tests on Fedora; Flatpak bundle; release on tags
MIT, see LICENSE.