Ports ZIG/src/terminal/kitty/graphics_*.zig: - image loading for direct, file, temp-file and shared-memory transmissions (rustix::shm, no unsafe), chunking, zlib and PNG decode - image storage with placements (incl. virtual) tracked by PageList pins, all delete modes, byte-limit eviction and dirty tracking - unicode placeholder decoding and placement iteration - command execution, generic over an ExecTerminal trait Deviations: storage methods take the PageList explicitly; evicted or replaced placements now release their tracked pins (Zig leaks them); file transmissions with S= truncate per the kitty spec. Zig quirks kept for parity: delete-range matches all placements, eviction stops only after freeing strictly more than requested, set_limit(0) resets. Not yet wired: Screen still holds the KittyImages placeholder, so Terminal does not implement ExecTerminal; integration steps are in docs/rewrite/notes/ghostty-terminal-kitty.md. Test Plan: - cargo test -p ghostty-terminal (66 kitty tests; all Zig tests ported)
6.6 KiB
6.6 KiB
ghostty-terminal: kitty graphics (agent K1)
Ported (Zig -> Rust, crates/ghostty-terminal/src/kitty/)
| Zig | Rust | tests |
|---|---|---|
| graphics_image.zig | graphics_image.rs (LoadingImage, Image, Rect, Error) |
11/11 Zig + 3 extra (nul//proc path rejection, file offset+size, shared memory) |
| graphics_storage.zig | graphics_storage.rs (ImageStorage, Placement, PlacementKey, TerminalInfo) |
19/19 Zig + 3 extra (placement replace releases pin, eviction order + limit, image_by_number newest) |
| graphics_unicode.zig | graphics_unicode.rs (placement_iterator, Placement, render_placement) |
16/16 Zig + 2 extra |
| graphics_render.zig | graphics_render.rs (Placement) |
no tests in Zig |
| graphics_exec.zig | graphics_exec.rs (execute, ExecTerminal trait) |
8/8 Zig + 4 extra (run against a harness, see below) |
| kitty.zig / graphics.zig | kitty/mod.rs (re-exports; the command parser stays in ghostty_vt::kitty::graphics_command) |
- |
pub mod kitty; added to lib.rs. Test data copied to kitty/testdata/.
Deps added to ghostty-terminal: flate2, png, rustix (features shm, fs), thiserror.
Zig tests skipped: none. (debugDump not ported: debug-only helper.)
Deviations
- Borrowing: Zig passes
*Screen/*Terminalto storage methods. Rust'sImageStorageis meant to live inScreen.kitty_images, so methods take&mut PageList(disjoint field borrow&mut screen.pages) plus aTerminalInfo { cols, rows, width_px, height_px, cursor_x, cursor_y }snapshot (TerminalInfo::from_terminal(&Terminal)).deinit(pages),delete(pages, &info, cmd),set_limit(pages, limit),add_image(pages, img),add_placement(pages, image_id, placement_id, p). add_image/add_placement/set_limittake the PageList only so that placements evicted or replaced release their tracked pin (Zig leaks them inevictImageand when an external placement id is re-used).- Placement location is
Location::Pin(TrackedPin)(handle into the PageList) instead of*Pin;Placement::rect(&image, &info, &pages). imageById/imageByNumberreturnOption<&Image>(no copy). Placements are aBTreeMap(deterministic order); images aHashMap.- zlib via
flate2, PNG viapngcrate (EXPAND|STRIP_16, then converted to 8-bit RGBA like wuffs' RGBA_NONPREMUL); shm viarustix::shm::open+File::read_exact_at(no mmap, so no unsafe). The segment is unlinked after reading, as in Zig. - File medium
S=(size): data is truncated tomin(S, 400MB). (Zig'sappendRemaining(.limited(n))may error when the file is longer; I chose the Kitty-spec behavior. Zig tests only use S=0.) Placement::rectusessaturating_sub(1)on rows/cols (Zig would underflow for a 0-sized grid).- Preserved Zig quirks:
delete rangeuses||(id >= first || id <= last, matches everything); eviction stops only whenevicted > req(strictly);set_limit(0)resets storage to defaults (limit then set to 0); screen reset.{ .dirty = true }also resets total_limit to default. execute(terminal, &cmd)has no allocator;Commandstays borrowed (the loading image copies the payload).
graphics_exec and Terminal (TODO(integration), needs T3)
Screen.kitty_images is still the placeholder KittyImages { dirty }, so Terminal cannot implement ExecTerminal yet.
execute is generic over the ExecTerminal trait; the exec tests run against a Harness { t: Terminal, storage: ImageStorage }
(real Terminal::index/set_cursor_pos/pages; storage held beside it). No tests are ignored.
Integration steps
screen.rs: replaceKittyImageswithpub kitty_images: crate::kitty::graphics_storage::ImageStorage(Default= Zig.{}).Screen::new: after constructing,result.kitty_images.set_limit(&mut result.pages, opts.kitty_image_storage_limit).unwrap()(infallible there;set_limitreturnsResult). Opts default is already 320_000_000.Screen::reset(Zigself.kitty_images.deinit(self); self.kitty_images = .{ .dirty = true }):self.kitty_images.deinit(&mut self.pages); self.kitty_images = ImageStorage { dirty: true, ..Default::default() };Drop/explicit teardown:kitty_images.deinit(&mut pages)must run before pages are dropped (tracked pins).clone_screendoes not clone images.- the existing
self.kitty_images.dirty = truesites keep working unchanged.
terminal.rs:erase_display.completeand.scrollback... (Zig lines ~2495/2552):let info = TerminalInfo::from_terminal(self); let s = self.screens.active_mut(); s.kitty_images.delete(&mut s.pages, &info, Delete::All(true));switch_screen_mode/set_alternate: new-screenkitty_image_storage_limit= primarykitty_images.total_limit; thennew.kitty_images.dirty = true.- add
Terminal::kitty_graphics_command(&mut self, cmd: &Command) -> Option<Response> { kitty::graphics_exec::execute(self, cmd) }(ZigTerminal.kittyGraphics). impl ExecTerminal for Terminal:fn kitty_parts(&mut self) -> (&mut ImageStorage, &mut PageList) { let s = self.screens.active_mut(); (&mut s.kitty_images, &mut s.pages) } fn exec_info(&self) -> TerminalInfo { TerminalInfo::from_terminal(self) } fn exec_cursor_pin(&self) -> Pin { self.screens.active().cursor_pin() } fn exec_index(&mut self) -> Result<(), page::Error> { self.index() } fn exec_set_cursor_pos(&mut self, row: usize, col: usize) { self.set_cursor_pos(row, col) }print: Zig sets rowkitty_virtual_placeholderwhen printingkitty::graphics_unicode::PLACEHOLDER(already present in Rust, uses its own constant; can switch tocrate::kitty::unicode::PLACEHOLDER).
stream_readonly/termio: APCG->ghostty_vt::apcalready yields a parsedCommand; termio callsterminal.kitty_graphics_command(&cmd)and, ifSome(resp), writesresp.encode(..)to the pty. Termio config:screen.kitty_images.set_limit(&mut screen.pages, limit)(Termio.zig 265/469); clear-screen binding:Delete::All(true)as in step 2 (Termio.zig 615).- Renderer (
renderer/image.zigequivalent): iteratestorage.placements/storage.images,Placement::rect/grid_size/calculated_size,graphics_unicode::placement_iterator(&pages, top, Some(bot))+Placement::render_placement(storage, img, cell_w, cell_h); resetstorage.dirty = falseafter upload. Font shaper useskitty::unicode::PLACEHOLDER. - Once Terminal implements the trait, the exec tests'
Harnesscan be replaced byTerminaldirectly.
Quality gate (2026-10-11)
cargo build/test/clippy -D warnings -p ghostty-terminal pass (1243 tests incl. 66 kitty: 54 ported + 12 extra); rustfmt applied to kitty/*.rs.