1. Hybrid transport: UDP for input, TCP for bulk
1. Hybrid transport: UDP for input, TCP for bulk
Rationale: input tolerates loss and demands lowest latency; a dropped
mouse move is irrelevant a millisecond later, so UDP avoids head-of-line
blocking. Clipboard/files need reliable ordered delivery → TCP.
Consequences: two code paths and two sets of keys, but the fast path never
waits on the slow path. This is the core latency decision.
2. Rust
2. Rust
Rationale: predictable low-latency (no GC pauses), single static binary,
easy cross-compilation, strong cross-platform input crates
(rdev/enigo/arboard). Consequences: contributors need Rust; end users get
a tiny dependency-free binary.
3. Dedicated blocking UDP socket, not async
3. Dedicated blocking UDP socket, not async
Rationale: an async runtime adds scheduler jitter on the hot path. A
dedicated thread with a blocking socket has the most predictable latency.
Consequences:
transport.rs is intentionally synchronous; measured ~6 µs
one-way overhead confirms it.4. Relative mouse motion, not absolute
4. Relative mouse motion, not absolute
Rationale: absolute coordinates couple the two machines’ screen
geometries and break on differing resolutions/DPI. Relative deltas “just
work” and are tiny on the wire. Consequences: the client integrates deltas
to know its cursor position (
cursor.rs).5. Portable Key enum, never raw scancodes
5. Portable Key enum, never raw scancodes
Rationale: macOS and Windows use different raw keycodes; forwarding them
raw would mistranslate keys. We map native → portable
Key → native (the
Synergy/Deskflow approach). Consequences: keymap.rs maintains the
translation tables; unmapped keys degrade to Unknown and are dropped.6. Per-tick event coalescing
6. Per-tick event coalescing
Rationale: high mouse polling rates (1000+ Hz) would flood the network and
cause “jumpiness” when polling exceeds display refresh. Coalescing a tick’s
events into one packet fixes both. Consequences:
InputMsg::Events(Vec<..>) carries a batch.8. WireGuard-style crypto: X25519 + PSK + ChaCha20-Poly1305
8. WireGuard-style crypto: X25519 + PSK + ChaCha20-Poly1305
Rationale: ephemeral ECDH gives forward secrecy; a PSK mixed into the
HKDF salt authenticates peers without a PKI (MITM-resistant); ChaCha is fast
in software so encryption costs ~20 ns per input packet. See
Security. Consequences: users share a passphrase; no
certificate infrastructure needed.
9. Nonce counters are implicit where possible
9. Nonce counters are implicit where possible
Rationale: transmitting a nonce per packet wastes bytes. TCP is ordered →
both peers keep in-sync counters (transmit nothing). UDP reuses the packet
seq as the counter. Consequences: never reuse a (key, nonce) pair;
per-direction keys guarantee this.10. mDNS for discovery, PSK for identity
10. mDNS for discovery, PSK for identity
Rationale: typing IPs is bad UX; mDNS finds peers automatically. Security
is unaffected because discovery only yields a candidate address — the PSK
handshake still authenticates. Consequences: an imposter can advertise the
service but cannot complete the handshake.
11. native / tray feature split
11. native / tray feature split
Rationale: keep a testable, permissionless core that CI can build and
unit-test headlessly; isolate heavy GUI + OS deps behind opt-in flags.
Consequences: 23 unit tests run without a display; the GUI is built only
for releases.
12. No-argument launch opens the tray
12. No-argument launch opens the tray
Rationale: a double-clicked
.app/.exe passes no CLI args; end users
expect a GUI, not a help dump. Consequences: main.rs routes
no-subcommand → tray::run() (or help if built without tray).13. Ship one-click installers, not source
13. Ship one-click installers, not source
Rationale: end users won’t install Rust. CI builds a universal macOS
.dmg and a Windows .exe installer on every tag and publishes them to
GitHub Releases. Consequences: see Releasing;
unsigned until certificates are acquired.Known open questions
- Sliding-window anti-replay for UDP (currently monotonic-counter only).
- Multiple simultaneous clients (needs swappable per-session UDP cipher).
- Code signing / notarization once certificates are available.