Mojo

Mojo documentation

Mojo streams a computer's monitors to another computer with hardware video encoding and sends your mouse, keyboard, clipboard and files back. Unlike most remote desktops, the viewer can ask the remote computer to create extra monitors that don't physically exist.

Mojo has three programs and one app:

  • mojo-host runs on the computer you want to reach. It captures monitors, creates virtual ones, encodes video and injects input.
  • mojo-client is the viewer. It opens one window per remote monitor.
  • mojo-relay is optional. It introduces hosts and viewers by device ID across the internet and relays traffic when a direct path is impossible.
  • Mojo (the desktop app) wraps the host and viewer with a device list, a monitor planner and one-click sharing.
Mojo is a codename and the project is young. Linux is tested on real desktops, Windows and macOS hosts in full-desktop VMs, and the Android apps on phone and TV emulators. The platform table is kept current.

Install

Download the mojo-windows-x64 artifact from the latest CI run and unzip it anywhere. It contains:

  • Mojo.exe — the desktop app (needs the WebView2 runtime, which Windows 10 and 11 already have)
  • mojo-client.exe, mojo-host.exe and the FFmpeg DLLs they use
  • connect.bat and share-this-pc.bat — edit and double-click shortcuts

Virtual monitors on a Windows host need the SudoVDA virtual display driver installed once. Without it, the host still shares its physical monitors.

Share a computer

Start the host on the computer you want to reach. With no options it makes up a random passphrase (like kp7m-4xq2-v9tn-c3wr) and prints it; choose your own with --password, or let people in one by one with --approve:

mojo-host                                   # prints a random passphrase
mojo-host --password 'pick-a-long-passphrase'
mojo-host --password '…' --approve          # also asks "Allow Sam's iPad?" for each viewer

It listens on UDP port 9450, announces itself on the local network, and keeps a TLS identity in its config folder so viewers can recognise it next time. --source auto picks the right capture backend for the OS.

In the desktop app, open Share; it generates a passphrase and starts the host for you. Turn on Ask before someone connects to approve each viewer in a dialog (unanswered requests are declined after a minute).

Linux

  • X11 sessions use XShm capture, XTest input and RandR monitors for virtual displays.
  • GNOME on Wayland or X11 uses Mutter's remote desktop and screen cast APIs; GNOME may ask once to allow remote control.
  • Desktop audio comes from the PulseAudio or PipeWire monitor of your default output.

Windows

  • Run mojo-host.exe in your user session (it captures with DXGI Desktop Duplication).
  • Install SudoVDA for virtual monitors. Mojo creates and removes them per session.
  • Desktop audio uses WASAPI loopback of the default output device.

macOS

macOS asks for three permissions the first time; grant them in System Settings → Privacy & Security and restart the host:

  • Screen Recording — to capture monitors and system audio.
  • Accessibility — to move the mouse and type.
  • Automation → Finder — only to learn which Finder window you dropped files on.

Virtual monitors are created with CGVirtualDisplay at the exact pixel size you ask for. Set MOJO_MAC_HIDPI=1 before starting the host to make them Retina (HiDPI) displays instead.

Connect to a computer

Find hosts on your network, then connect by name or address:

mojo-client discover
mojo-client 192.168.1.20 --password 'pick-a-long-passphrase'

The first connection remembers the host's certificate fingerprint (trust on first use). If the fingerprint ever changes, the viewer refuses to connect. After a legitimate reinstall of the host, run once with --forget-host. To require a specific fingerprint from the start, pass --pin AA:BB:… (print it on the host with mojo-host --print-id).

If the connection drops, the windows stay open and the viewer reconnects with the same layout.

Monitors

By default the viewer mirrors the host's primary monitor. Every --virtual asks the host to create another monitor for this session; they disappear when you disconnect.

# Primary monitor plus two new ones
mojo-client studio-pc --password '…' --mirror primary \
  --virtual 2560x1440@144 --virtual 1920x1080@60

# Every existing monitor, at 120 fps
mojo-client studio-pc --password '…' --mirror all --fps 120

# Only virtual monitors (the physical screens stay private)
mojo-client studio-pc --password '…' --virtual 3840x2160@60

Each monitor gets its own window and its own encoder, so a 144 fps monitor and a 60 fps monitor don't slow each other down. In the desktop app, the monitor planner shows the remote arrangement: click physical monitors to include them and add virtual ones with resolution and refresh chips.

No monitor attached? A host with no screen plugged in (a server, a GPU box in a cupboard, a cloud VM) creates a 1920×1080 virtual display by itself, so there is always a desktop to connect to. Choose its size with --headless-display 2560x1440@120.

How many monitors? There is no fixed limit in Mojo. The practical limits are the host's encoder sessions (consumer NVIDIA cards allow several simultaneous NVENC sessions; software x264 has no cap but costs CPU) and the OS: X11's framebuffer is typically 16384×16384 pixels.

Clipboard and files

Clipboard

Copy on either computer, paste on the other. Text syncs instantly. Copied files and folders are sent in the background and placed on the other computer's clipboard, so a normal paste in Explorer, Finder or a Linux file manager drops them there. Copies over 2 GB are skipped; drag those instead. Turn sync off with --no-clipboard.

Drag and drop

Drag files or folders from your computer onto a remote monitor's window. Mojo sends them and puts them where you let go:

Dropped onLands in
A File Explorer window (Windows)That window's folder
A Finder window (macOS)That window's folder
A file manager window on X11 (Nautilus, Nemo, Thunar, Caja, PCManFM, Dolphin)That window's folder
The desktopThe Desktop folder
Anything else, or GNOME on Wayland with several folders openDownloads

Existing files are never overwritten; a second copy becomes report (2).pdf. Paths that try to escape the destination are rejected. The viewer's title bar confirms where the files were saved. For scripts, --send PATH uploads to the host's Downloads.

Audio and cursor

The host streams its desktop audio as 48 kHz stereo Opus in 10 ms frames; the viewer keeps a 30 ms jitter buffer. Use --no-audio on the viewer or --audio none on the host to turn it off.

The remote pointer is not drawn into the video. Instead the host sends each new pointer shape, and the viewer makes it your own OS cursor, which moves with zero network latency. --video-cursor switches back to drawing it into the video, which some games prefer.

Viewer hotkeys

KeysAction
Ctrl Alt Shift FToggle fullscreen for this monitor's window
Ctrl Alt Shift GLock the mouse to the window and send raw movement (games); press again to release
Ctrl Alt Shift SShow live stats (fps, bitrate, latency) in the title bar
Ctrl Alt Shift RStart or stop recording (saved to Videos/Mojo)
Ctrl Alt Shift PPrivacy mode: darken the host's screens and lock its keyboard and mouse
Ctrl Alt Shift VSend your microphone to the host
Ctrl Alt Shift Del (or End)Ctrl+Alt+Del on the remote computer
Ctrl Alt Shift QDisconnect

Move the pointer to the top edge of a window to reveal the session bar with the same actions.

Phones, tablets and TVs

The Android app runs on phones, tablets, Chromebooks and Android TV; the Apple app on iPhone, iPad and Apple TV. Computers sharing with Mojo on your network show up automatically; you can also type an address or device ID.

  • Trackpad mode (default): drag to move the pointer, tap to click, tap then drag (Android) or long-press then drag (iPhone, iPad) to drag, two-finger tap to right-click, two-finger drag to scroll, pinch to zoom, three-finger tap for the keyboard.
  • Direct mode: tap where you want to click, long-press to right-click. Switch in Settings or the in-session menu.
  • Keyboard: the on-screen keyboard types any language (it's sent as text), and the key bar above it adds Esc, Tab, Ctrl, Alt, Win, Shift, arrows, Home, End, Del and F1–F12. Ctrl, Alt, Win and Shift stay held for the next key, so Ctrl then C copies. Hardware keyboards and mice work too.
  • Displays: show the main display, all displays (side by side on tablets and TVs, one at a time on phones), or New display, which adds a virtual monitor to the computer at your device's resolution and refresh rate.
  • The menu: Back on Android, Menu on the Siri Remote, the handle at the top of the screen on iPhone and iPad, or Start + Back on a controller. It has the keyboard, display switcher, touch mode, Ctrl+Alt+Del, "type clipboard", stats and Disconnect.
  • Clipboard: text you copy on the computer lands on the device's clipboard; text you copy on the device goes to the computer when you return to Mojo.

Controllers

Up to four controllers per viewer. On Windows (with ViGEmBus installed) and Linux (with access to /dev/uinput) each one appears to games as an Xbox 360 controller. Everywhere else, or when you set Controllers to Mouse & keyboard, a controller drives the desktop:

ControlDoes
Left stickMove the pointer (hold LT for fine control)
Right stickScroll
A / BLeft click / right click
D-padArrow keys
XBackspace
YOn-screen keyboard
Start / Back (Menu / View)Enter / Escape
LB / RBBrowser back / forward
Start + Back togetherMojo's menu

TV remotes work without a controller: on Android TV the D-pad moves the pointer and OK clicks (switch the remote to arrow keys in the menu); on Apple TV the Siri Remote's touch surface moves the pointer, a click clicks and Play/Pause right-clicks.

Games

  • Frame rate: up to 240 fps per monitor (--fps 240, or a 240 Hz virtual monitor). At 1080p240 a frame reaches the viewer about 5 ms after capture starts.
  • Relative mouse: shooters and 3D games need raw mouse movement, not a pointer position. Turn on Lock the mouse for games in Settings, pass --relative-mouse, or press Ctrl Alt Shift G during a session. The pointer is locked to the window and every movement is sent as-is.
  • Rumble: when a game vibrates its controller, the viewer's controller vibrates too (desktop viewers, Android, iPhone, iPad and Apple TV with a controller that has haptics).
  • Sharp text for strategy games and work: Pixel-sharp text (4:4:4) in Settings or --444 sends full-resolution colour. It needs about half again as much bandwidth; hosts without a 4:4:4 hardware encoder fall back to software up to 1080p, or to normal colour.

Chat, unattended access and pairing

Chat

In the desktop app, press Chat next to a live session (or next to a viewer in Share) to message the person at the other end. Their messages pop the chat open and bring the app forward. On phones, tablets and TVs, Chat is in the in-session menu, and new messages show briefly over the picture.

Wake-on-LAN

Hosts advertise their network card's address. Once a viewer has seen a computer, it can wake it later: Wake it up in the desktop app, Wake on Android and iOS, or mojo-client HOST --wake. The computer needs Wake-on-LAN enabled in its firmware and network settings.

Unattended access

In the desktop app's Share tab, turn on Unattended access. Mojo then runs as a background service with your sharing settings, whether or not the app is open:

  • Windows: a system service (Windows asks for administrator permission once). From another device you can sign in, unlock the lock screen, answer UAC prompts and press Ctrl+Alt+Del (Ctrl Alt Shift Del on desktop viewers, or the in-session menu on phones and TVs). It starts with Windows and moves with you when users switch.
  • macOS and Linux: Mojo keeps running whenever you're signed in (a LaunchAgent, or a systemd user service).

Turn on Same password every time first if you want to connect with a password; otherwise only paired devices can connect. On the command line: mojo-host --service install --password …, --service status, --service uninstall.

Pair a device

Press Pair a device in Share (or run mojo-host --pair, or mojo-host --service pair for the background service). Enter the 6-digit code on the other device: Pair with a code on the connect screen of the desktop, Android and Apple apps, or mojo-client HOST --pair 123456. It works for 5 minutes, once. After that, that device connects without the password. Remove it from the paired list any time.

Privacy mode, recording and microphone

  • Privacy mode darkens the host's screens and locks its keyboard and mouse while you work; you still see everything. Windows shows a "being used remotely" curtain. Ctrl+Alt+Del on the host always works as a way out.
  • Record saves the session exactly as it streamed (video and sound, no re-encode) as a .ts file that VLC, mpv and editors open. Desktop: Videos/Mojo. Android: Movies/Mojo. iPhone and iPad: Files › On My iPhone › Mojo.
  • Microphone sends your voice to a virtual microphone on the host, so calls and voice chat there hear you. Linux hosts create "Mojo Microphone" automatically; Windows needs the free VB-CABLE and macOS the free BlackHole.

Over the internet

On a LAN, Mojo connects directly. Between networks, hosts register with a rendezvous server and get a 9-digit device ID:

mojo-host --password '…' --rendezvous relay.example.com
# logs: device_id=530 437 196

mojo-client 530437196 --password '…' --rendezvous relay.example.com

Both sides then try to reach each other directly through their NATs from the same UDP socket they will stream on. Most home routers allow this. When they don't (carrier-grade or symmetric NAT), traffic flows through the relay. Set MOJO_RENDEZVOUS instead of passing the flag every time.

Host a relay

Any small VPS works; introductions need almost no CPU. Relayed sessions use bandwidth equal to the stream bitrate, around 10–20 Mbps for 1080p60.

git clone https://github.com/moderniselife/MojoRDP && cd MojoRDP
docker build -f docker/relay.Dockerfile -t mojo-relay .
docker run -d --name mojo-relay --restart unless-stopped --network host \
  -v mojo-relay:/data mojo-relay --public-ip <VPS_PUBLIC_IP> --relay-ports 40000-40999

Open UDP 9460 for rendezvous and UDP 40000–40999 for relaying (two ports per relayed session). Without Docker, cargo build --release -p mojo-relay; the relay needs no FFmpeg or GPU.

Security

  • Every session is QUIC with TLS 1.3 between viewer and host. Relays forward packets they cannot read.
  • Each host has a persistent certificate. Viewers remember it on first connect and refuse a different one; --pin enforces a fingerprint up front.
  • Hosts require a password (a random one unless you set your own), check it in constant time, and lock out an address after repeated failures. --approve adds a per-viewer prompt.
  • Pairing uses SPAKE2 with a one-time 6-digit code, bound to the host's certificate so an impostor in the middle can't complete it. Each paired device gets its own token; the host keeps only a hash and can revoke it.
  • Device IDs are tied to the host's certificate: the relay refuses mismatched registrations and viewers verify the pairing.
  • Every network decoder is size-bounded and fuzz-tested.
  • Pass secrets through MOJO_PASSWORD instead of the command line so they don't show up in process lists.
  • Files received from a viewer can never overwrite existing files or leave the destination folder.
Accepting anyone requires the explicit --insecure-no-password flag, meant for isolated test rigs. The full model and known gaps are in SECURITY.md.

Performance

The viewer measures every frame from the moment the host started capturing it to the moment a decoded picture is ready. Medians on an RTX 3070 with host and viewer on the same machine:

CodecResolutionfpsMbpsEncodeDecodeCapture → decodedp99
H.2641280×7201449.41.5 ms1.0 ms2.6 ms3.1 ms
H.2641920×108014421.23.1 ms1.8 ms5.0 ms5.4 ms
H.2642560×144014437.75.7 ms3.2 ms9.3 ms12.3 ms
H.2643840×21606050.013.5 ms6.8 ms20.8 ms24.7 ms
HEVC1920×108014414.83.0 ms1.2 ms4.3 ms4.8 ms
HEVC2560×144014426.35.3 ms1.9 ms7.3 ms8.2 ms
HEVC1920×108024029.1––5.0 ms8.9 ms
HEVC2560×144024048.2––9.1 ms16.0 ms

Tuning

  • --codec h265 cuts bitrate by about a third at the same quality when both ends have HEVC hardware.
  • --bitrate caps each stream in kbps. By default Mojo picks a bitrate from resolution and frame rate, then adapts to loss and queueing.
  • If the viewer can't decode fast enough, the host lowers that stream's frame rate instead of letting frames queue up.
  • --vsync removes tearing at the cost of up to one frame of latency.
  • --no-hwdec forces software decoding (useful to rule out driver problems).
  • mojo-host --list-encoders shows which encoders this build can use; --encoder forces one.

Benchmark your own setup headlessly: mojo-client HOST --bench 10 prints a JSON report.

Bad networks

Mojo is tested against simulated loss, jitter, distance and bandwidth caps (scripts/netem-matrix.py). 1080p60 HEVC, 15 seconds per condition; latency includes the simulated network delay:

Networkfpsp50p95Frames lostKeyframes
Clean LAN60/604.7 ms5.6 ms00
1% packet loss60/604.7 ms5.2 ms20
5% packet loss59/605.7 ms6.2 ms40
10% bursty loss60/604.7 ms5.2 ms30
Wi-Fi jitter (2 ms ± 4 ms)60/6012.0 ms14.8 ms00
Same city (10 ms each way)60/6015.7 ms16.3 ms00
Cross-country (35 ms each way, 1% loss)59/6041.2 ms42.9 ms20
Bad hotel Wi-Fi (20 ms, 8 ms jitter, 3% loss)59/6029.5 ms31.7 ms20
Capped at 8 Mbit/s60/604.7 ms5.5 ms00

Forward error correction rebuilds most damaged frames. When one can't be rebuilt, the viewer tells the host the last frame it has; the encoder drops the lost references and the very next frame repairs the picture, so no keyframe burst is needed. Rate control watches both queueing delay and delivered throughput and starts gently, so on a capped link it settles just under the cap instead of filling the router's queue. Details in BENCHMARKS.md.

Command line

mojo-host

FlagDefaultWhat it does
--passwordrandomPassword viewers must give; a random one is printed if you don't set one. Env: MOJO_PASSWORD.
--approveoffAsk on the terminal (or in the app) before each viewer gets in.
--insecure-no-passwordoffAccept anyone who can reach the host. Test rigs only.
--bind0.0.0.0:9450Address and UDP port to listen on.
--sourceautoauto, x11, gnome, windows, macos or synthetic (test pattern).
--display$DISPLAYX display to capture (X11 only).
--synthetic-size1920x1080Resolution of the synthetic source.
--encoderautoForce an encoder, such as h264_nvenc or libx264.
--max-fps240Upper bound for any stream's frame rate.
--audioautoauto (system output), tone (440 Hz test) or none.
--rendezvousnoneRendezvous server for device-ID connections. Env: MOJO_RENDEZVOUS.
--no-advertiseoffDon't announce on the local network.
--config-dirOS config dirWhere the host identity lives. Env: MOJO_CONFIG_DIR.
--print-idPrint device ID and certificate fingerprint as JSON, then exit.
--list-encodersPrint encoders in this build, then exit.
--headless-display1920x1080@60With no monitor attached, create a virtual display of this size (and again if every display goes away). off disables it.
--pairShow a 6-digit pairing code for 5 minutes. --list-paired and --unpair ID manage paired devices.
--service ACTIONinstall [flags], uninstall, status or pair: run the host as a background service (Windows: as administrator).
--view-only, --no-clipboardoffViewers watch without control; don't sync the clipboard.

mojo-client

mojo-client HOST [flags], where HOST is an address, a host name, or a 9-digit device ID with --rendezvous. mojo-client discover lists hosts on the LAN.

FlagDefaultWhat it does
--passwordnoneHost password. Env: MOJO_PASSWORD.
--mirrorprimaryprimary, all or a display ID. Default only when no --virtual is given.
--virtual WxH@FPSCreate a virtual monitor on the host. Repeat for more.
--fps60Frame rate for mirrored monitors.
--codech264h264, h265 or av1.
--bitrate0 (auto)Per-stream bitrate cap in kbps.
--fullscreenoffOpen windows fullscreen.
--vsyncoffPresent on vsync (no tearing, up to a frame more latency).
--no-hwdecoffDecode in software.
--no-audiooffDon't play desktop audio.
--no-clipboardoffDon't sync the clipboard.
--video-cursoroffDraw the remote pointer into the video.
--relative-mouseoffStart with the mouse locked and raw movement (games).
--444offFull-resolution colour (4:4:4) for sharp text.
--wakeoffSend Wake-on-LAN to a remembered host before connecting.
--rendezvousnoneRendezvous server for device IDs. Env: MOJO_RENDEZVOUS.
--force-relayoffSkip the direct attempt and use the relay (testing).
--pinRequire this SHA-256 certificate fingerprint.
--forget-hostoffForget the remembered certificate for this host.
--send PATHUpload files or folders to the host's Downloads. Repeatable.
--bench SECONDSHeadless benchmark with a JSON report.
--dump PREFIXWith --bench: save one decoded frame per stream as PPM.

mojo-relay

FlagDefaultWhat it does
--bind0.0.0.0:9460Rendezvous address and UDP port.
--public-ipautoPublic IP to hand out for relay ports.
--relay-portsanyUDP port range for relay sockets, such as 40000-40999.
--relay-idle-secs60Close a relay after this long without traffic.
--no-relayoffIntroductions and hole punching only.

Platform support

The goal is identical features on Linux, Windows and macOS, on both ends. The matrix on the home page shows each feature's status per OS. In short:

  • Linux host: X11 and GNOME (Wayland or X11) tested end to end, including virtual monitors, input, audio, cursor, clipboard and files. KDE Wayland is planned.
  • Windows host and viewer: tested in a Windows 11 VM: DXGI capture, SudoVDA virtual monitors, SendInput including Unicode text, folder drops, the desktop app and the installer. Software encoding is used when there's no GPU. The D3D11 zero-copy path is behind MOJO_WIN_GPU=1.
  • macOS host and viewer: tested in a macOS 14 VM: ScreenCaptureKit capture, VideoToolbox encoding, CGEvent input and Unicode text, the permissions flow. Virtual monitors (CGVirtualDisplay) still need testing on a real Mac.
  • Android and Android TV: tested on emulators against a Linux host: streaming, touch, keyboard, controllers, reconnects.
  • iPhone, iPad and Apple TV: the app typechecks against the iOS SDK and runs (as a Mac Catalyst build) streaming from a macOS host. Device builds need Xcode.

Troubleshooting

The viewer says the host certificate changed

Either the host was reinstalled or its config folder was deleted, or someone is impersonating it. If you know it was reinstalled, connect once with --forget-host.

Virtual monitors look sheared or stretched on X11

Some compositors (xfwm4's) don't handle outputless RandR monitors. Disable compositing, or use a window manager such as Openbox. GTK desktops may ignore RandR monitors that have no output; the GNOME backend avoids this entirely.

Black screen or no input on macOS

Check Screen Recording and Accessibility for the host in System Settings → Privacy & Security, then restart the host. macOS only applies those permissions to new processes.

Hardware encoding isn't used

Run mojo-host --list-encoders. Mojo tries hardware encoders first and checks each with a test frame before using it; anything that fails falls back to x264. The host log names the encoder each stream opened.

Can't connect across the internet

Make sure both sides use the same --rendezvous server and that UDP 9460 (and the relay port range) are open on it. --force-relay tests the relay path on its own.

Architecture

Mojo is a Rust workspace: mojo-proto (wire protocol, forward error correction, clock sync), mojo-net (QUIC, certificates, congestion control, discovery, clipboard and file transfer), mojo-codec (FFmpeg hardware encode and decode, colour conversion, Opus), mojo-host, mojo-client and mojo-relay, plus the Tauri desktop app.

Each monitor runs a two-stage pipeline on the host: capture and colour conversion on one thread, upload, encode and send on another, dropping frames rather than queueing them. Video travels as QUIC datagrams with Reed-Solomon parity; control, input and files use reliable QUIC streams. The viewer reassembles frames within an 8 ms reorder window, decodes on the GPU and renders with wgpu.

Read the full design in ARCHITECTURE.md and what's next in the roadmap.