Commit Graph
84 Commits
Author SHA1 Message Date
ddidderr 4d7ad7cdba fix-deviant(termio): resolve bare program names on PATH on Windows
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
2026-10-12 17:23:38 +02:00
ddidderr 97a262dbe6 fix-deviant(config): accept absolute Windows theme paths
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
2026-10-12 17:23:38 +02:00
ddidderr b9ad595ca1 fix-deviant(termio): stop waiting for a child that died before setsid
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
2026-10-12 17:23:38 +02:00
ddidderr a8d0d624da test(windows): run the Windows code under Wine; theme preview parity
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)
2026-10-12 17:23:24 +02:00
ddidderr 11c8e057ff fix-deviant(terminal): no panics on input-reachable terminal states
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
2026-10-12 16:53:27 +02:00
ddidderr 4fa55855e8 fix(os,termio): FreeBSD support, dev-profile resource precedence, docs
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
2026-10-12 16:53:27 +02:00
ddidderr 2040677b05 fix-deviant(renderer): don't over-allocate Metal buffers by sizeof(T)
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
2026-10-12 16:23:54 +02:00
ddidderr 132f7c27ba fix: inspector font/formatting, FreeType-only backend on Apple
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
2026-10-12 16:23:54 +02:00
ddidderr b244567faa fix-deviant(os): register and test the NOTIFY_SOCKET bounds fix
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
2026-10-12 15:56:21 +02:00
ddidderr 389266720d feat(font): add the FreeType-only font backend (font-backend-freetype)
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
2026-10-12 15:56:21 +02:00
ddidderr 4a55abf1c1 fix-deviant(os): always reap the URL opener child process
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
2026-10-12 15:37:38 +02:00
ddidderr 65dd9d579f fix-deviant(gtk): reject malformed +new-window arguments instead of aborting
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)
2026-10-12 15:37:38 +02:00
ddidderr 465a85b6d5 fix: URL opener and +show-face output match Ghostty, register fixes
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
2026-10-12 15:37:24 +02:00
ddidderr 410d8fc1a9 fix-deviant(cli): never lose or overwrite CLI output like Zig does
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
2026-10-12 15:15:40 +02:00
ddidderr f76179f49a fix: +show-face u21/UTF-8 semantics, Zig output buffering, isAbsolute
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
2026-10-12 15:15:18 +02:00
ddidderr 3068e3a389 fix(config): port Zig 0.15.2 basename (not 0.16) for theme names
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.
2026-10-12 14:59:07 +02:00
ddidderr f9094c8152 feat: honor async-backend with epoll/io_uring, fix last RV13 items
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)
2026-10-12 14:49:00 +02:00
ddidderr f68b11d469 fix: pixel-identical rendering (z2d stroker port) and last P3 items
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
2026-10-12 14:20:49 +02:00
ddidderr 4bbe0d7fe9 fix: last RV11 gaps - bench enum args, theme NUL paths, Zig error names
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
2026-10-12 13:54:15 +02:00
ddidderr 64aa8b6793 feat: visual parity with the original Ghostty (pixel-diff pipeline)
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)
2026-10-12 13:52:22 +02:00
ddidderr 4eb03f637d test: differential fuzzing of the terminal core against Ghostty's Zig code
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)
2026-10-12 13:24:12 +02:00
ddidderr f2ccb04aa4 fix: C action buffer lengths, byte-faithful callbacks, runtime finalize
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)
2026-10-12 13:09:04 +02:00
ddidderr a1ed0cd776 fix: use Zig's numeric parsing in protocol and escape-sequence parsers
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
2026-10-12 12:45:55 +02:00
ddidderr a57ff41d34 fix(config): match Zig's numeric grammar, NUL handling and diagnostics
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
2026-10-12 12:35:37 +02:00
ddidderr 672e8a4ae1 refactor(config): parse config values from raw bytes like Zig
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
2026-10-12 11:57:01 +02:00
ddidderr db954441fa fix(input): carry key and preedit text as bytes like Zig
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
2026-10-12 11:01:17 +02:00
ddidderr 0b06038859 test(core): serialize screen-file test with the TMPDIR-mutating test
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.
2026-10-12 10:54:03 +02:00
ddidderr e6564cb838 fix: systematic byte fidelity for config strings and RV7 gaps
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
2026-10-12 10:50:20 +02:00
ddidderr 5e5d33324c fix: close the RV6 verification gaps
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
2026-10-12 10:27:37 +02:00
ddidderr f7fec6f44d refactor: type escaped OS strings and fix the final audit findings
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)
2026-10-12 10:06:07 +02:00
ddidderr 72bfac1cc8 feat(crash): Breakpad-style minidumps on macOS and Windows
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
2026-10-12 08:55:27 +02:00
ddidderr 1a13e8b556 fix: close the tail items from the targeted re-audit
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
2026-10-12 08:45:21 +02:00
ddidderr 231ee35ea9 build: add reproducible macOS, Windows and wasm cross-check scripts
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
2026-10-12 08:16:14 +02:00
ddidderr a9f3856b15 fix: Windows core build, embedded init, byte-exact paths, version info
Closes the remaining core/lib/termio/GTK rows (final-Z2.md):
- ghostty-core compiles for Windows (Unix-only exec fields and file
  modes gated); checked with cargo check --target x86_64-pc-windows-gnu.
- Embedded init: ensure_locale, SIGPIPE ignored in ghostty_init for
  C/Swift hosts, Apple crash tags; the argv passed to ghostty_init feeds
  ghostty_config_load_cli_args; ghostty_config_load_file accepts
  non-UTF-8 paths.
- OSC 7 pwd, embedded surface cwd/command/env/initial input,
  ghostty_surface_text and GTK file drops keep raw bytes.
- XTVERSION, TERM_PROGRAM_VERSION, the About dialog and crash metadata
  use ghostty_build_info instead of hardcoded versions.

Test Plan:
- cargo test --workspace (3327 passed)
- cargo clippy --workspace --all-targets -- -D warnings
2026-10-12 08:14:02 +02:00
ddidderr 395cff8981 fix: preserve non-UTF-8 bytes in OS, config and CLI layers
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)
2026-10-12 08:14:02 +02:00
ddidderr e4cdd8e007 feat(dist): website data, library installs, XCFramework, dist tarball
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
2026-10-12 08:14:02 +02:00
ddidderr f6002d7519 fix: C allocator contract, shadertoy overloads/MSL stride, minidumps
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
2026-10-12 08:14:02 +02:00
ddidderr 75bf604375 feat(lib): port libghostty, Ghostty's embedded C API
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)
2026-10-12 06:38:33 +02:00
ddidderr f2252bb165 feat: port GTK version fallbacks, helpgen, bench modes, CLI leftovers
- 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)
2026-10-12 06:38:33 +02:00
ddidderr bab49ee596 feat(macos): port Ghostty's macOS code paths (Metal, CoreText, Darwin)
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)
2026-10-12 06:02:39 +02:00
ddidderr 76771bed0d feat(windows): port Ghostty's Windows code paths
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
2026-10-12 06:02:28 +02:00
ddidderr 84f665cdfb test: verify IME, clipboard dialogs, quick terminal and Flatpak paths
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
2026-10-12 06:02:28 +02:00
ddidderr b3145ad7c1 feat(crash): port Ghostty's local crash reporting
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)
2026-10-12 06:02:28 +02:00
ddidderr 516c780f84 feat(wasm): use zig-js's exact JS interop ABI
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)
2026-10-12 05:26:53 +02:00
ddidderr abb89a80e9 perf: close the last benchmark gaps to Zig (render state, parser)
- 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
2026-10-12 05:22:01 +02:00
ddidderr d89691b0ce feat(wasm): port Ghostty's wasm32 build
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)
2026-10-12 05:21:14 +02:00
ddidderr 0915f7b935 feat(vt-capi): port the libghostty-vt C API
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)
2026-10-12 05:16:41 +02:00
ddidderr 8bcb85a29a style: apply nightly rustfmt import grouping across the workspace
rustfmt.toml sets group_imports, imports_granularity and imports_layout,
which are nightly-only; agents formatted with stable rustfmt, which
ignored them. This is a formatting-only pass with `cargo +nightly fmt`.

Test Plan:
- cargo clippy --workspace --all-targets -- -D warnings
- cargo test --workspace
2026-10-12 05:03:37 +02:00
ddidderr 81d4119d85 test(apprt-gtk): replay mouse input and verify real-world TUIs
The GTK mouse handlers now delegate to *_impl methods taking button and
modifier state, so the real controllers and the debug input replay share
one code path. The replay hook gained mouse-move/press/release, click,
drag, scroll, resize, clipboard-set/dump, windows-dump and
window-screenshot steps (replayed presses bypass the focus-transfer
first-click rule since the replay window isn't compositor-focused).

Verified in the real app on Wayland (docs/rewrite/notes/verification-
V3.md): nvim (colors, status line, cursor shapes, alt-screen restore),
htop (rendering, mouse-click column sorting), less (alternate scroll),
tmux (splits, status bar), drag/double/triple-click selection into
PRIMARY, clipboard copy, context menu, scrollback and reflow on resize,
CJK/emoji/ZWJ/nerd-font widths, kitty graphics images and the unsafe
paste confirmation dialog. No product bugs were found.

Test Plan:
- cargo test -p ghostty-apprt-gtk (incl. parse_mouse_steps)
2026-10-12 05:02:48 +02:00
ddidderr 2aa2a12de0 fix: close remaining gaps found by the completeness audit
Addresses the partial ports listed in
docs/rewrite/notes/review-completeness-codex.md (details in
docs/rewrite/notes/gaps-X2.md):
- Flatpak: the exit watcher no longer consumes the host command; a
  shared FlatpakSignalHandle lets closing a surface still SIGHUP the
  host process group.
- Child environment is byte-preserving end to end (ghostty_os::EnvMap
  of OsStrings) and Snap paths are filtered like filterSnapPaths.
- argv handling no longer panics on non-UTF-8 arguments.
- URI percent-decoding keeps raw bytes; OSC 7 pwd is passed through
  without lossy conversion.
- Keybinding comparison/hashing uses full Unicode case folding from a
  table generated from CaseFolding.txt (Unicode 17.0.0).
- Link regexes set Oniguruma's whole-search retry limit through the new
  minimal ghostty-onig crate (one SAFETY-commented FFI call).
- OSC 9 NUL-capacity check ordering matches Zig.
- Wayland registry binds/clears globals advertised after startup.
- Custom shaders may pass sampler2D to helper functions (naga adapter).
- +list-themes preview renders OSC 8 links, styled underlines and
  follows color-scheme reports.

Also includes the inspector's integration edits in shared files
(renderer state hook, termio VT recording, core/apprt-gtk wiring), X1's
config/input/cli additions for completion generation, and a fix for a
race in the termio backpressure test (it wrote before `stty raw`
applied, so the cooked line discipline dropped bytes past MAX_CANON).

Test Plan:
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
2026-10-12 04:39:24 +02:00