The clipboard is a chain of backends, and OSC 52 is one of them - #2
Merged
Merged
Conversation
pbcopy first, then the terminal's own clipboard over OSC 52. RECITE_BACKEND forces one and never silently falls back. The payload is streamed, so it is never in a variable and never in argv. OSC 52 is write-only, so it reports `sent` rather than `copied` -- recite-clip names the backend on one line of stdout and recite owns the wording. --version's clip row carries it, and that --which call runs with stdin CLOSED: an older sink is `exec pbcopy`, and pbcopy handed a readable /dev/null exits 0 and empties the clipboard. Openability is an append-mode open on a spare descriptor, not [ -w ]: access(2) says yes with no controlling tty and the open then fails. And `true`, not `:`, with stderr silenced first -- a redirection error on a special builtin kills a non-interactive shell outright.
A command group exits with its LAST command's status, so a base64 that existed and then failed was invisible to `|| exit 4`: what went out was a well-formed OSC 52 with an EMPTY payload, which the terminal reads as "set the clipboard to nothing". Cleared clipboard, `sent`, exit 0. The encode now stages into a file, where its status is its own, and the emit is an && chain. The staging file is the user's captured output, so it takes the same EXIT/INT/TERM/HUP traps recite gives its buffers. have_osc52 still checks for an encoder: the probe is what --which answers from and what `auto` selects on, and naming a backend that cannot encode is the dishonesty this tool exists to avoid. --which also stops answering before the arity check. The suite was green against every bug above, and each case here was confirmed red first. Two traps it now pins: the probe is unreachable from the write path, so only a --which case can cover it -- and a stub that ignores argv leaves the assertion named for it green. The prose that made claims the code cannot back goes with it, in the README and in the headers.
Four situations printed it: nothing resolved, a path that is not there, a file that is not executable, and a clip that RAN and answered nothing. The path on the same line separates the first three. It does not separate the fourth, which is itself two installs. Status separates them. 4 is this clip refusing: no pbcopy here, and no terminal to write an escape sequence to. Anything else is a clip predating --which, which is `exec pbcopy` and aborts on the closed stdin. `(no backend)` and `(no --which)`. Both tokens carry a space and overflow the column, so the cases assert the whole row rather than a field. Also drops shorthand the tree could not explain: the cross-shell gate is now named for what it gates, and the signal note claims the security hole directly.
Merged
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.
With no
pbcopyto reach — a remote host, or inside tmux — recite writes the block to the terminal's own clipboard with an OSC 52 escape, so the text lands on the machine you are sitting at.What lands
recite-clip. pbcopy first, then OSC 52.RECITE_BACKENDforces one and never silently falls back;--whichreports the choice without writing. The payload is streamed, so it is never in a variable and never in argv.sentrather thancopied— a claim nothing can back does not get made.--version's clip row carries the backend, and separates a clip that refused ((no backend)) from one predating--which((no --which)).base64that existed and then failed put a well-formed OSC 52 with an empty payload on the wire — which the terminal reads as "set the clipboard to nothing". Cleared clipboard,sent, exit 0. The encode now stages into a file, where its status is its own.Testing
test/clip-tests.shis new and hermetic —RECITE_TTYfor the OSC 52 target,RECITE_BACKENDfor selection, so the real clipboard is never touched. Its staging-file cases run under both/bin/shand/bin/dash, because macOS bash 3.2 runs the EXIT trap even when it dies of an untrapped signal and would green-light deletedTERM/HUPtraps.Every case here was confirmed red against its own bug first. Two that were green against the code they named: the probe is unreachable from the write path (only a
--whichcase can cover it), and a stub ignoring argv left the assertion named for it passing.test/all.shgreen.