mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-09-14 18:01:58 +00:00
Follow up to #13978 `ghostty_terminal_paste` no longer takes the clipboard's data up front. The request now carries only the list of available MIME types plus a a callback that writes one representation's bytes into a `GhosttyWriter`. Previously an embedder had to load every representation for every MIME type into memory before pasting. For a clipboard holding a large image or video next to some text that could be hundreds of megabytes that were never used. I also took care to make sure that the data is only read once, to avoid any time-of-check/time-of-use (TOCTOU) issues. There is only one case where data might be fully buffered in memory now: unsafe text data that needs to be checked. This is true for how Ghostty GUI works today too.
256 lines
9.7 KiB
C
256 lines
9.7 KiB
C
/**
|
|
* @file paste.h
|
|
*
|
|
* Paste - paste into a terminal, and validate and encode paste data.
|
|
*/
|
|
|
|
#ifndef GHOSTTY_VT_PASTE_H
|
|
#define GHOSTTY_VT_PASTE_H
|
|
|
|
/** @defgroup paste Paste
|
|
*
|
|
* Pasting into a terminal, plus the terminal-free utilities for
|
|
* validating and encoding paste data.
|
|
*
|
|
* ## Pasting into a Terminal
|
|
*
|
|
* What a paste writes to the pty depends on the terminal's state, so
|
|
* the recommended way to paste is ghostty_terminal_paste(). The embedder
|
|
* hands over the MIME types the clipboard holds (just `text/plain` for
|
|
* an ordinary paste), a GhosttyMimeReader that produces the data of
|
|
* any one of them, and where the paste came from, and the terminal
|
|
* decides how its current modes apply:
|
|
*
|
|
* - If Kitty clipboard protocol paste events (mode 5522,
|
|
* GHOSTTY_MODE_PASTE_EVENTS) are enabled, the paste was user-initiated
|
|
* (GHOSTTY_PASTE_SOURCE_CLIPBOARD), and a clipboard_read callback is
|
|
* installed, the terminal sends the program a paste event listing the
|
|
* clipboard's MIME types with a one-time password instead of the data.
|
|
* The program then reads what it wants through the clipboard_read
|
|
* callback, which arrives with `granted` set so no permission prompt
|
|
* is needed. No data is read for the event.
|
|
* - Otherwise the first text representation is written: unsafe control
|
|
* bytes are replaced with spaces, and it is wrapped in bracketed paste
|
|
* sequences if mode 2004 (GHOSTTY_MODE_BRACKETED_PASTE) is enabled, or
|
|
* has its newlines converted to carriage returns if not.
|
|
*
|
|
* The data is pulled through GhosttyPaste::reader only when a
|
|
* representation is actually pasted (so a clipboard holding a large
|
|
* image next to some text costs nothing), and the encoded bytes
|
|
* stream to the write_pty callback (GHOSTTY_TERMINAL_OPT_WRITE_PTY)
|
|
* in chunks as they are produced, never in one piece. The callback
|
|
* may be invoked several times for a single paste; the pieces must be
|
|
* written to the pty in order.
|
|
*
|
|
* Text that could inject commands (a newline when unbracketed, or the
|
|
* bracketed paste terminator when bracketed) is refused with
|
|
* GHOSTTY_REJECTED and nothing written unless GhosttyPaste::allow_unsafe
|
|
* is set. The usual flow is to call once, confirm with the user on
|
|
* GHOSTTY_REJECTED, and call again with `allow_unsafe` set. Each call
|
|
* reads the text at most once and buffers it whole while the rule is
|
|
* applied, so the source needs no stability across reads (the
|
|
* confirmed retry simply pastes whatever the source holds then) and a
|
|
* refused or failed paste writes nothing at all.
|
|
*
|
|
* @snippet c-vt-paste/src/main.c terminal-paste
|
|
*
|
|
* ## Building Blocks
|
|
*
|
|
* For embedders that encode without a terminal, ghostty_paste_is_safe()
|
|
* checks if paste data contains potentially dangerous sequences
|
|
* (conservatively, regardless of terminal state) and
|
|
* ghostty_paste_encode() encodes paste data for writing to the pty,
|
|
* including bracketed paste wrapping and unsafe byte stripping.
|
|
*
|
|
* ### Safety Check
|
|
*
|
|
* @snippet c-vt-paste/src/main.c paste-safety
|
|
*
|
|
* ### Encoding
|
|
*
|
|
* @snippet c-vt-paste/src/main.c paste-encode
|
|
*
|
|
* @{
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <ghostty/vt/types.h>
|
|
#include <ghostty/vt/io.h>
|
|
#include <ghostty/vt/terminal.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/**
|
|
* Why a paste happened.
|
|
*/
|
|
typedef enum GHOSTTY_ENUM_TYPED {
|
|
/** The user pasted from a clipboard: keybind, menu, middle click. */
|
|
GHOSTTY_PASTE_SOURCE_CLIPBOARD = 0,
|
|
|
|
/**
|
|
* Text inserted some other way: IME commit, drag and drop, scripted
|
|
* input. Always written as text, never as a paste event, matching
|
|
* kitty. This is not a way to opt out of paste events; an embedder
|
|
* that doesn't want them doesn't install a clipboard_read callback.
|
|
*/
|
|
GHOSTTY_PASTE_SOURCE_TEXT = 1,
|
|
GHOSTTY_PASTE_SOURCE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
|
|
} GhosttyPasteSource;
|
|
|
|
/**
|
|
* A paste of clipboard contents into the terminal.
|
|
*
|
|
* This is a sized struct; set `size` to `sizeof(GhosttyPaste)`. The
|
|
* MIME type array and the strings it points to are borrowed only for
|
|
* the duration of the ghostty_terminal_paste() call, as is everything
|
|
* the reader produces.
|
|
*/
|
|
typedef struct {
|
|
/** Size of this struct in bytes. */
|
|
size_t size;
|
|
|
|
/**
|
|
* The clipboard the contents came from. Reported to the program on a
|
|
* paste event (the selection and primary locations are both reported
|
|
* as the primary selection, the protocol knows only two); no effect
|
|
* on a text paste.
|
|
*/
|
|
GhosttyClipboardLocation location;
|
|
|
|
/** Why this paste happened. */
|
|
GhosttyPasteSource source;
|
|
|
|
/**
|
|
* Borrowed array of the MIME types of the representations available,
|
|
* in preferred order. A text paste reads and writes the first entry
|
|
* with a text MIME type such as "text/plain" and ignores the rest. A
|
|
* paste event lists every entry and reads none. May be NULL when
|
|
* mimes_len is zero, which is nothing to paste.
|
|
*/
|
|
const GhosttyString* mimes;
|
|
|
|
/** Number of entries in mimes. */
|
|
size_t mimes_len;
|
|
|
|
/**
|
|
* Produces the data of a representation on demand. Required when
|
|
* mimes_len is nonzero.
|
|
*
|
|
* Called at most once per ghostty_terminal_paste() call: for the
|
|
* text representation being pasted, never for anything else and
|
|
* never for a paste event. The MIME type requested is always an
|
|
* entry of `mimes`, passed through exactly as given there (the same
|
|
* pointer and length), so the callback may identify the
|
|
* representation by pointer or by content. A false return fails the
|
|
* paste with GHOSTTY_IO_ERROR.
|
|
*/
|
|
GhosttyMimeReader reader;
|
|
|
|
/**
|
|
* Write text that could inject commands. Call with false, confirm
|
|
* with the user on GHOSTTY_REJECTED, and call again with true.
|
|
*/
|
|
bool allow_unsafe;
|
|
} GhosttyPaste;
|
|
|
|
/**
|
|
* Paste into the terminal according to its current state: a Kitty
|
|
* clipboard protocol paste event if mode 5522 is enabled and a
|
|
* clipboard_read callback is installed, otherwise the text framed per
|
|
* mode 2004. See the group documentation for the full behavior. Output
|
|
* streams through the write_pty callback in chunks. The viewport is not
|
|
* scrolled; that is up to the embedder, as for key input.
|
|
*
|
|
* A paste event records a session grant for its one-time password only
|
|
* once the event is written; a failed call never leaves a grant for an
|
|
* event that was never sent.
|
|
*
|
|
* @param terminal The terminal handle
|
|
* @param paste The paste request, borrowed for the duration of the call
|
|
* @param[out] out_written On success, whether anything was written to
|
|
* the pty (the encoded text or a paste event). False means
|
|
* there was nothing to paste: no non-empty text
|
|
* representation. May be NULL.
|
|
* @return GHOSTTY_SUCCESS on success (see @p out_written);
|
|
* GHOSTTY_REJECTED if the text could inject commands and
|
|
* GhosttyPaste::allow_unsafe is false (nothing was written);
|
|
* GHOSTTY_INVALID_VALUE for a NULL terminal or paste, MIME
|
|
* types without a reader, or when no write_pty callback is
|
|
* installed; GHOSTTY_OUT_OF_MEMORY; GHOSTTY_IO_ERROR if the
|
|
* reader failed or there is no secure entropy source to mint a
|
|
* paste event password (wasm32-freestanding without
|
|
* GHOSTTY_SYS_OPT_RANDOM_SECURE set). Errors write nothing.
|
|
*/
|
|
GHOSTTY_API GhosttyResult ghostty_terminal_paste(
|
|
GhosttyTerminal terminal,
|
|
const GhosttyPaste* paste,
|
|
bool* out_written);
|
|
|
|
/**
|
|
* Check if paste data is safe to paste into the terminal.
|
|
*
|
|
* Data is considered unsafe if it contains:
|
|
* - Newlines (`\n`) which can inject commands
|
|
* - The bracketed paste end sequence (`\x1b[201~`) which can be used
|
|
* to exit bracketed paste mode and inject commands
|
|
*
|
|
* This check is conservative and considers data unsafe regardless of
|
|
* current terminal state. ghostty_terminal_paste() applies the
|
|
* terminal-state-aware rule itself (newlines are safe inside a
|
|
* bracketed paste); use this to apply the stricter rule on top.
|
|
*
|
|
* @param data The paste data to check (must not be NULL)
|
|
* @param len The length of the data in bytes
|
|
* @return true if the data is safe to paste, false otherwise
|
|
*/
|
|
GHOSTTY_API bool ghostty_paste_is_safe(const char* data, size_t len);
|
|
|
|
/**
|
|
* Encode paste data for writing to the terminal pty.
|
|
*
|
|
* This function prepares paste data for terminal input by:
|
|
* - Stripping unsafe control bytes (NUL, ESC, DEL, etc.) by replacing
|
|
* them with spaces
|
|
* - Wrapping the data in bracketed paste sequences if @p bracketed is true
|
|
* - Replacing newlines with carriage returns if @p bracketed is false
|
|
*
|
|
* The input @p data buffer is modified in place during encoding. The
|
|
* encoded result (potentially with bracketed paste prefix/suffix) is
|
|
* written to the output buffer.
|
|
*
|
|
* If the output buffer is too small, the function returns
|
|
* GHOSTTY_OUT_OF_SPACE and sets the required size in @p out_written.
|
|
* The caller can then retry with a sufficiently sized buffer.
|
|
*
|
|
* This is the encoder ghostty_terminal_paste() uses for a text paste;
|
|
* use it directly when there is no terminal to paste into.
|
|
*
|
|
* @param data The paste data to encode (modified in place, may be NULL)
|
|
* @param data_len The length of the input data in bytes
|
|
* @param bracketed Whether bracketed paste mode is active
|
|
* @param buf Output buffer to write the encoded result into (may be NULL)
|
|
* @param buf_len Size of the output buffer in bytes
|
|
* @param[out] out_written On success, the number of bytes written. On
|
|
* GHOSTTY_OUT_OF_SPACE, the required buffer size.
|
|
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_SPACE if the buffer
|
|
* is too small
|
|
*/
|
|
GHOSTTY_API GhosttyResult ghostty_paste_encode(
|
|
char* data,
|
|
size_t data_len,
|
|
bool bracketed,
|
|
char* buf,
|
|
size_t buf_len,
|
|
size_t* out_written);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
/** @} */
|
|
|
|
#endif /* GHOSTTY_VT_PASTE_H */
|