Commit Graph
175 Commits
Author SHA1 Message Date
ddidderr 0137168c66 feat(macos): foundation of the macOS app ported from Swift to Rust
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
2026-10-12 19:25:44 +02:00
ddidderr fb79cc8d5c docs(rewrite): start phase 9 - port the Swift macOS app to Rust 2026-10-12 18:51:17 +02:00
ddidderr ae9cfde068 docs(rewrite): status update 2026-10-12 18:50:02 +02:00
ddidderr 30863e1f0b test(gtk): live D-Bus/portal comparison against the original Ghostty
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)
2026-10-12 18:49:53 +02:00
ddidderr e44b9271d6 docs(rewrite): status update 2026-10-12 17:32:00 +02:00
ddidderr 78e550bdfc docs(rewrite): final status - no known unfixed differences remain 2026-10-12 17:30:52 +02:00
ddidderr a7e8eb0ac6 docs(rewrite): status update 2026-10-12 17:24:18 +02:00
ddidderr e8932674fe docs(rewrite): record commit hashes for deviant fixes 37-39 2026-10-12 17:23:52 +02:00
ddidderr 596b179dbb 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 e4a6ee7bf4 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 5c5962ea95 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 1431a8a1d8 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 d1eb32b4f7 docs(rewrite): status update 2026-10-12 16:55:22 +02:00
ddidderr b828cab2e5 docs: close out the rewrite in README and STATUS
README now describes the full scope (Linux app, C APIs, WASM, crash
reporting, macOS/Windows compile-checked), the verification tooling
(difftest, visual-parity, check-all) and links DEVIANT_FIXES.md and
KNOWN_DIFFERENCES.md. STATUS records completion and the stopping
criterion.
2026-10-12 16:53:55 +02:00
ddidderr 325288b7ee 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 7b59457cb8 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 729b544820 docs(rewrite): status update 2026-10-12 16:39:38 +02:00
ddidderr 3d11f28d03 docs(rewrite): record final audit round RV18 2026-10-12 16:39:17 +02:00
ddidderr 26d66ae119 docs(rewrite): define stopping criterion for review rounds 2026-10-12 16:33:58 +02:00
ddidderr 96b688c9e4 docs(rewrite): register commit for row 34; status update 2026-10-12 16:24:10 +02:00
ddidderr 80e8502eaf 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 e536b603b2 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 75fd25d1f1 docs(rewrite): status update 2026-10-12 16:11:05 +02:00
ddidderr cc6a5b017a docs(rewrite): record final verification RV17 2026-10-12 16:10:47 +02:00
ddidderr 2a80d98483 docs(rewrite): status update 2026-10-12 15:56:32 +02:00
ddidderr 05d225b800 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 ff1169abed 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 ebd603f3da docs(rewrite): status update 2026-10-12 15:47:25 +02:00
ddidderr daa1629717 docs(rewrite): record final verification RV16 2026-10-12 15:47:09 +02:00
ddidderr c46b595fd8 docs(rewrite): status update 2026-10-12 15:39:43 +02:00
ddidderr 3017a1b50e docs(rewrite): record commit hashes of the new fix-deviant commits 2026-10-12 15:37:50 +02:00
ddidderr e1844e8fc7 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 67a7b2937a 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 03e70e4276 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 9d5a8994a6 docs(rewrite): status update 2026-10-12 15:29:39 +02:00
ddidderr 9ece0f0027 docs(rewrite): record final verification RV15 2026-10-12 15:29:23 +02:00
ddidderr ef9193270f docs(rewrite): status update 2026-10-12 15:19:53 +02:00
ddidderr 0170d74a5c docs(rewrite): status update 2026-10-12 15:18:02 +02:00
ddidderr 4b6ab7235d docs(rewrite): register all intentional deviations from Ghostty
Adds docs/rewrite/DEVIANT_FIXES.md, the single list of places where the
Rust port deliberately behaves differently from Ghostty 1.3.1:
- 30 upstream bugs fixed instead of replicated (memory safety/UB,
  infinite loops, crashes, data loss), each with Zig file:line, both
  behaviours, the commit that introduced the Rust behaviour and its test
  (a few Zig locations are marked unverified);
- 11 upstream quirks deliberately kept as in Zig;
- 18 other deliberate deviations (out-of-scope Swift app, platforms that
  are only compile-checked, library substitutions with observable
  differences).
Deviations made from now on are committed alone as fix-deviant(<scope>)
and added here; earlier ones are bundled in larger commits, which is why
the register records their hashes.
2026-10-12 15:18:02 +02:00
ddidderr 0398b87cfc docs(rewrite): status update 2026-10-12 15:15:54 +02:00
ddidderr abc897524c 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 3aab04bdce docs(rewrite): commit intentional deviations as fix-deviant(...) and register them 2026-10-12 15:13:45 +02:00
ddidderr 21490817b4 docs(rewrite): status update 2026-10-12 14:59:39 +02:00
ddidderr 4eb5522813 docs(rewrite): record final verification RV14 2026-10-12 14:59:23 +02:00
ddidderr ee9b163d06 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 df517db221 docs(rewrite): status update 2026-10-12 14:49:10 +02:00
ddidderr 1bd918159a 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 3ce3225642 docs(rewrite): status update 2026-10-12 14:30:14 +02:00
ddidderr 459d4a9c3c docs(rewrite): record final verification RV13 2026-10-12 14:30:00 +02:00
ddidderr 8f3eee6c98 docs(rewrite): status update 2026-10-12 14:20:59 +02:00