Skip to content

Linux port: adopt structure, tests, jobs, JPEG, wss, CJK typing from jinwei-pikmin/linux-rust-port #32

Description

@chaodu-agent

Context

jinwei-pikmin/instance-mcp linux-rust-port (0ed709c, 2026-09-28) is an independent Rust port of the daemon. Built on rpi1: 3 m 17 s cold, 5 MB binary, cargo test 54/54. It is complementary to our poc/reverse-attach-linux/ (#29, #30): it has the ADR-shaped structure and several capabilities we lack; we have the wlroots desktop backend (its own comment: "wlroots compositors (sway, labwc) … need a grim/ydotool backend, not written yet"; on rpi1 it lists only sys_info exec*), the MCP_UPSTREAM browser proxy (theirs ⏳), and the deployed/verified path (lent session, Connect Screens pane, Pi headless seat).

This issue tracks what we adopt from it into our code, as PRs we own. Each item is one PR, roughly in this order. Baseline for "done": smoke.sh still passes on rpi1 and in CI, the lent kiro-1040 session and the Connect Screens pane keep working.

Items

1. Structure: split main.rs into modules behind a Desktop trait (#18 shape)

Today: one 1.9k-line main.rs. Target layout, mirroring theirs so a later convergence is a diff not a rewrite:
auth.rs · http.rs · mcp.rs (dispatch + upstream proxy) · attach/{mod,client}.rs · tools/{sysinfo,screen,bash,input}.rs · platform/desktop.rs (trait: capture, pointer, key, displays) · platform/linux/wlroots.rs (grim/wlrctl/wtype — what we have).
Acceptance: behaviour-preserving; cargo clippy -D warnings; smoke.sh 38/38.

2. Unit tests, especially the attach state machine

They have 54 tests, 13 on attach disposition/backoff/cancel. We have 0 unit tests and rely on smoke.sh + the #24 conformance vectors. Port the disposition/backoff/replacement/DELETE cases as #[test]s against ra_vectors.json so CI does not need the mock runtime for the core logic. Keep smoke.sh as the integration layer.
Acceptance: ≥ the 23 #24 vectors + cancel/replace/deadline cases covered; runs in the linux-hands-node CI job.

3. Concurrent replies on the attach socket

Theirs: each inbound frame is answered on its own task and replies go out through an mpsc by JSON-RPC id, so a slow screenshot (≈1 s on a Pi) does not block sys_info. Ours answers frames serially inside the read loop. With std threads: spawn per request, single writer thread draining a channel; preserve the 1 s read-timeout cancel path and the Close-frame flush.
Acceptance: a bash sleep 3 and a sys_info sent back-to-back over the mock return sys_info first.

4. Background jobs: exec_start / exec_poll / exec_list / exec_cancel

Same contract as the Swift daemon (README table): job_id, tee'd .out/.err under $XDG_STATE_HOME/oab-instance-mcp/jobs/, byte-offset incremental poll, setsid + killpg, timeout_secs=0 = no timeout, 10 most recent finished kept, logs GC'd after 7 days. Their tools/jobs.rs is a good reference (background writers survive exec; see their last commit).
Acceptance: start a 30 s job, poll twice with offsets, cancel, poll once more → killed, exit code present; smoke cases added.

5. In-process JPEG via the image crate

Debian grim has no libjpeg, so every frame is PNG (~2 MB at 1080p; Connect polls ≤2 FPS → 4 MB/s). Decode grim's PNG and encode JPEG at the requested quality (Connect sends 0.6; accept both 0–1 and 1–100 like the Swift daemon). Keep PNG when asked.
Acceptance: Connect-shaped call returns image/jpeg ≤ 300 KB at 1080p q0.6; structuredContent.points {width,height} present (Connect reads it).

6. wss:// mint and the TLS revoke fast path

Dial already works over TLS (rustls). Missing: mint over https:// (our http_post is http-only) and the 1 s read-timeout cancel is applied only to MaybeTlsStream::Plain, so DELETE /attach/{id} on a wss:// runtime takes effect at the next inbound frame. Either use rustls directly or switch the tiny client to ureq/hyper — pick whichever keeps the dependency count low.
Acceptance: mock runtime behind a self-signed TLS listener (add --tls to mock_runtime.py): mint + dial + DELETE within 1 s.

7. systemd unit hardening

KillMode=process (apps opened via bash survive a daemon restart), PartOf=graphical-session.target, Documentation=. Apply in scripts/install-linux.sh and docs/linux-setup.md.

8. --log-requests and Swift-style flags

One log line per session open / tools/call / deny (Swift agent.log shape) behind a flag; accept --allow-login, --token-file, --insecure-local, --no-attach, --no-desktop, --host/--port as CLI flags in addition to the env vars, so Connect/Remote docs apply to both platforms unchanged.

9. Unicode / CJK typing

wtype handles unicode natively for most apps, but their IBus ctrl+shift+u + hex + space path covers GTK/Qt apps where virtual-keyboard unicode fails. Add it as a fallback in key.type when the text has non-ASCII and wtype reports failure or the app is known to drop it. Also accept cmd as an alias for ctrl in key.press like theirs.
Acceptance: type 你好 into the Chromium URL bar on rpi1 and read it back via screenshot.

10. Stuck-modifier safety

Their review-fix commit releases modifiers if a press fails mid-sequence. Our tool_key -M … -k … -m … argv is atomic per wtype call, but a wtype failure after -M could leave a modifier down in the compositor. Wrap with a best-effort wtype -m <mod> on error.

Not adopting

  • xdg-desktop-portal RemoteDesktop backend: GNOME/KDE only, needs a consent dialog; our targets are wlroots seats (Pi OS labwc, sway). Keep as a possible second Desktop impl if a GNOME node ever appears — item 1 makes that a drop-in.
  • Sandbox without a shell: decided the other way in Generalize "lend my Mac" to a hands-node registry (macmini + rpi1 + ...) #27 (bash in both profiles).

Convergence

Once items 1–3 land our tree has the same shape as theirs; at that point the sensible end state is one Rust crate with two Desktop impls (portal + wlroots) and the upstream proxy. Whether that happens by us merging their portal backend or them merging ours is a conversation for when their PR appears; this issue is about making our side ready for it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions