feat(clipboard): add rich clipboard module with macOS backend#211
Open
kdroidFilter wants to merge 8 commits intomainfrom
Open
feat(clipboard): add rich clipboard module with macOS backend#211kdroidFilter wants to merge 8 commits intomainfrom
kdroidFilter wants to merge 8 commits intomainfrom
Conversation
- clipboard-common: Clipboard façade with suspend read/write for text, HTML, RTF, images (PNG bytes) and file lists; ClipboardWriteScope DSL for atomic multi-format writes; Flow<ClipboardEvent> watcher (metadata only, safe under macOS 15.4+ pasteboard privacy); ServiceLoader-based backend SPI with NoOpBackend fallback. - clipboard-macos: NSPasteboard JNI backend. Single NSPasteboardItem for multi-format writes, PNG + TIFF on images for web/AppKit compatibility, modern NSURL reads with legacy NSFilenamesPboardType fallback, HTML BOM stripping, NSPasteboard.accessBehavior guarded via respondsToSelector. - Example: new Clipboard tab with live watcher + multi-format read/write. - Docs: runtime/clipboard-common.md + runtime/clipboard-macos.md, mkdocs nav, llms.txt, roadmap, README updated. - UI: parent tabs grouped with per-group dropdown menus (custom Popup styled to match the tab chips, no Material DropdownMenu) to tame the flat tab row.
- Clipboard.accessBehavior: AccessBehavior? reads NSPasteboard.accessBehavior, null when the host OS has no privacy model. - Clipboard.isAccessBehaviorSupported gates UI based on respondsToSelector: probing for both getter and setter. - Native nativeGetAccessBehavior + nativeIsAccessBehaviorSupported in the JNI bridge; both guarded via NSInvocation + respondsToSelector. - Example Clipboard tab: new "Privacy — macOS 15.4+" card showing the current policy with chips for the three values; disabled chips + explanatory copy on older macOS / other OSes. - Docs updated (runtime/clipboard-common.md, runtime/clipboard-macos.md).
…elector The respondsToSelector: probe for -accessBehavior / -setAccessBehavior: was returning NO on macOS Tahoe under the unsigned JVM, causing the example's Privacy card to claim the API was unsupported even though the runtime does expose it. Switch to [NSProcessInfo isOperatingSystemAtLeastVersion:] for the support flag — more reliable, independent of ObjC metadata visibility. The selector probes on get/set remain as a safety net.
Implement full clipboard support for Linux across both X11 (XCB + XFixes) and Wayland (wl-clipboard delegation). X11 backend handles format negotiation, INCR protocol for large transfers, change detection via XFixes. Wayland delegates to wl-copy/wl-paste with atomic format selection. Platform detection and fallback logic ensures compatibility across session types, including Xwayland on Wayland hosts.
- Add MIME type negotiation: try image/png then jpeg/webp/bmp/gif/tiff - Fix stream-drain race in runCaptureBytes: join reader thread fully after process exit (was capped at 200ms, could truncate large images) - Add fallback: read image files directly from disk when clipboard contains file URIs (Nautilus copy on .png files) - Bump image read timeout to 5 seconds (screenshots may take time) - Demo: display image thumbnails inline in the event log when images are copied or read - Add WaylandImageSmokeTest integration test
X11 ICCCM compliance (#4/#5): - SetSelectionOwner now uses real server timestamp via PropertyNotify probe, not XCB_CURRENT_TIME (violates ICCCM §2.1). Added get_server_timestamp_locked() which fires a zero-byte ChangeProperty to trigger timestamp event. - TIMESTAMP replies now return g_own_ts (real value) instead of truncated 0. - Verified: xclip -o -t TIMESTAMP returns non-zero after our clipboard write. INCR cleanup (#3): - On INCR read timeout, delete property to unblock sender waiting for PropertyNotify=Delete (ICCCM compliant termination). Process lifecycle (#7): - Wayland: runCaptureBytes, runSilently, writeBytes now escalate to destroyForcibly() if SIGTERM doesn't terminate after 500ms grace. AccessBehavior mapping (#12): - Kotlin: explicit when() mapping (0→AlwaysAllow, 1→AskEveryTime, 2→AlwaysDeny) instead of ordinal/entries.getOrNull (fragile with future macOS versions). - ObjC: validate input 0..2 on set; return -1 if get() returns out-of-range. Documentation & robustness (#13, #1): - Clipboard.watch() doc: clarify poll interval is always honored; source of counter differs by backend (Mach IPC / XFixes / wl-paste). - Re-check isActive after slow availableFormats() to avoid emitting to cancelled flow. Added X11TimestampSmokeTest to verify real timestamps are used.
Implements full Win32 clipboard support via JNI (user32, gdi32, gdiplus): - Reads/writes: text (CF_UNICODETEXT), HTML (CF_HTML wrapper), RTF, images (PNG + CF_DIBV5 with Chromium alpha sanitization), file lists (CF_HDROP) - Change detection via GetClipboardSequenceNumber (no message pump needed) - Open-clipboard retry loop (5 × 10ms) for rdpclip.exe contention - GDI+ transcoding: PNG ↔ DIB via synthetic BMP-in-memory - x64 + ARM64 DLLs built via MSVC (build.bat)
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
clipboard-common— suspendClipboardfaçade with reads/writes for text, HTML, RTF, PNG images and file lists;write { }DSL for atomic multi-format publication;watch(): Flow<ClipboardEvent>reactive watcher. Backends discovered viaServiceLoader, no-op fallback when none are on the classpath.clipboard-macos—NSPasteboardJNI backend. SingleNSPasteboardItemfor multi-format writes, PNG + TIFF dual publication for web/AppKit compatibility, modernNSURLreads with legacyNSFilenamesPboardTypefallback, HTML BOM stripping,NSPasteboard.accessBehaviorguarded viarespondsToSelector:(macOS 15.4+).availableFormats()never read payload bytes, so they stay outside the scope of the new pasteboard-privacy prompt.AccessBehaviorAPI exposes the new policy.Popup-based dropdowns styled to match the existing chip tabs (no MaterialDropdownMenu).docs/runtime/clipboard-common.md+docs/runtime/clipboard-macos.md,mkdocs.ymlnav,docs/runtime/index.md,docs/llms.txt,docs/roadmap.md, andREADME.mdall updated.Test plan
./gradlew :clipboard-common:compileKotlin :clipboard-macos:compileKotlin :example:compileKotlin— passes on JDK 21./gradlew :clipboard-common:detekt :clipboard-common:ktlintCheck :clipboard-macos:detekt :clipboard-macos:ktlintCheck :example:ktlintCheck— all greenbuild.shproduces bothdarwin-aarch64anddarwin-x64dylibs exporting all 11Java_...symbolsmkdocs buildprocesses both new pages without warnings./gradlew run→ switch to System → Clipboard, exercise each read/write button, observe the live watcher firing on external copiesbuild-natives.yamland verify/download steps in the 6 consumer workflows before publishing (follow-up task)