Files
ghostty-rs/docs/rewrite/notes/ghostty-cli.md
T
ddidderr 3266108c10 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

98 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.