Low-latency Wayland remote desktop over SSH. Forward a remote Wayland compositor's display and input to your local machine with zero configuration — just remoteway user@host.
Local machine Remote machine
(client) SSH (server)
+-----------+ stdin/stdout pipe +-----------+
| Display | <---- decompress <------- | Capture |
| (Wayland) | + interpolate | (Wayland) |
| | | |
| Input | -----> serialize --------> | Inject |
| (pointer, | (priority queue) | (virtual |
| keyboard)| | pointer) |
+-----------+ +-----------+
RemoteWay captures frames from the remote compositor, delta-encodes and compresses them, streams them over SSH, decompresses on the client, optionally interpolates between frames, and displays them in a local Wayland window. Input events travel the reverse path with the highest priority.
- Zero config: uses your existing SSH setup, no VPN or port forwarding needed
- Low latency: thread-per-core pipeline, lock-free SPSC queues, SIMD delta encoding,
SCHED_FIFOreal-time priorities - Wayland native: wlr-screencopy, ext-image-capture, xdg-desktop-portal + PipeWire (GNOME)
- Adaptive compression: LZ4 (fast) or Zstd (better ratio), automatic delta encoding
- Frame interpolation: GPU-accelerated backends (wgpu, AMD FSR2/3, NVIDIA Optical Flow) with CPU fallback
- Multi-window: each remote toplevel gets its own local xdg_toplevel surface
- Input forwarding: pointer, keyboard, scroll — with local cursor overlay for instant feedback
- Clipboard forwarding: text, HTML, PNG between local and remote
- Rust 1.85+ (edition 2024)
- System libraries:
libwayland-dev(client + server)libwayland-protocols(wlr + misc)
-
Remote machine: Wayland compositor (wlroot based, Niri, GNOME, KDE)
-
Local machine: Wayland compositor
-
SSH access to the remote machine
-
remoteway-serverinstalled on the remote machine -
For portal capture:
gst-plugin-pipewireGStreamer plugin# Arch sudo pacman -S gst-plugin-pipewire # Fedora sudo dnf install gstreamer1-plugin-pipewire # Debian/Ubuntu sudo apt install gstreamer1.0-pipewire
# Clone and build
git clone https://github.com/user/remoteway.git
cd remoteway
cargo build --release
# Install binaries
sudo install -m 755 target/release/remoteway-client /usr/local/bin/remoteway
sudo install -m 755 target/release/remoteway-server /usr/local/bin/remoteway-serverThe server binary must be in $PATH on the remote machine (or specify its path with --server-bin).
cargo build --release --features portalThis enables the xdg-desktop-portal + GStreamer capture backend for GNOME and KDE.
# All GPU backends
cargo build --release --features gpu
# Specific backends
cargo build --release --features dlss # NVIDIA DLSS (RTX 40-series+)
cargo build --release --features fsr3 # AMD FSR 3 (RDNA3+)
cargo build --release --features fsr2 fsr2-native # AMD FSR 2 (Vulkan)
cargo build --release --features nvidia-of # NVIDIA Optical Flow
# Everything (portal + all GPU backends + RIFE)
cargo build --release --features all,portalAuto-detection order: dlss → fsr3 → fsr2 → cpu (linear-blend fallback).
# Connect to a remote machine and see its desktop
remoteway user@host
# Run a specific application
remoteway user@host -- firefox
# Run with verbose logging
RUST_LOG=remoteway=debug remoteway user@hostUsage: remoteway-client [OPTIONS] <HOST> [-- <COMMAND>...]
Arguments:
<HOST>
Remote host in [user@]host format
[COMMAND]...
Command to launch on the remote side (passed after --)
Options:
--capture <CAPTURE>
Capture backend for the remote server
Possible values:
- auto: Auto-detect on the server (ext-image-capture → wlr-screencopy → portal)
- wlr-screencopy: wlr-screencopy-unstable-v1 (Hyprland, Sway, wlroots)
- ext-image-capture: ext-image-capture-source-v1 (modern Wayland protocol)
- portal: xdg-desktop-portal Screencast over `PipeWire` via `GStreamer`. Server must be built with `--features portal`
[default: auto]
--compress <COMPRESS>
Preferred compression algorithm
Possible values:
- none: No compression — frames travel as raw bytes. Use on loopback or fast LAN where CPU spent on LZ4/zstd is the real bottleneck. Server must be started with `--compress none` too (matched out-of-band; there is no in-band negotiation of the codec)
- lz4: Fast LZ4 block compression (default)
- zstd: Higher-ratio zstd compression
[default: lz4]
--no-interpolate
Disable frame interpolation
--debug
Show an FPS counter overlay in the top-left corner. Counts real frames received from the server (interpolated frames inherit the same number, since they're synthesized between reals)
--interpolation-backend <INTERPOLATION_BACKEND>
Interpolation backend to use (overrides auto-detection). Auto order: fsr3 → fsr2 → linear-blend. Available: fsr3, fsr2, fsr2-rife, linear-blend
--server-scale <SERVER_SCALE>
Server-side downscale factor forwarded to remoteway-server (0.1–1.0). 1.0 = native, 0.5 = half resolution before compression
[default: 1.0]
--upscale <UPSCALE>
Client-side upscale factor applied after receiving (1.0–2.0). 1.0 = display as-is, 2.0 = double the received frame size
[default: 1.0]
--app-id <APP_ID>
Capture a specific window by `app_id` on the remote side (e.g. "org.mozilla.firefox"). Forwarded to remoteway-server as --app-id
--capture-fps <CAPTURE_FPS>
Server-side capture FPS limit (10–500, default 100). Caps frame capture rate to prevent pipeline congestion
[default: 100]
--server-bin <SERVER_BIN>
Path to remoteway-server on remote host
[default: remoteway-server]
--ssh-opt <SSH_OPT>
Additional SSH options (e.g. "-p 2222")
-h, --help
Print help (see a summary with '-h')
# Custom SSH port
remoteway --ssh-opt "-p 2222" user@host
# Disable interpolation for lowest latency
remoteway --no-interpolate user@host
# Use Zstd compression (better ratio, slightly more CPU)
remoteway --compress zstd user@host
# Force portal capture on the remote server
remoteway --capture portal user@host
# Downscale remote 4K display to 1080p
remoteway --resolution 1920x1080 user@host
# Capture a specific remote window
remoteway --app-id org.mozilla.firefox user@host -- firefox
# Specify server binary location
remoteway --server-bin /opt/remoteway/remoteway-server user@host
# Run a remote terminal
remoteway user@host -- weston-terminalThe server is normally launched automatically by the client via SSH. For manual use:
remoteway-server [OPTIONS] [-- <COMMAND>...]
Options:
--capture <BACKEND> Capture backend [default: auto]
[auto|wlr-screencopy|ext-image-capture|portal]
--compress <lz4|zstd> Compression algorithm [default: lz4]
--output <NAME> Output to capture (e.g. "DP-1")
--app-id <APP_ID> Capture a specific window by app_id
(e.g. "org.mozilla.firefox").
Mutually exclusive with --output
--scale <FACTOR> Downscale factor before compression (0.1–1.0)
1.0 = native, 0.5 = half [default: 1.0]
[SERVER]
wlr-screencopy (Core 1, SCHED_FIFO 90)
| rtrb SPSC
Delta encode + LZ4 compress (Core 2)
| rtrb SPSC
SSH stdout (priority queue: input > anchor > frame)
Input receive <- SSH stdin (Core 0, SCHED_FIFO 99)
| immediately
wlr-virtual-pointer / virtual-keyboard inject
[CLIENT]
SSH stdout -> StreamParser (priority queue)
| rtrb SPSC
Decompress + delta reconstruct (Core 1-2)
| optional: FrameInterpolator (GPU)
wl_shm -> wl_surface.commit (Core 3)
Input capture -> SSH stdin (SCHED_FIFO 99, bypasses frame queue)
Cursor overlay - drawn locally, instant
| Crate | Purpose |
|---|---|
remoteway-proto |
Wire protocol types (zerocopy, fixed-size headers) |
remoteway-core |
BufferPool, ThreadConfig, LatencyHistogram, BandwidthMeter |
remoteway-capture |
Screen capture (wlr-screencopy, ext-image-capture, PipeWire) |
remoteway-compress |
Delta encoding (SIMD), LZ4/Zstd compression |
remoteway-transport |
SSH multiplexed transport with priority queues |
remoteway-input |
Input capture (client) and injection (server, wlr + libei) |
remoteway-display |
Wayland display, surface management, cursor overlay |
remoteway-interpolate |
Frame interpolation (CPU blend, wgpu, FSR2/3, NVIDIA OF) |
remoteway-server |
Server binary |
remoteway-client |
Client binary |
For lowest latency, grant the binaries real-time scheduling capability:
# Allow real-time scheduling without root
sudo setcap cap_sys_nice=ep /usr/local/bin/remoteway
sudo setcap cap_sys_nice=ep /usr/local/bin/remoteway-serverOr set the RLIMIT:
# /etc/security/limits.d/remoteway.conf
your_user soft rtprio 99
your_user hard rtprio 99
your_user soft memlock unlimited
your_user hard memlock unlimitedBoth binaries call mlockall(MCL_CURRENT | MCL_FUTURE) at startup to prevent page faults on the hot path. If you see a warning about mlockall, increase RLIMIT_MEMLOCK:
ulimit -l unlimitedRemoteWay integrates Tracy for frame-level profiling:
# Build with Tracy support
cargo build --release --features tracy
# Run with Tracy profiler attached
remoteway-server --features tracyCPU flamegraphs:
cargo install flamegraph
cargo flamegraph --bin remoteway-server -- --capture auto# Compression pipeline benchmarks (delta encode, LZ4, full pipeline)
cargo bench -p remoteway-compress
# Transport throughput
cargo bench -p remoteway-transport
# Buffer pool acquire/release
cargo bench -p remoteway-core| Compositor | Capture | Input | Status |
|---|---|---|---|
| Sway | wlr-screencopy | wlr-virtual-pointer | Fully supported |
| Hyprland | wlr-screencopy / ext-image-capture | wlr-virtual-pointer | Fully supported |
| GNOME/Mutter | xdg-desktop-portal + PipeWire | libei | Requires --features portal + gst-plugin-pipewire |
| KDE Plasma | xdg-desktop-portal + PipeWire | libei | Requires --features portal + gst-plugin-pipewire |
| wlroots-based | Auto-detected | Auto-detected | Fully supported |
# Format, lint, test (run before every commit)
cargo fmt --all
cargo clippy --all -- -D warnings
cargo test --workspace
# Coverage report
cargo llvm-cov --workspace --html
open target/llvm-cov/html/index.html
# Fuzz testing
cargo install cargo-fuzz
cd fuzz
cargo fuzz run fuzz_frame_header
cargo fuzz run fuzz_stream_parserLicensed under the MIT License.