The shared clipboard now works on iPhone and iPad #250
Merged
enricobuehler
merged 1 commits from 2026-08-15 16:15:15 +00:00
worktree-worktree-ios-clipboard into main
1
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8740f48c92 |
feat(apple): the shared clipboard now works on iPhone and iPad
ci / web (pull_request) Successful in 1m11s
ci / docs-site (pull_request) Successful in 1m16s
apple / swift (pull_request) Successful in 2m2s
apple / distribute (pull_request) Skipped
apple / screenshots (pull_request) Skipped
ci / bun-nix (pull_request) Successful in 4m29s
ci / rust-arm64 (pull_request) Successful in 5m55s
ci / rust (pull_request) Failing after 14m20s
The clipboard bridge was written against NSPasteboard and gated `#if os(macOS)`, so the iOS half of the same universal app had a per-host toggle it could not show and a wire plane it never opened — even though the core's clipboard ABI is in every slice of the framework and nothing below the pasteboard was platform-specific. Rather than keep a second copy of the subtle half (one drain thread, offer sequence numbers, the pending fetches a blocked paste waits on, echo suppression), that logic moves into a `ClipboardPasteboard` seam and stays shared. What genuinely differs is small and lives in two adapters: AppKit fulfils a paste by blocking a provider thread, UIKit by answering an NSItemProvider load handler; AppKit transcodes images through NSImage, UIKit through UIImage. macOS behaviour is unchanged — same poll interval, same timeouts, same lock discipline. Two things the iOS side needs that the Mac does not. Backgrounding the app ends the session, so a lazy promise on the pasteboard would outlive anything able to answer it and "copy on the host, switch to Safari, paste" would hand Safari nothing. So a host offer still unpasted when the sync tears down is pulled across then — bounded to 8 MiB and 3 seconds, skipped entirely if it was already pasted. Everything else stays lazy: copy on the host, stay in the app, paste nothing, and no clipboard bytes move. And since iOS 14 reading pasteboard *contents* is a privacy event the user sees, while reading its change count and type list is not. That maps onto the lazy design exactly, so the announce poll stays silent however long a session runs; the one real read happens when someone on the host pastes. It now runs off the drain thread, because on iOS 16+ that read can sit waiting for the user's answer, and the drain thread is what would deliver the host's cancel. One bug fixed while moving the code: ownership of the pasteboard was recorded from a write's resulting change count without checking the write had landed. A dropped write would have made the sync read the user's own next copy as its own echo and never announce it again. tvOS has no pasteboard, so the guards became `#if !os(tvOS)` rather than widening to every platform. Verified: macOS `swift build` + 319 tests green (15 new), iOS and tvOS typechecks via the `--triple` recipe, and `xcodebuild` of the shipping Punktfunk-iOS scheme. |