aboutsummaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
Diffstat (limited to 'vendor')
-rw-r--r--vendor/libghostty-vt.patches.md42
-rw-r--r--vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h9
-rw-r--r--vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig55
-rw-r--r--vendor/patches/libghostty-vt/0002-expose-kitty-image-transmit-time-ns.patch112
4 files changed, 218 insertions, 0 deletions
diff --git a/vendor/libghostty-vt.patches.md b/vendor/libghostty-vt.patches.md
index 98b7569e..94bb998b 100644
--- a/vendor/libghostty-vt.patches.md
+++ b/vendor/libghostty-vt.patches.md
@@ -38,3 +38,45 @@ verification:
zig build test-lib-vt -Demit-lib-vt -Doptimize=ReleaseSafe -Dtest-filter="resize shrinks both axes with cursor at bottom"
zig build test-lib-vt -Demit-lib-vt -Doptimize=ReleaseSafe -Dtest-filter="PageList resize less rows and cols cursor at bottom"
```
+
+## 0002 expose kitty image transmit time in the C API
+
+status: active
+
+patch: `vendor/patches/libghostty-vt/0002-expose-kitty-image-transmit-time-ns.patch`
+
+herdr issue: https://github.com/ogulcancelik/herdr/issues/947
+
+upstream discussion: https://github.com/ghostty-org/ghostty/discussions/13177
+(proposes extending the kitty graphics inspection C API from
+https://github.com/ghostty-org/ghostty/pull/12145, which has no transmit
+time/serial accessor yet)
+
+introduced upstream: not yet
+
+vendored base: `0f7cd84b880b203c98683e520e84b9db0c5938d8`
+
+local files:
+
+- `vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h`
+- `vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig`
+
+reason: herdr fingerprints kitty image data to decide when to re-encode an
+image for render clients. Hashing the full payload on every render is too
+expensive for multi-megabyte images, and sampling windows misses small
+changes, freezing streaming sources. The image's transmit time already
+refreshes on every (re)transmission, so exposing it as
+`GHOSTTY_KITTY_IMAGE_DATA_TRANSMIT_TIME_NS` gives herdr an exact, O(1) change
+serial to invalidate a cached full-data fingerprint.
+
+remove when: the vendored source commit exposes the image transmit time (or an
+equivalent transmission serial) in the C API and
+`ghostty::tests::kitty_image_fingerprint_refreshes_on_retransmission` passes
+without this patch.
+
+verification:
+
+```sh
+zig build test-lib-vt -Dtest-filter="image_get transmit_time_ns changes on retransmission"
+cargo nextest run kitty_image_fingerprint
+```
diff --git a/vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h b/vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h
index 9bace3a3..d5b50067 100644
--- a/vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h
+++ b/vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h
@@ -343,6 +343,15 @@ typedef enum GHOSTTY_ENUM_TYPED {
*/
GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN = 8,
+ /**
+ * Timestamp of the image's most recent transmission, in nanoseconds.
+ * The epoch is unspecified; only equality and ordering between values
+ * read from the same terminal are meaningful.
+ *
+ * Output type: uint64_t *
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_TRANSMIT_TIME_NS = 9,
+
GHOSTTY_KITTY_IMAGE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyKittyGraphicsImageData;
diff --git a/vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig b/vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig
index d50b8c4c..a00632ed 100644
--- a/vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig
+++ b/vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig
@@ -178,6 +178,7 @@ pub const ImageData = enum(c_int) {
compression = 6,
data_ptr = 7,
data_len = 8,
+ transmit_time_ns = 9,
pub fn OutType(comptime self: ImageData) type {
return switch (self) {
@@ -187,6 +188,7 @@ pub const ImageData = enum(c_int) {
.compression => ImageCompression,
.data_ptr => [*]const u8,
.data_len => usize,
+ .transmit_time_ns => u64,
};
}
};
@@ -258,11 +260,22 @@ fn imageGetTyped(
.compression => out.* = image.compression,
.data_ptr => out.* = image.data.ptr,
.data_len => out.* = image.data.len,
+ .transmit_time_ns => out.* = instantNanos(image.transmit_time),
}
return .success;
}
+/// Flattens an Instant's platform timestamp to nanoseconds for the C API.
+/// The epoch is unspecified; only equality and ordering are meaningful.
+fn instantNanos(instant: std.time.Instant) u64 {
+ if (@TypeOf(instant.timestamp) == u64) return instant.timestamp;
+ const ts = instant.timestamp;
+ const sec: u64 = @intCast(@max(ts.sec, 0));
+ const nsec: u64 = @intCast(@max(ts.nsec, 0));
+ return sec *| std.time.ns_per_s +| nsec;
+}
+
pub fn placement_iterator_new(
alloc_: ?*const CAllocator,
out: *PlacementIterator,
@@ -977,6 +990,48 @@ test "image_get_handle and image_get with transmitted image" {
try testing.expect(data_len > 0);
}
+test "image_get transmit_time_ns changes on retransmission" {
+ if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
+
+ var t: terminal_c.Terminal = null;
+ try testing.expectEqual(Result.success, terminal_c.new(
+ &lib.alloc.test_allocator,
+ &t,
+ .{ .cols = 80, .rows = 24, .max_scrollback = 0 },
+ ));
+ defer terminal_c.free(t);
+
+ const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2;////////\x1b\\";
+ terminal_c.vt_write(t, cmd.ptr, cmd.len);
+
+ var graphics: KittyGraphics = undefined;
+ try testing.expectEqual(Result.success, terminal_c.get(
+ t,
+ .kitty_graphics,
+ @ptrCast(&graphics),
+ ));
+
+ var first: u64 = undefined;
+ try testing.expectEqual(Result.success, image_get(
+ image_get_handle(graphics, 1),
+ .transmit_time_ns,
+ @ptrCast(&first),
+ ));
+ try testing.expect(first > 0);
+
+ std.Thread.sleep(1 * std.time.ns_per_ms);
+ const retransmit = "\x1b_Ga=t,t=d,f=24,i=1,s=1,v=2;AAAAAAAA\x1b\\";
+ terminal_c.vt_write(t, retransmit.ptr, retransmit.len);
+
+ var second: u64 = undefined;
+ try testing.expectEqual(Result.success, image_get(
+ image_get_handle(graphics, 1),
+ .transmit_time_ns,
+ @ptrCast(&second),
+ ));
+ try testing.expect(second > first);
+}
+
test "placement_rect with transmit and display" {
if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
diff --git a/vendor/patches/libghostty-vt/0002-expose-kitty-image-transmit-time-ns.patch b/vendor/patches/libghostty-vt/0002-expose-kitty-image-transmit-time-ns.patch
new file mode 100644
index 00000000..4289740c
--- /dev/null
+++ b/vendor/patches/libghostty-vt/0002-expose-kitty-image-transmit-time-ns.patch
@@ -0,0 +1,112 @@
+diff --git a/vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h b/vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h
+index 9bace3a..d5b5006 100644
+--- a/vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h
++++ b/vendor/libghostty-vt/include/ghostty/vt/kitty_graphics.h
+@@ -343,6 +343,15 @@ typedef enum GHOSTTY_ENUM_TYPED {
+ */
+ GHOSTTY_KITTY_IMAGE_DATA_DATA_LEN = 8,
+
++ /**
++ * Timestamp of the image's most recent transmission, in nanoseconds.
++ * The epoch is unspecified; only equality and ordering between values
++ * read from the same terminal are meaningful.
++ *
++ * Output type: uint64_t *
++ */
++ GHOSTTY_KITTY_IMAGE_DATA_TRANSMIT_TIME_NS = 9,
++
+ GHOSTTY_KITTY_IMAGE_DATA_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
+ } GhosttyKittyGraphicsImageData;
+
+diff --git a/vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig b/vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig
+index d50b8c4..a00632e 100644
+--- a/vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig
++++ b/vendor/libghostty-vt/src/terminal/c/kitty_graphics.zig
+@@ -178,6 +178,7 @@ pub const ImageData = enum(c_int) {
+ compression = 6,
+ data_ptr = 7,
+ data_len = 8,
++ transmit_time_ns = 9,
+
+ pub fn OutType(comptime self: ImageData) type {
+ return switch (self) {
+@@ -187,6 +188,7 @@ pub const ImageData = enum(c_int) {
+ .compression => ImageCompression,
+ .data_ptr => [*]const u8,
+ .data_len => usize,
++ .transmit_time_ns => u64,
+ };
+ }
+ };
+@@ -258,11 +260,22 @@ fn imageGetTyped(
+ .compression => out.* = image.compression,
+ .data_ptr => out.* = image.data.ptr,
+ .data_len => out.* = image.data.len,
++ .transmit_time_ns => out.* = instantNanos(image.transmit_time),
+ }
+
+ return .success;
+ }
+
++/// Flattens an Instant's platform timestamp to nanoseconds for the C API.
++/// The epoch is unspecified; only equality and ordering are meaningful.
++fn instantNanos(instant: std.time.Instant) u64 {
++ if (@TypeOf(instant.timestamp) == u64) return instant.timestamp;
++ const ts = instant.timestamp;
++ const sec: u64 = @intCast(@max(ts.sec, 0));
++ const nsec: u64 = @intCast(@max(ts.nsec, 0));
++ return sec *| std.time.ns_per_s +| nsec;
++}
++
+ pub fn placement_iterator_new(
+ alloc_: ?*const CAllocator,
+ out: *PlacementIterator,
+@@ -977,6 +990,48 @@ test "image_get_handle and image_get with transmitted image" {
+ try testing.expect(data_len > 0);
+ }
+
++test "image_get transmit_time_ns changes on retransmission" {
++ if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
++
++ var t: terminal_c.Terminal = null;
++ try testing.expectEqual(Result.success, terminal_c.new(
++ &lib.alloc.test_allocator,
++ &t,
++ .{ .cols = 80, .rows = 24, .max_scrollback = 0 },
++ ));
++ defer terminal_c.free(t);
++
++ const cmd = "\x1b_Ga=T,t=d,f=24,i=1,p=1,s=1,v=2;////////\x1b\\";
++ terminal_c.vt_write(t, cmd.ptr, cmd.len);
++
++ var graphics: KittyGraphics = undefined;
++ try testing.expectEqual(Result.success, terminal_c.get(
++ t,
++ .kitty_graphics,
++ @ptrCast(&graphics),
++ ));
++
++ var first: u64 = undefined;
++ try testing.expectEqual(Result.success, image_get(
++ image_get_handle(graphics, 1),
++ .transmit_time_ns,
++ @ptrCast(&first),
++ ));
++ try testing.expect(first > 0);
++
++ std.Thread.sleep(1 * std.time.ns_per_ms);
++ const retransmit = "\x1b_Ga=t,t=d,f=24,i=1,s=1,v=2;AAAAAAAA\x1b\\";
++ terminal_c.vt_write(t, retransmit.ptr, retransmit.len);
++
++ var second: u64 = undefined;
++ try testing.expectEqual(Result.success, image_get(
++ image_get_handle(graphics, 1),
++ .transmit_time_ns,
++ @ptrCast(&second),
++ ));
++ try testing.expect(second > first);
++}
++
+ test "placement_rect with transmit and display" {
+ if (comptime !build_options.kitty_graphics) return error.SkipZigTest;
+