Phase 9, MA1: crates/ghostty-macos (AppKit via objc2; empty stub on
non-Apple targets) ports the foundation of Ghostty 1.3.1's Swift macOS
app (ghostty/macos): main, AppDelegate (+Ghostty) with lifecycle, dock
menu, open-file, notifications, shortcut sync and quit/reopen paths;
MainMenu.xib as generated menu tables built into NSMenus; the
Ghostty.App/Config/Surface wrappers driving libghostty in-process via
ghostty-lib; the generic helpers; scripts/macos-bundle.sh to assemble
Ghostty.app (dry-run on Linux). Feature areas are stubbed for MA2-MA5.
Design and type mapping: docs/rewrite/notes/macos-app-design.md
(App Intents is a Swift-only framework and cannot be ported).
Compile-checked only (no macOS here): just check-macos passes for
aarch64/x86_64-apple-darwin with -D warnings; 125 platform-independent
tests run on Linux.
Test Plan:
- just check-macos
- cargo test -p ghostty-macos
Adds scripts/live-dbus-test.sh (`just live-dbus`): each scenario runs
the installed Ghostty 1.3.1 and the Rust build on a private
dbus-run-session bus inside a private headless weston (the user's
session bus is never written to), with Python/Gio mocks for
org.freedesktop.Notifications, the GlobalShortcuts/Settings portal and
org.freedesktop.systemd1, plus a real xdg-desktop-portal run. Scenarios:
single-instance (+detect), +new-window with arguments, notifications,
global shortcuts, color-scheme settings, cgroup scope creation, URL
opening. tests/live_dbus.rs compares the Rust build against goldens
recorded from the original (skips when tools or a fresh production
build are missing).
Two Rust bugs found and fixed (no upstream deviation):
- GlobalShortcuts CreateSession/BindShortcuts payloads used the wrong
D-Bus signatures ((v), double-boxed values), which a real portal
rejects; signature tests added.
- CreateSession was sent twice at startup; the config is now bound only
after the D-Bus connection is set, as in Zig.
All nine scenarios now behave identically to the original
(docs/rewrite/notes/live-dbus.md).
Test Plan:
- just live-dbus
- cargo test --test live_dbus (needs target/vp/production/ghostty)
The +list-themes interactive preview dropped the second emoji of ZWJ
clusters (e.g. woman + ZWJ + rocket) in the search box. Zig's libvaxis
enables Unicode grapheme mode 2027 for its session and sizes text per
grapheme cluster; the Rust preview did neither, so the terminal split
the cluster at the ZWJ and the cursor column was computed from summed
per-codepoint widths (4 instead of 2).
- The preview sends ?2027h on entry and ?2027l on exit like vaxis.
- tui::gwidth follows vaxis's unicode width rules per grapheme cluster
(VS15/VS16, emoji presentation, regional-indicator pairs) using the
ghostty-unicode emoji table; the preview's print path uses it.
+list-colors and +list-keybinds pretty output (also using gwidth) stays
byte-identical to /usr/bin/ghostty at widths 40/80/132.
Test Plan:
- cargo test -p ghostty-cli --test list_themes_pty
zwj_emoji_cluster_is_drawn_whole_in_search_box
- cargo test --workspace --no-fail-fast
Intentional deviation from Ghostty 1.3.1 (DEVIANT_FIXES.md row 39).
Zig's Command.startWindows (Command.zig:261,347-358) passes the command
path as CreateProcessW's lpApplicationName, which is taken literally:
a partial name is completed with the current directory only, never the
search path. A bare `cmd.exe` (e.g. `command = direct:cmd.exe` or
`-e cmd.exe`) therefore fails to start in Ghostty on Windows.
A bare program name (no drive or separator) is now looked up on PATH,
appending .exe when there is no extension as CreateProcess does;
anything else or a failed lookup is passed through unchanged. Verified
under Wine by the exec_windows ConPTY tests.
Test Plan:
- cargo test -p ghostty-termio resolve_application_searches_path_for_bare_names
- just test-windows-wine
Intentional deviation from Ghostty 1.3.1 (DEVIANT_FIXES.md row 38).
Zig's Theme.parseCLI (config/Config.zig:9788-9799) treats any value
containing ',', '=' or ':' as a light/dark pair. Every absolute Windows
path (C:\themes\x, \\?\C:\..., \\server\share\x) contains a colon, so
Ghostty on Windows rejects all absolute theme paths.
A value that is a single Windows absolute path is now taken as a theme
path; the light:/dark: pair syntax still works, including with drive
paths as values.
Test Plan:
- cargo test -p ghostty-config windows_absolute_path_detection
theme_windows_absolute_path_and_pair_with_paths
Intentional deviation from Ghostty 1.3.1 (DEVIANT_FIXES.md row 37).
On shutdown Zig's termio/Exec.zig:1188-1224 (getpgid) loops until the
child's process group differs from ours, i.e. until the child has
called setsid. If the child exits before ever calling setsid and is not
yet reaped, its pgid stays ours forever and Zig spins indefinitely.
The Rust port previously capped the wait at ~5 s (which also differed
from Zig for slow children). It now waits without a cap like Zig, but
stops as soon as waitid(WNOWAIT) shows the child has exited.
Test Plan:
- cargo test -p ghostty-termio getpgid_waits_for_late_setsid
getpgid_gives_up_on_exited_child_without_setsid
Windows was previously only compile-checked. With Wine 11 (staging)
and mingw available, scripts/windows-wine-test.sh (`just
test-windows-wine`) builds expat/FreeType/fontconfig for mingw under
target/wine-deps and runs the test suites of ghostty-vt, unicode,
datastruct, input, os, config, terminal, crash, termio and cli under
Wine (~2,700 tests). New termio/tests/exec_windows.rs drives real
cmd.exe children over ConPTY through Termio (output, exit codes,
typing, 120 KiB writes, stop, start failure). Fixes found: Windows
paths from fs::canonicalize leaked the \\?\ prefix (new
ghostty_os::path::realpath strips it like Zig); ghostty-font compiles
hb-ft for bundled HarfBuzz on Windows; ~25 tests had Unix assumptions.
Wine limitations are ignored with reasons (ResizePseudoConsole stub,
no POSIX rename over existing files) - docs/rewrite/notes/windows-wine.md.
+list-themes preview parity (former KNOWN_DIFFERENCES rows 5-9): the
search box ports vaxis TextInput (word navigation and editing bindings,
grapheme-wise editing), ASCII-only case folding, light initial colors
until terminal reports arrive (no COLORFGBG), and load errors end the
preview with exit 1; PTY tests compare with /usr/bin/ghostty.
Test Plan:
- just test-windows-wine
- cargo test -p ghostty-cli --test list_themes_pty
- cargo test --workspace --no-fail-fast (3601 passed)
Intentional deviation from Ghostty 1.3.1 (DEVIANT_FIXES.md entry 10).
Two terminal inputs reach states that Zig handles only by assertion
(Debug/ReleaseSafe abort; ReleaseFast continues):
- Toggling mode 2027 around a BOM before a wide character hits
`assert(!b)` in print_grapheme. This repository's release profile
keeps debug assertions, so terminal output could crash a release
build. The assertion is removed and the path behaves like Zig
ReleaseFast.
- A grapheme (e.g. a Devanagari conjunct) at the right margin on the
bottom row with scrolling margins returned InvalidCoordinate and left
a grapheme flag without data, panicking later in the formatter. The
grapheme now moves from the cell that actually holds it and no cell is
flagged without data.
tools/difftest gains a Rust-only panic hunt (`difftest_fuzz panics`,
lenient driver): ~60k debug-build cases now run without any panic, and
3000 Zig differential cases show 0 divergences.
Test Plan:
- cargo test -p ghostty-terminal difftest_mode_2027_toggle_with_bom_does_not_panic
difftest_grapheme_wrap_scrolling_margins_keeps_grapheme
- cargo run -p difftest --bin difftest_fuzz -- panics
Final fix pass per the stopping criterion (docs/rewrite/PLAN.md),
resolving the remaining RV18 build/behaviour items (final-Z21.md):
- termio errno access uses the per-target libc accessor (__error on
FreeBSD/DragonFly/Apple, __errno on OpenBSD/NetBSD/Android) instead of
__errno_location everywhere, unblocking non-Linux BSD builds.
- FreeBSD resource discovery follows Zig (site-terminfo, local/share).
- Zig's Debug-only resource precedence (and the dev workspace-resources
fallback) key off a ghostty_debug_build cfg set from PROFILE=="debug"
instead of debug_assertions, so release builds with assertions honor
GHOSTTY_RESOURCES_DIR first like Zig ReleaseSafe.
- KNOWN_DIFFERENCES.md lists the remaining P3 differences deliberately
left unfixed (theme-preview editing details, a cleanup timeout edge
case) and what cannot be verified on this machine.
- DEVIANT_FIXES.md registers the PTY-hangup output drain (35), the
canvas BGRA conversion (36) and the dev resources fallback.
Test Plan:
- cargo test --workspace --no-fail-fast (exit 0)
- cargo check --target x86_64-unknown-freebsd -p ghostty-os
Intentional deviation from Ghostty 1.3.1 (DEVIANT_FIXES.md row 34).
When a Metal buffer grows, Zig's renderer/metal/buffer.zig:89 and :146
compute the new size as twice the required *byte* count and then
multiply by @sizeOf(T) again, so large cell buffers allocate
sizeof(T) times more GPU memory than intended. The Rust Metal backend
(since bab49ee) allocates exactly twice the required bytes; this commit
factors the computation into metal_layout::grown_buffer_bytes, adds a
regression test and records the deviation.
Test Plan:
- cargo test -p ghostty-renderer
grown_buffer_bytes_is_double_the_required_bytes_not_scaled_by_size_of_t
- just check-macos
Resolves the parity items of docs/rewrite/notes/review-rv17-codex.md
(final-Z20.md):
- FreeType-only font backend is also available on Apple targets
(discovery-free, embedded emoji fonts); ghostty_surface_quicklook_font
returns null for every backend except plain CoreText like Zig.
- The +list-fonts test keys off the font crate's selected backend
(HAS_DISCOVERY), correct under feature unification and on Apple.
- Inspector font: 32 px rasterized with FreeType (dear-imgui freetype)
like Ghostty; a new `visual-parity.sh inspector` scenario shows 0.031%
differing pixels (hover highlight) vs the original on Linux. Windows,
Apple and wasm keep stb_truetype at 32 px (registered as a deliberate
deviation).
- Inspector DCS/APC descriptions follow Zig 0.15.2's formatting
(checked by running Zig), incl. the terminal.Parser.Action prefix.
- The ImGui demo window appears only in dev-profile builds, mirroring
Zig's .Debug-only gate.
Test Plan:
- cargo test --workspace --no-fail-fast (3582 passed)
- just check-macos (both, also with --features font-backend-freetype)
- scripts/visual-parity.sh inspector
Intentional deviation from Ghostty 1.3.1 (DEVIANT_FIXES.md row 33).
Zig's os/systemd.zig:116 accepts a NOTIFY_SOCKET path of exactly 108
bytes and then writes the terminating NUL to path[108], one past the
end of sockaddr_un.sun_path (out-of-bounds write). The Rust port's
socket address construction (since f8c5ce0) accepts at most 108 bytes
without a NUL and rejects longer paths, so it never writes out of
bounds; this commit records that deviation and adds a regression test.
Test Plan:
- cargo test -p ghostty-os notify_maximum_length_socket_path_is_rejected_safely
Ghostty can be built with -Dfont-backend=freetype, which uses FreeType
without any font discovery. The Rust port only had fontconfig+freetype
(default on Linux) and the CoreText variants. The new cargo feature
`font-backend-freetype` on ghostty-font (forwarded by ghostty-cli and
the root crate; non-Apple, non-wasm targets) behaves like Zig's mode:
font-family discovery, fallback search and codepoint-map overrides are
skipped (embedded fonts only); +list-fonts and +show-face print Zig's
"built without a font discovery mechanism" message and exit 1; +version
reports `.freetype`. The default backend is unchanged.
Also registers the Rust-only benchmark tooling extensions (--time,
--handler=full, ghostty-synthetic +sgr) in DEVIANT_FIXES.md.
Note: `--all-features` now selects this backend for the workspace.
Test Plan:
- cargo test -p ghostty-font --features font-backend-freetype (198)
- cargo build --features font-backend-freetype && ghostty +list-fonts
Intentional deviation from Ghostty 1.3.1 (DEVIANT_FIXES.md row 32).
Zig's os/open.zig runs `try collectOutput(...)` before `wait()`: when
collection fails (e.g. a stream exceeds the 50 KiB limit), the error
returns early and the child is never waited for, leaving a zombie
process for the lifetime of Ghostty.
The Rust opener thread now always waits for the child (the pipes are
already closed at that point) and still logs the collection error.
Test Plan:
- cargo test -p ghostty-os open
Intentional deviation from Ghostty 1.3.1 (DEVIANT_FIXES.md row 31).
Zig's apprt/gtk/ipc/new_window.zig:49-53 builds the D-Bus argument
array from raw bytes as a *trusted* GVariant; an argument with invalid
UTF-8 or an interior NUL violates GVariant's string invariants, which
GLib turns into a process abort. The Rust port mirrored this with an
explicit abort().
new_window now validates every argument before touching the bus: a bad
argument prints an error to stderr and returns Error::IpcFailed, so the
CLI exits with failure and nothing crashes. Valid arguments are sent
byte-identically to Ghostty.
Test Plan:
- cargo test -p ghostty-apprt-gtk new_window
(invalid_dbus_strings_are_rejected_without_abort)
Resolves the parity items of docs/rewrite/notes/review-rv15-codex.md
(final-Z18.md):
- os::open inherits stdin like Zig's Child.init, strips LD_LIBRARY_PATH
only in Snap builds (new `snap` cargo feature, off by default, like
Zig's snap build option) and bounds output collection to 50 KiB per
stream (StdoutStreamTooLong/StderrStreamTooLong).
- +show-face writes diagnostics and lookup lines in the exact fragments
Zig's formatter produces, so buffer drains happen at the same byte
positions (0 diffs vs /usr/bin/ghostty over 0-44 bad options for
three codepoints); its tests no longer parse the test runner's argv.
- DEVIANT_FIXES.md: five entries reworded and four removed that were no
longer deviations (verified against code and oracle).
Test Plan:
- cargo test -p ghostty-os open; cargo test -p ghostty-cli show_face
Intentional deviation from Ghostty 1.3.1 (upstream data-loss bugs, see
docs/rewrite/DEVIANT_FIXES.md and docs/rewrite/notes/final-Z17.md):
- Zig 0.15's File.writer writes positionally (pwrite) at each writer's
own offset, starting at 0, while the file is seekable. When stdout is
a regular file, two writers (e.g. +show-face) overwrite each other and
any existing content, and File.Writer.end truncates. ZigWriter always
appends at the current end and end() only flushes.
- Writers that are never flushed lose their buffered output in Zig, e.g.
+list-themes' stderr buffer, so "No themes found..." is never printed.
ZigWriter flushes on drop.
Zig's buffering and flush order (and thus pipe/tty interleaving) is kept
unchanged; only the data loss is not replicated.
Test Plan:
- cargo test -p ghostty-cli zig_writer
(regular_file_output_is_complete_and_appended,
unflushed_buffer_is_flushed_on_drop)
- ghostty +list-themes with a missing resources dir prints the message
Resolves RV14 (docs/rewrite/notes/review-rv14-codex.md, final-Z17.md):
- +show-face --cp parses as u21 (new U21 field type): out-of-range
values give an invalid-value diagnostic and exit 1 instead of a panic;
--string accepts raw bytes and fails with error.InvalidUtf8 after the
codepoint lookup like Zig.
- CLI actions write through ZigWriter, a model of Zig 0.15's buffered
File.writer (fixed buffer, drain on overflow, explicit flush), so
stdout/stderr interleave in Zig's flush order (+show-face, help,
version, validate-config, show-config, list-*, ssh-cache, crash-report,
edit-config, new-window).
- Zig 0.15.2 std.fs.path.isAbsolute port (Windows: \theme and /theme are
absolute) replaces Path::is_absolute at every Zig isAbsolute site.
- io_uring is available iff a 256-entry ring can be created, like
libxev; the reactor falls back to IORING_OP_TIMEOUT without EXT_ARG.
- Windows basename (`λ:theme`) verified against 0.15.2 (test added).
Test Plan:
- cargo test --workspace --no-fail-fast
- diff +show-face outputs (pipe) against /usr/bin/ghostty
The first basename port followed Zig 0.16's ComponentIterator-based
implementation (the system std); Ghostty 1.3.1 builds with 0.15.2. The
theme-name check now ports 0.15.2's basenamePosix/basenameWindows: a ':'
at byte 1 ends the drive designator for any first byte, so '1:theme'
yields 'theme'. Tests use the 0.15.2 cases.
Resolves the 4 P3 items in docs/rewrite/notes/review-rv13-codex.md
(final-Z16.md):
- async-backend now takes effect like Ghostty: ghostty_os::xev ports
libxev's backend detection (io_uring, then epoll); global init detects
and logs it, the GTK startup applies the config with Zig's messages,
and +version reports the real backend. The termio IO loop runs on a
new reactor with epoll, io_uring (io-uring crate) and poll(2) (kept
for macOS, where libxev uses kqueue). The 21 exec integration tests
run under every backend; io_uring was exercised here.
- auto-update-channel defaults to the build's release channel.
- Windows error names: sharing violation -> AccessDenied, plus
ERROR_FILE_EXISTS -> PathAlreadyExists and virus errors ->
AntivirusInterference, mirroring std.os.windows.OpenFile's NTSTATUS
mapping in Zig 0.15.2 (verified against its source by the
orchestrator).
- Exact ports of std.fs.path.basenameWindows/basenamePosix for theme
name validation (drive, UNC and device roots).
Test Plan:
- cargo test --workspace --no-fail-fast (3555 passed, exit 0)
- just check-macos (both), just check-windows, just check-wasm
- +show-config --default --docs vs /usr/bin/ghostty (only the
build-specific gtk-opengl-debug line differs)
Resolves the 3 P3 items in docs/rewrite/notes/review-rv12-codex.md
(final-Z15.md):
- Sprite strokes: tiny-skia is replaced by a port of z2d 0.10's stroke
plotter (sprite/stroke.rs) plus z2d's arc segmentation and truncating
integer compositing, feeding the ported 4x4 multisample rasterizer.
All 36 reference sprite sheets now match Ghostty's byte for byte (test
tolerance 0), and `just visual-parity` with a production build shows
0 differing pixels (max delta 0) against the installed Ghostty 1.3.1
for the test page (block and bar cursor) and the tabs+splits scenario,
terminal area and chrome alike. tiny-skia dependency removed.
- zig_error maps errno values per target via libc (Linux, Darwin/BSD)
and Win32 codes the way Zig std does on Windows.
- theme=. / theme=.. report "it is a directory" via a port of
std.fs.path.basename, matching Ghostty.
Test Plan:
- cargo test --workspace --no-fail-fast (3498 passed, exit 0)
- cargo build --profile production && just visual-parity
- just check-macos (both), just check-windows, just check-wasm
Resolves the 3 remaining gaps in docs/rewrite/notes/review-rv11-codex.md
(final-Z14.md):
- ghostty-bench validates every occurrence of enum options in order
(Args::take_enum; empty value resets) like cli/args.zig, so
`--mode=bad --mode=noop` fails as in Zig.
- Theme paths with an interior NUL open the prefix like Zig's C-string
path handling (verified against /usr/bin/ghostty).
- Filesystem diagnostics print Zig's error names (error.FileNotFound,
NotDir, AccessDenied, IsDir, NameTooLong, SymLinkLoop, ...) via a
shared ghostty_os::zig_error mapping used by theme/path/config-file/
file_load/edit-config/list-themes; messages match the oracle.
Test Plan:
- cargo test --workspace --no-fail-fast
- just check-macos (both), just check-windows, just check-wasm
Adds scripts/visual-parity.sh (`just visual-parity`): launches the
installed Ghostty 1.3.1 and the Rust build in an isolated headless
nested weston (GL renderer, Mesa forced, weston-screenshooter via
--debug), with identical isolated config, a deterministic test page
(ASCII, ligatures, all SGR styles and underline kinds, 16/256/truecolor,
box/block/braille/powerline/nerd icons, CJK, emoji, wide chars,
cursor) and a tabs+splits scenario driven through Xwayland, then diffs
the captures (terminal area and chrome separately, heatmaps).
Fixes found by comparing against the original:
- A pane stayed blank after a nested split: the GTK render path called
draw_frame(false) where Ghostty uses drawFrame(true) (a re-realized
GLArea has a new swap chain that nothing else redraws).
- Sprite glyph antialiasing now ports z2d's default 4x4 multisample
rasterizer instead of tiny-skia's analytic coverage (pixels differing
from Ghostty's reference sprite sheets: 5.76% -> 1.20%, none off by
more than 96 levels); the sheet test tolerance is tightened to 0.1%.
Result vs Ghostty 1.3.1: test page 0.020% of pixels differ (196 px at
curly-underline ends and diagonal box strokes, max delta 86 levels);
tabs+splits and headerbar/tab bar are bit-identical. Details in
docs/rewrite/notes/visual-parity-VP.md.
Test Plan:
- just visual-parity (needs weston, weston-screenshooter, xdotool)
- cargo test -p ghostty-font (sprite sheet comparison)
Adds tools/difftest: a Zig driver built against a copy of Ghostty
1.3.1's own src/terminal (Zig 0.15.2, set up under target/ by
tools/difftest/setup.sh) and a Rust twin on ghostty-terminal, both
printing a canonical dump of every scrollback/active row (cells,
graphemes, styles, hyperlinks, row flags), cursor, modes, regions,
tabstops and colors. A seeded generator produces escape-heavy streams
(UTF-8/wide/combining/emoji, C0, CSI with edge params, SGR, modes, ESC,
OSC, DCS, margins, insert/delete); `just difftest` runs it with Zig
assertions on (ReleaseSafe vs debug Rust) and off (ReleaseFast vs
production Rust), with minimizers and a triage script.
Divergences found and fixed in Rust (each with a regression test):
- ill-formed UTF-8 decoding follows the SIMD path a default Ghostty
build uses (not Zig's scalar fallback)
- PageList::erase_rows keeps Zig's row-flag behavior on regrow
- grapheme move on wrap with left/right margins uses the original cell
Upstream bugs found and deliberately NOT replicated (per the project
rule): a stale cell pointer after page growth in Terminal.zig:482/539
that drops a grapheme, plus several Zig assertion failures/segfaults;
documented with minimized repros in docs/rewrite/notes/difftest-DF.md.
Final run: 390k cases across many geometries, 0 divergences (Zig-abort
cases, ~0.1-0.5%, are not compared).
Test Plan:
- just difftest 20000 "1 2 3"
- cargo test -p ghostty-terminal (regression tests)
Resolves the 8 gaps in docs/rewrite/notes/review-rv10-codex.md
(final-Z13.md):
- P1 memory safety: the C key-table action (and hovered-link/open-URL
payloads) could advertise a length larger than the NUL-truncated
allocation. A counted_bytes helper now passes the full bytes plus a
trailing NUL with len == byte count, like Zig's backing slices; all
C pointer+len structs were audited and tested to read exactly len.
- ghostty-bench args follow cli/args.zig (ordered duplicates, empty
value resets, first bad value errors, parseBool spellings); the C
benchmark tokenizer uses the ArgIteratorGeneral port.
- OSC 7 derived titles, desktop notifications and OSC 8 hover links
forward raw bytes to the runtime; GTK converts only for display.
- C diagnostic getter exports original bytes; escaped surrogates report
Utf8CannotEncodeSurrogateHalf; config float formatters print `nan`.
- Config::finalize_for(AppRuntime): GTK-only finalization (gtk-single-
instance=detect) and termio's cgroup hooks apply only to the GTK
runtime, so libghostty keeps `detect` like Zig.
Test Plan:
- cargo test --workspace --no-fail-fast (exit 0)
- cargo test -p ghostty-lib --features test-platform
- just check-wasm (macOS/Windows clippy re-run after the concurrent
terminal changes land)
Rust's str::parse accepts and rejects different inputs than Zig's
std.fmt.parseInt/parseFloat (underscores, base prefixes, signs). The
config layer already used the Zig ports; now the protocol parsers do
too (call site -> Zig reference -> status in final-Z12.md):
- zig_fmt moves into the leaf crate ghostty-zigfmt (ghostty-vt cannot
depend on ghostty-os); ghostty-os re-exports it unchanged.
- ghostty-vt: kitty graphics keys, kitty text sizing, OSC 4/5/10-19
color indices, hex color components and intensities, OSC 9 sleep and
progress, OSC 133 exit codes, OSC 3008 fields, kitty color palette.
- ghostty-terminal: tmux layout, format variables and pane tabs.
- ghostty-os: URI ports and %XX decoding; ghostty-bench CLI arguments.
Expected values were cross-checked against the real Zig toolchain.
Test Plan:
- cargo test --workspace --no-fail-fast
- just check-macos (both), just check-windows, just check-wasm
Resolves the 6 differential gap groups in docs/rewrite/notes/review-
rv9-codex.md (details in final-Z11.md):
- ghostty_os::zig_fmt ports std.fmt.parseInt/parseUnsigned/parseFloat
(underscores, 0x/0o/0b prefixes, hex floats, inf/nan) with the Zig std
tests and replaces every int/float parse in config, keybind
parameters, ssh-cache and desktop detection.
- Interior NUL no longer panics in font names and ends C strings like
Zig's backing bytes (fontconfig, C config getters, title actions,
clipboard callback, relative paths, config-file, shaders, background
image, GTK CSS).
- GTK new-window arguments are trimmed of ASCII whitespace only.
- Command tokenization uses a port of std.process.ArgIteratorGeneral
(incl. CR/LF separators) with its std tests.
- UTF-8 validation happens where Zig's field parsers do it, so error
kinds match (metric modifiers, quick-terminal size, link, ...).
A scripted differential run of 202 fields x ~220 hostile/valid values
against /usr/bin/ghostty (+validate-config and +show-config: exit code,
stdout, stderr) now has zero mismatches besides build-specific defaults
(theme resources path, auto-update-channel, gtk-opengl-debug); it also
surfaced and fixed background-blur failure state, NaN formatting and
NaN/-0 finalize clamps. Regression tests: ghostty-config/tests/
zig_parity.rs.
Test Plan:
- cargo test --workspace --no-fail-fast (3456 passed, exit 0)
- just check-macos (both), just check-windows, just check-wasm
Zig parses config values (cli/args.zig, Config.zig field parsers,
config/string.zig) from raw byte slices, and each field type decides
whether it requires UTF-8 (InvalidValue otherwise) or stores bytes. The
port escape-encoded raw bytes into Strings at the line reader and let
fields parse the escaped text, so fields could observe encoding
artifacts; successive audits kept finding such leaks (RV5-RV8).
Field parsers now receive the original bytes: text fields validate UTF-8
where Zig does, byte/path fields store bytes (EncodedOs/OsString). This
resolves all 9 RV8 findings (docs/rewrite/notes/review-rv8-codex.md),
each with a regression test: Windows path conversion fails with
BadPathName instead of substituting U+FFFD; Windows process launch uses
strict UTF-8 like Zig's Command; the WTF-8 decoder accepts paired-
surrogate encodings like Zig; literal U+FDD0 Unicode key triggers;
selection-word-chars and clipboard-codepoint-map reject invalid UTF-8;
font style / codepoint-map family names use original bytes; C title
actions forward bytes; D-Bus new-window/Flatpak conversions match Zig.
Design, sweep and validation: docs/rewrite/notes/final-bytes-refactor.md.
Also moves the bundled iTerm2-Color-Schemes license out of
resources/themes/ so +list-themes no longer lists it as a theme
(output is now byte-identical to Ghostty 1.3.1).
Done by Codex (gpt-6-astra), reviewed and verified by the orchestrator.
Test Plan:
- cargo test --workspace --no-fail-fast (3428 passed, exit 0)
- just check-macos (both), just check-windows, just check-wasm
- +show-config --default --docs, +list-keybinds, +list-actions --docs,
+list-themes --plain compared with /usr/bin/ghostty
Zig's KeyEvent.utf8 and the embedded API's key/preedit text are byte
slices passed through to key encoding and the pty; the port converted
them lossily to UTF-8.
- KeyEvent.utf8 is &[u8] end to end (ghostty-input encoder, core,
ghostty-lib C API, libghostty-vt key events, GTK callers). Where Zig
validates with Utf8View (alternates, modify-other-keys, ctrl CSI-u,
associated text, binding unicode lookup) invalid text takes the same
"no UTF-8" fallback.
- Invalid preedit UTF-8 behaves like Zig's `try Utf8View.init`: the
prior preedit is cleared and marked dirty, the callback returns an
error (logged by the C API) and no preedit is set.
- The inspector key widget shows invalid key text lossily, display only.
Test Plan:
- cargo test --workspace --no-fail-fast (3407 passed)
- cargo test -p ghostty-lib --features test-platform (C-API key text
0xff 0xfe reaches the pty unchanged)
- just check-macos (both), just check-windows, just check-wasm
screen_file_contents_and_write occasionally failed with NotFound when
run in parallel with screen_temp_file_keeps_non_utf8_path_bytes: that
test sets TMPDIR to a short-lived directory via temp_env, and
std::env::temp_dir() evaluated outside temp_env's lock could resolve
into it. The temp-file section now runs under temp_env's lock with
TMPDIR unset. 25 consecutive runs of the core lib tests pass.
Resolves the 9 gap groups in docs/rewrite/notes/review-rv7-codex.md and
audits every string-typed config field against its consumers
(field -> consumers -> decode status table in final-Z9.md):
- Windows EnvMap keys use a port of Zig's nls.zig uppercase table
(upcaseW); Windows locale init follows Zig's chain (setlocale +
_putenv_s); malformed WTF-8 now fails process launch conversions like
Zig instead of substituting U+FFFD; kitty file paths convert WTF-8.
- bash --rcfile/--init-file, configured TERM and inherited GTK LANG
keep their original bytes.
- C API clipboard completion and ghostty_surface_binding_action take
bytes; complete_clipboard_request is byte-typed through the core.
- text:/csi:/esc: bindings, enquiry-response, input = path:, embedded
initial_input, search needles, titles, link regexes, clipboard
codepoint map, command palette text and GTK font/namespace values
decode the escape encoding before use.
Remaining by design: D-Bus a{ss} values and +new-window arguments are
UTF-8 by D-Bus type. C API key/preedit text follows separately.
Test Plan:
- cargo test --workspace
- just check-macos (both), just check-windows, just check-wasm
Resolves the 7 gap groups in docs/rewrite/notes/review-rv6-codex.md
(final-Z8.md):
- crash (macOS): request_abort_dump returns false on mach_msg receive
timeout/error so the signal-path fallback runs; EXC_SOFT_SIGNAL keeps
the signal number in the minidump exception info.
- Default SHELL read as an OS string and stored in encoded command form.
- macOS executable search works on raw PATH bytes; the default path is
used only when PATH is unset.
- Windows: config env overrides are decoded; EnvMap compares keys as
WTF-16 units with per-unit Unicode simple uppercasing (no lossy
conversion, distinct lone-surrogate keys stay distinct).
- +show-config writes original bytes for encoded OS data, so export +
reload round-trips; UTF-8 configs are byte-identical as before.
- Zig's ensureLocale chain lives in ghostty-os for all Unix targets and
is used by both the GTK app and the C API init.
Also makes the quick-terminal GTK replay test independent of compositor
focus: GTK's is-active is read-only, so when niri actually focuses the
layer surface the simulated focus-out cannot drive autohide and that
assertion is skipped (the production logic is unchanged and matches Zig).
Test Plan:
- cargo test --workspace (3390 passed, incl. tests/gtk_replay.rs)
- just check-macos (both), just check-windows, just check-wasm
Escaped config strings (ghostty_os::osbytes) kept reaching OS
boundaries undecoded, and decoding was occasionally applied to plain
text. The root cause was that both were plain `String`s. Now:
- `EncodedOs` is a newtype (no Display/AsRef<Path>) that decodes only
via to_os/to_path/to_bytes/to_wide; plain text enters via
from_plain_text. OS helpers (env, home, xdg, passwd, resources dir,
path expansion), config::Path, WorkingDirectory, theme/file_load/edit
paths, terminal pwd, OpenUrl/Pwd actions and termio cwd use it, so the
compiler forces decoding at every boundary. os::open takes &OsStr.
Config output is unchanged.
Fixes all rows of docs/rewrite/notes/review-rv5-codex.md (final-Z7.md):
screen-file copy; regex/OSC 8 URLs no longer decoded (regression);
lossless Windows WTF-8/WTF-16 for args, env and CreateProcessW; Windows
resources dir/TERMINFO; color OSC request lists and X11 color lookup no
longer allocate globally (C zero-allocation test extended); macOS Mach
handler resumes all threads before KERN_FAILURE (hang regression) and
SIGABRT minidumps go through the exception thread; plus open-config,
background images, crash dir, ssh cache, exe dir/PATH, fontconfig
filenames, file links, kitty TMPDIR, .hushlogin and benchmark paths.
The typing also surfaced and fixed gtk-custom-css, bell audio, bash
HISTFILE, OSC 52 writes and the C config getters.
Test Plan:
- cargo test --workspace (3376 passed)
- just check-macos (both), just check-windows, just check-wasm
- diff <(ghostty-rs +show-config --default --docs) \
<(ghostty +show-config --default --docs)
Ghostty links sentry-native with the breakpad backend on every platform
(SharedDeps.zig), so it writes minidumps on macOS and Windows as well.
- macOS: a Mach exception port handler thread (structured like
Breakpad's exception_handler) catches EXC_BAD_ACCESS/BAD_INSTRUCTION/
ARITHMETIC/BREAKPOINT, suspends other threads, reads the crashed
thread state and writes a minidump via minidump-writer as the
event.minidump attachment, then restores the previous ports so the
BSD signal and default termination follow. The signal path remains
the fallback (and handles SIGABRT); a watchdog alarm bounds dumping.
- Windows: SetUnhandledExceptionFilter hands off to a pre-started dump
thread using minidump-writer's dump_crash_context.
- The screen-file open action decodes the escaped path to raw OS bytes
(also for the C action passed to embedders).
macOS/Windows code is compile-checked (clippy -D warnings) only; the
shared Mach message decoding and minidump attachment logic have Linux
unit tests. Platform matrix in docs/rewrite/notes/crash-CR.md.
Test Plan:
- cargo test --workspace (3348 passed)
- just check-macos; just check-macos x86_64-apple-darwin; just check-windows
Resolves the 13 rows still partial in docs/rewrite/notes/review-rv4-
codex.md (details in final-Z5.md):
- Escaped config strings are decoded to raw bytes at every OS boundary:
Flatpak cwd/argv to D-Bus, screen-file paths, the default config
template, GHOSTTY_RESOURCES_DIR/TERMINFO/shell integration/
bindtextdomain consumers, custom shader loading, list-themes hyperlinks.
- osbytes escapes literal U+FDD0 on non-Unix too (bijective everywhere);
the GTK paste dialog only decodes text that was actually encoded.
- shadertoy: whole-array uses of iChannelTime rebuild a plain float[4]
from the std140-padded members (naga-validated, MSL offsets checked).
- libghostty-vt: the OSC parser holds caller-allocated storage inline
and command payloads borrow from it, so ghostty_osc_end performs no
global allocations (verified from Rust and C).
- crash: aarch64 Linux minidump context fixed; macOS fatal signals record
siginfo, registers and a fault-safe stack walk.
- tripwire: selectionString map allocation-failure cleanup test ported.
- libghostty-vt emits the versioned soname libghostty-vt.so.0 at build
time (no patchelf needed at install).
Test Plan:
- cargo test --workspace (3342 passed)
- just check-macos; just check-macos x86_64-apple-darwin;
just check-windows; just check-wasm
- cargo check --target aarch64-unknown-linux-gnu -p ghostty-crash
The non-Linux code paths can only be compile-checked on this machine;
agents had used ad-hoc wrapper setups. These scripts make the checks
reproducible and are wired into `just check-macos`, `check-windows`,
`check-wasm` and `check-all`:
- macos-crosscheck.sh: zig cc wrappers (dropping -arch/-mmacosx-
version-min) plus a declaration-only os/log.h stub so the few C build
scripts compile; clippy -D warnings on all 17 cross-platform crates
for aarch64/x86_64-apple-darwin.
- windows-crosscheck.sh: mingw toolchain and a stub fontconfig.pc;
clippy -D warnings for x86_64-pc-windows-gnu.
- wasm-check.sh: wasm32 clippy/build and the node-based wasm tests.
Also fixes a needless_pass_by_value lint in osbytes' non-Unix branch
that only the Windows check surfaces.
Test Plan:
- just check-macos; just check-macos x86_64-apple-darwin
- just check-windows; just check-wasm
Zig treats paths, arguments and environment values as byte slices; the
port converted them lossily in many places. Now (final-Z1.md):
- argv, env (incl. present non-UTF-8 values), HOME, PATH lookup,
passwd, resources dir, TMPDIR, cgroup, config path canonicalization,
+validate-config, +list-themes and Zig-string escapes (\xFF) keep raw
bytes. String-based config values carry bytes via ghostty_os::osbytes,
a bijective escape (noncharacter U+FDD0 + byte) that leaves all valid
UTF-8 - including Nerd Font private-use icons - unchanged.
- os/file rlimit fallbacks exist on all targets.
- i18n locale canonicalization is an exact port of libintl's tables
(e.g. pa-Guru -> pa_IN).
- New ghostty-build-info crate is the single source of version, channel,
commit and build mode for all consumers.
Test Plan:
- cargo test -p ghostty-os -p ghostty-config -p ghostty-cli
(incl. randomized osbytes round-trip property tests)
Closes the remaining artifact rows of the final audit
(docs/rewrite/notes/final-Z4.md):
- webgen: config.mdx, actions.mdx, commands.mdx (byte-identical to
Zig's webgen output once its missing stdout flush is added)
- `just install-lib`: libghostty (+ ghostty.h, modulemap) and
libghostty-vt with versioned symlinks, headers and libghostty-vt.pc
- scripts/xcframework.sh: per-arch builds, lipo and
xcodebuild -create-xcframework for GhosttyKit (dry-run verified only)
- `just install` builds --profile production so the installed app id is
com.mitchellh.ghostty; other profiles install the -debug id/name
- `just update-translations`: xgettext/msgmerge pipeline; extracted
msgids equal upstream's .pot (found and fixed an untranslated string
in the child-exited banner)
- `just dist` / `distcheck`: versioned source tarball with VERSION and a
prebuilt GTK resource bundle
- Windows: icon, version resource and DPI/common-controls manifest
embedded via embed-resource, GUI subsystem
Test Plan:
- just install-lib /tmp/p release all && pkg-config --cflags \
--libs /tmp/p/share/pkgconfig/libghostty-vt.pc
- just distcheck
Closes the remaining library rows of the final completeness audit
(docs/rewrite/notes/final-Z3.md):
- libghostty-vt: OSC parser storage goes through the caller-supplied C
allocator via a ByteStorage abstraction; ghostty_osc_new reports
GHOSTTY_OUT_OF_MEMORY and failed grows invalidate the OSC like Zig.
Tested with a counting/failing/limiting C allocator.
- shadertoy: sampler parameter rewriting is keyed by full signature so
overloads work; the Metal uniform block keeps std140 strides for
iChannelTime[4] (MSL offsets checked against the upload layout).
- tripwire.zig ported (named trip points, hit counts, verification) and
used to port the previously skipped allocation-failure tests that have
a fallible Rust path; the rest are documented.
- crash: fatal signals capture siginfo + registers and a forked helper
writes a minidump (minidump-writer) as an envelope attachment, like
sentry-native/Breakpad; tests parse it with the minidump crate.
- kitty LoadingImage::debug_dump (debug builds) and the Darwin
Instruments signpost around benchmark runs.
Test Plan:
- cargo test -p ghostty-vt-capi --test c_allocator
- cargo test -p ghostty-crash --test minidump
Ports apprt/embedded.zig, main_c.zig and config/CApi.zig + c_get.zig as
crates/ghostty-lib (cdylib/staticlib/rlib `libghostty`), exporting every
function of the unchanged include/ghostty.h - the ABI Ghostty's Swift
macOS/iOS app links against. The embedded runtime implements the
runtime traits over the host's C callbacks, so the real core drives it.
- ghostty-core selects the renderer per target: Metal on Apple (surface
NSView/UIView via RtSurface::platform_view), OpenGL elsewhere.
- ghostty_config_get follows Zig's c_get rules via a CValue type (143
supported keys, checked against the Zig field types).
- The inspector C API is complete; its Metal backend replaces ImGui's
Metal renderer and has not run.
Verification: generated C ABI probe (sizeof/alignof/offsetof of every
struct/union, 755 enum values, every function linked, nm exports, headers
byte-identical to upstream); a C host on Linux covering init, CLI
actions, config, app, keys, focus and color scheme; an opt-in
test-platform feature creating real surfaces on Linux with headless EGL
and a pty (13 tests). Apple targets are clippy-clean but not run; the
Swift app has not been built against it.
Test Plan:
- cargo test -p ghostty-lib (and --features test-platform)
- cargo clippy --target aarch64-apple-darwin -p ghostty-lib (cross env
in docs/rewrite/notes/macos-M1.md)
- apprt-gtk: Ghostty's runtime+build-time version gating (at_least)
with cargo features adw_1_3/adw_1_4/adw_1_5/gtk_4_16 (default on) and
the fallbacks for older libraries: MessageDialog (adw < 1.5),
clipboard dialog without SwitchRow, pre-Banner debug warning and
child-exited widgets, the old about dialog, GTK 4.16 CSS error source
bytes. The minimum stays GTK 4.14 / libadwaita 1.5 (Ghostty's floor).
The root build links with -z lazy so version-gated symbols don't keep
older libraries from loading.
- ghostty-helpgen (port of helpgen.zig) generates CLI action help and
keybinding action docs from doc comments at build time, replacing the
checked-in help/*.txt; +help and +list-actions --docs stay
byte-identical to Ghostty 1.3.1.
- ghostty-bench: libc wcwidth mode and separate simd/table and
uucode/table code paths like Zig.
- ghostty-cli: Windows branches of edit-config, list-colors,
list-keybinds and boo; ssh-cache DiskCache flock locking (no-op on
Windows, as in Zig) with an exclusivity test.
Test Plan:
- cargo test --workspace
- diff <(cargo run -q -- +list-actions --docs) \
<(ghostty +list-actions --docs)
Ports the macOS-specific Zig code behind cfg(target_os = "macos"):
- renderer: a GraphicsApi trait with generic::Renderer<G>; Metal backend
(all renderer/metal/* files, shaders.metal verbatim, runtime library
compile, IOSurface layer, CVDisplayLink), shadertoy -> MSL via naga;
OpenGL remains the backend elsewhere.
- font: CoreText face, shaper and discovery with build.rs backend
selection (coretext default; coretext-freetype / coretext-harfbuzz /
coretext-noshape features), no fontconfig/freetype/harfbuzz on Apple.
- input: KeymapDarwin via UCKeyTranslate, option-as-alt, mac keycodes.
- os: macos, mach, cf_release_thread, NSProcessInfo args and macOS
branches; config: macOS defaults, keybinds, Application Support paths.
- termio/core: login(1) wrapping, openpty/execvpe, Darwin child exit
handling, os_log logging.
Compile-checked for aarch64/x86_64-apple-darwin (clippy -D warnings
clean) using the cross setup in docs/rewrite/notes/macos-M1.md; cannot
run here. The embedded runtime/C API (embedded.zig) is the next step.
Also includes the cross-platform integration edits shared by the crash,
verification, Windows and macOS work (Cargo target-gating, thread
crash-context hooks, apprt-gtk surface/IME refactor, termio/os cfg
gates), a cfg fix so ghostty-config builds on wasm32, and an allow for
the intentional recursion in the crash overflow test.
Test Plan:
- cargo test --workspace (3224 passed)
- cargo clippy --workspace --all-targets -- -D warnings
- cargo check --target aarch64-apple-darwin -p ghostty-renderer (with
the documented cross env)
Ports the Windows branches of the Zig code behind cfg(windows):
ConPTY pty (CreatePseudoConsole, pipes, resize), CreateProcessW with the
pseudo-console attribute list, env blocks and command-line quoting, a
pipe read thread with CancelIoEx and a WaitForMultipleObjects IO loop,
plus os helpers (home dir, env, paths, temp dirs, hostname, XDG via
LOCALAPPDATA) and config/input/terminal/crash Windows branches.
Compile-checked with `cargo check --target x86_64-pc-windows-gnu` for
vt, unicode, datastruct, input, os, config, terminal and termio (setup
in docs/rewrite/notes/windows-WIN.md); cannot run here.
Platform-independent pieces (quoting with a CommandLineToArgvW
round-trip, env block encoding) have unit tests.
Test Plan:
- cargo test -p ghostty-termio windows_args
- cargo check --target x86_64-pc-windows-gnu -p ghostty-termio
Exercises the runtime paths that had never run (details in
docs/rewrite/notes/verification-VR.md):
- IME: replay steps drive the surface's real GtkIMContext handlers with
a real preedit + attributes; preedit renders under the cursor and
commits reach the pty, incl. dead-key and compose orderings.
- Paste and OSC 52 read/write confirmation dialogs: allow, deny and
remember flows.
- Quick terminal on niri via wlr-layer-shell: positions, sizes, toggle
and autohide. Fixes a real bug: link order could put libwayland-client
ahead of libgtk4-layer-shell, silently disabling the quick terminal;
apprt-gtk now loads libwayland-client via wayland-sys dlopen.
- Flatpak host commands against a zbus mock of
org.freedesktop.Flatpak.Development under dbus-run-session: spawn,
exit status, cwd/env/fd forwarding and SIGHUP on stop, both directly
and through termio Exec (flatpak-info path seam added).
- Horizontal-scroll tab switching thresholds and reset.
tests/gtk_replay.rs runs the real binary against the session display
(skips without one).
Test Plan:
- cargo test --test gtk_replay (needs a Wayland/X11 display)
- cargo test -p ghostty-os --test flatpak_mock; -p ghostty-termio
--test flatpak_exec
Ports ZIG/src/crash/* as crates/ghostty-crash. Like Ghostty (which uses
sentry-native only to write local files), crashes are written as Sentry
envelopes (<uuid>.ghosttycrash) to $XDG_STATE_HOME/ghostty/crash:
- envelope parse/serialize (all Zig tests ported)
- panic hook plus SIGSEGV/SIGBUS/SIGILL/SIGFPE/SIGABRT handlers on an
alternate stack; the signal path is allocation-free and re-raises with
the default action so exit status and core dumps are unchanged
- thread-local crash context (main/renderer/io/read thread, surface
dimensions) set from the corresponding threads
- `sentry` cargo feature on the root binary, off by default like
upstream's -Dsentry; +crash-report lists exactly what is written
Deviation: attachments always carry "type":"attachment" (Zig's
in-memory attachments omit it and become unparseable).
Test Plan:
- cargo test -p ghostty-crash (19)
- cargo test --test crash (child panics, SIGSEGV, stack overflow and
abort each produce a valid envelope listed by +crash-report)
Ghostty's wasm code talks to JavaScript through the zig-js library
(mitchellh/zig-js, pinned in build.zig.zon). The first port invented its
own import ABI because the dependency wasn't available; now:
- ghostty-os/src/wasm/js.rs is a Rust port of zig-js's Zig side (Ref,
Value, Object get/set/call/new, global, runtime, string) using its
"zig-js" import module, function names and NaN-boxed value refs.
- web_canvas face and the atlas debug canvas make the same JS calls as
Ghostty's Zig code.
- zig-js's unmodified JS host (+ LICENSE) is vendored under
crates/ghostty-wasm/js/zig-js and drives the node test.
Test Plan:
- cargo test -p ghostty-wasm (node >= 24; asserts zig-js imports and no
leaked host values)
- RenderState stores styles sparsely (per-cell u16 style ids plus one
Style per distinct run) and skips plain cells with a single mask test
(Cell::is_plain). screen-clone render: 11.5 ms -> 4.1 ms (Zig 4.7).
row::never_extend_bg now takes a style accessor instead of a slice.
- Parser::next_with(byte, |action| ..) hands actions to a closure
instead of moving a 64-byte action array out; Parser::next remains a
wrapper over the same core (equivalence test added).
terminal-parser SGR: 538 ms -> 231 ms (Zig 354).
All Ghostty benchmarks are now at or below Zig's time; table in
docs/rewrite/notes/perf-P1.md (the parser benchmark uses next_with with
a discarding closure, which like Zig's `_ = actions` lets the optimizer
skip unused work).
Test Plan:
- cargo test --workspace
- ghostty-bench +screen-clone --mode=render / +terminal-parser
Ports main_wasm.zig as crates/ghostty-wasm (cdylib exporting the Zig
names: atlas_*, face_*, deferred_face_*, shaper_*, config_*), the
web_canvas font face and shaper (backend selected on wasm32), os/wasm
(logging, target, JS interop) and config/Wasm.zig.
C-library dependencies (freetype, harfbuzz, fontconfig, onig, gettext,
D-Bus, rustix) are target-gated. Like Zig's wasm build, link regex
search (StringMap::search_iterator) is unavailable without Oniguruma;
Collection/SharedGrid/harfbuzz are compiled out on wasm32. ghostty-os
switches from forbid to deny unsafe_code for the JS import blocks.
The JS import ABI is currently a documented handle-based interface with
a reference host (js/ghostty_host.js); aligning it with zig-js's exact
import names follows separately.
Test Plan:
- cargo build --target wasm32-unknown-unknown -p ghostty-font \
-p ghostty-config -p ghostty-terminal -p ghostty-vt -p ghostty-wasm
- cargo test -p ghostty-wasm (node runs the module against a fake canvas)
Ports ZIG/src/lib_vt.zig and terminal/c/* as crates/ghostty-vt-capi
(cdylib + staticlib + rlib, soname libghostty-vt.so) implementing the
unchanged include/ghostty/vt.h + vt/*.h ABI: OSC parser, SGR parser,
key events and key encoding, paste safety, colors, result codes - all
38 native symbols, plus the ghostty_wasm_* helpers on wasm32.
Verification:
- tests/abi.rs generates a C probe from the headers, compiled with cc,
that checks sizeof/alignof/offsetof of every struct/union, every
enumerator and #define against Rust, and links every declared symbol;
nm -D exports equal the declared functions.
- Ghostty's example/c-vt* programs compile unchanged against the Rust
library (shared and static) and produce the expected output.
- The wasm32 build runs Ghostty's wasm-key-encode/wasm-sgr example
scripts unmodified under node (upstream's wasm-sgr tag table is stale
vs sgr.h; a regenerated copy checks full output).
Test Plan:
- cargo test -p ghostty-vt-capi (22 unit + 2 ABI + 5 C + 3 wasm tests)