A Rust remote desktop for Ubuntu / GNOME (Wayland). Screen capture via XDG portals, H.264 encoding, QUIC transport, input injection. Linux server, Linux + Windows client.
| Feature | State |
|---|---|
| GNOME portal screen capture (ScreenCast + RemoteDesktop) | ✅ |
| H.264 encoding — software (openh264) | ✅ |
| H.264 encoding — hardware VA-API (h264_vaapi, AMD/Intel) | ✅ --encoder hardware |
| QUIC transport (quinn) + argon2 auth | ✅ |
| Input injection — pointer + keyboard + scroll | ✅ |
| Desktop audio (PipeWire → cpal) | ✅ |
| Multi-client (first gets input control, rest view-only) | ✅ |
| Restore token — skips portal dialog on subsequent starts | ✅ |
| Client letterboxing + aspect-ratio-correct scaling | ✅ |
TLS cert pinning (--cert) |
✅ optional |
| Windows client binary (cross-compiled in CI) | ✅ |
| Client launcher GUI (Linux + Windows) | ✅ |
| Server control GUI (Linux user service) | ✅ |
| DMA-BUF zero-copy into VA-API | ❌ AMD radeonsi doesn't support RGB32 DRM_PRIME_2 import |
| Clipboard / file transfer | ⏳ not planned |
| GDM / login screen | ❌ out of scope (portal requires logged-in user) |
remote-rs/
├── Cargo.toml # workspace
├── .woodpecker.yml # CI: fmt/check/test/clippy + Linux + Windows build + Forgejo release on tag
└── crates/
├── remote-protocol/ # bincode wire types — handshake, control, input, frame/audio headers
├── remote-server/ # portal capture, PipeWire audio, VA-API encoding, QUIC server
├── remote-client/ # winit window, softbuffer renderer, openh264 decoder, cpal audio, QUIC client
├── remote-client-gui/ # native launcher for non-technical client setup
└── remote-server-control/ # Linux GUI for installing/configuring the user service
sudo apt-get install -y \
libpipewire-0.3-dev libei-dev libeis-dev liboeffis-dev libclang-dev pkg-config \
libasound2-dev \
libavcodec-dev libavutil-dev libva-dev # hardware-encoding only
# Software encoder (no GPU deps)
cargo build --release -p remote-server
cargo build --release -p remote-client
cargo build --release -p remote-client-gui
cargo build --release -p remote-server-control
# Hardware VA-API encoder
cargo build --release -p remote-server --features hardware-encoding# Server — software encoder
./target/release/remote-server --bind 0.0.0.0:7878 --password hunter2
# Server — hardware VA-API encoder (AMD/Intel GPU)
./target/release/remote-server --bind 0.0.0.0:7878 --password hunter2 --encoder hardware
# Server control GUI — Linux user service
./target/release/remote-server-control
# Client — Linux
./target/release/remote-client --server 192.168.1.50:7878 --password hunter2 --width 1920 --height 1080
# Client launcher GUI — Linux / Windows
./target/release/remote-client-gui
# Client — Windows (pre-built .exe from Forgejo release)
remote-client-vX.Y.Z-windows-x86_64.exe --server 192.168.1.50:7878 --password hunter2 --width 1920 --height 1080
# Client — auto size to the primary monitor
remote-client-vX.Y.Z-windows-x86_64.exe --server 192.168.1.50:7878 --password hunter2 --auto-resolutionThe first server start pops the GNOME portal dialog — pick the monitor to share. The restore token is saved to ~/.config/remote-rs/restore-token; subsequent starts skip the dialog. Delete the file to force it to reappear.
remote-server-control is a Linux GUI for the user service. It can copy a
released remote-server binary from the same folder into
~/.local/bin/remote-server, write runtime settings to ~/.config/remote-rs/env,
write the shared password to ~/.config/remote-rs/password with owner-only
permissions, install remote-server.service into ~/.config/systemd/user/, and
run systemctl --user actions for enable, disable, start, stop, and restart.
Package remote-server-control, remote-server, and remote-server.service
together so the control GUI can install the server binary and service unit.
All CLI flags have env var equivalents: REMOTE_PASSWORD, REMOTE_SERVER, REMOTE_CERT, REMOTE_ENCODER, REMOTE_BIND, REMOTE_MONITOR, REMOTE_BITRATE_MBPS, REMOTE_MAX_FPS.
| Flag | Default | Notes |
|---|---|---|
--bind |
0.0.0.0:7878 |
UDP/QUIC listen address |
--password |
required | argon2-hashed at startup |
--password-file |
— | alternative to --password; never appears in ps |
--encoder |
auto |
auto | software | hardware |
--monitor |
0 |
which portal stream index to capture |
--bitrate-mbps |
4 |
H.264 target bitrate |
--max-fps |
60 |
0 = unlimited |
| Flag | Default | Notes |
|---|---|---|
--server |
required | host:port |
--password |
required | |
--auto-resolution |
false |
size the window from the primary monitor |
--width / --height |
1920 / 1080 |
initial window & desired logical resolution |
--scale |
1.0 |
HiDPI scale factor |
--cert |
— | path to server's server.crt for pinning |
remote-client-gui is a small native launcher for non-technical users. It collects the
server address, password, certificate path, and display sizing options, saves the
non-secret settings under the user's config directory, then starts its embedded
streaming client mode. The password is passed to the child process through
REMOTE_PASSWORD, kept in memory only, and is not written to disk.
The standalone remote-client CLI is still released for scripted use, but the GUI
launcher no longer needs to sit next to it.
Client Server (Ubuntu / GNOME, Wayland)
────── ─────────────────────────────────
winit window ashpd ScreenCast+RemoteDesktop portal
├── softbuffer renderer │ one combined session, keyboard+pointer
├── openh264 decoder │ restore token skips dialog after first run
├── cpal audio output │
├── input → control stream ▼
└── QUIC client (quinn) PwVideoCapture (native pipewire crate)
│ TLS self-signed │ SHM frames → broadcast::Sender<CapturedFrame>
│ ALPN: remote-rs/1 │
│ ▼
│ control stream (bidi): H264Encoder
│ ClientHello/ServerHello ├── Software: openh264 (BGRA→I420→H.264)
│ ClientControl::{ └── Hardware: BGRA→NV12 (CPU) → av_hwframe_transfer_data
│ Input, Resize, Bye} → h264_vaapi (GPU)
│ │
│ video frames (uni): │ PwAudioCapture (native pipewire crate)
│ STREAM_VIDEO tag │ → 48 kHz stereo F32 → broadcast
│ FrameHeader + H.264 NALs │
│ ▼
│ audio stream (uni): QUIC server (quinn)
│ STREAM_AUDIO tag ├── per-client pump_frames task
│ AudioHeader + F32 PCM └── per-client pump_audio task
│
└──── input injection: portal notify_pointer_* / notify_keyboard_keysym
- One QUIC unidirectional stream per frame. The network drops stale frames cleanly under congestion without head-of-line blocking on the control stream.
- Client-logical → capture-pixel mapping is server-side. The client sends events in its own window coordinates and reports its current window size via
Resize. The server scales them to the captured monitor's pixel space before injecting. - Argon2 auth over TLS. Password travels over the QUIC TLS channel; server stores only the argon2 hash.
- SHM frames always. AMD's radeonsi VAAPI driver rejects RGB32 DRM_PRIME_2 surface import (
VA_STATUS_ERROR_INVALID_PARAMETER), so DMA-BUF zero-copy is disabled. SHM gives CPU-accessible BGRA frames; the hardware encoder does CPU BGRA→NV12 +av_hwframe_transfer_databefore GPU H.264 encode (~11ms total at 3440×1440 on RX 7800 XT, CPU-bound on the colour conversion). - Native pipewire + ashpd. No third-party screen-capture wrappers — direct use of the
pipewireandashpdcrates.
- "portal session returned zero streams": dismissed the dialog or unchecked every monitor. Re-run.
- Blank window / no frames: add
RUST_LOG=remote_server=debugand check for errors in the PipeWire or encoder path. InvalidCertificateon the client: pass--cert ~/.config/remote-rs/server.crt(copy from the server) or omit--certfor skip-verify.- Want to re-pick the monitor:
rm ~/.config/remote-rs/restore-tokenand restart the server. - Hardware encoder not found: ensure
libavcodec-dev libavutil-dev libva-devare installed and rebuild with--features hardware-encoding. Falls back to software automatically in--encoder automode.
Pre-built binaries at repo.indexarr.net/indexarr/remote-rs/releases:
remote-server-vX.Y.Z-linux-x86_64— server (software encoder, no GPU deps)remote-server-vX.Y.Z-linux-x86_64-hw— server with VA-API hardware encoderremote-server— unversioned software server binary used byremote-server-controlinstallerremote-server-control-vX.Y.Z-linux-x86_64— Linux server service GUIremote-server.service— systemd user service unitremote-client-vX.Y.Z-linux-x86_64— clientremote-client-gui-vX.Y.Z-linux-x86_64— Linux client launcher GUI with embedded client moderemote-client-vX.Y.Z-windows-x86_64.exe— Windows clientremote-client-gui-vX.Y.Z-windows-x86_64.exe— Windows client launcher GUI with embedded client modeSHA256SUMS-vX.Y.Z.txt
The release binary for the server is software-only. For hardware VA-API encoding, build from source with --features hardware-encoding.