Skip to content

Implement clipboard outbound push#53

Merged
jserv merged 1 commit into
mainfrom
clipboard
Jul 12, 2026
Merged

Implement clipboard outbound push#53
jserv merged 1 commit into
mainfrom
clipboard

Conversation

@jserv

@jserv jserv commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Make the CLIPBOARD selection two-way: when an X client claims ownership, mirror its text out to SDL's clipboard so external host apps can paste it.

A hidden InputOnly requestor window fires an async SelectionRequest for UTF8_STRING at the new owner; convertEvent routes replies destined for that window into clipboardConsumeInternalEvent, which pushes the text via SDL_SetClipboardText. Large payloads arrive via INCR: an INCR-typed reply acks by deleting the property and accumulates PropertyNotify chunks into a 128 MiB-capped buffer, pushing on the zero-length terminator. When an owner declines UTF8_STRING, retry legacy XA_STRING once for the current owner.

Stale-reply defense: each fetch draws its receiving property from a small rotating pool, so a superseded fetch's late reply lands on a different slot and is ignored; declines are discriminated by target. Ownership expiry rides the native SDL_CLIPBOARDUPDATE event: an external host clipboard change (distinguished from our own echo via the last pushed text) revokes the X owner. A clipboard update during the first fetch, before anything has been pushed, does not expire the owner it is fetching from.

Image and other non-text targets are text-only by design (SDL2 has no image clipboard and the SDL2-spelled shim does not wrap SDL3 SDL_SetClipboardData); the dropped target is logged by name.


Summary by cubic

Mirror X11 CLIPBOARD to the host: when an X client claims CLIPBOARD, we fetch its text and push it to SDL so external apps can paste. Supports UTF8_STRING, XA_STRING fallback with Latin‑1→UTF‑8, and INCR streaming with a 128 MiB cap, with safeguards against stale replies, non‑text data, and echo loops.

  • New Features

    • Outbound mirroring via a hidden InputOnly internal requestor; async SelectionRequest for UTF8_STRING, retry XA_STRING once if declined (Latin‑1 transcoded to UTF‑8).
    • INCR support with chunk acks (property deletes), reassembly, and a 128 MiB cap; also caps single‑shot replies; zero‑length terminator triggers push; malformed INCR hints are dropped.
    • Stale‑reply defense using a rotating pool of private properties and a fetch‑pending gate; late or unsolicited replies are ignored.
    • Host clipboard changes (SDL_CLIPBOARDUPDATE) revoke the X owner unless it’s our own echo; the first‑fetch update doesn’t abort the fetch.
    • Non‑text or wrong‑format targets are skipped by design; the dropped target is logged.
  • Refactors

    • Internal window plumbing: added createInternalWindow and a Window.internal flag; internal windows don’t emit Create/DestroyNotify and are hidden from XQueryTree and state snapshots.
    • Event routing: convertEvent now calls clipboardConsumeInternalEvent for the requestor window and clipboardHandleExternalUpdate on SDL_CLIPBOARDUPDATE.

Written for commit f84b8f1. Summary will update on new commits.

Review in cubic

cubic-dev-ai[bot]

This comment was marked as resolved.

Make the CLIPBOARD selection two-way: when an X client claims ownership,
mirror its text out to SDL's clipboard so external host apps can paste
it.

A hidden InputOnly requestor window fires an async SelectionRequest for
UTF8_STRING at the new owner; convertEvent routes replies destined for
that window into clipboardConsumeInternalEvent, which pushes the text
via SDL_SetClipboardText. Large payloads arrive via INCR: an INCR-typed
reply acks by deleting the property and accumulates PropertyNotify
chunks into a 128 MiB-capped buffer, pushing on the zero-length
terminator. When an owner declines UTF8_STRING, retry legacy XA_STRING
once for the current owner.

Stale-reply defense: each fetch draws its receiving property from a small
rotating pool, so a superseded fetch's late reply lands on a different
slot and is ignored; declines are discriminated by target. Ownership
expiry rides the native SDL_CLIPBOARDUPDATE event: an external host
clipboard change (distinguished from our own echo via the last pushed
text) revokes the X owner. A clipboard update during the first fetch,
before anything has been pushed, does not expire the owner it is fetching
from.

Image and other non-text targets are text-only by design (SDL2 has no
image clipboard and the SDL2-spelled shim does not wrap
SDL3 SDL_SetClipboardData); the dropped target is logged by name.
@jserv jserv merged commit 1dc83b6 into main Jul 12, 2026
28 checks passed
@jserv jserv deleted the clipboard branch July 12, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant