terminal: add clipboard_set effect for OSC 52 clipboard writes (#13182)

libghostty-vt was already parsing OSC 52 into the clipboard_contents
action but the stream handler dropped, so there was no way to observe
clipboard writes (in this case, a program using go-libghostty). This
adds a clipboard_set effect following the existing bell/title_changed
pattern and expose it through the C API as
`GHOSTTY_TERMINAL_OPT_CLIPBOARD_SET`.

The callback receives the OSC 52 kind byte and the base64 payload
exactly as received; decoding and kind interpretation are left to the
embedder, matching how decoding is typically deferred.

Note this intentionally does not deal with clipboard read requests given
the security implications.

AI disclosure: Fable (via Claude Code) did the majority of the work
here, I validated it on the client side and fully understand the pattern
we're fitting into.
This commit is contained in:
Mitchell Hashimoto
2026-07-10 09:27:21 -07:00
committed by GitHub
8 changed files with 683 additions and 3 deletions

View File

@@ -1,9 +1,13 @@
# Example: `ghostty-vt` Terminal Effects
This contains a simple example of how to register and use terminal
effect callbacks (`write_pty`, `bell`, `title_changed`) with the
effect callbacks (`write_pty`, `bell`, `title_changed`, and
`clipboard_write`) with the
`ghostty-vt` C library.
The clipboard effect receives one atomic write with decoded, binary-safe
MIME representations rather than protocol-specific OSC 52 data.
This uses a `build.zig` and `Zig` to build the C program so that we
can reuse a lot of our build logic and depend directly on our source
tree, but Ghostty emits a standard C library that can be used with any

View File

@@ -39,6 +39,37 @@ void on_title_changed(GhosttyTerminal terminal, void* userdata) {
}
//! [effects-title-changed]
//! [effects-clipboard-write]
GhosttyClipboardWriteResult on_clipboard_write(
GhosttyTerminal terminal,
void* userdata,
const GhosttyClipboardWrite* write) {
(void)terminal;
(void)userdata;
printf(" clipboard write (location=%d, contents=%zu)\n",
(int)write->location, write->contents_len);
if (write->contents_len == 0) {
printf(" clear\n");
}
for (size_t i = 0; i < write->contents_len; i++) {
const GhosttyClipboardContent* content = &write->contents[i];
printf(" ");
if (content->mime.len > 0) {
fwrite(content->mime.ptr, 1, content->mime.len, stdout);
}
printf(" (%zu bytes): ", content->data.len);
if (content->data.len > 0) {
fwrite(content->data.ptr, 1, content->data.len, stdout);
}
printf("\n");
}
return GHOSTTY_CLIPBOARD_WRITE_RESULT_SUCCESS;
}
//! [effects-clipboard-write]
//! [effects-register]
int main() {
// Create a terminal
@@ -64,6 +95,8 @@ int main() {
(const void *)on_bell);
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_TITLE_CHANGED,
(const void *)on_title_changed);
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_CLIPBOARD_WRITE,
(const void *)on_clipboard_write);
// Feed VT data that triggers effects:
@@ -85,7 +118,14 @@ int main() {
ghostty_terminal_vt_write(terminal, (const uint8_t*)decrqm,
strlen(decrqm));
// 4. Another bell to show the counter increments
// 4. Clipboard write (OSC 52 ; c ; <base64 data> ST)
printf("Sending clipboard write:\n");
const char* clipboard_seq =
"\x1B]52;c;SGVsbG8gY2xpcGJvYXJk\x1B\\";
ghostty_terminal_vt_write(terminal, (const uint8_t*)clipboard_seq,
strlen(clipboard_seq));
// 5. Another bell to show the counter increments
printf("Sending another BEL:\n");
ghostty_terminal_vt_write(terminal, &bel, 1);