Goals, in priority order
The big picture
Why two channels?
Input and bulk data have opposite requirements:| Input (mouse/keys) | Bulk (clipboard/files) | |
|---|---|---|
| Needs | lowest latency | reliable, ordered delivery |
| Tolerates loss? | yes (next move supersedes) | no |
| Transport | UDP | TCP |
| Why | no head-of-line blocking | a lost byte corrupts the paste |
Crate layout
protocol is deliberately tiny and platform-agnostic so it can be unit-tested
anywhere and reused (e.g. a future mobile client). app holds everything that
touches the OS or the network.
Module map (crates/app/src)
| Module | Responsibility | Native? |
|---|---|---|
main.rs | CLI parsing (clap); dispatch; no-arg → tray | — |
transport.rs | UDP InputChannel: framing, seq numbers, encryption | — |
bench.rs | Loopback latency benchmark (the primary metric) | — |
bulk.rs | TCP BulkConn: length-prefixed frames, handshake, encryption | — |
filexfer.rs | Chunked file send + reassembly (received/) | — |
config.rs | TOML settings + monitor manager (machine/edge layout) | — |
edge.rs | Server-side screen-edge hit detection | — |
cursor.rs | Client-side cursor integration for auto-return | — |
control.rs | Shared control state (active? entry edge) | — |
capture.rs | rdev global grab: capture + suppress local input | Yes |
emit.rs | enigo input injection | Yes |
keymap.rs | rdev ⇄ portable Key ⇄ enigo translation | Yes |
clipboard.rs | Bidirectional clipboard sync (text + image) | Yes |
discovery.rs | mDNS advertise/browse | Yes |
run.rs | Wires everything into serve / connect loops | Yes |
tray.rs | Menu-bar / system-tray GUI (tray feature) | Yes, plus tray |
--no-default-features, which is what CI
uses for fast, permissionless unit tests.
Feature flags
| Flag | Pulls in | Purpose |
|---|---|---|
native (default) | enigo, rdev, arboard, mdns-sd | real input/clipboard/discovery |
tray | native + tray-icon, tao | the GUI menu-bar/tray app |
How a keypress reaches the other machine
Capture & swallow
capture.rs (rdev grab) sees the key. If control is on the client, it maps
the native key to a portable Key, forwards it, and swallows it locally.Coalesce & seal
run.rs::run_server_input coalesces the tick’s events into one
InputMsg::Events, and transport.rs seals + sends it over UDP.Decrypt & de-dup
On the client,
transport.rs decrypts, drops stragglers by sequence number,
and hands the batch to run.rs::connect.Control handoff (the “KVM” part)
Two ways control moves from server to client:- Automatic edge switch:
edge.rsdetects the server cursor hitting a bordered screen edge (from the monitor-manager layout) →Control.active = trueand the entry position is recorded. - F12 hotkey: manual toggle, always available as a fallback.
cursor.rs detects the cursor crossing back
and sends Leave; the server clears active. See Decision #7.
Next
Wire protocol
The exact message types on each channel.
Latency
Where the microseconds actually go.