Phase 2: status, TOML layouts, clipboard formats, multi-monitor, loopback demo, systemd - #14
Merged
Merged
Conversation
Phase 2 work exposing daemon internals that already existed. status (#13): the daemon now writes a StatusSnapshot (peers with name/state/latency + which peer holds focus) to a JSON file in the runtime dir on a 2s timer, and `cross-control status` renders it as a table. Latency is measured, not stubbed: each peer is pinged on the same cadence and the round-trip time recorded when the Pong returns, via a new per-session LatencyTracker. Mirrors the PID-file pattern rather than a full IPC socket; the rendered output is stable if that's swapped later. layouts (#9): Config::validate() runs at load and rejects layouts that would silently misroute the cursor — empty/duplicate screen names, a screen named like this machine, two screens on one local edge, self-loop adjacency, one screen with two neighbors on an edge, and adjacency blocks that never connect back to this machine. Multi-hop screens introduced only via [[screen_adjacency]] are accepted (reachability to a fixpoint). examples/config.toml now documents the format with a worked multi-hop example. DaemonEvent::SessionReady boxes its PeerSession payload to keep the enum variants balanced after the session grew a latency tracker. 84 tests pass (was 70); clippy clean under -D warnings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01By4dqQfiNPtKJMCG16UkDu
loopback example: `cargo run -p cross-control-daemon --example loopback` runs two daemons in one process on 127.0.0.1 and drives a full session — handshake, device announce, edge crossing, input forwarding — with no second machine, no root, and no display. It asserts the forwarded keypress lands on the receiver and narrates each step. Built with `--features linux -- --real`, the receiver injects into a real uinput device so the cursor visibly moves. Everything between the two physical ends is now reproducible on one box; only real evdev capture and a live compositor remain for hardware bring-up. systemd (#11): systemd/cross-control.service is now a correct, documented user unit — dropped network-online.target ordering (not available in the user manager; the daemon reconnects on its own), added install and prerequisite comments. Reconciled the divergent unit install.sh generated so the two stay in sync, and pinned its ExecStart to the resolved install path. README gains a "Run as a service" section (enable, logs, lingering, input/uinput prerequisites) and a "Try it on one machine" pointer to the loopback example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01By4dqQfiNPtKJMCG16UkDu
The clipboard path is no longer text-only. ClipboardProvider gains get_format(format). The arboard backend reads and writes HTML (get().html() / set_html) and images, converting between the wire's PNG bytes and the raw RGBA the platform clipboard uses (via the `image` crate, png feature only). available_formats() probes text, HTML, and image. On hand-off the controlled side now requests the richest format the controller offers that it can apply — image, else HTML, else plain text — instead of always taking plain text. Payloads over clipboard.max_size (default 10 MiB) are dropped with a warning on both send and receive rather than put on the control stream; chunked streaming of large images over a dedicated QUIC stream remains a follow-up. 92 tests pass (was 84): PNG encode/decode round-trip, mock get_format for html/png, and daemon integration tests for HTML and image hand-off. Clippy clean under -D warnings; clipboard crate still builds mock-only (image is gated behind the arboard feature). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01By4dqQfiNPtKJMCG16UkDu
Introduce DisplayLayout { monitors: Vec<ScreenGeometry> } describing a
machine's full monitor arrangement, configured via a [[monitors]] list
(each with width, height, and top-left x/y offset). When no monitors are
listed the daemon falls back to a single monitor sized by
daemon.screen_width/screen_height, so existing configs are unchanged.
The daemon derives the union bounding box of all monitors and runs all
cursor clamping and edge/barrier detection against it — so on a
dual-monitor machine the cursor now traverses the whole combined desktop
before crossing to another machine, instead of stopping at the first
monitor's inner edge. The crossing logic itself is untouched; only the
screen bounds it operates on now come from the layout.
Hello/Welcome/ScreenUpdate carry the DisplayLayout instead of a single
ScreenGeometry so peers see each other's real monitor arrangement, and
sessions store the remote layout. Config validation rejects a monitor
with a zero dimension.
Protocol version bumped to 0.2: the handshake screen field changed type,
a breaking wire change within the pre-1.0 alpha (major stays 0).
100 tests pass (was 92): DisplayLayout bounding-box/edge cases and
roundtrip, config display_layout fallback + monitor validation. Clippy
clean under -D warnings; loopback example still green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01By4dqQfiNPtKJMCG16UkDu
CI's stable toolchain moved to Rust 1.96, whose clippy flags `.map(..).unwrap_or(false)` on a Result. Use `is_ok_and` instead. Behaviour is identical; this is pre-existing code surfaced by the newer lint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01By4dqQfiNPtKJMCG16UkDu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A batch of Phase 2 work — closing the solo-codeable items and adding a hardware-free way to exercise the full KVM path. Each item exposes or completes machinery that mostly already existed in the daemon; the crossing/routing logic is unchanged throughout.
Changes
status(cross-control status: show peers, latency, current focus #13) — the daemon writes aStatusSnapshot(peers, state, latency, focus) to a JSON file on a 2s timer;cross-control statusrenders it as a table. Latency is real: each peer is pinged on the same cadence and RTT recorded onPong.Config::validate()rejects layouts that would silently misroute the cursor (dup/empty names, two screens on one edge, self-loops, dead adjacency islands) while accepting multi-hop screens via reachability to a fixpoint.examples/config.tomldocuments the format.ClipboardProvider::get_format; the arboard backend reads/writes HTML and PNG (converting between wire PNG and raw RGBA via theimagecrate). Hand-off requests the richest offered format; payloads overclipboard.max_sizeare dropped with a warning.DisplayLayout { monitors: Vec<ScreenGeometry> }, configured via[[monitors]]. Edge detection runs against the union bounding box, so the cursor traverses the whole combined desktop.Hello/Welcome/ScreenUpdatecarry the layout; protocol bumped to 0.2. Falls back to a single monitor when unset.cargo run -p cross-control-daemon --example loopbackruns two daemons on127.0.0.1and drives handshake → crossing → input forwarding end to end, no second machine/root/display.--features linux -- --realinjects into a real uinput device.network-online.target), reconciled the copyinstall.shgenerates, and added a README "Run as a service" section.Testing
cargo fmt --all -- --checkcargo clippy --workspace -- -D warningscargo test --workspace— 100 pass, 2 ignored (need a display/multicast), up from 64 at the start of the branchcargo run -p cross-control-daemon --example loopbackruns green end to endRelated Issues
Closes #6
Closes #7
Closes #8
Closes #9
Closes #11
Closes #13
🤖 Generated with Claude Code