Files
ghostty-rs/docs/rewrite/notes/ghostty-cli.md
T
ddidderr ad76ae2301 feat(core): port App/Surface and wire up the runnable ghostty binary
This is the integration step that turns the ported crates into a working
terminal: `cargo run` now opens a GTK window running the user's shell.

ghostty-core ports App.zig, Surface.zig (split into keyboard, mouse,
binding, clipboard and config submodules), surface_mouse.zig and
global.zig, and implements the runtime traits. perform_binding_action
handles every binding action.

Cross-crate integration:
- The core<->runtime traits (CoreApp, CoreSurface, RtApp, RtSurface,
  Waker) move from apprt-gtk into ghostty_apprt::runtime_api so the core
  stays runtime-agnostic; Scrollbar/RendererHealth stand-ins are replaced
  by the real terminal/renderer types.
- termio now uses ghostty_renderer::state::{State, SharedState}; the
  renderer calls kitty_update; image-storage-limit is applied at init
  and on config change.
- The search thread locks the terminal through a TerminalLock trait so
  it can share the render state mutex; termio's Wakeup gained a hook to
  wake the renderer thread.
- apprt-gtk: GL proc addresses are resolved through libepoxy's
  epoxy_glFoo pointer variables (plain glFoo symbols resolved to null),
  default DPI fixed to 96, libadwaita version read without GTK init so
  --version works headless.

The root binary dispatches +actions to ghostty-cli (list-fonts,
show-face and new-window are now wired) and otherwise runs the GTK app
with the real core.

Verified at runtime on Wayland and X11: shell prompt and colored output
render, child exit closes the window cleanly. Not yet verified: keyboard
and mouse input, clipboard, IME, tabs and splits (no key injection was
possible in this environment). GHOSTTY_DEBUG_SCREENSHOT=<png> dumps the
renderer's last frame for headless checks.

Test Plan:
- cargo test --workspace (2890 passed)
- cargo clippy --workspace --all-targets -- -D warnings
- GSETTINGS_BACKEND=memory GHOSTTY_RESOURCES_DIR=/usr/share/ghostty \
  cargo run
2026-10-12 01:33:44 +02:00

6.9 KiB
Raw Blame History

ghostty-cli port notes (C1)

Crate crates/ghostty-cli (deps: ghostty-config, ghostty-input, ghostty-os; plus ratatui/crossterm for TUIs, flate2 for boo frames, unicode-width). Run end to end with cargo run -p ghostty-cli --example cli -- +list-actions --docs (the example behaves like ghostty for actions via ghostty_cli::run_from_args, which also reproduces main_ghostty.zig's "multiple/unknown action" errors). Do not edit the root crate: wire run_from_args(&std::env::args().collect::<Vec<_>>()) into its main later.

API

Every action module exposes pub fn run(args: &[String]) -> Result<u8, Error>; args is the full argv (argv[0] skipped, +action args ignored, like args.argsIterator). --help/-h yields Error::ActionHelpRequested, which Action::run turns into the help text (stdout, exit 0). Action::{ALL,name,from_name,file,help_text,run}, action::{detect_iter,ActionEnum,SpecialCase}.

File -> module

Zig Rust
cli.zig / action.zig action.rs (generic detection + 7 tests)
ghostty.zig ghostty.rs (Action, dispatch, help, run_from_args; 5 tests)
help.zig, version.zig help.rs, version.rs (+ build_info.rs)
list_actions/list_colors/list_keybinds same names; pretty output via tui.rs
list_themes.zig list_themes.rs (listing) + list_themes/preview.rs (ratatui TUI)
show_config/validate_config/edit_config/crash_report same names
ssh_cache.zig + ssh-cache/{Entry,DiskCache} ssh_cache.rs, ssh_cache/{entry,disk_cache}.rs
boo.zig (+ build/framegen/frames) boo.rs, boo_frames.deflate (235 frames, raw deflate of frames joined by \x01)
tui.zig tui.rs (+ a small vaxis prettyPrint clone)
list_fonts/show_face/new_window stubs: Options + help + Error::NotYetAvailable (TODO(integration))

Help texts (help/*.txt) are the run doc comments of the Zig files, extracted with the helpgen rules (common-indent strip); --help output is byte-identical to Ghostty 1.3.1.

Oracle verification (Ghostty 1.3.1 at /usr/bin/ghostty)

Byte-identical (stdout, stderr, exit code): +list-actions (+--docs, --help), +list-keybinds (--plain, --default, --docs; also with a custom config containing chords, chains, tables, catch_all, unicode keys), +list-themes --plain (--path, --color=dark|light; with GHOSTTY_RESOURCES_DIR=/usr/share/ghostty), +help, --help, -h, +validate-config (ok and missing file), +crash-report, +ssh-cache, bad action / multiple actions / bad flag errors, --help of every action, +show-config (except the two known config deviations: auto-update-channel tip vs stable, gtk-opengl-debug debug build). Pretty (tty) output of +list-colors and +list-keybinds is byte-identical in a pty for widths 20..200 (including line wrapping and the first-row/teardown sequences of libvaxis); verified with a pty harness.

Known differences:

  • +list-colors --plain: Ghostty 1.3.1 never flushes its 4 KiB stdout buffer on this path, so its output is truncated at 617 of 782 lines. We print all 782 (first 617 identical).
  • +list-colors in a tty narrower than one column: Zig divides by zero; we clamp to one column.
  • +version differs in version/build info (placeholders in build_info; TODO(integration): root crate / apprt-gtk should call build_info::set with the real GTK/libadwaita versions, commit and channel). "Zig version" row became "Rust version", "libxev" row prints n/a.
  • +edit-config exec failure prints error.FileNotFound/error.AccessDenied for those kinds, otherwise the Rust io error text.
  • +new-window: the Zig parse hook swallows --help; ported as is. IPC not available.

Interactive +list-themes (ratatui)

Same layout as the vaxis version: 32-column list with ❯ name ❮ selection, preview (header, 16 color swatches with decimal/hex numbers, 24-line code sample, lorem ipsum filler, diagnostics / load-error panels), help (60x22), search (3-high box at bottom) and save (92x17) overlays, all key bindings (q/Esc/^C, ?/F1/^H, /, ^X ^/, j/k/arrows, PgUp/PgDn (20), Home/End, h/x/d hex toggle, c / C copy name/path via OSC 52, f cycle filter, Enter save dialog, w write auto/theme.ghostty), mouse wheel / click / hover. Differences: no OSC 8 hyperlinks on names, double/dashed/dotted/curly underlines drawn as single, color scheme not queried live (defaults to light like Zig; COLORFGBG used as a hint), theme load errors during filtering are ignored (Zig aborts), zf fuzzy ranking replaced by a subsequence matcher (only matching, not order, is used). Logic (filtering, navigation, windowing, search, save) is unit tested; rendering is tested against a ratatui Buffer.

Tests

Ported every Zig test in the ported files: action.zig (7 detect tests), ghostty.zig (5 parse action tests), ssh-cache/Entry.zig (6), DiskCache.zig (4 operation tests + isValidHost, isValidUser, isValidCacheKey). Skipped: none. 86 tests total including new ones (help/version output, show-config, validate-config, crash-report, ssh-cache action, list-colors/keybinds/themes, tui row rendering vs vaxis bytes, boo frame parsing of all 235 frames, preview state machine, new-window arg hook).

TODO(integration) / gaps

  • list-fonts, show-face (need ghostty-font), new-window (GTK IPC): return Error::NotYetAvailable.
  • build_info::set for real version/GTK info.
  • ghostty-vt x11_color lacks a key listing and loses name case: list_colors re-parses ghostty-vt/src/res/rgb.txt through a relative include_str! path.
  • ghostty-input ActionKey::docs() has an extra trailing blank line for 4 actions (adjust_selection, ...) compared to Ghostty's help strings; list_actions::generate_plaintext is a local copy of helpgen_actions::generate that normalises trailing newlines. Fix the data in ghostty-input and switch back to helpgen_actions::generate.
  • crash-report only lists (sentry is not ported). ssh-cache locking uses File::try_lock; like Zig, a lock held by another process is ignored (the boolean result is unused upstream).
  • Config::load and finalize are called twice in validate-config (as in Zig).

Integration pass: list-fonts / show-face / new-window (wired)

  • list_fonts.rs, show_face.rs now use ghostty-font (fontconfig Discover, SharedGridSet, SharedGrid::get_index, Collection::get_face); ghostty-cli depends on ghostty-font and ghostty-apprt. Output is byte-identical to /usr/bin/ghostty for +list-fonts (plain, --family, --bold) and +show-face (--cp, --string, --presentation). Like Zig, show-face writes its "stderr" messages to stdout.
  • new_window.rs: set_ipc_handler(fn(&ipc::Target, &ipc::Action) -> Result<bool, String>) (OnceLock). The root binary must register it (map ghostty_apprt_gtk::ipc::perform_ipc's Err(IpcFailed) to Err(new_window::IPC_FAILED.into())). Without a handler: prints "+new-window is not supported on this platform." and exits 1 (apprt none behaviour).
  • Still open: build_info::set.