feat(x11): opt-in capture-time X11 takeover so XWayland can paste images - #75
Merged
Conversation
No version bump — this needs testing on a real copy before it ships. An image copied on Wayland cannot be pasted into an XWayland app (Claude Desktop runs --ozone-platform=x11). Xwayland exports text selections to X11 but never image ones, and a wlr-data-control clip — what CosmicShot's wl-copy writes — never reaches X11 at all, so those apps have nothing to read. Recovering the same clip from the panel already works, because that path makes our helper the selection owner. The disabled `_MIRROR_ON_CAPTURE` constant existed to do the same at capture time; it shipped in 1.4.21 and was reverted in 1.4.22 after both channels were measured dead ~35s after a copy, and the comment concluded that owning X11 was itself the problem. Measured since, with the GTK consumer probe rather than xclip: the recover path does **not** decay. 22211 bytes fetched in 0.0s at t+8s, t+46s and t+85s, decoding fine each time, helper holding throughout. Owning X11 is therefore not what breaks. The difference is that a recover displaces the previous owner, while a capture-time grab races an app that still holds the Wayland selection — two authorities for one selection, the same failure class the CosmicShot side concluded not to create. That makes capture-time takeover worth retesting rather than presuming dead, so the constant becomes the `x11_image_takeover` setting: default off, images only, still behind the existing `_mirror_allowed()` rate cap, still short-circuiting on our own echo by content hash. It is a hypothesis until someone copies from a real app with it on. If it regresses, the symptom is both channels going empty a few seconds after any image copy — turn it back off. scripts/x11_takeover_gate_test.py (new, in CI) pins the parts that must not drift: ships off, off publishes nothing and still releases, on publishes and does not release, our own echo short-circuits before either, and the rate cap bounds republishes. Mutation-checked — flipping the default to True, or ignoring the setting, each fail it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
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.
No version bump. This wants testing on a real copy before it ships.
The gap
An image copied on Wayland can't be pasted into an XWayland app — Claude Desktop runs
--ozone-platform=x11. Xwayland exports text selections to X11 but never image ones, and awlr-data-controlclip (what CosmicShot'swl-copywrites) never reaches X11 at all. So those apps aren't reading it slowly; they have nothing to read.Recovering the same clip from the Clippy panel does paste — because that path makes our helper the X11 selection owner and it serves the bytes itself.
Why this was previously considered dead
_MIRROR_ON_CAPTUREexisted to do exactly that at capture time. Shipped in 1.4.21, reverted in 1.4.22: cosmic-comp copies our X11 offer into the Wayland selection, Xwayland re-takes X11 ~35s later to proxy it, Xwayland can't serve image data, and both channels end up advertising an offer nothing backs. The comment concluded owning X11 was itself the problem.What changed: that conclusion doesn't hold
Re-measured with the GTK consumer probe (not
xclip, which lies about this):The recover path publishes through the same helper and does not decay. So owning X11 isn't what breaks it. The difference is that a recover displaces the previous owner, while a capture-time grab races an app still holding the Wayland selection — two authorities for one selection, the same failure class the CosmicShot side deliberately avoided.
That's worth retesting rather than presuming dead.
The change
_MIRROR_ON_CAPTUREbecomes thex11_image_takeoversetting — default off, images only, still behind the existing_mirror_allowed()rate cap, still short-circuiting on our own echo by content hash.Restart the daemon, copy an image in CosmicShot, paste into Claude Desktop.
Failure mode if the hypothesis is wrong: both channels go empty a few seconds after any image copy — clipboard dead system-wide, not just in XWayland apps. Turn it back off. That risk is why it's opt-in rather than a default.
Tests
scripts/x11_takeover_gate_test.py(new, in CI) pins what must not drift: ships off; off publishes nothing and still releases; on publishes and does not release; our own echo short-circuits before either path; the rate cap bounds republishes.Mutation-checked — flipping the default to
True, and ignoring the setting, each fail it:Full suite green locally (8 scripts).