The `clipboard_write` effect now is similar to read: it must response
to a "reply" callback synchronously. This lets the embedder ask for write
permission, too.
We also now pass through program name and grant information from Kitty
clipboard protocol so that embedders can use that if they want.
This is a breaking ABI change.
Translate printable physical keybindings through the current macOS
keyboard layout before assigning menu key equivalents. Previously these
bindings could not be represented because SwiftUI shortcuts are
character-based, so actions such as `super+backquote` had no native menu
shortcut.
Keep native keycodes as dispatch identity so translated display
characters do not change physical semantics or precedence over Unicode
bindings. Refresh shortcuts when the input source changes, and prevent
AppKit from transforming equivalents that are already localized.
**AI Usage:** The approach was suggested by GPT 5.6 Sol, but I wrote
most of the code and understand it all.
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.
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.
**Note: this has no changes for Ghostty GUI yet.** This only impacts
libghostty-vt.
This introduces a new `ghostty_terminal_paste` C API along with a
central `terminal.paste.paste` function that handles (1) mode 5522
(Kitty clipboard) (2) bracketed paste (3) normal paste all in one place,
combined with unsafe value detection and proper xterm-style newline
handling.
Terminal pasting is now stateful because for the Kitty clipboard
protocol in particular, it must mint "grants" that stay with the
terminal. Previously, paste encoding was stateless.
To start, this is only exposed/used by libghostty to enable Kitty
clipboard handling.
Other changes:
- **IO: randomSecure.** This also adds the `io.randomSecure`
implementation to `TinyIo` and a global sys override for it because
Kitty clipboard requires the ability to create one-time passwords and
the implementation (following Kitty) requires a crypto random source.
The sys model is for libghostty embedders.
- **New C result value: rejected.** This introduces a new C result enum
value "rejected" for values that are valid but rejected for some reason.
Its very possible that prior "invalid value" users will have to update
to this, and I recognize that its close to both but it fills an
important semantic difference.
Also note this still _eagerly_ requires all clipboard contents. I want
to move to a callback based model but it made the PR much more
complicated. I plan on playing with that before converting apprt's to
this.
This builds out the core logic and state machine for the Kitty drag and
drop protocol for _drops only_. This hooks it into libghostty-vt's Zig
API but it isn't available to the C API and it isn't hooked up to any
Ghostty GUI. It isn't really recommended that Zig consumers integrate
this yet because I'm sure the API will continue to change dramatically
as I address the missing features: drag source, remote drops, etc.
The major thing this does it the core `src/terminal/kitty/dnd.zig` stuff
with e2e tests extracted from Kitty's own `kitty_tets/dnd.py`. So this
verifies that what we have so far is working properly.
This implements the full Kitty clipboard protocol for libghostty-vt.
libghostty users need to only have the clipboard read/write effect for
this to work. This doesn't yet do mode 5522.
Hi, I noticed that this XKB config line was causing problems for
Ghosttty:
```
key <DELE> { [ ISO_Level3_Shift ] };
```
That's a valid reconfiguration of the "delete key" as a modifier, and
the same worked find in other terminal emulators (like Alacritty). With
Ghosttty, I was getting actual `<delete>` behaviour whenever I pressed
the modifier key (although the modifier engaged after that).
This patch fixes it. In line with your AI disclose policy: I made the
patch with Codex Sol. The logical fix is very succinct, and the bulk of
the patch works around an underlying issue to do with GDK not
recognising some modifiers as modifiers. I've tested the implementation
and the patch definitely resolves it!
This implements only the clipboard _write_ side of the Kitty clipboard
protocol for libghostty-vt. libghostty users don't need to do anything,
this all automatically works since it just piggy-backs on the previous
clipboard write effect.
Clipboard reading is far more complicated because we don't have anything
designed yet for libghostty-vt that does async requests (e.g. to ask the
user for permission). I need to think about that more.
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 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 includes only parsing of the OSC. You cannot use OSC 99 to send
notifications. Uses lazy parsing of the metadata modelled on the new OSC
133 behavior.
This adds all the core logic and tests for the full Kitty Clipboard
protocol in the `src/terminal` package.
This is purposefully shaped similarly to the way we organize Kitty
graphics. There is an umbrella `clipboard.zig` and then a bunch of leaf
zig files that cover: request parsing, response encoding, state
management, etc. I think that worked really well for Kitty graphics so
we're doing it here too.
The core logic covers every part of the protocol: read and write.
The only thing hooked up to the end user is a DECRQM for mode 5522 will
return unset. And it can't be set currently (since it never works yet).
Outside of that, nothing in this diff is actually used in the real
binary.
**AI usage:** Validation against the spec and Kitty impl, test writing
and coverage validation, of course some code writing but within the
broad organizational shape I defined. I went through and either rewrote
or wrote all the comments myself plus this PR message.
Fixes#11261.
`Terminal.print`'s grapheme path caches a `*Cell` for the previous cell
and keeps using it after writing other cells. Writing the wide spacer
tail calls `printCell`, which can grow the page to make room for the
cursor hyperlink. Growing clones the page and frees the old one, so the
cached pointer dangles and the following `appendGrapheme` writes into
freed memory. The second test case in the issue reproduces it.
Rather than recomputing `prev` on every use, which is too expensive for
this path, the fix records the cursor page identity before the spacer
write and reloads the cell only if the page actually changed. Node
pointer plus serial is used because nodes are pooled and a replacement
can land on the same address. Nothing changes when the page does not
grow.
Three other pointers in the same function were held across an operation
that can replace a page, so they are now read through the cursor or a
freshly resolved pin: the grapheme move after a wrap, the grapheme
append loop, and `printCell`'s assert on a failed hyperlink write.
Tests:
- `Terminal: VS16 widening when the spacer tail grows the page` fills
the page hyperlink map so the spacer tail is what forces growth. It
crashes without the fix.
- `Terminal: grapheme transfer when widening wraps to the next line`
covers the wrap path where the previous cell already holds grapheme
data, which had no test before.
`zig build test` passes.