Skip to content

Phase 2: status, TOML layouts, clipboard formats, multi-monitor, loopback demo, systemd - #14

Merged
cptfinch merged 5 commits into
mainfrom
claude/project-status-next-steps-e5n7x8
Jul 7, 2026
Merged

cptfinch merged 5 commits into
mainfrom
claude/project-status-next-steps-e5n7x8

Conversation

@cptfinch

@cptfinch cptfinch commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

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

  • Live status (cross-control status: show peers, latency, current focus #13) — the daemon writes a StatusSnapshot (peers, state, latency, focus) to a JSON file on a 2s timer; cross-control status renders it as a table. Latency is real: each peer is pinged on the same cadence and RTT recorded on Pong.
  • TOML layout validation (Configurable layouts in TOML #9) — 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.toml documents the format.
  • Clipboard HTML + images (Clipboard backend — HTML #6, Clipboard backend — images (size cap + streaming) #7) — ClipboardProvider::get_format; the arboard backend reads/writes HTML and PNG (converting between wire PNG and raw RGBA via the image crate). Hand-off requests the richest offered format; payloads over clipboard.max_size are dropped with a warning.
  • Multi-monitor (Multi-monitor support per machine #8) — new 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/ScreenUpdate carry the layout; protocol bumped to 0.2. Falls back to a single monitor when unset.
  • Loopback demo (de-risks Real two-machine bring-up on physical hardware #1) — cargo run -p cross-control-daemon --example loopback runs two daemons on 127.0.0.1 and drives handshake → crossing → input forwarding end to end, no second machine/root/display. --features linux -- --real injects into a real uinput device.
  • systemd user service (systemd user service that just works #11) — fixed the unit (dropped the user-scope-invalid network-online.target), reconciled the copy install.sh generates, and added a README "Run as a service" section.

Testing

  • cargo fmt --all -- --check
  • cargo clippy --workspace -- -D warnings
  • cargo test --workspace — 100 pass, 2 ignored (need a display/multicast), up from 64 at the start of the branch
  • cargo run -p cross-control-daemon --example loopback runs green end to end

Related Issues

Closes #6
Closes #7
Closes #8
Closes #9
Closes #11
Closes #13

🤖 Generated with Claude Code

claude added 4 commits July 3, 2026 23:24
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
@cptfinch cptfinch changed the title feat: live status readout (#13) and TOML layout validation (#9) Phase 2: status, TOML layouts, clipboard formats, multi-monitor, loopback demo, systemd Jul 7, 2026
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
@cptfinch
cptfinch merged commit 9e9fd2a into main Jul 7, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants