fix(tao): route the Linux clipboard through GTK instead of AWT - #583
Conversation
Compose Desktop's only clipboard implementation goes through java.awt.Toolkit.getSystemClipboard(), which on Linux is X11-only. A Tao window is a GTK window on whichever GDK backend the session provides, so on Wayland the two disagree: KWin publishes the Wayland selection to XWayland only while an X11 window is active, so Ctrl+V in a Wayland-native window pastes nothing, and with no XWayland at all the AWT call is headless. Adds libnucleus_tao_linux_clipboard.so (GTK/GObject dlopen-ed, -ldl only) and installs a Clipboard / ClipboardManager pair over LocalClipboard and LocalClipboardManager in TaoComposeSceneHostLinux, so copy/paste lands on the same backend as the window. GDK rather than wl-clipboard/arboard: it speaks the current backend with the window's own seat, needs no wlr-data-control, serves the data from this process and forks nothing. Everything falls back to the inherited AWT clipboard when the helper is unavailable, so macOS and Windows are untouched. Text crosses JNI as UTF-8 byte arrays, not jstring: GetStringUTFChars produces modified UTF-8 and would corrupt every non-BMP character. Only text/plain crosses the process boundary. Compose's AnnotatedString flavor is JVM-local and no real selection can carry it (AWT cannot either), so the last published entry is cached and handed back while the selection still holds that text, preserving span styles in-app. Closes #582
Replacing LocalClipboard on Linux narrowed every clip entry to text: images and file lists went from "whatever AWT could see" to null on read and to nothing at all on write. AWT does carry those flavors — the problem #582 describes is the channel, not the format — so the fix must not cost them. Reads now fall back to the AWT entry when GTK reports no text, unless that entry offers text itself: GTK just said there is none, so its text is the stale X11 selection, and returning it would reintroduce the bug. Writes hand any entry without a text/plain flavor to AWT untouched. GTK can carry images (gtk_clipboard_set_image) and file lists (text/uri-list) too, which would free them from XWayland as well; until then the fallback keeps parity.
|
Follow-up commit: the first version narrowed every clip entry to text on Linux, so images and file lists went from whatever AWT could see to
Third headful case added ( Carrying images and file lists natively ( |
Text was the only format going through GTK, so images and file lists still depended on AWT — that is, on XWayland, which is the dependency this branch set out to remove. AWT has always carried both; the gap was never the format, only the channel. Images go through GdkPixbuf, which is what makes GTK advertise and convert between image/png, image/bmp, image/jpeg and image/tiff; PNG is the exchange format across JNI. File lists go out as text/uri-list plus x-special/gnome-copied-files, the target GTK file managers actually paste, served from a payload the helper owns until the selection changes hands. What the selection advertises now decides which flavors a clip entry exposes, and the image and the file list are only fetched if something reads them: pasting text while a screenshot sits on the clipboard should not drag megabytes through JNI. Only a selection offering nothing this code knows about (RTF, private targets) is left to AWT. Listing the targets and fetching the content are two round-trips, so the selection can change hands in between; that now reports an empty clipboard instead of asking AWT, which would answer about a different selection. Three headful cases join the two existing ones, covering both directions for images and file lists against wl-copy / wl-paste. They encode two Wayland rules that a clipboard test gets wrong easily: a client is handed the selection as it gains keyboard focus, so the external tool has to own it first, and wl-copy returns before its serving process has taken over, so the handover is confirmed through wl-paste rather than assumed.
|
Images and file lists now go through GTK too, so the AWT fallback is no longer load-bearing for anything but formats this code does not know (RTF, private targets).
Five headful cases, both directions per format, against Two Wayland rules turned out to matter more than the code, and both are now encoded in the harness rather than left to luck:
Diagnosing those took most of the work; each of my first three attempts at the flakiness treated a symptom. |
Closes #582.
Summary
java.awt.Toolkit.getSystemClipboard(), which is X11-only on Linux. A Tao window is a GTK window on whichever GDK backend the session provides, so on Wayland the two disagree: KWin publishes the Wayland selection to XWayland only while an X11 window is active, soCtrl+Vin a Wayland-native window pastes nothing, and with no XWayland at all the AWT call is headless.libnucleus_tao_linux_clipboard.so(GTK/GObjectdlopen-edRTLD_LOCAL, links-ldlonly) plus aClipboard/ClipboardManagerpair installed overLocalClipboard/LocalClipboardManagerinTaoComposeSceneHostLinux.wl-clipboard/arboard: it speaks the current backend with the window's own seat, needs nowlr-data-control, serves the data from this process and forks nothing.jstring:GetStringUTFCharsproduces modified UTF-8 and would corrupt every non-BMP character (emoji).text/plaincrosses the process boundary. Compose'sAnnotatedStringflavor is JVM-local and no real selection can carry it (AWT cannot either), so the last published entry is cached and handed back while the selection still holds that text — span styles survive an in-app copy/paste.LocalClipboardManageris deprecated upstream but still provided: legacy call sites would otherwise keep reading the X11 selection while everything else reads the Wayland one. Its blocking reads spin a nested GTK main loop, so off-thread callers are handed to AWT instead.build.shsection + listings, the three CI verify lists (build-natives,pre-merge,publish-maven), andreachability-metadata.jsonentries for the bridge, the callback interface and its single named implementation.Notes from testing on a real Wayland session
set_selectionwith a serial from a real input event. No impact on real usage (Ctrl+C/Ctrl+Vhappen in a focused window), but a test that skips focusing measures GTK's local cache only.wl-pastedeadlocks both sides. The production path never blocks it.Toolkit.getSystemClipboard()appears only in the three Compose classes this bypasses, so there is no second AWT path left infoundation/ui.Test plan
:decorated-window-tao:taoHeadfulTest -Dnucleus.tao.headful.filter=582— 2 new real-window cases, green on GNOME Wayland: reading a selection owned by another process (wl-copy, the reported bug) and publishing the app's selection sowl-pastesees it, round-trip byte-identical with emoji, sameClipEntryinstance returned:decorated-window-tao:test:decorated-window-tao:ktlintCheck,:detekt,:apiChecknucleus_tao_linux_clipboard.ccompiles clean under-Wall -Wextralinux-aarch64slot)