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
@@ -0,0 +1,197 @@
|
||||
//! The CVDisplayLink used to drive the rendering loop in sync with the
|
||||
//! display (`pkg/macos/video/display_link.zig`, used by `generic.zig`).
|
||||
//!
|
||||
//! This is only functional on macOS. On every other platform
|
||||
//! [`DisplayLink`] is an uninhabited type, so a `None` is all the generic
|
||||
//! renderer ever has (Zig: `DisplayLink = void`).
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The error of display link operations (`DisplayLink.Error`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("invalid operation")]
|
||||
InvalidOperation,
|
||||
}
|
||||
|
||||
/// Called on every vsync from the display link's thread.
|
||||
pub type Callback = Arc<dyn Fn() + Send + Sync>;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use self::macos::DisplayLink;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub use self::unsupported::DisplayLink;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
mod unsupported {
|
||||
use std::convert::Infallible;
|
||||
|
||||
use super::{Callback, Error};
|
||||
|
||||
/// There is no display link on this platform.
|
||||
pub struct DisplayLink(Infallible);
|
||||
|
||||
impl DisplayLink {
|
||||
pub fn create_with_active_cg_displays() -> Option<DisplayLink> {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<(), Error> {
|
||||
match self.0 {}
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Result<(), Error> {
|
||||
match self.0 {}
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
match self.0 {}
|
||||
}
|
||||
|
||||
pub fn set_current_cg_display(&self, _display_id: u32) -> Result<(), Error> {
|
||||
match self.0 {}
|
||||
}
|
||||
|
||||
pub fn set_output_callback(&self, _callback: Callback) -> Result<(), Error> {
|
||||
match self.0 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[allow(unsafe_code, deprecated)]
|
||||
mod macos {
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
ptr::NonNull,
|
||||
sync::{Mutex, PoisonError},
|
||||
};
|
||||
|
||||
use objc2_core_foundation::CFRetained;
|
||||
use objc2_core_video::{CVDisplayLink, CVOptionFlags, CVReturn, CVTimeStamp, kCVReturnSuccess};
|
||||
|
||||
use super::{Callback, Error};
|
||||
|
||||
/// A CVDisplayLink.
|
||||
pub struct DisplayLink {
|
||||
link: CFRetained<CVDisplayLink>,
|
||||
|
||||
/// The callback handed to CoreVideo as `user_info` (a leaked
|
||||
/// `Box<Callback>`), freed when the link is dropped or the callback is
|
||||
/// replaced.
|
||||
callback: Mutex<Option<NonNull<Callback>>>,
|
||||
}
|
||||
|
||||
// SAFETY: CVDisplayLink is a thread-safe CoreVideo object; the callback
|
||||
// pointer is only freed with the lock held, after the link is stopped.
|
||||
unsafe impl Send for DisplayLink {}
|
||||
// SAFETY: see above.
|
||||
unsafe impl Sync for DisplayLink {}
|
||||
|
||||
fn check(r: CVReturn) -> Result<(), Error> {
|
||||
if r == kCVReturnSuccess {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayLink {
|
||||
/// `createWithActiveCGDisplays`.
|
||||
pub fn create_with_active_cg_displays() -> Option<DisplayLink> {
|
||||
let mut out: *mut CVDisplayLink = std::ptr::null_mut();
|
||||
// SAFETY: `out` is a valid out pointer.
|
||||
let r =
|
||||
unsafe { CVDisplayLink::create_with_active_cg_displays(NonNull::from(&mut out)) };
|
||||
if r != kCVReturnSuccess {
|
||||
return None;
|
||||
}
|
||||
let ptr = NonNull::new(out)?;
|
||||
// SAFETY: the function returns a +1 retained link.
|
||||
let link = unsafe { CFRetained::from_raw(ptr) };
|
||||
Some(DisplayLink {
|
||||
link,
|
||||
callback: Mutex::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start(&self) -> Result<(), Error> {
|
||||
check(self.link.start())
|
||||
}
|
||||
|
||||
pub fn stop(&self) -> Result<(), Error> {
|
||||
check(self.link.stop())
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.link.is_running()
|
||||
}
|
||||
|
||||
pub fn set_current_cg_display(&self, display_id: u32) -> Result<(), Error> {
|
||||
check(self.link.set_current_cg_display(display_id))
|
||||
}
|
||||
|
||||
/// Set the callback called on every vsync.
|
||||
///
|
||||
/// Note: this purposely throws away a ton of arguments we don't need.
|
||||
pub fn set_output_callback(&self, callback: Callback) -> Result<(), Error> {
|
||||
let boxed = NonNull::from(Box::leak(Box::new(callback)));
|
||||
// SAFETY: `trampoline` matches the callback signature and
|
||||
// `user_info` stays valid until the link is dropped.
|
||||
let r = unsafe {
|
||||
self.link
|
||||
.set_output_callback(Some(trampoline), boxed.as_ptr().cast::<c_void>())
|
||||
};
|
||||
if let Err(e) = check(r) {
|
||||
// SAFETY: we just leaked this box and CoreVideo rejected it.
|
||||
drop(unsafe { Box::from_raw(boxed.as_ptr()) });
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
let old = self
|
||||
.callback
|
||||
.lock()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.replace(boxed);
|
||||
if let Some(old) = old {
|
||||
// Make sure the old callback can't be running anymore.
|
||||
let _ = self.link.stop();
|
||||
// SAFETY: the pointer came from `Box::leak` above and the
|
||||
// link no longer references it (replaced + stopped).
|
||||
drop(unsafe { Box::from_raw(old.as_ptr()) });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DisplayLink {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.link.stop();
|
||||
if let Some(cb) = self
|
||||
.callback
|
||||
.get_mut()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.take()
|
||||
{
|
||||
// SAFETY: the link is stopped and released right after.
|
||||
drop(unsafe { Box::from_raw(cb.as_ptr()) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C-unwind" fn trampoline(
|
||||
_display_link: NonNull<CVDisplayLink>,
|
||||
_in_now: NonNull<CVTimeStamp>,
|
||||
_in_output_time: NonNull<CVTimeStamp>,
|
||||
_flags_in: CVOptionFlags,
|
||||
_flags_out: NonNull<CVOptionFlags>,
|
||||
user_info: *mut c_void,
|
||||
) -> CVReturn {
|
||||
// SAFETY: `user_info` is the leaked `Box<Callback>` set in
|
||||
// `set_output_callback`, alive until the link is stopped.
|
||||
if let Some(cb) = unsafe { user_info.cast::<Callback>().as_ref() } {
|
||||
cb();
|
||||
}
|
||||
kCVReturnSuccess
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user