OSC 52 clipboard read wired into IClipboardService#10
Merged
Conversation
…dService
The write side (OSC 52 set) shipped earlier; this adds the read side and hooks
it into the UI layer's clipboard interface.
Core:
- DeviceResponseKind.Clipboard + the interpreter's OSC 52 response arm (payload
is "<targets>;<base64>", the consumer decodes — the PaletteColor precedent).
- ClipboardWriter.WriteQuery emits `OSC 52 ; c ; ? ST`.
- Family-gated ClipboardRead capability (Kitty/Ghostty/Rio/WezTerm/Foot/iTerm2/
Alacritty/Xterm/Tmux). Deliberately NOT probed at startup — probing would flash
the per-read permission prompt these terminals gate the read behind; the cap
means "worth attempting", and a denied/unsupported read simply never replies.
UI:
- TerminalClipboardService.TryGetTextAsync: emits the query out-of-band, registers
a one-shot device-response waiter, arms a frame-aligned UITimer for the timeout,
and subscribes BeginShutdown; the first of {reply, timeout, shutdown} completes
and tears down all three (single-threaded on the UI loop, so TrySetResult is the
only race guard). Decodes targets;base64 — empty selection => "", echoed `?` =>
null. CanRead now reflects the negotiated cap.
- TextBox Ctrl+V / Shift+Insert kick the read (2 s timeout for the human-scale
permission prompt) and insert via the FromPaste path; bracketed paste stays the
primary inbound path. An in-flight guard coalesces a held/double chord (OSC 52
has no request id, so one reply completes every pending read) and the completion
routes faults through the app exception funnel.
Adversarial review fixes (mutation-verified):
- SECURITY: EmitDeviceResponse drops every device response while _inPaste. The
classifier is a pure framing layer with no paste awareness, so an ESC embedded
in pasted content reaches the OSC dispatcher mid-paste — a hidden `ESC]52;c;..`
in copied web content would otherwise forge a Clipboard reply and satisfy a
pending read, bypassing the terminal's permission gate (clipboard poisoning).
The guard also closes the pre-existing OSC 4/10/11/12 color-forgery variant.
- Decode tolerates whitespace-wrapped and unpadded base64 (tmux/line-wrapping
terminals) so a real reply isn't misread as "no clipboard"; a DEBUG breadcrumb
separates decode-failure from no-reply.
Tests: Core interpreter + writer, ClipboardReadTests (round-trip, timeout, late
reply, no-cap, empty, echoed query, UTF-8, malformed, whitespace/unpadded,
multi-target, concurrent-reads fan-out, shutdown), TextBox C11_23a/b/c, and the
paste-forgery + outside-paste interpreter rows. Core 962 / UI 2948 / Bars 296 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175wtwXrafD4fWAZ3i5THu6
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.
Adds system-clipboard read via the OSC 52 query/response round-trip and hooks it into the UI layer's
IClipboardService(the write side shipped earlier).Core
DeviceResponseKind.Clipboard+ the interpreter's OSC 52 response arm (payload<targets>;<base64>, consumer decodes — thePaletteColorprecedent).ClipboardWriter.WriteQueryemitsOSC 52 ; c ; ? ST.ClipboardReadcapability. Not probed at startup — probing would flash the per-read permission prompt supporting terminals gate the read behind; the cap means "worth attempting", and a denied/unsupported read simply never replies.UI
TerminalClipboardService.TryGetTextAsync: emits the query out-of-band, registers a one-shot device-response waiter, arms a frame-alignedUITimerfor the timeout, subscribesBeginShutdown; the first of {reply, timeout, shutdown} completes and tears down all three (single-threaded on the UI loop). Decodestargets;base64— empty selection →"", echoed?→ null.TextBoxCtrl+V / Shift+Insert kick the read (2 s timeout for the human-scale permission prompt) and insert via theFromPastepath; bracketed paste stays the primary inbound path. An in-flight guard coalesces a held/double chord (OSC 52 has no request id, so one reply completes every pending read); the completion routes faults through the app exception funnel.Adversarial-review fixes (mutation-verified)
EmitDeviceResponsedrops every device response while_inPaste. The classifier is a pure framing layer with no paste awareness, so anESCembedded in pasted content reaches the OSC dispatcher mid-paste — a hiddenESC]52;c;…in copied web content would otherwise forge aClipboardreply and satisfy a pending read, bypassing the terminal's permission gate (clipboard poisoning). The guard also closes the pre-existing OSC 4/10/11/12 color-forgery variant.Tests
Core interpreter + writer;
ClipboardReadTests(round-trip, timeout, late reply, no-cap, empty, echoed query, UTF-8, malformed, whitespace/unpadded, multi-target, concurrent-reads fan-out, shutdown); TextBoxC11_23a/b/c; the paste-forgery + outside-paste interpreter rows. Core 962 / UI 2948 / Bars 296 green.🤖 Generated with Claude Code