Shared clipboard: wire protocol, host backends (pf-clipboard) + macOS client #4

Merged
enricobuehler merged 4 commits from feat/shared-clipboard-v2 into main 2026-07-17 11:43:54 +00:00
Owner

Port of the stranded feat/shared-clipboard work (authored 2026-07-12 on home-worker-5, pre-W6 base) onto current main, reshaped for the W6 crate decomposition. Design: punktfunk-planning/design/clipboard-and-file-transfer.md.

Commits

  • Phase 0 (wire + client-core) — cherry-picked; deviations from the original: ABI v6 → v8 (main took v6/v7 for reanchor gate + typed rejection), CLIP_CANCELLED_CODE 0x60 → 0x70 (main's pairing-rejection close block claimed 0x60–0x67), Negotiated.host_caps coexists with main's tuple plumbing (needed across ready_tx for punktfunk_connection_host_caps).
  • Phase 1 host (Linux) + Phase 3 (Windows) — backends land as a pf-clipboard subsystem crate (the pf-inject/pf-capture pattern) instead of growing punktfunk-host back out; the old ~340-line punktfunk1.rs integration re-implemented against the native.rs/control.rs/handshake.rs split.
  • libc fix — the Linux backends call libc:: fully-qualified (pipe2/poll/fcntl); caught by the Linux leg.

Opt-in default OFF (PUNKTFUNK_CLIPBOARD).

Verification — all green at da057877

  • PR CI: all 4 workflow runs (full Linux workspace: clippy -D warnings, tests, C-ABI harness, header-freshness gate)
  • Linux (.21): clippy clean; core 161/161; pf-clipboard 7/7; e2e session test green (clipboard_control_and_fetch_decline_over_session — real QUIC client↔host: cap advertise → ClipState ack → fetch decline)
  • Windows (.173): clippy -D warnings clean under x86_64-pc-windows-msvc (windows-host.yml has no PR trigger — checked out-of-band)
  • macOS: core 160/160, clippy clean

Draft until: on-glass smoke against a real compositor + the macOS client (in progress on this branch).

🤖 Generated with Claude Code

Port of the stranded `feat/shared-clipboard` work (authored 2026-07-12 on home-worker-5, pre-W6 base) onto current main, reshaped for the W6 crate decomposition. Design: `punktfunk-planning/design/clipboard-and-file-transfer.md`. ## Commits - **Phase 0 (wire + client-core)** — cherry-picked; deviations from the original: ABI **v6 → v8** (main took v6/v7 for reanchor gate + typed rejection), `CLIP_CANCELLED_CODE` **0x60 → 0x70** (main's pairing-rejection close block claimed 0x60–0x67), `Negotiated.host_caps` coexists with main's tuple plumbing (needed across `ready_tx` for `punktfunk_connection_host_caps`). - **Phase 1 host (Linux) + Phase 3 (Windows)** — backends land as a **`pf-clipboard` subsystem crate** (the pf-inject/pf-capture pattern) instead of growing `punktfunk-host` back out; the old ~340-line `punktfunk1.rs` integration re-implemented against the `native.rs`/`control.rs`/`handshake.rs` split. - **libc fix** — the Linux backends call `libc::` fully-qualified (pipe2/poll/fcntl); caught by the Linux leg. Opt-in default OFF (`PUNKTFUNK_CLIPBOARD`). ## Verification — all green at `da057877` - **PR CI**: all 4 workflow runs ✅ (full Linux workspace: clippy `-D warnings`, tests, C-ABI harness, header-freshness gate) - **Linux (.21)**: clippy clean; core 161/161; pf-clipboard 7/7; **e2e session test green** (`clipboard_control_and_fetch_decline_over_session` — real QUIC client↔host: cap advertise → ClipState ack → fetch decline) - **Windows (.173)**: clippy `-D warnings` clean under `x86_64-pc-windows-msvc` (`windows-host.yml` has no PR trigger — checked out-of-band) - **macOS**: core 160/160, clippy clean Draft until: on-glass smoke against a real compositor + the macOS client (in progress on this branch). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
enricobuehler added 2 commits 2026-07-17 11:03:55 +00:00
The portable shared-clipboard plane in punktfunk-core, all behind the `quic`
feature (design/clipboard-and-file-transfer.md §3):

- Control messages 0x40–0x44 (ClipControl / ClipOffer / ClipFetch...) and the
  HOST_CAP_CLIPBOARD capability bit, negotiated in the Welcome caps.
- Per-transfer QUIC bi-streams ("PKFs" magic) for lazy fetch of offered content,
  with ClipFetchHdr status/size framing (quic::clipstream).
- The §3.5 portable wire-MIME vocabulary (text/plain;utf-8, text/html, text/rtf,
  image/png) shared by both ends.
- Client-side clipboard task (client.rs) + C ABI surface bumped to v8 (abi.rs,
  regenerated include/punktfunk_core.h).
- Loopback transport tests (quic::tests).

No OS clipboard integration yet — that is the host backends (Phase 1/3) and the
macOS client (Phase 1).

Ported from feat/shared-clipboard (af3a7d8c, pre-W6 base) onto current main;
three deliberate deviations from the original commit:
- ABI v6 → v8: main took v6 (reanchor gate) and v7 (typed connect rejection)
  in the meantime; the clipboard C surface re-lands as v8.
- CLIP_CANCELLED_CODE 0x60 → 0x70: main's pairing-rejection close codes claimed
  the 0x60–0x67 block; the vocabularies stay disjoint on purpose.
- Negotiated.host_caps coexists with main's 6-tuple host_caps plumbing: main
  needs the worker-local copy for gamepad snapshots, the clipboard path needs it
  across ready_tx to build the NativeClient handle (punktfunk_connection_host_caps).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(clipboard): Linux + Windows host clipboard backends as the pf-clipboard crate (Phase 1 host + Phase 3)
ci / web (pull_request) Successful in 1m9s
apple / swift (pull_request) Successful in 1m19s
apple / screenshots (pull_request) Has been skipped
ci / docs-site (pull_request) Successful in 1m30s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 5m16s
ci / bench (pull_request) Successful in 6m8s
ci / rust (pull_request) Failing after 7m6s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 6m33s
android / android (pull_request) Successful in 12m39s
391f8fb9f7
The host half of the shared clipboard (design/clipboard-and-file-transfer.md §4),
ported from feat/shared-clipboard (6bd8c18b) into the post-W6 crate shape: the
backends land as a pf-clipboard subsystem crate (the pf-inject/pf-capture
pattern) instead of growing punktfunk-host back out, and the ~340-line
punktfunk1.rs integration is re-implemented against the native.rs/control.rs
split that replaced it.

pf-clipboard:
- host::wayland — ext-data-control-v1 (KWin / wlroots / Sway / Hyprland).
- host::mutter — GNOME via Mutter's *direct* org.gnome.Mutter.RemoteDesktop
  clipboard (no data-control at any GNOME version; the xdg portal needs an
  interactive grant a headless host can't answer).
- host::windows + host::winfmt — Win32 clipboard on a hidden message-loop
  window: WM_CLIPBOARDUPDATE listener + OLE delayed rendering (WM_RENDERFORMAT)
  for text / CF_HTML / RTF / PNG.
- host::session — the backend-agnostic coordinator bridging HostClipboard to
  the QUIC clipboard plane (offers, fetch accept-loop, remote offers, pastes).
- A portable facade (policy / enabled / cap_advertised / ClipCoordCmd / start /
  spawn_decline_loop) so the orchestrator compiles cfg-free on every platform;
  ClipCoordCmd moves into the crate (it was host-owned before).

punktfunk-host glue:
- handshake.rs advertises HOST_CAP_CLIPBOARD via pf_clipboard::cap_advertised.
- serve_session starts the coordinator (gated on a real compositor — the
  synthetic source stays out of the session clipboard) and spawns the
  CLIP_FETCH_UNAVAILABLE decline loop when the policy is on but no backend bound.
- control.rs gains the ClipControl/ClipOffer arms + the host-offer forward
  branch, and the e2e session test (cap advertise → ClipState ack with
  BACKEND_UNAVAILABLE → fetch decline) rides in native.rs's tests.

Still opt-in default OFF (PUNKTFUNK_CLIPBOARD). Remaining: the macOS client
(design §5) — then this becomes user-visible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
enricobuehler added 1 commit 2026-07-17 11:07:17 +00:00
fix(pf-clipboard): declare the libc dep the Linux backends call fully-qualified
ci / web (pull_request) Successful in 54s
ci / docs-site (pull_request) Successful in 1m15s
apple / swift (pull_request) Successful in 1m15s
apple / screenshots (pull_request) Has been skipped
ci / bench (pull_request) Successful in 6m0s
android / android (pull_request) Successful in 12m29s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 5m24s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 6m3s
ci / rust (pull_request) Successful in 21m5s
da0578771e
wayland.rs (pipe2/poll on the paste pipes) and mutter.rs (fcntl un-nonblocking
on the transfer fd) reference libc:: inline — caught by the Linux leg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
enricobuehler added 1 commit 2026-07-17 11:40:51 +00:00
feat(apple/clipboard): macOS client half of the shared clipboard (Phase 1 §5)
ci / web (pull_request) Successful in 48s
ci / docs-site (pull_request) Successful in 53s
apple / swift (pull_request) Successful in 1m16s
apple / screenshots (pull_request) Has been skipped
android / android (pull_request) Has been cancelled
ci / rust (pull_request) Has been cancelled
ci / bench (pull_request) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (pull_request) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (pull_request) Has been cancelled
5d0e23d6a5
The NSPasteboard bridge completing Phase 1 (design/clipboard-and-file-transfer.md
§5) — with the host backends on this branch, copy/paste now crosses the wire in
both directions on macOS. Lazy in both directions:

- PunktfunkConnection grows the clipboard plane: its own clipboardLock (close()
  joins it like the other pullers), hostCaps/hostSupportsClipboard from the
  Welcome, the typed ClipEvent vocabulary, and the six ABI wrappers
  (clipControl/clipOffer/clipFetch/clipServe/clipCancel/nextClipboard — borrowed
  event payloads copied out before the next poll).
- ClipboardSync (PunktfunkKit, macOS-only): one drain thread bridging
  NSPasteboard.general ↔ the QUIC clipboard plane. Local copies announce format
  lists via a 500 ms changeCount poll (+ immediate on app activation); bytes
  leave only on a host FetchRequest, answered from the live pasteboard and
  seq-guarded against staleness. Host copies install one NSPasteboardItem whose
  data provider fires only when a Mac app actually pastes, then blocks its
  provider thread (never main) on a 10 s-bounded fetch. Concealed/Transient
  pasteboards (password managers) are never announced; our own writes are
  changeCount-suppressed (§3.4). Text/RTF/HTML/PNG; files ride Phase 2.
- UI: per-host "Share clipboard with this host" toggle (StoredHost.clipboardSync,
  optional for saved-JSON forward-compat — wire-format tests extended), a
  mid-session Share/Stop Sharing Clipboard item in the Stream menu (⌃⌥⇧C,
  greyed without HOST_CAP_CLIPBOARD), SessionModel owning the lifecycle
  (start on streaming after the trust gate, drain joined off-main on teardown).

swift build + swift test green (macOS). Requires the ABI v8 xcframework
(scripts/build-xcframework.sh).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
enricobuehler changed title from WIP: Shared clipboard: wire protocol + host backends (pf-clipboard) to Shared clipboard: wire protocol, host backends (pf-clipboard) + macOS client 2026-07-17 11:43:41 +00:00
enricobuehler scheduled this pull request to auto merge when all checks succeed 2026-07-17 11:43:50 +00:00
enricobuehler merged commit e8b64ffe43 into main 2026-07-17 11:43:54 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#4