Remote desktop for Linux with Wayland streaming + KDE Connect features over Tailscale. Pure Rust, <100ms latency, no port forwarding needed.
Rust
5
517 commits
updated Sep 24, 2026
Remote desktop for Linux with Wayland-native screen streaming + full KDE Connect feature set — all over Tailscale. No port forwarding required.
Target: Sub-100ms latency screen streaming + KDE Connect integration + zero-config Tailscale connectivity
Linux Link is a pure Rust remote desktop solution built specifically for Linux (Wayland/Hyprland). It combines:
| Feature | Linux Link | RustDesk | Sunshine/Moonlight | KDE Connect |
|---|---|---|---|---|
| Tailscale-native | ✅ | ❌ | ❌ | ❌ |
| Wayland-native | ✅ | ⚠️ Partial | ⚠️ Partial | ✅ |
| Hyprland optimized | ✅ | ❌ | ⚠️ Limited | ⚠️ Limited |
| Full KDE Connect | ✅ | ⚠️ Partial | ❌ | ✅ |
| Screen streaming | ✅ | ✅ | ✅ | ❌ |
| Pure Rust | ✅ | ✅ | ❌ (C++) | ❌ (C++) |
| Open Source | ✅ | ✅ | ✅ | ✅ |
zwlr_screencopy on Hyprland (no portal grant dialog), damage-driven variable frame rate; automatic fallback to PipeWire capture via XDG Desktop Portal on any other Wayland compositor, X11 grab lastwl-clipboardlinux-link pair); unpaired connections are locked out — the control channel (TCP and v2/QUIC) and the video/input stream all enforce it — unless pairing_required = false# Install latest release
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash
# Install specific version
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash -s -- v0.1.0
# Non-interactive install (no prompts)
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash -s -- --yes
# Install to custom prefix
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash -s -- --prefix /opt
# Preview without changes
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash -s -- --dry-run
| Flag | Description |
|---|---|
--yes, -y | Non-interactive; accept all defaults |
--dry-run | Preview actions without making changes |
--verbose | Show detailed debug output |
--force | Force reinstall even if same version |
--no-service | Skip systemd service installation |
--no-config | Skip config file creation |
--no-docs | Skip documentation installation |
--no-man | Skip man page installation |
--prefix PATH | Install prefix (default: /usr) |
--check-updates | Check if installed version is current |
--list-versions | List all available releases |
--status | Show installation status |
--rollback | Roll back to previous version |
--uninstall | Remove installation |
Management commands (after install):
linux-link status # Tailscale status of this machine
linux-link sessions # Recent streaming-session outcomes
linux-link bench --baseline bench/baselines # Encode tail vs the committed baseline
linux-link capabilities # KDE Connect capability sets in use
linux-link pair --grant 15m # Time-boxed one-off support PIN
linux-link kick <id|prefix|peer-ip|all> # Drop a live session
The update/rollback/uninstall verbs belong to the install script, not the binary:
./scripts/install.sh --status # Installation info (prefix, version, service)
./scripts/install.sh --check-updates # Compare installed version with GitHub
./scripts/install.sh --list-versions # List published releases
./scripts/install.sh --rollback # Return to the previously installed version
./scripts/install.sh --uninstall # Remove the installation
# Clone the repo
git clone https://github.com/sreevarshan-xenoz/Linux-Link.git
cd Linux-Link
# Build the workspace
cargo build --release
# Run tests
cargo test --workspace
# Run lints
cargo fmt --check
cargo clippy --workspace -- -D warnings
# Using an AUR helper
yay -S linux-link
# Or manually
git clone https://aur.archlinux.org/linux-link.git
cd linux-link
makepkg -si
# Using the installed binary
linux-link start
# Or with cargo
cargo run --release --bin linux-link -- start
There is no --config flag: the server reads $XDG_CONFIG_HOME/linux-link/config.toml
(default ~/.config/linux-link/config.toml) if present, and falls back to built-in defaults.
-v/--verbose and RUST_LOG control logging.
Copy the example config and customize:
mkdir -p ~/.config/linux-link
cp config.toml.example ~/.config/linux-link/config.toml
# ~/.config/linux-link/config.toml
control_port = 1716 # KDE Connect compatible
streaming_port = 4716 # QUIC streaming port
log_level = "info" # trace/debug/info/warn/error
video_quality = "balanced" # low/balanced/high
pairing_required = true # PIN-pair devices before the control channel or streaming serves requests
# allow_hevc = false # let negotiating phones drive an H.265/HEVC stream (R4 C1)
The server captures the desktop session (Wayland/X11) and talks to PipeWire, so
it runs as a systemd user service — a system unit started at boot could
never see the graphical session. install.sh drops the unit into
~/.config/systemd/user/ and enables it for you; from a desktop session:
systemctl --user enable --now linux-link
systemctl --user status linux-link
journalctl --user -u linux-link -f
The unit is WantedBy=graphical-session.target, so it starts at login and
stops with the session. To run it without a login session (headless), enable
lingering first: sudo loginctl enable-linger $USER.
| Command | Description |
|---|---|
linux-link start | Start the server daemon |
linux-link stop | Stop the running daemon |
linux-link status | Show connection status |
linux-link list | List available peers on tailnet |
linux-link watch | Watch for peer discovery events |
linux-link connect <peer> | Connect to a specific peer |
linux-link pair [pin] [--grant 15m] | Print a 5-minute pairing PIN for the phone (generate or set); --grant time-boxes the trust pairing stores (s/m/h/d, e.g. 45s, 15m, 2h, 1h30m) for one-off support sessions |
linux-link unpair [device-id] | Remove a paired device from the trust store (all if omitted) |
linux-link sessions [--count N] [--json] | Show recorded streaming-session outcomes (LAN/WAN-punched/WAN-relayed tally + recent log tail); each line carries the transport's own numbers — packets/bytes lost, datagrams sent, path changes, seconds rode a relay, peak congestion window, discovered path MTU (read off the path the session actually rides, on both transports) — and the p50/p90/p95/p99/max tails for the whole chain: rtt_*/enc_* measured on the desktop, dec_*/rnd_*/e2e_* reported back by the device doing the decoding, plus its reading of the path as phone_rtt/phone_lost; --json emits the retained per-session records as JSON lines for tooling and benchmark comparison |
linux-link bench [--target software|vaapi] [--baseline PATH] [--record PATH] | Run the pinned 720p encode clip on this machine; with --baseline, exit non-zero if a percentile regressed past --tolerance-pct (exit 3 means no baseline matches this host, which is a skip, not a pass) |
linux-link capabilities [--json|--markdown] | Show the negotiated protocol versions, transports, capture backends and codecs this build actually speaks, plus its KDE Connect capability sets |
linux-link kick <id|prefix|peer-ip|all> | Drop a live streaming session (R4 D2) |
linux-link capabilities is the single source of truth for the four numbers and
lists a reader otherwise has to trust prose for: the negotiated protocol version,
which transports a given build carries, which capture backends exist and in what
order Auto tries them, and which codecs can be encoded/decoded on each side.
Every value is read from the constants and decision tables the wire uses —
protocol::{HANDSHAKE_HELLO, ALPN_V1_STREAM}, protocol::v2::{ALPN_V2, V2_MIN_VERSION..},
streaming::capture::capture_attempts, VideoCodec::ALL, AudioConfig::default() —
so a client-profile build reports capture: unavailable instead of an empty list,
and a version bump appears in the report the moment it appears in the code.
linux-link capabilities # human listing
linux-link capabilities --json # for scripts
linux-link capabilities --markdown # the source of docs/capabilities.md
docs/capabilities.md is that Markdown output, committed,
and server/tests/capabilities_doc.rs fails when it stops matching the build —
regenerate it in the same commit as the constant you changed.
linux-link bench exists because "it felt laggy today" is not a regression test. It encodes
one generated 720p clip — 300 frames, 5 Mbit/s, veryfast, H.264 — and reports the per-frame
encode distribution (p50/p90/p95/p99/max). The clip is a pure function of its frame index
(scene_frame), so the workload is byte-identical between runs without committing a gigabyte
of raw frames, and it mixes a static gradient with a moving block so the encoder cannot coast
on zero-residual skip blocks and measure nothing.
linux-link bench --target vaapi --repeat 5 # measure this machine
linux-link bench --baseline bench/baselines # grade against a committed record
linux-link bench --record bench/baselines # add this machine's record
A baseline is only comparable to a run on the same host, backend, geometry, bitrate, preset
and codec — anything else is reported as SKIP (exit 3), never as a pass, because encode
throughput does not travel between CPUs. --repeat N keeps the fastest run: other work on the
machine can only make an encode slower, and on a desktop with a load average near 6 the best of
seven runs reproduced the quiet-machine number (p50 7 ms) while a single run reported 11 ms.
Encode time is the half this measures; decode and render live on the phone. CI runs the same
command on every push — see bench/README.md.
man linux-link
The Android client is a native Kotlin app in android/, bridged to the shared Rust core via JNI/UniFFI.
cd android
./gradlew assembleDebug # Debug build
./gradlew assembleRelease # Release build
Requires JDK 17+ and Android SDK/NDK.
┌─────────────────────────┐ Tailscale ┌─────────────────────────┐
│ Android Client │◄──── Encrypted P2P ────────│ Linux Server │
│ (Kotlin + Rust FFI) │ (Tailscale IP) │ (Hyprland) │
├─────────────────────────┤ ├─────────────────────────┤
│ UI Layer (Compose) │ │ Rust Daemon (tokio) │
│ ├── Connection Screen │ │ ├── Screen Capture │
│ ├── Remote Desktop │ │ ├── FFmpeg H.264 Enc. │
│ ├── File Browser │ │ ├── QUIC Transport │
│ └── Settings │ │ └── Adaptive Bitrate │
├─────────────────────────┤ ├─────────────────────────┤
│ Rust Backend (FFI) │ │ KDE Connect Plugins │
│ ├── Connection Mgr │ │ ├── Battery │
│ ├── Video Decoder │ │ ├── Clipboard │
│ ├── File Transfer │ │ ├── Notification │
│ └── Input Handler │ │ ├── Share │
└─────────────────────────┘ │ ├── File Browse │
│ └── Input │
└─────────────────────────┘
Data Flow (Streaming):
PipeWire → BGRA Frame → FFmpeg H.264 → QUIC Datagram → MediaCodec → SurfaceView
Data Flow (Input):
Touch Gesture → Rust FFI → QUIC/TCP → KDE mousepad packet → enigo → Wayland
Linux-Link/
├── core/ # Shared Rust library
│ └── src/
│ ├── protocol/ # KDE Connect protocol + connection handling
│ ├── streaming/ # Capture, encoder, QUIC transport, adaptive bitrate
│ └── tailscale/ # Tailscale integration
├── server/ # Linux server daemon
│ └── src/
│ ├── plugins/ # KDE plugins (5 + file browse)
│ ├── cli.rs # CLI argument parsing
│ ├── config.rs # TOML configuration
│ ├── kde.rs # Plugin registry + service setup
│ └── service.rs # Main server loop
├── android/ # Android client (native Kotlin)
│ ├── app/ # Kotlin app module (UI, services, MediaCodec decode)
│ └── bridge/ # Rust cdylib crate (UniFFI/JNI bindings to core)
├── aur/ # AUR packaging (PKGBUILD)
├── docs/ # Design specs and plans
├── man/ # Man pages
├── scripts/ # Install script
├── .github/workflows/ # CI/CD (build, test, release, audit)
├── CHANGELOG.md # Project changelog
├── CONTRIBUTING.md # Contributor guidelines
├── config.toml.example # Example configuration
├── linux-link.service # systemd service file
└── plan.md # Full development plan
| Check | Status |
|---|---|
cargo fmt | ✅ Pass |
cargo clippy -D warnings | ✅ Pass (0 warnings) |
cargo test --workspace | ✅ 52 tests pass |
cargo check --workspace | ✅ Clean compilation |
Linux Link is actively developed and looking for contributors!
ffmpeg-next), PipeWire, xdg-desktop-portal (for streaming)# Build + test
cargo build --workspace && cargo test --workspace
# Format + lint
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
# Android build
cd android && ./gradlew assembleDebug
See CONTRIBUTING.md for full guidelines.
All 6 development phases and the RustDesk research round (R4) have landed in code: the native
Kotlin client, the Rust↔JNI bridge, MediaCodec decode, and the streaming/transport/security stack
are in main, and v0.1.0 is tagged and pushed.
What is not done is verification and hardening, so treat this as beta rather than a release:
cargo fmt --all -- --check, clippy -D warnings across every core feature profile and
cargo test --workspace all pass from a clean checkout of main (verified 2026-09-24).
CI now runs that matrix plus the Android build, and cargo audit
can fail a run — but no workflow has executed yet, because the branch has to be pushed first.v0.1.0 is a tag only)The full backlog, with every item's verified status against this tree, is docs/roadmap-3000.md; how that backlog gets executed is docs/roadmap-execution-plan.md. See plan.md for the development journal.
Stars, forks, and PRs are welcome! If Linux Link sounds interesting to you, drop a ⭐ and say hi.
Rust
75.8%
Kotlin
18.3%
HTML
3.0%
Shell
2.9%
Remote desktop for Linux with Wayland streaming + KDE Connect features over Tailscale. Pure Rust, <100ms latency, no port forwarding needed.
Rust
5
517 commits
updated Sep 24, 2026
Remote desktop for Linux with Wayland-native screen streaming + full KDE Connect feature set — all over Tailscale. No port forwarding required.
Target: Sub-100ms latency screen streaming + KDE Connect integration + zero-config Tailscale connectivity
Linux Link is a pure Rust remote desktop solution built specifically for Linux (Wayland/Hyprland). It combines:
| Feature | Linux Link | RustDesk | Sunshine/Moonlight | KDE Connect |
|---|---|---|---|---|
| Tailscale-native | ✅ | ❌ | ❌ | ❌ |
| Wayland-native | ✅ | ⚠️ Partial | ⚠️ Partial | ✅ |
| Hyprland optimized | ✅ | ❌ | ⚠️ Limited | ⚠️ Limited |
| Full KDE Connect | ✅ | ⚠️ Partial | ❌ | ✅ |
| Screen streaming | ✅ | ✅ | ✅ | ❌ |
| Pure Rust | ✅ | ✅ | ❌ (C++) | ❌ (C++) |
| Open Source | ✅ | ✅ | ✅ | ✅ |
zwlr_screencopy on Hyprland (no portal grant dialog), damage-driven variable frame rate; automatic fallback to PipeWire capture via XDG Desktop Portal on any other Wayland compositor, X11 grab lastwl-clipboardlinux-link pair); unpaired connections are locked out — the control channel (TCP and v2/QUIC) and the video/input stream all enforce it — unless pairing_required = false# Install latest release
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash
# Install specific version
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash -s -- v0.1.0
# Non-interactive install (no prompts)
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash -s -- --yes
# Install to custom prefix
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash -s -- --prefix /opt
# Preview without changes
curl -fsSL https://raw.githubusercontent.com/sreevarshan-xenoz/Linux-Link/main/scripts/install.sh | bash -s -- --dry-run
| Flag | Description |
|---|---|
--yes, -y | Non-interactive; accept all defaults |
--dry-run | Preview actions without making changes |
--verbose | Show detailed debug output |
--force | Force reinstall even if same version |
--no-service | Skip systemd service installation |
--no-config | Skip config file creation |
--no-docs | Skip documentation installation |
--no-man | Skip man page installation |
--prefix PATH | Install prefix (default: /usr) |
--check-updates | Check if installed version is current |
--list-versions | List all available releases |
--status | Show installation status |
--rollback | Roll back to previous version |
--uninstall | Remove installation |
Management commands (after install):
linux-link status # Tailscale status of this machine
linux-link sessions # Recent streaming-session outcomes
linux-link bench --baseline bench/baselines # Encode tail vs the committed baseline
linux-link capabilities # KDE Connect capability sets in use
linux-link pair --grant 15m # Time-boxed one-off support PIN
linux-link kick <id|prefix|peer-ip|all> # Drop a live session
The update/rollback/uninstall verbs belong to the install script, not the binary:
./scripts/install.sh --status # Installation info (prefix, version, service)
./scripts/install.sh --check-updates # Compare installed version with GitHub
./scripts/install.sh --list-versions # List published releases
./scripts/install.sh --rollback # Return to the previously installed version
./scripts/install.sh --uninstall # Remove the installation
# Clone the repo
git clone https://github.com/sreevarshan-xenoz/Linux-Link.git
cd Linux-Link
# Build the workspace
cargo build --release
# Run tests
cargo test --workspace
# Run lints
cargo fmt --check
cargo clippy --workspace -- -D warnings
# Using an AUR helper
yay -S linux-link
# Or manually
git clone https://aur.archlinux.org/linux-link.git
cd linux-link
makepkg -si
# Using the installed binary
linux-link start
# Or with cargo
cargo run --release --bin linux-link -- start
There is no --config flag: the server reads $XDG_CONFIG_HOME/linux-link/config.toml
(default ~/.config/linux-link/config.toml) if present, and falls back to built-in defaults.
-v/--verbose and RUST_LOG control logging.
Copy the example config and customize:
mkdir -p ~/.config/linux-link
cp config.toml.example ~/.config/linux-link/config.toml
# ~/.config/linux-link/config.toml
control_port = 1716 # KDE Connect compatible
streaming_port = 4716 # QUIC streaming port
log_level = "info" # trace/debug/info/warn/error
video_quality = "balanced" # low/balanced/high
pairing_required = true # PIN-pair devices before the control channel or streaming serves requests
# allow_hevc = false # let negotiating phones drive an H.265/HEVC stream (R4 C1)
The server captures the desktop session (Wayland/X11) and talks to PipeWire, so
it runs as a systemd user service — a system unit started at boot could
never see the graphical session. install.sh drops the unit into
~/.config/systemd/user/ and enables it for you; from a desktop session:
systemctl --user enable --now linux-link
systemctl --user status linux-link
journalctl --user -u linux-link -f
The unit is WantedBy=graphical-session.target, so it starts at login and
stops with the session. To run it without a login session (headless), enable
lingering first: sudo loginctl enable-linger $USER.
| Command | Description |
|---|---|
linux-link start | Start the server daemon |
linux-link stop | Stop the running daemon |
linux-link status | Show connection status |
linux-link list | List available peers on tailnet |
linux-link watch | Watch for peer discovery events |
linux-link connect <peer> | Connect to a specific peer |
linux-link pair [pin] [--grant 15m] | Print a 5-minute pairing PIN for the phone (generate or set); --grant time-boxes the trust pairing stores (s/m/h/d, e.g. 45s, 15m, 2h, 1h30m) for one-off support sessions |
linux-link unpair [device-id] | Remove a paired device from the trust store (all if omitted) |
linux-link sessions [--count N] [--json] | Show recorded streaming-session outcomes (LAN/WAN-punched/WAN-relayed tally + recent log tail); each line carries the transport's own numbers — packets/bytes lost, datagrams sent, path changes, seconds rode a relay, peak congestion window, discovered path MTU (read off the path the session actually rides, on both transports) — and the p50/p90/p95/p99/max tails for the whole chain: rtt_*/enc_* measured on the desktop, dec_*/rnd_*/e2e_* reported back by the device doing the decoding, plus its reading of the path as phone_rtt/phone_lost; --json emits the retained per-session records as JSON lines for tooling and benchmark comparison |
linux-link bench [--target software|vaapi] [--baseline PATH] [--record PATH] | Run the pinned 720p encode clip on this machine; with --baseline, exit non-zero if a percentile regressed past --tolerance-pct (exit 3 means no baseline matches this host, which is a skip, not a pass) |
linux-link capabilities [--json|--markdown] | Show the negotiated protocol versions, transports, capture backends and codecs this build actually speaks, plus its KDE Connect capability sets |
linux-link kick <id|prefix|peer-ip|all> | Drop a live streaming session (R4 D2) |
linux-link capabilities is the single source of truth for the four numbers and
lists a reader otherwise has to trust prose for: the negotiated protocol version,
which transports a given build carries, which capture backends exist and in what
order Auto tries them, and which codecs can be encoded/decoded on each side.
Every value is read from the constants and decision tables the wire uses —
protocol::{HANDSHAKE_HELLO, ALPN_V1_STREAM}, protocol::v2::{ALPN_V2, V2_MIN_VERSION..},
streaming::capture::capture_attempts, VideoCodec::ALL, AudioConfig::default() —
so a client-profile build reports capture: unavailable instead of an empty list,
and a version bump appears in the report the moment it appears in the code.
linux-link capabilities # human listing
linux-link capabilities --json # for scripts
linux-link capabilities --markdown # the source of docs/capabilities.md
docs/capabilities.md is that Markdown output, committed,
and server/tests/capabilities_doc.rs fails when it stops matching the build —
regenerate it in the same commit as the constant you changed.
linux-link bench exists because "it felt laggy today" is not a regression test. It encodes
one generated 720p clip — 300 frames, 5 Mbit/s, veryfast, H.264 — and reports the per-frame
encode distribution (p50/p90/p95/p99/max). The clip is a pure function of its frame index
(scene_frame), so the workload is byte-identical between runs without committing a gigabyte
of raw frames, and it mixes a static gradient with a moving block so the encoder cannot coast
on zero-residual skip blocks and measure nothing.
linux-link bench --target vaapi --repeat 5 # measure this machine
linux-link bench --baseline bench/baselines # grade against a committed record
linux-link bench --record bench/baselines # add this machine's record
A baseline is only comparable to a run on the same host, backend, geometry, bitrate, preset
and codec — anything else is reported as SKIP (exit 3), never as a pass, because encode
throughput does not travel between CPUs. --repeat N keeps the fastest run: other work on the
machine can only make an encode slower, and on a desktop with a load average near 6 the best of
seven runs reproduced the quiet-machine number (p50 7 ms) while a single run reported 11 ms.
Encode time is the half this measures; decode and render live on the phone. CI runs the same
command on every push — see bench/README.md.
man linux-link
The Android client is a native Kotlin app in android/, bridged to the shared Rust core via JNI/UniFFI.
cd android
./gradlew assembleDebug # Debug build
./gradlew assembleRelease # Release build
Requires JDK 17+ and Android SDK/NDK.
┌─────────────────────────┐ Tailscale ┌─────────────────────────┐
│ Android Client │◄──── Encrypted P2P ────────│ Linux Server │
│ (Kotlin + Rust FFI) │ (Tailscale IP) │ (Hyprland) │
├─────────────────────────┤ ├─────────────────────────┤
│ UI Layer (Compose) │ │ Rust Daemon (tokio) │
│ ├── Connection Screen │ │ ├── Screen Capture │
│ ├── Remote Desktop │ │ ├── FFmpeg H.264 Enc. │
│ ├── File Browser │ │ ├── QUIC Transport │
│ └── Settings │ │ └── Adaptive Bitrate │
├─────────────────────────┤ ├─────────────────────────┤
│ Rust Backend (FFI) │ │ KDE Connect Plugins │
│ ├── Connection Mgr │ │ ├── Battery │
│ ├── Video Decoder │ │ ├── Clipboard │
│ ├── File Transfer │ │ ├── Notification │
│ └── Input Handler │ │ ├── Share │
└─────────────────────────┘ │ ├── File Browse │
│ └── Input │
└─────────────────────────┘
Data Flow (Streaming):
PipeWire → BGRA Frame → FFmpeg H.264 → QUIC Datagram → MediaCodec → SurfaceView
Data Flow (Input):
Touch Gesture → Rust FFI → QUIC/TCP → KDE mousepad packet → enigo → Wayland
Linux-Link/
├── core/ # Shared Rust library
│ └── src/
│ ├── protocol/ # KDE Connect protocol + connection handling
│ ├── streaming/ # Capture, encoder, QUIC transport, adaptive bitrate
│ └── tailscale/ # Tailscale integration
├── server/ # Linux server daemon
│ └── src/
│ ├── plugins/ # KDE plugins (5 + file browse)
│ ├── cli.rs # CLI argument parsing
│ ├── config.rs # TOML configuration
│ ├── kde.rs # Plugin registry + service setup
│ └── service.rs # Main server loop
├── android/ # Android client (native Kotlin)
│ ├── app/ # Kotlin app module (UI, services, MediaCodec decode)
│ └── bridge/ # Rust cdylib crate (UniFFI/JNI bindings to core)
├── aur/ # AUR packaging (PKGBUILD)
├── docs/ # Design specs and plans
├── man/ # Man pages
├── scripts/ # Install script
├── .github/workflows/ # CI/CD (build, test, release, audit)
├── CHANGELOG.md # Project changelog
├── CONTRIBUTING.md # Contributor guidelines
├── config.toml.example # Example configuration
├── linux-link.service # systemd service file
└── plan.md # Full development plan
| Check | Status |
|---|---|
cargo fmt | ✅ Pass |
cargo clippy -D warnings | ✅ Pass (0 warnings) |
cargo test --workspace | ✅ 52 tests pass |
cargo check --workspace | ✅ Clean compilation |
Linux Link is actively developed and looking for contributors!
ffmpeg-next), PipeWire, xdg-desktop-portal (for streaming)# Build + test
cargo build --workspace && cargo test --workspace
# Format + lint
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
# Android build
cd android && ./gradlew assembleDebug
See CONTRIBUTING.md for full guidelines.
All 6 development phases and the RustDesk research round (R4) have landed in code: the native
Kotlin client, the Rust↔JNI bridge, MediaCodec decode, and the streaming/transport/security stack
are in main, and v0.1.0 is tagged and pushed.
What is not done is verification and hardening, so treat this as beta rather than a release:
cargo fmt --all -- --check, clippy -D warnings across every core feature profile and
cargo test --workspace all pass from a clean checkout of main (verified 2026-09-24).
CI now runs that matrix plus the Android build, and cargo audit
can fail a run — but no workflow has executed yet, because the branch has to be pushed first.v0.1.0 is a tag only)The full backlog, with every item's verified status against this tree, is docs/roadmap-3000.md; how that backlog gets executed is docs/roadmap-execution-plan.md. See plan.md for the development journal.
Stars, forks, and PRs are welcome! If Linux Link sounds interesting to you, drop a ⭐ and say hi.
Rust
75.8%
Kotlin
18.3%
HTML
3.0%
Shell
2.9%