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

102 lines
2.8 KiB
Rust

//! Integration test: run a real child attached to a pty.
#![cfg(unix)]
#![allow(clippy::unwrap_used)]
use std::{
fs::File,
io::Read,
os::fd::{AsRawFd, OwnedFd},
};
use ghostty_termio::{Command, Pty, Winsize, command::Exit, pty::child_pre_exec};
fn dup(fd: &OwnedFd) -> OwnedFd {
fd.try_clone().unwrap()
}
/// Read from the master until the child side hangs up (EIO on Linux).
fn read_until_hangup(master: OwnedFd) -> String {
let mut f = File::from(master);
let mut out = Vec::new();
let mut buf = [0u8; 4096];
loop {
match f.read(&mut buf) {
Ok(0) | Err(_) => break,
Ok(n) => out.extend_from_slice(&buf[..n]),
}
}
String::from_utf8_lossy(&out).into_owned()
}
fn run_on_pty(script: &str, size: Winsize) -> (String, Exit) {
let mut pty = Pty::open(size).unwrap();
let slave = pty.slave.take().unwrap();
let (master_raw, _) = pty.raw_fds();
let slave_raw = slave.as_raw_fd();
let mut cmd = Command::new("/bin/sh", &["/bin/sh", "-c", script]);
cmd.stdin = Some(dup(&slave));
cmd.stdout = Some(dup(&slave));
cmd.stderr = Some(dup(&slave));
cmd.os_pre_exec = Some(Box::new(move || {
// Make the slave our controlling terminal in a new session; this
// also closes the master/slave descriptors in the child.
match child_pre_exec(master_raw, slave_raw) {
Ok(()) => None,
Err(_) => Some(1),
}
}));
cmd.start().unwrap();
// Close our copies of the slave so we see a hangup when the child exits.
drop(cmd.stdin.take());
drop(cmd.stdout.take());
drop(cmd.stderr.take());
drop(slave);
let exit = cmd.wait(true).unwrap();
let out = read_until_hangup(pty.master);
(out, exit)
}
#[test]
fn echo_and_stty_size_on_pty() {
let size = Winsize {
ws_row: 31,
ws_col: 97,
ws_xpixel: 0,
ws_ypixel: 0,
};
let (out, exit) = run_on_pty("echo hi; stty size", size);
assert_eq!(exit, Exit::Exited(0));
// A pty translates \n to \r\n.
assert_eq!(out, "hi\r\n31 97\r\n");
}
#[test]
fn child_has_controlling_terminal() {
// `tty` succeeds only with a terminal on stdin; /dev/tty only opens when
// we have a controlling terminal (TIOCSCTTY).
let (out, exit) = run_on_pty(
"tty >/dev/null && echo tty; echo x > /dev/tty && echo ctty",
Winsize::default(),
);
assert_eq!(exit, Exit::Exited(0));
assert!(out.contains("tty\r\n"), "{out:?}");
assert!(out.contains("ctty\r\n"), "{out:?}");
}
#[test]
fn resize_is_visible_to_child() {
let size = Winsize {
ws_row: 10,
ws_col: 20,
ws_xpixel: 0,
ws_ypixel: 0,
};
let (out, _) = run_on_pty("stty size", size);
assert_eq!(out, "10 20\r\n");
}