Files
ddidderr 452b782149 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

97 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# macOS port (M1): os, input, config, termio, core, crash glue
Scope of this note: everything of M1 except the two big sub-ports, which have their own notes:
* `macos-M1-font.md` – CoreText face/shaper/discovery, backend selection (`ghostty-font`).
* `macos-M1-renderer.md` – Metal backend, `GraphicsApi` abstraction (`ghostty-renderer`).
Nothing here has ever run on macOS. It is compile-checked (`cargo check|clippy --target
aarch64-apple-darwin|x86_64-apple-darwin`) and every piece of platform-independent logic was
extracted so its macOS variant is unit-tested on Linux (via explicit `darwin: bool` parameters, or
`ghostty_input::platform::with_darwin` in test builds). Linux behaviour is unchanged.
## Cross-checking on Linux (no macOS SDK)
The C build scripts of the dependency tree (onig_sys, gettext-sys, dear-imgui-sys, oslog) cannot
cross-compile without the SDK. For the *check only* (no repo change) use `zig cc`/`zig c++`
wrappers (`-target aarch64-macos`/`x86_64-macos`):
```
export CARGO_BUILD_RUSTC_WRAPPER= # kache breaks cc-rs
export CC_aarch64_apple_darwin=<zcc-arm> CXX_aarch64_apple_darwin=<zcxx-arm> AR_aarch64_apple_darwin=<zig ar>
export GETTEXT_DIR=/nonexistent # makes gettext-sys skip its from-source build
export CFLAGS_<target>=-I<dir with a stub os/log.h> # oslog's wrapper.c needs <os/log.h>
export ZIG_GLOBAL_CACHE_DIR/ZIG_LOCAL_CACHE_DIR=<writable dir>
cargo check --target aarch64-apple-darwin -p ghostty-vt -p ghostty-unicode -p ghostty-datastruct \
-p ghostty-os -p ghostty-input -p ghostty-config -p ghostty-terminal -p ghostty-font \
-p ghostty-renderer -p ghostty-termio -p ghostty-core
```
(the wrappers drop `-arch`, `-mmacosx-version-min` and `--target=` that cc-rs adds). Real macOS builds
need none of this: onig_sys compiles with the system clang, `gettext-rs` builds libintl from source
(like Zig's `pkg/libintl`), `oslog`/imgui use the SDK. Root crate (`ghostty`, GTK) and
`ghostty-apprt-gtk` are Linux-only and are not part of the macOS check (the macOS app links the embedded
`libghostty`, L1's job).
## ghostty-os
| Zig | Rust |
|---|---|
| `os/macos.zig` | `macos.rs`: `QosClass`, `set_qos_class`, `pthread_setname_np`, `is_at_least_version`, `app_support_dir`, `cache_dir` (NSFileManager via objc2-foundation), pure `join_dir`/`QosClass` values tested everywhere |
| `os/mach.zig` | `mach.rs`: `VmTag` (+`make`), `tagged::TaggedPageAllocator` (mmap with VM tag in the fd arg; Rust has no stable allocator API so it is an alloc/unmap/realloc struct) |
| `os/cf_release_thread.zig` | `cf_release_thread.rs`: bounded(64) mailbox + stop channel instead of xev; injectable release fn (tests count releases on Linux); `CFRelease` via `CFRetained` |
| `os/args.zig` IteratorMacOS | `args::iterator()` on macOS reads `NSProcessInfo.arguments` |
| `os/mouse.zig` | `click_interval()` = `NSEvent.doubleClickInterval` ceil ms (`click_interval_ms` tested) |
| `os/desktop.zig` | macOS: ppid == 1 (+ `GHOSTTY_MAC_LAUNCH_SOURCE=app` for the lib artifact: `launched_from_desktop_lib`), `DesktopEnvironment::Macos`, iOS true |
| `os/homedir.zig` | macOS `homeDirectoryForCurrentUser` after the `HOME` check |
| `os/locale.zig` | Darwin `ensure_locale`: LANG from NSLocale (`<lang>_<country>.UTF-8`) + `LANGUAGE` from preferred languages (`preferred_language_value`), setlocale fallback chain (module `darwin`, unsafe allowed there) |
| `os/open.zig` | `open [-t] url`; iOS `Unsupported`; Windows branch kept |
| `os/resourcesdir.zig` | `Contents/Resources` bundle lookup first, `terminfo/78/xterm-ghostty` sentinel |
| `os/pipe.zig` | Darwin: `pipe` + `FD_CLOEXEC` on both ends (no `pipe2`) |
| `os/TempDir.zig` | non-Linux random via `/dev/urandom` (no `getrandom`) |
| `os/systemd.zig`, `passwd.zig` flatpak | Linux/Android only; `launched_by_systemd` false and `notify` no-ops elsewhere |
| `os/i18n.zig` canonicalize | still the pure-Rust approximation on all platforms (the real `_libintl_locale_name_canonicalize` is only linked in Zig's own libintl build) |
`xdg::Error` gained `AppleApiFailed`. Deps (apple only): objc2, objc2-foundation, objc2-app-kit, objc2-core-foundation, libc.
## ghostty-input
* `platform.rs`: `is_darwin()`/`is_macos()` consts with a test-only per-thread override (`with_darwin`).
* Darwin branches enabled: `Mods::translation` (option-as-alt with sides), `ctrl_or_super` (Mods, Key, free fn
+ `ctrl_or_super_for(mods, darwin)`), keycode table column 4 (mac) on Apple (`native_for_code` for tests),
key_encode: kitty `alt_prevents_text`, modifyOtherKeys alt handling, `legacy_alt_prefix` option-as-alt,
"super+text encodes nothing" on macOS. The 7 macOS-only Zig tests + `translation macos-option-as-alt` are ported
and run on Linux through the override.
* `keymap_darwin.rs` (`KeymapDarwin.zig`): Carbon `TISCopyCurrentKeyboardLayoutInputSource`/`UCKeyTranslate`
FFI (macOS only); `CarbonMods`, `carbon_modifier_state`, `utf16_to_utf8` tested on Linux; `keymap_noop.rs`;
`ghostty_input::{Keymap, KeymapState, KeymapTranslation}` pick by platform. Crate lint is now `unsafe_code = "deny"`.
## ghostty-config
macOS defaults: `font-size` 13, `alpha-blending` native, `copy-on-select` (true on Linux/macOS, false elsewhere),
`quick-terminal-autohide` true, `quit-after-last-window-closed` Linux-only, `linux-cgroup` never off Linux.
`Keybinds::init_for(darwin)` is now a faithful port of the whole Zig `init` (platform lists + the
Mac-specific block: cmd+q/k/a, undo/redo, mac windowing/splits/search/inspector, natural text editing);
`init()` selects with the target. Linux output verified identical to the previous dump; macOS tested.
`file_load`: Application Support paths (`default/legacy/preferred_app_support_path`, `preferred_default_file_path`
macOS order), `edit::config_path_candidates`, `load_default_files` loads app-support + XDG and writes the
template to app support; `probable_cli_environment` macOS desktop branch; `load_cli_args` reads
`ghostty_os::args::iterator()` (NSProcessInfo) and the Linux/BSD-only `xdg-terminal-exec` + no-args fast path.
## ghostty-termio / crash / core / terminal
* `pty.rs`: Darwin `openpty()` (libc) + CLOEXEC on the master; `command.rs`: dup2 path clears CLOEXEC on the
source fd, hand-rolled `execvpe` (PATH search like Zig's `execvpeZ`, candidates precomputed pre-fork,
tested), `__error` for errno; `wakeup.rs`: non-blocking pipe instead of eventfd; `exec.rs`: no pidfd (poll
`waitpid`), `login(1)` wrapping (`exec_command_with(command, darwin, passwd)`, `-q` for `.hushlogin`, tests ported +
extra), `XDG_DATA_DIRS`/`MANPATH` bundle env (`darwin_bundle_env`), killpg EPERM ignored on Darwin, terminfo-missing
warnings; `shell_integration.rs`: `/bin/bash` is not integrated on Darwin (`detect_shell_for`).
* `ghostty-crash`: `getentropy` instead of `getrandom` on Apple.
* `ghostty-terminal` search thread: `pthread_setname_np` + `QosClass::Utility` on Apple.
* `ghostty-core`: Darwin child-exit handling (any quick exit is abnormal, no exit code line), macOS
`encode_key_opts` (`macos-option-as-alt` from config or `RtApp::keyboard_layout().detect_option_as_alt()`),
rectangle select = option only, `global::Logging.macos` + `os_log` logger (oslog crate, unified logging, levels
debug/info/err/fault like Zig; `GHOSTTY_LOG=macos` parsed).
* `ghostty-apprt` (tiny): `RtApp::keyboard_layout()` default method (`Unknown`); the embedded runtime (L1) overrides it
with `ghostty_input::Keymap::source_id` → `KeyboardLayout::map_apple_id`.
## Not done / for L1 and later
* apprt embedded.zig macOS bits (NSView/surface plumbing, CVDisplayLink hosting, `keyboardLayout`, app menu actions): L1.
* Render/IO thread names on Darwin rely on `std::thread::Builder::name` (std calls `pthread_setname_np` for the
new thread on Darwin, which is what the Zig explicit call works around). QoS for the renderer thread: see renderer note.
* The font crate keeps a private CF release thread (`shaper/cf_release.rs`); `ghostty_os::cf_release_thread` is available
for the renderer to own one like Zig's `renderer/Thread.zig`.
* Skipped Zig tests: `macos.zig cacheDir paths` runs only on macOS (ported as a pure + `cfg(apple)` test),
KeymapDarwin's empty `test {}`, `Command.zig` `/private/tmp` cwd expectation (Linux test untouched).