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)
This commit is contained in:
ddidderr committed 2026-10-12 06:02:39 +02:00
1 parent 76771bed0d
commit bab49ee596
124 files changed
+14936 -1242

No files matched your search

+70
View File
@@ -1,5 +1,7 @@
#[cfg(unix)]
use std::process::{Command, Stdio};
#[cfg(unix)]
use crate::passwd;
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
@@ -28,6 +30,7 @@ pub fn home() -> Result<Option<String>, Error> {
/// Like [`home`] but emulates the fixed output buffer of the Zig version:
/// errors with `BufferTooSmall` if the result is longer than `cap` bytes.
#[cfg(unix)]
pub fn home_with_capacity(cap: usize) -> Result<Option<String>, Error> {
let check = |s: String| {
if cap < s.len() {
@@ -42,6 +45,18 @@ pub fn home_with_capacity(cap: usize) -> Result<Option<String>, Error> {
return check(result.to_string_lossy().into_owned());
}
// On macOS: [NSFileManager defaultManager].homeDirectoryForCurrentUser.path
#[cfg(target_os = "macos")]
{
use objc2_foundation::NSFileManager;
let manager = NSFileManager::defaultManager();
let home_url = manager.homeDirectoryForCurrentUser();
if let Some(path) = home_url.path() {
return check(path.to_string());
}
}
// We try passwd. This doesn't work on multi-user mac but we try it anyways.
if let Ok(pw) = passwd::get()
&& let Some(result) = pw.home
@@ -65,6 +80,34 @@ pub fn home_with_capacity(cap: usize) -> Result<Option<String>, Error> {
Ok(None)
}
/// Windows: `%HOMEDRIVE%%HOMEPATH%` (`homeWindows`). Returns `None` if
/// either variable is missing.
#[cfg(windows)]
pub fn home_with_capacity(cap: usize) -> Result<Option<String>, Error> {
home_windows(
std::env::var("HOMEDRIVE").ok().as_deref(),
std::env::var("HOMEPATH").ok().as_deref(),
cap,
)
}
/// Pure part of [`home_with_capacity`] on Windows.
#[cfg(any(windows, test))]
fn home_windows(
drive: Option<&str>,
path: Option<&str>,
cap: usize,
) -> Result<Option<String>, Error> {
let (Some(drive), Some(path)) = (drive, path) else {
return Ok(None);
};
if cap < drive.len() + path.len() {
return Err(Error::BufferTooSmall);
}
Ok(Some(format!("{drive}{path}")))
}
#[cfg(unix)]
fn trim_space(input: &str) -> &str {
input.trim_matches([' ', '\n', '\t'])
}
@@ -80,6 +123,14 @@ pub fn expand_home(path: &str) -> Result<String, ExpandError> {
/// Like [`expand_home`] with an explicit output capacity (emulating the
/// caller-provided buffer of the Zig version).
#[cfg(windows)]
pub fn expand_home_with_capacity(path: &str, _cap: usize) -> Result<String, ExpandError> {
// `~/` is not an idiom generally used on Windows
Ok(path.to_owned())
}
/// See the Windows variant above.
#[cfg(unix)]
pub fn expand_home_with_capacity(path: &str, cap: usize) -> Result<String, ExpandError> {
if !path.starts_with("~/") {
return Ok(path.to_owned());
@@ -100,6 +151,25 @@ pub fn expand_home_with_capacity(path: &str, cap: usize) -> Result<String, Expan
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn home_windows_joins_drive_and_path() {
assert_eq!(
home_windows(Some("C:"), Some("\\Users\\me"), 4096),
Ok(Some("C:\\Users\\me".to_owned()))
);
assert_eq!(home_windows(None, Some("\\x"), 4096), Ok(None));
assert_eq!(home_windows(Some("C:"), None, 4096), Ok(None));
assert_eq!(
home_windows(Some("C:"), Some("\\Users"), 3),
Err(Error::BufferTooSmall)
);
}
}
#[cfg(all(test, unix))]
mod tests_unix {
use super::*;
use crate::testutil::with_env_lock;
#[test]