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)
This commit is contained in:
ddidderr committed 2026-10-12 06:38:33 +02:00
1 parent 4787565397
commit 49d4ecefae
47 files changed
+10296 -34

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
//! `ghostty_benchmark_cli` (ZIG/src/benchmark/CApi.zig).
#![allow(unsafe_code)]
use std::ffi::{CStr, c_char};
use ghostty_bench::{args::Args, benches};
/// Split the argument string like `std.process.ArgIteratorGeneral(.{})`:
/// whitespace separated, with double quotes grouping and backslash
/// escapes inside quotes.
fn split_args(s: &str) -> Vec<String> {
let mut out = Vec::new();
let mut cur = String::new();
let mut have = false;
let mut in_quote = false;
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
match c {
'"' => {
in_quote = !in_quote;
have = true;
}
'\\' if in_quote => {
if let Some(n) = chars.next() {
cur.push(n);
}
}
c if c.is_whitespace() && !in_quote => {
if have {
out.push(std::mem::take(&mut cur));
have = false;
}
}
c => {
cur.push(c);
have = true;
}
}
}
if have {
out.push(cur);
}
out
}
/// Run the Ghostty benchmark CLI with the given action and arguments.
///
/// # Safety
/// `action_name` and `args` must be NUL terminated strings.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ghostty_benchmark_cli(
action_name: *const c_char,
args: *const c_char,
) -> bool {
if action_name.is_null() || args.is_null() {
return false;
}
// SAFETY: per the function contract.
let (action, args) = unsafe { (CStr::from_ptr(action_name), CStr::from_ptr(args)) };
let action = action.to_string_lossy();
if !benches::ACTIONS.contains(&action.as_ref()) {
log::warn!("unknown action={action}");
return false;
}
let argv = split_args(&args.to_string_lossy());
let parsed = match Args::parse(argv) {
Ok(a) => a,
Err(err) => {
log::warn!("failed to run action={action} err={err}");
return false;
}
};
match benches::main_action(&action, parsed) {
Ok(_) => true,
Err(err) => {
log::warn!("failed to run action={action} err={err}");
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_args_basic() {
assert_eq!(split_args("a b c"), ["a", "b", "c"]);
assert_eq!(split_args("--x=\"a b\" c"), ["--x=a b", "c"]);
assert_eq!(split_args(" ").len(), 0);
}
#[test]
fn unknown_action_returns_false() {
// SAFETY: valid NUL terminated strings.
assert!(!unsafe { ghostty_benchmark_cli(c"nope".as_ptr(), c"".as_ptr()) });
}
}