aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 23:13:56 +0200
committeruser <user@clank>2026-08-14 23:13:56 +0200
commitd1442bc9550b00080c2f5671fa93eaad67fb0299 (patch)
treea745d9485501510c354ee02eaf8d0017dbfed4f2
parentsl0ppty: a shader's strength can be an expression (diff)
downloadslosh-d1442bc9550b00080c2f5671fa93eaad67fb0299.tar.gz
slosh-d1442bc9550b00080c2f5671fa93eaad67fb0299.tar.xz
slosh-d1442bc9550b00080c2f5671fa93eaad67fb0299.zip
vendor: keep kitty images across a screen clear
libghostty-vt's eraseDisplay called kitty_images.delete(.{ .all = true }) for ED 2 and ED 3, where the `true` also frees the image *data* of anything no longer placed. A full-screen program that clears and redraws therefore loses every image it uploaded, and every later placement of that id draws nothing. That breaks the half of the protocol worth having. Kitty separates transmitting an image (`a=t`, stored under an id) from placing it (`a=p`, put on screen) precisely so a program can upload once and place many times -- which is also why the delete command distinguishes `d=a` (placements) from `d=A` (placements and data). A screen clear should be the former. The patch removes only the freeing: placements on the cleared screen still go, and the image store still bounds itself with its 320MB limit and LRU eviction on insert -- the mechanism that makes keeping them safe, and that would be pointless if a clear dropped everything anyway. Found with a bouncing-DVD screensaver that uploads ten frames up front and places one per frame. It works in kitty; under us it drew nothing. D12 said "with a patches file if we ever need one". vendor/patches is that file, with the reasoning and what a re-vendor has to re-apply; the vendor json lists it. Worth sending upstream.
-rw-r--r--vendor/libghostty-vt.vendor.json4
-rw-r--r--vendor/libghostty-vt/src/terminal/Terminal.zig18
-rw-r--r--vendor/patches/0001-keep-kitty-images-across-a-screen-clear.patch44
-rw-r--r--vendor/patches/README.md38
4 files changed, 99 insertions, 5 deletions
diff --git a/vendor/libghostty-vt.vendor.json b/vendor/libghostty-vt.vendor.json
index 9a6a5fa..bfe92a5 100644
--- a/vendor/libghostty-vt.vendor.json
+++ b/vendor/libghostty-vt.vendor.json
@@ -5,5 +5,7 @@
"dist_archive": "libghostty-vt-1.3.2-main-+bedb6f2.tar.gz",
"version": "1.3.2-main-+bedb6f2",
"built_with_zig": "0.16.0",
- "patches": []
+ "patches": [
+ "patches/0001-keep-kitty-images-across-a-screen-clear.patch"
+ ]
}
diff --git a/vendor/libghostty-vt/src/terminal/Terminal.zig b/vendor/libghostty-vt/src/terminal/Terminal.zig
index b157994..e10132a 100644
--- a/vendor/libghostty-vt/src/terminal/Terminal.zig
+++ b/vendor/libghostty-vt/src/terminal/Terminal.zig
@@ -3341,12 +3341,17 @@ pub fn eraseDisplay(
self.screens.active.cursor.pending_wrap = false;
if (comptime build_options.kitty_graphics) {
- // Clear all Kitty graphics state for this screen
+ // Clear the placements on this screen, but keep the images
+ // themselves: a client transmits with `a=t` once and places
+ // with `a=p` per frame, and freeing the data on a screen clear
+ // makes every later placement of that id draw nothing. The
+ // storage limit (320MB, LRU) is what bounds this, which is why
+ // it exists. sl0ppty patch; see vendor/patches.
self.screens.active.kitty_images.delete(
self.io(),
self.screens.active.alloc,
self,
- .{ .all = true },
+ .{ .all = false },
);
}
},
@@ -3399,12 +3404,17 @@ pub fn eraseDisplay(
self.screens.active.cursor.pending_wrap = false;
if (comptime build_options.kitty_graphics) {
- // Clear all Kitty graphics state for this screen
+ // Clear the placements on this screen, but keep the images
+ // themselves: a client transmits with `a=t` once and places
+ // with `a=p` per frame, and freeing the data on a screen clear
+ // makes every later placement of that id draw nothing. The
+ // storage limit (320MB, LRU) is what bounds this, which is why
+ // it exists. sl0ppty patch; see vendor/patches.
self.screens.active.kitty_images.delete(
self.io(),
self.screens.active.alloc,
self,
- .{ .all = true },
+ .{ .all = false },
);
}
diff --git a/vendor/patches/0001-keep-kitty-images-across-a-screen-clear.patch b/vendor/patches/0001-keep-kitty-images-across-a-screen-clear.patch
new file mode 100644
index 0000000..e5781a6
--- /dev/null
+++ b/vendor/patches/0001-keep-kitty-images-across-a-screen-clear.patch
@@ -0,0 +1,44 @@
+diff --git a/vendor/libghostty-vt/src/terminal/Terminal.zig b/vendor/libghostty-vt/src/terminal/Terminal.zig
+index b157994..e10132a 100644
+--- a/vendor/libghostty-vt/src/terminal/Terminal.zig
++++ b/vendor/libghostty-vt/src/terminal/Terminal.zig
+@@ -3341,12 +3341,17 @@ pub fn eraseDisplay(
+ self.screens.active.cursor.pending_wrap = false;
+
+ if (comptime build_options.kitty_graphics) {
+- // Clear all Kitty graphics state for this screen
++ // Clear the placements on this screen, but keep the images
++ // themselves: a client transmits with `a=t` once and places
++ // with `a=p` per frame, and freeing the data on a screen clear
++ // makes every later placement of that id draw nothing. The
++ // storage limit (320MB, LRU) is what bounds this, which is why
++ // it exists. sl0ppty patch; see vendor/patches.
+ self.screens.active.kitty_images.delete(
+ self.io(),
+ self.screens.active.alloc,
+ self,
+- .{ .all = true },
++ .{ .all = false },
+ );
+ }
+ },
+@@ -3399,12 +3404,17 @@ pub fn eraseDisplay(
+ self.screens.active.cursor.pending_wrap = false;
+
+ if (comptime build_options.kitty_graphics) {
+- // Clear all Kitty graphics state for this screen
++ // Clear the placements on this screen, but keep the images
++ // themselves: a client transmits with `a=t` once and places
++ // with `a=p` per frame, and freeing the data on a screen clear
++ // makes every later placement of that id draw nothing. The
++ // storage limit (320MB, LRU) is what bounds this, which is why
++ // it exists. sl0ppty patch; see vendor/patches.
+ self.screens.active.kitty_images.delete(
+ self.io(),
+ self.screens.active.alloc,
+ self,
+- .{ .all = true },
++ .{ .all = false },
+ );
+ }
+
diff --git a/vendor/patches/README.md b/vendor/patches/README.md
new file mode 100644
index 0000000..bb97cc2
--- /dev/null
+++ b/vendor/patches/README.md
@@ -0,0 +1,38 @@
+# Patches to the vendored libghostty-vt
+
+D12 vendors libghostty-vt pinned by commit, "with a patches file if we ever
+need one". This is that file.
+
+Patches are applied **to the vendored tree in this repository**, which is
+committed, so a normal `make vendor` build already has them. The `.patch`
+files here exist for provenance: they say what we changed, why, and what has
+to be re-applied when the vendored version is bumped. `vendor/*.vendor.json`
+lists them under `patches`.
+
+Re-vendoring is therefore: drop in the new upstream tree, apply each patch in
+order, resolve anything that has moved, rebuild, run `make test`.
+
+## 0001 — keep kitty images across a screen clear
+
+`Terminal.eraseDisplay` called `kitty_images.delete(..., .{ .all = true })`
+for both `ED 2` and `ED 3`, where the `true` means "and free the image data
+for anything no longer placed".
+
+The kitty graphics protocol separates *transmitting* an image (`a=t`, which
+stores it under an id) from *placing* it (`a=p`, which puts it on screen), so
+that a program can upload once and place many times — that is the whole point
+of having two verbs, and the reason the delete command distinguishes `d=a`
+(placements) from `d=A` (placements and data). Freeing the data on a screen
+clear breaks it: any full-screen program that clears and redraws loses every
+image it uploaded, and every later placement of that id draws nothing at all.
+
+Found with a bouncing-DVD screensaver that transmits ten frames up front and
+then places one per frame. It works in kitty; under us it drew nothing, and
+the reason was three layers down.
+
+The patch removes only the freeing. Placements on the cleared screen still go
+— clearing the screen should clear what is on it — and the image store still
+bounds itself the way it always did, with a 320MB limit and LRU eviction on
+insert, which is exactly the mechanism that makes keeping them safe.
+
+Worth sending upstream.