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
6.9 KiB
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-colorsin a tty narrower than one column: Zig divides by zero; we clamp to one column.+versiondiffers in version/build info (placeholders inbuild_info; TODO(integration): root crate / apprt-gtk should callbuild_info::setwith the real GTK/libadwaita versions, commit and channel). "Zig version" row became "Rust version", "libxev" row printsn/a.+edit-configexec failure printserror.FileNotFound/error.AccessDeniedfor 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): returnError::NotYetAvailable.build_info::setfor real version/GTK info.- ghostty-vt
x11_colorlacks a key listing and loses name case:list_colorsre-parsesghostty-vt/src/res/rgb.txtthrough a relativeinclude_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_plaintextis a local copy ofhelpgen_actions::generatethat normalises trailing newlines. Fix the data in ghostty-input and switch back tohelpgen_actions::generate. - crash-report only lists (sentry is not ported).
ssh-cachelocking usesFile::try_lock; like Zig, a lock held by another process is ignored (the boolean result is unused upstream). Config::loadandfinalizeare called twice invalidate-config(as in Zig).
Integration pass: list-fonts / show-face / new-window (wired)
list_fonts.rs,show_face.rsnow useghostty-font(fontconfigDiscover,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 (mapghostty_apprt_gtk::ipc::perform_ipc'sErr(IpcFailed)toErr(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.