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.
Terminal.print's grapheme path holds a raw pointer to the previous cell
while it writes other cells. Writing the wide spacer tail can grow the
page to fit the cursor hyperlink, and growing replaces the page, so the
pointer is left dangling and the following appendGrapheme writes into
freed memory.
Record the cursor page identity (node pointer plus serial, since pooled
nodes can reuse an address) before the spacer write and reload the cell
only when the page actually changed, so the common path costs nothing.
The same function had three more pointers held across an operation that
can replace a page: the grapheme move after a wrap, the grapheme append
loop, and printCell's assert on a failed hyperlink write. Those now read
through the cursor or a freshly resolved pin.
Fixes#11261
Fixes#5255
This adds support for the Kitty graphics animation frames
(https://sw.kovidgoyal.net/kitty/graphics-protocol/#animation),
completely (transmission, control, composition, rendering, etc.).
This does it in a somewhat naive way: we pre-compose all frames and
store the full RGBA in-memory. Animation is already rare enough, and I
wanted to focus on things working first, so I didn't optimize this very
well. I also wanted this PR to be relatively understandable up front. We
can add complexity later.
But, this adds very little overhead to a non-animation using Kitty
graphics user. The animation state is heap-allocated only when its
needed. So, it just costs a pointer sized field on every image. Plus a
little bit of overhead in the loading state (which itself is heap
allocated during image load only).
On the renderer side, this **unifies Kitty graphics animations and
custom shader animations into a single animation abstraction.** This
simplified our renderer thread and made the generic renderer more
complicated (slightly, its not much!).
**AI usage:** Test writing, validation against the spec/reference
implementation. I drove the main architecture and shaped out the
functions and params, animation storage, etc. I had AI fill in some of
the blanks that I spaced out. Commit messages, comments, and this PR
message are written by me.
## Demo
https://github.com/user-attachments/assets/91be3d66-a5ff-4cab-b3e9-e672f39861c9
Translate the remaining 181 untranslated strings in po/zh_TW.po,
bringing it to 100% coverage (252/252), including the strings added by
the recent template update.
All 72 existing translations are preserved unchanged.
This modifies our wuffs dependency so that it no longer requires libc.
This unblocks using wuffs from libghostty-vt on freestanding targets,
which we'll eventually want for some Kitty graphics stuff.
This modifies our wuffs dependency so that it no longer requires libc.
This unblocks using wuffs from libghostty-vt on freestanding targets,
which we'll eventually want for some Kitty graphics stuff.
The gaToRgba swizzle requested a YA_PREMUL source pixel format from the
wuffs pixel swizzler, but wuffs does not support YA_PREMUL as a swizzle
source. As a result, gaToRgba returned error.WuffsError for every input.
The path can't happen in Ghostty GUI today since our PNG decoding always
produces RGBA, but it is possible via libghostty that submit grey+alpha
directly.
The gaToRgba swizzle requested a YA_PREMUL source pixel format from
the wuffs pixel swizzler, but wuffs does not support YA_PREMUL as a
swizzle source. As a result, gaToRgba returned error.WuffsError for every input.
The path can't happen in Ghostty GUI today since our PNG decoding always
produces RGBA, but it is possible via libghostty that submit grey+alpha
directly.
This adds full support for relative placements:
https://sw.kovidgoyal.net/kitty/graphics-protocol/#relative-placements
(`R=` and `Q=`). The high-level description is that this allows images
to be placed relative to other images. For more details, the spec
explains it better than I can, or the demo video below.
The implementation here was pretty straightforward. It mainly revolved
around adding parent resolution logic to puts (and validation), and
proper unparented placement reaping in the right points, and then the
various tests around that. The renderer logic itself was also
straigthforward, I had to add some offset resolution to the core
graphics storage but we can reuse that. The main complexity -- as always
-- (MY OWN EMDASHES) is Unicode placeholders. But... also not too hard.
After this, the only feature we don't support from the protocol is
animation.
**AI usage:** Test writing, validation against spec/reference, judging,
and some "fill in the function/block". I setup the overall shape of the
implementation.
## Demo
Ghostty on the left, Kitty on the right
https://github.com/user-attachments/assets/dc2ec3ac-480a-4587-94e4-06a842f9b3f4