mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-27 01:21:39 +00:00
terminal: support pending image payloads for kitty graphics
Represent Kitty image data as a complete/pending tagged union. Kitty images can now be completed _later_ if we have all their other metadata up front. This will be used by the snapshot API to transmit lightweight information up front so that renderers of the snapshot can show placeholders and accept mutating pty data, while the real image data streams in later.
This commit is contained in:
@@ -277,7 +277,13 @@ pub const State = struct {
|
||||
while (it.next()) |kv| {
|
||||
switch (kv.key_ptr.*) {
|
||||
// We're only looking at Kitty images
|
||||
.kitty => |id| if (storage.imageById(id) == null) {
|
||||
.kitty => |id| if (storage.imageById(id)) |image| {
|
||||
// A pending source must not keep an older texture for
|
||||
// the same ID renderable while its placements wait.
|
||||
if (image.data.isPending()) {
|
||||
kv.value_ptr.image.markForUnload();
|
||||
}
|
||||
} else {
|
||||
kv.value_ptr.image.markForUnload();
|
||||
},
|
||||
|
||||
@@ -408,6 +414,10 @@ pub const State = struct {
|
||||
image: *const terminal.kitty.graphics.Image,
|
||||
p: *const terminal.kitty.graphics.ImageStorage.Placement,
|
||||
) PrepImageError!void {
|
||||
// Keep the native placement but do not create a renderer placement or
|
||||
// texture until the decoded bytes arrive.
|
||||
if (image.data.isPending()) return;
|
||||
|
||||
// Get the rect for the placement. If this placement doesn't have
|
||||
// a rect then its virtual or something so skip it.
|
||||
const rect = p.rect(image.*, t) orelse return;
|
||||
@@ -478,6 +488,7 @@ pub const State = struct {
|
||||
);
|
||||
return;
|
||||
};
|
||||
if (image.data.isPending()) return;
|
||||
|
||||
const rp = p.renderPlacement(
|
||||
storage,
|
||||
@@ -597,6 +608,7 @@ pub const State = struct {
|
||||
alloc: Allocator,
|
||||
image: *const terminal.kitty.graphics.Image,
|
||||
) PrepImageError!void {
|
||||
const data = image.data.bytes() orelse unreachable;
|
||||
try self.prepImage(
|
||||
alloc,
|
||||
.{ .kitty = image.id },
|
||||
@@ -615,7 +627,7 @@ pub const State = struct {
|
||||
// constCasts are always gross but this one is safe is because
|
||||
// the data is only read from here and copied into its own
|
||||
// buffer.
|
||||
.data = @constCast(image.data.ptr),
|
||||
.data = @constCast(data.ptr),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -963,3 +975,62 @@ pub const Image = union(enum) {
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "kitty renderer ignores pending payloads and retains native placements" {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
const io = testing.io;
|
||||
|
||||
var t = try terminal.Terminal.init(io, alloc, .{ .rows = 3, .cols = 3 });
|
||||
defer t.deinit(alloc);
|
||||
t.width_px = 30;
|
||||
t.height_px = 30;
|
||||
|
||||
var state: State = .empty;
|
||||
defer state.deinit(alloc);
|
||||
|
||||
const storage = &t.screens.active.kitty_images;
|
||||
const pending = try storage.addPendingImage(io, alloc, .{
|
||||
.id = 1,
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.format = .rgba,
|
||||
.data = .{ .pending = 4 },
|
||||
});
|
||||
const pin = try t.screens.active.pages.trackPin(
|
||||
t.screens.active.cursor.page_pin.*,
|
||||
);
|
||||
try storage.addPlacement(io, alloc, 1, 1, .{
|
||||
.location = .{ .pin = pin },
|
||||
.columns = 1,
|
||||
.rows = 1,
|
||||
});
|
||||
|
||||
state.kittyUpdate(alloc, &t, .{ .width = 10, .height = 10 });
|
||||
try testing.expectEqual(@as(usize, 1), storage.placements.count());
|
||||
try testing.expectEqual(@as(usize, 0), state.kitty_placements.items.len);
|
||||
try testing.expect(state.images.get(.{ .kitty = 1 }) == null);
|
||||
|
||||
const pixels = try alloc.dupe(u8, "rgba");
|
||||
try testing.expect(pending.complete(storage, io, pixels));
|
||||
state.kittyUpdate(alloc, &t, .{ .width = 10, .height = 10 });
|
||||
try testing.expectEqual(@as(usize, 1), state.kitty_placements.items.len);
|
||||
try testing.expectEqual(
|
||||
pending.generation,
|
||||
state.images.get(.{ .kitty = 1 }).?.generation,
|
||||
);
|
||||
|
||||
// A newer pending replacement clears the renderer placement and marks
|
||||
// the copied texture data for unload without deleting the native pin.
|
||||
_ = try storage.addPendingImage(io, alloc, .{
|
||||
.id = 1,
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.format = .rgba,
|
||||
.data = .{ .pending = 4 },
|
||||
});
|
||||
state.kittyUpdate(alloc, &t, .{ .width = 10, .height = 10 });
|
||||
try testing.expectEqual(@as(usize, 1), storage.placements.count());
|
||||
try testing.expectEqual(@as(usize, 0), state.kitty_placements.items.len);
|
||||
try testing.expect(state.images.get(.{ .kitty = 1 }).?.image.isUnloading());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user