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:
1 parent
3c8fae1089
commit
452b782149
124 files changed
+14936
-1242
No files matched your search
@@ -1,6 +1,9 @@
|
||||
//! Default config file locations and opening (port of `config/file_load.zig`).
|
||||
//!
|
||||
//! macOS Application Support paths are not supported (Linux only).
|
||||
//! On macOS the Application Support paths are used in addition to XDG; the
|
||||
//! helpers that only need `open` + two candidate paths are platform
|
||||
//! independent (and unit tested everywhere), the Application Support lookup
|
||||
//! itself uses `NSFileManager` and is macOS-only.
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
@@ -40,13 +43,94 @@ pub fn preferred_xdg_path() -> Result<String, xdg::Error> {
|
||||
Ok(xdg_path)
|
||||
}
|
||||
|
||||
/// Default path for the macOS Application Support configuration file.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn default_app_support_path() -> Result<String, xdg::Error> {
|
||||
app_support_dir("config.ghostty")
|
||||
}
|
||||
|
||||
/// Ghostty <1.3.0 default path for the macOS Application Support
|
||||
/// configuration file.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn legacy_default_app_support_path() -> Result<String, xdg::Error> {
|
||||
app_support_dir("config")
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn app_support_dir(sub_path: &str) -> Result<String, xdg::Error> {
|
||||
ghostty_os::macos::app_support_dir(sub_path)
|
||||
.map(|p| p.to_string_lossy().into_owned())
|
||||
.map_err(|_| xdg::Error::AppleApiFailed)
|
||||
}
|
||||
|
||||
/// Preferred default path for the macOS Application Support configuration
|
||||
/// file.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn preferred_app_support_path() -> Result<String, xdg::Error> {
|
||||
Ok(preferred_of(
|
||||
default_app_support_path()?,
|
||||
legacy_default_app_support_path()?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Pick between a new path and its legacy path: the new one if it exists,
|
||||
/// else the legacy one if it exists, else the new one.
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
fn preferred_of(new: String, legacy: String) -> String {
|
||||
// If the new path exists, use that.
|
||||
if open(&new).is_ok() {
|
||||
return new;
|
||||
}
|
||||
|
||||
// Try the legacy path
|
||||
if open(&legacy).is_ok() {
|
||||
return legacy;
|
||||
}
|
||||
|
||||
// Legacy path and new path both don't exist. Return the new one.
|
||||
new
|
||||
}
|
||||
|
||||
/// macOS: prefer the Application Support file if it exists, else the XDG
|
||||
/// one if it exists, else Application Support.
|
||||
#[cfg(any(target_os = "macos", test))]
|
||||
fn preferred_macos(
|
||||
app_support_path: String,
|
||||
xdg_path: &dyn Fn() -> Result<String, xdg::Error>,
|
||||
) -> Result<String, xdg::Error> {
|
||||
// macOS prefers the Application Support directory if it exists.
|
||||
if open(&app_support_path).is_ok() {
|
||||
return Ok(app_support_path);
|
||||
}
|
||||
|
||||
// Try the XDG path if it exists
|
||||
let xdg_path = xdg_path()?;
|
||||
if open(&xdg_path).is_ok() {
|
||||
return Ok(xdg_path);
|
||||
}
|
||||
|
||||
// If neither file exists, use app support
|
||||
Ok(app_support_path)
|
||||
}
|
||||
|
||||
/// Returns the path to the preferred default configuration file.
|
||||
/// This is the file where users should place their configuration.
|
||||
///
|
||||
/// This doesn't create or populate the file with any default
|
||||
/// contents; downstream callers must handle this.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn preferred_default_file_path() -> Result<String, xdg::Error> {
|
||||
// All supported platforms use XDG only
|
||||
preferred_macos(preferred_app_support_path()?, &preferred_xdg_path)
|
||||
}
|
||||
|
||||
/// Returns the path to the preferred default configuration file.
|
||||
/// This is the file where users should place their configuration.
|
||||
///
|
||||
/// This doesn't create or populate the file with any default
|
||||
/// contents; downstream callers must handle this.
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn preferred_default_file_path() -> Result<String, xdg::Error> {
|
||||
// All other platforms use XDG only
|
||||
preferred_xdg_path()
|
||||
}
|
||||
|
||||
@@ -123,6 +207,47 @@ mod tests {
|
||||
std::fs::remove_file(&p).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferred_of_prefers_existing_new_then_legacy() {
|
||||
let dir = std::env::temp_dir().join(format!("ghostty-pref-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let new = dir.join("config.ghostty").to_str().unwrap().to_owned();
|
||||
let legacy = dir.join("config").to_str().unwrap().to_owned();
|
||||
let _ = std::fs::remove_file(&new);
|
||||
let _ = std::fs::remove_file(&legacy);
|
||||
|
||||
// Neither exists: the new one.
|
||||
assert_eq!(preferred_of(new.clone(), legacy.clone()), new);
|
||||
// Only legacy exists: the legacy one.
|
||||
std::fs::write(&legacy, "a=b").unwrap();
|
||||
assert_eq!(preferred_of(new.clone(), legacy.clone()), legacy);
|
||||
// Both exist: the new one.
|
||||
std::fs::write(&new, "a=b").unwrap();
|
||||
assert_eq!(preferred_of(new.clone(), legacy.clone()), new);
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preferred_macos_order() {
|
||||
let dir = std::env::temp_dir().join(format!("ghostty-pref-mac-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let app = dir.join("app").to_str().unwrap().to_owned();
|
||||
let xdg_p = dir.join("xdg").to_str().unwrap().to_owned();
|
||||
let _ = std::fs::remove_file(&app);
|
||||
let _ = std::fs::remove_file(&xdg_p);
|
||||
let xdg_fn = || Ok(xdg_p.clone());
|
||||
|
||||
// Neither exists: app support.
|
||||
assert_eq!(preferred_macos(app.clone(), &xdg_fn).unwrap(), app);
|
||||
// XDG exists only: XDG.
|
||||
std::fs::write(&xdg_p, "a=b").unwrap();
|
||||
assert_eq!(preferred_macos(app.clone(), &xdg_fn).unwrap(), xdg_p);
|
||||
// App support exists: app support wins.
|
||||
std::fs::write(&app, "a=b").unwrap();
|
||||
assert_eq!(preferred_macos(app.clone(), &xdg_fn).unwrap(), app);
|
||||
std::fs::remove_dir_all(&dir).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_paths_end_with_expected_names() {
|
||||
if let (Ok(a), Ok(b)) = (default_xdg_path(), legacy_default_xdg_path()) {
|
||||
|
||||
Reference in new issue
Block a user