Files
ghostty-rs/docs/rewrite/notes/ghostty-terminal-kitty.md
T
ddidderr 08452ac84f refactor(terminal): wire kitty graphics and drop PageList workarounds
Integration pass over the terminal core (Codex/gpt-6-astra, reviewed):
- Screen now owns the real kitty ImageStorage; new/reset/teardown,
  erase-display deletes and alternate-screen limit inheritance hook in
  where Zig does, and Terminal implements ExecTerminal so kitty APC
  commands execute against it. Exec tests use a real Terminal.
- The PageList gains the APIs Screen/Terminal needed (pin iterators,
  cross-page row/partial-row clone, string allocator probing, dedup
  before allocating hyperlinks, Top viewport fixup after erase, append_
  grapheme no longer marking rows dirty), and the Screen/Terminal
  workarounds for their absence are removed so the code mirrors Zig.

Public signatures are unchanged; downstream crates build as before.

Test Plan:
- cargo test -p ghostty-terminal (1252 passed, 0 ignored)
- cargo clippy -p ghostty-terminal --all-targets -- -D warnings
2026-10-11 22:57:35 +02:00

5.8 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 + 8 extra (real Terminal, including lifecycle integration)
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/*Terminal to storage methods. Rust's ImageStorage is meant to live in Screen.kitty_images, so methods take &mut PageList (disjoint field borrow &mut screen.pages) plus a TerminalInfo { 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_limit take the PageList only so that placements evicted or replaced release their tracked pin (Zig leaks them in evictImage and 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/imageByNumber return Option<&Image> (no copy). Placements are a BTreeMap (deterministic order); images a HashMap.
  • zlib via flate2, PNG via png crate (EXPAND|STRIP_16, then converted to 8-bit RGBA like wuffs' RGBA_NONPREMUL); shm via rustix::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 to min(S, 400MB). (Zig's appendRemaining(.limited(n)) may error when the file is longer; I chose the Kitty-spec behavior. Zig tests only use S=0.)
  • Placement::rect uses saturating_sub(1) on rows/cols (Zig would underflow for a 0-sized grid).
  • Preserved Zig quirks: delete range uses || (id >= first || id <= last, matches everything); eviction stops only when evicted > 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; Command stays borrowed (the loading image copies the payload).

graphics_exec and Terminal integration — complete

Screen.kitty_images is the real ImageStorage. Initialization applies the configured limit, reset deinitializes storage and restores default limits with dirty = true, and Drop releases placement pins before dropping PageList. clone_screen does not copy images. The default is still 320,000,000 bytes.

Terminal implements ExecTerminal by splitting active-screen storage/pages, snapshotting TerminalInfo, and forwarding cursor/index operations. Both kitty_graphics(&mut Command) and the new borrowed kitty_graphics_command(&Command) execute commands and return real responses. All graphics_exec tests now use Terminal directly; the separate harness is gone.

Reference hook audit:

  • .complete and .scroll_complete display erasure call Delete::All(true). .scrollback only erases history, as in Terminal.zig.
  • Lazy alternate-screen creation inherits the primary image limit; screen switching marks the new screen's images dirty.
  • All existing image dirty hooks in Screen and Terminal remain at their Zig locations (scrolling, index, insert/delete lines, screen switching).
  • Full reset drops alternate storage and resets primary storage. Placeholder printing uses the shared graphics_unicode::PLACEHOLDER constant.

Four additional integration regressions cover screen clone/reset and pin release, erasure hooks, alternate-screen isolation/limit inheritance/full reset, and the legacy command entrypoint plus in-progress transfer reset.

API changes and consumer wiring

Added Terminal::kitty_graphics_command(&Command) and ExecTerminal for Terminal; retained Terminal::kitty_graphics(&mut Command) unchanged. Screen.kitty_images now has the required concrete ImageStorage type; existing .dirty access remains valid. No downstream callers needed edits.

Termio can pass APC G commands through either terminal entrypoint and encode returned responses to the pty. Storage limits use screen.kitty_images.set_limit(&mut screen.pages, limit); clear-screen bindings use Delete::All(true). Renderer consumers can inspect placements/images and Unicode placements and clear the storage dirty flag after updating graphics. Consumer implementation beyond the public terminal API remains owned by the concurrent renderer/termio agents.

Quality gate (2026-10-11 integration pass)

cargo test -p ghostty-terminal: 1252 unit tests (including 70 Kitty tests: 54 source cases + 16 extra) and 1 documentation test pass; 0 failed, 0 ignored. Clippy with --all-targets -- -D warnings, rustfmt, and the combined build of ghostty-font, ghostty-renderer, and ghostty-termio pass. No commit or version change was made. Existing non-integration deviations above are unchanged.