- What is Orbiscreen?
- Why Orbiscreen Exists
- Highlights
- Status
- Quick Start & Packaging
- Commands
- Android App
- Architecture
- License
Orbiscreen turns a spare Android tablet or phone into a real second monitor for your Linux desktop. Unlike X11-only or browser-only workarounds, Orbiscreen creates a kernel-level virtual display via DisplayLink's evdi, which appears as a genuine monitor to both X11 and Wayland compositors, and streams it using MPEG-TS/H.264 with reverse touch input natively on Android.
| Problem | Other Projects | Orbiscreen |
|---|---|---|
| No Linux host support | spacedesk refuses officially | Real kernel-level virtual display |
| X11-only workaround | VirtScreen unmaintained since 2018 | X11 and Wayland via evdi/DRM |
| Wayland second screen missing | Weylus caps it to X11 | Full Wayland path via ashpd + PipeWire |
| Manual IP configuration | Most projects | mDNS discovery + live network scan + manual add |
| Single-purpose client | Spacedesk only | Native Android screen + host control panel |
- Real virtual display via
evdi(X11 and Wayland). - Material 3 Android client — Jetpack Compose, Catppuccin Mocha / Latte brand palette matching
data/orbiscreen-app.svg, light/dark theme. - Brand UI Chrome & Splash — SplashScreen with brand background and vector adaptive launcher icon mirroring SVG.
- Live discovery — NSD scan of nearby hosts, manual
host:portentry, optional subnet scanner. - Native streaming — Main-thread ExoPlayer construction with
OkHttpDataSource+DefaultLoadControlfor low-latency MPEG-TS / H.264. - Reverse touch — absolute pointer / keyboard / stylus / wheel events flow Android → host.
- Host control panel — keyboard, lock, blank, Ctrl+Alt+Del, file manager, and retry actions.
- USB transport via
adb reverse, no special drivers. - Hardware encoding — VAAPI, NVENC, x264 software fallback.
- Cryptographic signing of every Linux and Android artifact.
| Phase | Goal | State |
|---|---|---|
| 0 | Workspace scaffolding + evdi feasibility | ✅ Completed |
| 1 | Display + capture + encode + input (X11) | ✅ Completed |
| 2 | Android client + USB transport + mDNS | ✅ Completed |
| 3 | Wayland capture + portal fallback + input | ✅ Completed |
| 4 | Packaging + GTK4 GUI + D-Bus service + Standalone installation | ✅ Completed |
| 5 | Material 3 UI + live discovery + control panel | ✅ Completed |
See
CHANGELOG.mdfor the complete release history.
Download pre-built packages from GitHub Releases:
-
Debian / Ubuntu (
.deb):sudo dpkg -i orbiscreen_amd64.deb || sudo apt-get install -f -
Fedora / RHEL (
.rpm):Note: Our RPM packages are cryptographically signed. You must import the public key first.
sudo rpm --import https://raw.githubusercontent.com/shadow-x78/orbiscreen/main/orbiscreen.asc sudo dnf install ./orbiscreen_x86_64.rpm
-
Universal AppImage (
.AppImage):chmod +x orbiscreen-x86_64.AppImage ./orbiscreen-x86_64.AppImage
-
Standalone Tarball (
.tar.gz):tar -xzvf orbiscreen-linux-x86_64.tar.gz cd release-bundle && ./install.sh
-
Android (
.apk): Installorbiscreen-android-release.apk(cryptographically signed release build to bypass Play Protect warnings).
# Clone the repository
git clone https://github.com/shadow-x78/orbiscreen.git ~/Orbiscreen
cd ~/Orbiscreen
# One-command installation for Linux
./scripts/install.sh
# Probe local capture, input, and display backends
orbiscreen probe
# Start the Orbiscreen daemon (EVDI DRM or Wayland Portal auto-fallback)
orbiscreen start| Command | Description |
|---|---|
orbiscreen start |
Create the virtual display and start streaming |
orbiscreen start --no-mdns |
Start without mDNS advertising |
orbiscreen list-displays |
List configured virtual displays |
orbiscreen probe |
Report capture / input / display backends |
orbiscreen print-config |
Print the resolved configuration |
orbiscreen uninstall |
Remove the daemon, systemd service, and desktop entries |
orbiscreen --config orbiscreen.toml --verbose probeThe Android client is a Material 3 + Jetpack Compose single-Activity app. Three screens are wired through Compose Navigation:
- Status banner that reports the active scan and the number of hosts found.
- Live list of
_orbiscreen._tcp.services discovered viaNsdManager, with name, IP, and port. - Quick chips for each host: tap to connect, or long-press for details.
- Add manually card — expand to reveal an
OutlinedTextFieldthat validateshost:portwith a regex. - USB mode card — pre-fills
127.0.0.1:8788foradb reverse tcp:8788 tcp:8788. - Refresh button to restart the scan.
- Recent host persisted in
SharedPreferencesand surfaced at the top of the list with a "Recent" chip.
- Background-black
Scaffoldwith Catppuccin Mocha/Latte themed top app bar and aControlToolbarrail. - ExoPlayer construction runs safely on the Main thread (
withContext(Dispatchers.Main)), wrapped inPlayerView(viaAndroidView) withuseController = falseso the in-app bar is the single source of UI. - A hardened
PlayerHolder.build()wraps all initialization steps in a try-catch block to surface construction errors cleanly asStreamEvent.Errorretry cards rather than crashing. StreamUrlalways builds a.tsURL withsetMimeType(MimeTypes.VIDEO_MP2T)so MPEG-TS over HTTP is decoded without sniffing.OkHttpDataSourceuses zero read-timeout for live streams and a tunedDefaultLoadControlfor stable buffering.
A floating action rail over the player surface with:
| Action | Effect |
|---|---|
| Keyboard | Show soft keyboard overlay + system-IME handoff |
| Lock | POST /api/control {action:"lock"} |
| Blank | Toggle `POST /api/control {action:"blank", state:"on" |
| Ctrl+Alt+Del | POST /api/control {action:"ctrl_alt_del"} |
| Files | POST /api/control {action:"open", target:"files"} |
| Retry | Re-prepare the player |
- Theme: System / Light / Dark (System default).
- Streaming: force software decoder; enable advanced subnet scanner.
- Recent host: view and forget the last successful connection.
- About: app version, SDK level, and copy-to-clipboard version info.
InputDispatcher translates Android touch events into absolute host coordinates using the host's reported display dimensions:
fun move(localX: Float, localY: Float, containerW: Int, containerH: Int) {
val (x, y) = map(localX, localY, containerW, containerH)
moves.tryEmit(JSONObject().apply {
put("Move", JSONObject().apply { put("x", x); put("y", y) })
})
}Pointer events are debounced through a MutableSharedFlow with BufferOverflow.DROP_OLDEST so the network never backs up during a fast drag.
The Android client uses three lightweight JSON endpoints in addition to the existing /stream and /input:
| Endpoint | Method | Purpose |
|---|---|---|
/api/info |
GET | Returns display resolution, encoder, and version |
/api/control |
POST | Host-side actions (blank, lock, ctrl-alt-del, open) |
/health |
GET | Liveness probe |
orbiscreen/
├── crates/
│ ├── orbiscreen-core/ # types, config, errors
│ ├── orbiscreen-display/ # evdi-backed virtual displays
│ ├── orbiscreen-capture/ # X11 (x11rb) + Wayland (ashpd + PipeWire)
│ ├── orbiscreen-encode/ # GStreamer pipeline (VAAPI / NVENC / x264)
│ ├── orbiscreen-input/ # evdevil + ashpd RemoteDesktop
│ ├── orbiscreen-transport/ # axum + mDNS + adb reverse + /api/info + /api/control
│ └── orbiscreen-daemon/ # CLI binary wiring every layer together
├── clients/
│ ├── web/ # browser WebRTC client (HTML / CSS / JS)
│ └── android/ # Material 3 Compose app
│ └── app/src/main/java/com/orbiscreen/android/
│ ├── MainActivity.kt
│ ├── data/ # PrefsStore (recent host + settings)
│ ├── net/ # DiscoveryService, SubnetScanner, HostApi
│ ├── player/ # PlayerHolder, StreamUrl
│ ├── input/ # InputDispatcher
│ └── ui/
│ ├── theme/ # Material 3 colour, typography, theme
│ ├── nav/ # Compose Navigation graph
│ ├── discovery/ # DiscoveryScreen + ViewModel
│ ├── stream/ # StreamScreen, PlayerSurface, ControlToolbar
│ └── settings/ # SettingsScreen
├── packaging/{flatpak,appimage,debian}/
├── scripts/{setup-dev-env.sh,test-evdi.sh,install.sh,uninstall.sh}
├── .github/{workflows/,ISSUE_TEMPLATE/,dependabot.yml}
└── .editorconfig, .gitignore, .gitattributes, deny.toml, rustfmt.toml
┌──────────────────────────────────────────────────────────────┐
│ orbiscreen-daemon (CLI, clap) │
│ ┌──────────────┐ ┌──────────────┐ ┌───────────────────┐ │
│ │ display │ │ capture │ │ encode │ │
│ │ evdi crate │ │ x11rb/ashpd │ │ gstreamer-rs │ │
│ └──────────────┘ └──────────────┘ └───────────────────┘ │
│ ┌──────────────┐ ┌──────────────────────────────────────┐ │
│ │ input │ │ transport │ │
│ │ evdevil/ashpd│ │ axum + webrtc-rs + mdns-sd + adb │ │
│ │ │ │ + /api/info + /api/control + /health │ │
│ └──────────────┘ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ core: shared types, config, errors │ │
│ └──────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
/dev/dri/... X11 / Wayland Network (mDNS + UDP)
| Document | Description |
|---|---|
| docs/ARCHITECTURE.md | System topology, zero-copy pipeline & D-Bus architecture |
| docs/PACKAGING.md | Multi-distro packaging specifications (.deb, .rpm, AppImage, Flatpak) |
| docs/DBUS_SPEC.md | D-Bus Session Bus IPC interface specifications |
| docs/TROUBLESHOOTING.md | Common issues, diagnostics & hardware acceleration fixes |
| SECURITY.md | Security model, WebRTC transport safety & network policies |
| CHANGELOG.md | Full release history and commit style |
| CONTRIBUTING.md | Guidelines for contributing and building from source |
Please read our Contributing Guidelines for details on how to set up the development environment, format your code, and submit Pull Requests.
When committing, follow the convention:
orbiscreen | <scope>: <message>
For example:
orbiscreen | android | player: retry on transient network errors
orbiscreen | docs | readme: clarify mDNS discovery flow
orbiscreen | v0.10.2 | release: hotfix for blank screen on Pixel 7
Distributed under the GPL-3.0 License.
Built by shadow-x78 · Changelog · Security
© 2026 Orbiscreen (shadow-x78)