mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-09-20 20:48:06 +00:00
libghostty: clipboard_read effect, enables OSC52 reads
This adds a `clipboard_read` effect to the stream terminal handler and a matching `GHOSTTY_TERMINAL_OPT_CLIPBOARD_READ` callback to the libghostty-vt C API so that embedders can answer OSC 52 read requests (the `?` payload). This is a _blocking_ effect: if the embedder needs to ask the user for permission, the entire VT processing pipeline is _blocked_ during the callback. This is a purposeful simplification choice compared to how Ghostty GUI works with async requests. I think its reasonable, it eliminates a TON of complexity. If the effect isn't set, then any clipboard reads are denied. This can be expanded easily to support Kitty clipboard protocol later.
This commit is contained in:
@@ -70,6 +70,46 @@ GhosttyClipboardWriteResult on_clipboard_write(
|
||||
}
|
||||
//! [effects-clipboard-write]
|
||||
|
||||
//! [effects-clipboard-read]
|
||||
void on_clipboard_read(
|
||||
GhosttyTerminal terminal,
|
||||
void* userdata,
|
||||
const GhosttyClipboardRead* read) {
|
||||
(void)terminal;
|
||||
(void)userdata;
|
||||
|
||||
// The read is synchronous: a real embedder would ask the user for
|
||||
// permission here (unless read->granted) and the VT stream waits until
|
||||
// this callback returns. The reply is sent to the program through the
|
||||
// write_pty callback.
|
||||
printf(" clipboard read (location=%d, mimes=%zu)\n",
|
||||
(int)read->location, read->mimes_len);
|
||||
for (size_t i = 0; i < read->mimes_len; i++) {
|
||||
printf(" ");
|
||||
fwrite(read->mimes[i].ptr, 1, read->mimes[i].len, stdout);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Reply with every requested representation we have. This example only
|
||||
// has text.
|
||||
const char* text = "Hello from the clipboard";
|
||||
GhosttyClipboardContent content = {
|
||||
.mime = {.ptr = (const uint8_t*)"text/plain", .len = 10},
|
||||
.data = {.ptr = (const uint8_t*)text, .len = strlen(text)},
|
||||
};
|
||||
GhosttyClipboardReadReply reply = {
|
||||
.size = sizeof(reply),
|
||||
.result = GHOSTTY_CLIPBOARD_READ_RESULT_SUCCESS,
|
||||
.contents = &content,
|
||||
.contents_len = 1,
|
||||
.available = NULL,
|
||||
.available_len = 0,
|
||||
.remember = false,
|
||||
};
|
||||
read->reply(read, &reply);
|
||||
}
|
||||
//! [effects-clipboard-read]
|
||||
|
||||
//! [effects-unknown-sequence]
|
||||
void on_unknown_sequence(
|
||||
GhosttyTerminal terminal,
|
||||
@@ -118,6 +158,8 @@ int main() {
|
||||
(const void *)on_title_changed);
|
||||
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_CLIPBOARD_WRITE,
|
||||
(const void *)on_clipboard_write);
|
||||
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_CLIPBOARD_READ,
|
||||
(const void *)on_clipboard_read);
|
||||
ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_UNKNOWN_SEQUENCE,
|
||||
(const void *)on_unknown_sequence);
|
||||
|
||||
@@ -154,13 +196,19 @@ int main() {
|
||||
ghostty_terminal_vt_write(terminal, (const uint8_t*)clipboard_seq,
|
||||
strlen(clipboard_seq));
|
||||
|
||||
// 5. Unsupported APC sequence
|
||||
// 5. Clipboard read (OSC 52 ; c ; ? ST)
|
||||
printf("Sending clipboard read:\n");
|
||||
const char* clipboard_read_seq = "\x1B]52;c;?\x1B\\";
|
||||
ghostty_terminal_vt_write(terminal, (const uint8_t*)clipboard_read_seq,
|
||||
strlen(clipboard_read_seq));
|
||||
|
||||
// 6. Unsupported APC sequence
|
||||
printf("Sending unknown APC:\n");
|
||||
const char* unknown_apc = "\x1B_private-command;payload\x1B\\";
|
||||
ghostty_terminal_vt_write(terminal, (const uint8_t*)unknown_apc,
|
||||
strlen(unknown_apc));
|
||||
|
||||
// 6. Another bell to show the counter increments
|
||||
// 7. Another bell to show the counter increments
|
||||
printf("Sending another BEL:\n");
|
||||
ghostty_terminal_vt_write(terminal, &bel, 1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user