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
76771bed0d
commit
bab49ee596
124 files changed
+14936
-1242
No files matched your search
@@ -0,0 +1,165 @@
|
||||
//! Small helpers over the CoreText / CoreFoundation bindings shared by the
|
||||
//! CoreText face, shaper and discovery (the Zig `macos` package conveniences
|
||||
//! they rely on). Apple targets only.
|
||||
#![allow(unsafe_code, clippy::trivially_copy_pass_by_ref)]
|
||||
|
||||
use std::{ops::Deref, ptr::NonNull};
|
||||
|
||||
use objc2_core_foundation::{CFData, CFRetained, CFString};
|
||||
use objc2_core_text::{CTFont, CTFontTableOptions};
|
||||
|
||||
use crate::face::coretext_math::utf16_units;
|
||||
|
||||
/// A retained CoreFoundation object that may be moved/shared across threads.
|
||||
///
|
||||
/// CoreText font objects (`CTFont`, `CTFontDescriptor`) are immutable and
|
||||
/// documented as thread safe by Apple ("CTFont ... are thread-safe"), but the
|
||||
/// generated bindings conservatively do not mark them `Send`/`Sync`.
|
||||
pub struct CfBox<T: ?Sized>(pub(crate) CFRetained<T>);
|
||||
|
||||
// SAFETY: see the type docs; the wrapped objects are immutable CoreText
|
||||
// objects, safe to use from any thread.
|
||||
unsafe impl<T: ?Sized> Send for CfBox<T> {}
|
||||
// SAFETY: as above.
|
||||
unsafe impl<T: ?Sized> Sync for CfBox<T> {}
|
||||
|
||||
impl<T: ?Sized> CfBox<T> {
|
||||
pub(crate) fn new(v: CFRetained<T>) -> Self {
|
||||
CfBox(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> Deref for CfBox<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: objc2_core_foundation::Type> Clone for CfBox<T> {
|
||||
fn clone(&self) -> Self {
|
||||
CfBox(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a `CFString` to a Rust `String`.
|
||||
pub(crate) fn cf_to_string(s: &CFString) -> String {
|
||||
s.to_string()
|
||||
}
|
||||
|
||||
/// Copy the sfnt table with the given tag, if present.
|
||||
pub(crate) fn font_table(font: &CTFont, tag: &[u8; 4]) -> Option<CFRetained<CFData>> {
|
||||
// SAFETY: plain CoreText call with valid arguments.
|
||||
unsafe { font.table(u32::from_be_bytes(*tag), CTFontTableOptions::NoOptions) }
|
||||
}
|
||||
|
||||
/// Copy the bytes of the sfnt table with the given tag, if present.
|
||||
pub(crate) fn font_table_bytes(font: &CTFont, tag: &[u8; 4]) -> Option<Vec<u8>> {
|
||||
font_table(font, tag).map(|d| d.to_vec())
|
||||
}
|
||||
|
||||
/// `CTFontGetGlyphsForCharacters` for a single UTF-32 codepoint. Returns the
|
||||
/// glyph ID of the first UTF-16 unit and whether *all* units mapped to glyphs
|
||||
/// (the C function's return value).
|
||||
pub(crate) fn glyphs_for_codepoint(font: &CTFont, cp: u32) -> (u16, bool) {
|
||||
// Turn UTF-32 into UTF-16 for the CT API
|
||||
let (mut unichars, len) = utf16_units(cp);
|
||||
let mut glyphs = [0u16; 2];
|
||||
// SAFETY: both buffers have at least `len` (<= 2) elements and outlive
|
||||
// the call.
|
||||
let ok = unsafe {
|
||||
font.glyphs_for_characters(
|
||||
NonNull::new_unchecked(unichars.as_mut_ptr()),
|
||||
NonNull::new_unchecked(glyphs.as_mut_ptr()),
|
||||
len as isize,
|
||||
)
|
||||
};
|
||||
(glyphs[0], ok)
|
||||
}
|
||||
|
||||
/// A font variation axis as reported by CoreText.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct Axis {
|
||||
pub name: String,
|
||||
pub id: u32,
|
||||
pub min: f64,
|
||||
pub max: f64,
|
||||
pub default: f64,
|
||||
}
|
||||
|
||||
/// The variation axes of a font (`kCTFontVariationAxesAttribute`), or `None`
|
||||
/// if the font has none.
|
||||
pub(crate) fn variation_axes(font: &CTFont) -> Option<Vec<Axis>> {
|
||||
use objc2_core_foundation::{CFArray, CFDictionary, CFNumber, CFType};
|
||||
use objc2_core_text::{
|
||||
kCTFontVariationAxisDefaultValueKey,
|
||||
kCTFontVariationAxisIdentifierKey,
|
||||
kCTFontVariationAxisMaximumValueKey,
|
||||
kCTFontVariationAxisMinimumValueKey,
|
||||
kCTFontVariationAxisNameKey,
|
||||
};
|
||||
|
||||
// SAFETY: plain CoreText call.
|
||||
let axes = unsafe { font.variation_axes() }?;
|
||||
// SAFETY: the documented element type of the axes array is CFDictionary.
|
||||
let axes: &CFArray<CFDictionary> = unsafe { axes.cast_unchecked() };
|
||||
|
||||
let mut out = Vec::with_capacity(axes.len());
|
||||
for i in 0..axes.len() {
|
||||
let Some(dict) = axes.get(i) else { continue };
|
||||
// SAFETY: the values stored under these keys are CFNumber / CFString
|
||||
// as documented for the variation axis keys.
|
||||
let (names, nums) = unsafe {
|
||||
(
|
||||
dict.cast_unchecked::<CFString, CFString>(),
|
||||
dict.cast_unchecked::<CFString, CFNumber>(),
|
||||
)
|
||||
};
|
||||
// SAFETY: the keys are valid static CFStrings.
|
||||
let (k_name, k_id, k_min, k_max, k_def) = unsafe {
|
||||
(
|
||||
kCTFontVariationAxisNameKey,
|
||||
kCTFontVariationAxisIdentifierKey,
|
||||
kCTFontVariationAxisMinimumValueKey,
|
||||
kCTFontVariationAxisMaximumValueKey,
|
||||
kCTFontVariationAxisDefaultValueKey,
|
||||
)
|
||||
};
|
||||
let _ = std::marker::PhantomData::<CFType>;
|
||||
let name = names.get(k_name).map(|s| s.to_string()).unwrap_or_default();
|
||||
let id = nums
|
||||
.get(k_id)
|
||||
.and_then(|n| n.as_i32())
|
||||
.map_or(0, |v| v as u32);
|
||||
let min = nums.get(k_min).and_then(|n| n.as_f64()).unwrap_or(0.0);
|
||||
let max = nums.get(k_max).and_then(|n| n.as_f64()).unwrap_or(0.0);
|
||||
let default = nums.get(k_def).and_then(|n| n.as_f64()).unwrap_or(0.0);
|
||||
out.push(Axis {
|
||||
name,
|
||||
id,
|
||||
min,
|
||||
max,
|
||||
default,
|
||||
});
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// The current value of each variation axis of the font instance
|
||||
/// (`kCTFontVariationAttribute`), keyed by axis id.
|
||||
pub(crate) fn variation_values(font: &CTFont) -> Option<std::collections::HashMap<u32, f64>> {
|
||||
use objc2_core_foundation::{CFDictionary, CFNumber};
|
||||
|
||||
// SAFETY: plain CoreText call.
|
||||
let values = unsafe { font.variation() }?;
|
||||
// SAFETY: the variation dictionary maps CFNumber axis ids to CFNumber
|
||||
// values.
|
||||
let values: &CFDictionary<CFNumber, CFNumber> = unsafe { values.cast_unchecked() };
|
||||
let (keys, vals) = values.to_vecs();
|
||||
Some(
|
||||
keys.iter()
|
||||
.zip(vals.iter())
|
||||
.filter_map(|(k, v)| Some((k.as_i32()? as u32, v.as_f64()?)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user