Skip to content

feat: forward application OSC 52 clipboard writes through boo ui#95

Merged
kylecarbs merged 2 commits into
mainfrom
forward-app-osc52-clipboard
Jun 26, 2026
Merged

feat: forward application OSC 52 clipboard writes through boo ui#95
kylecarbs merged 2 commits into
mainfrom
forward-app-osc52-clipboard

Conversation

@kylecarbs

Copy link
Copy Markdown
Member

Problem

boo ui renders each session from a client-side libghostty terminal and repaints the viewport from its state, rather than passing session output through raw (see the ui.zig header). libghostty-vt parses OSC 52 but exposes no clipboard effect, so when an application inside a session copies text itself (an editor yank, a mouse-driven TUI's selection, anything emitting ESC ] 52 ; c ; <base64> BEL), the sequence is parsed and dropped. The copy never reaches the user's clipboard, even over SSH.

This is specific to boo ui:

  • boo ui's own mouse selection already copies via OSC 52 (copySelection), but only when the focused app has not grabbed the mouse.
  • When a TUI enables mouse reporting, boo ui forwards the mouse to the app, the app does its own copy, and that OSC 52 is swallowed.
  • Plain boo attach is raw passthrough and already forwards an app's OSC 52 unchanged.

Fix

Add src/osc52.zig: a split-safe incremental scanner (modeled on oscquery.zig) that recognizes OSC 52 clipboard writes in a byte stream. The focused View runs it over session output in feedOutput and forwards each complete write verbatim to the real terminal (fd 1), the same destination as copySelection, before the view-canvas parses and drops it.

Only writes are forwarded. A read request (ESC ] 52 ; c ; ? ST) asks the terminal to hand the clipboard back to the application; forwarding it would let a remote session read the user's local clipboard, so a ? payload is recognized and ignored. Oversized writes (over 2 MiB) are dropped rather than buffered without bound.

Test plan

  • New unit tests in src/osc52.zig: BEL and ST terminators, read-request rejection, empty/clear payloads, split-across-feeds, OSC 520 and other-OSC near-misses, back-to-back writes, and a long under-cap payload.
  • New integration test ui: an application's own OSC 52 clipboard write reaches the real terminal. Confirmed it fails with timeout: waiting for output when the feedOutput forwarding line is removed, and passes with it.
  • zig build test-all (163 unit + 81 integration) and zig build test-all -Doptimize=ReleaseSafe: 244/244 pass. zig fmt --check clean.
Decision log
  • Where to fix. The daemon already forwards OSC 52 to the attached client; neither oscquery.Filter (OSC 11 queries) nor altscreen.Filter (screen toggles) strips it. The loss happens in the boo ui client, where feedOutput pipes bytes into the clipboard-less view-canvas. So the fix is client-side, in View.
  • Why a byte-level scanner, not a libghostty effect. libghostty-vt's effect set (write_pty, bell, color_scheme, device_attributes, enquiry, size, title/pwd, xtversion) has no clipboard callback, so there is nothing to hook. A small passthrough filter matches how OSC 11 and alt-screen toggles are already handled.
  • Forward verbatim. The exact bytes the app emitted are re-emitted, so the user's terminal sees the app's intent with no re-encoding. The terminal still gates whether OSC 52 writes are honored, exactly as for a plain boo attach.
  • Security. Read requests are intentionally not forwarded, so a remote session cannot exfiltrate the local clipboard.
  • Split safety. protocol.max_payload is 1 MiB, so a clipboard write can span multiple .output frames; the scanner carries candidate state across feeds.

Opened by Coder Agents on behalf of @kylecarbs.

boo ui renders each session from a client-side libghostty terminal
instead of passing output through raw, and libghostty-vt exposes no
clipboard effect, so an application's own OSC 52 clipboard write is
parsed and dropped. A copy made inside a mouse-driven TUI never reaches
the user's clipboard, even though boo ui's own selection already copies
via OSC 52.

Add a split-safe OSC 52 scanner (src/osc52.zig) that the focused view
runs over the session output stream, forwarding each clipboard write
verbatim to the real terminal, the same path copySelection uses. Read
requests (a "?" payload) are not forwarded, so a remote session cannot
read the user's local clipboard. Plain boo attach already forwards
OSC 52 as raw passthrough and is unchanged.
In the OSC 52 forwarder, skip non-ESC bytes to the next ESC with
indexOfScalar in the ground state, and accumulate a clipboard body up
to its BEL/ST terminator with a single appendSlice instead of a
per-byte append. Both replace per-byte work on the output hot path the
view-canvas already walks once; behavior is unchanged.

Also force-import osc52.zig in main.zig's test block, so its unit tests
run by the same convention as the sibling filters rather than only
because ui.zig happens to reference the module.
@kylecarbs kylecarbs merged commit 0cc7146 into main Jun 26, 2026
5 checks passed
@kylecarbs kylecarbs mentioned this pull request Jun 26, 2026
@kylecarbs kylecarbs deleted the forward-app-osc52-clipboard branch June 26, 2026 20:47
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