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
@@ -9,6 +9,7 @@ use crate::{
|
||||
config::OptionAsAlt,
|
||||
function_keys::{self, CursorMode, KeypadMode, ModifyKeys},
|
||||
key::{self, Key, KeyEvent},
|
||||
key_mods::ModSide,
|
||||
kitty::{self as kitty_table, Entry as KittyEntry},
|
||||
};
|
||||
|
||||
@@ -266,8 +267,16 @@ fn kitty<W: Write>(writer: &mut W, event: &KeyEvent, opts: &Options) -> io::Resu
|
||||
// Determine if the Alt modifier should be treated as an actual
|
||||
// modifier (in which case it prevents associated text) or as
|
||||
// the macOS Option key, which does not prevent associated text.
|
||||
// (Non-macOS: always true.)
|
||||
let alt_prevents_text = true;
|
||||
let alt_prevents_text = if crate::platform::is_macos() {
|
||||
match opts.macos_option_as_alt {
|
||||
OptionAsAlt::Left => all_mods.sides.alt == ModSide::Left,
|
||||
OptionAsAlt::Right => all_mods.sides.alt == ModSide::Right,
|
||||
OptionAsAlt::True => true,
|
||||
OptionAsAlt::False => false,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if !seq.mods.prevents_text(alt_prevents_text) {
|
||||
seq.text = event.utf8;
|
||||
@@ -373,7 +382,21 @@ fn legacy<W: Write>(writer: &mut W, event: &KeyEvent, opts: &Options) -> io::Res
|
||||
if let (Some(codepoint), None) = (it.next().map(u32::from), it.clone().next()) {
|
||||
// The mods we encode for this are just the binding mods (shift, ctrl,
|
||||
// super, alt unless it is actually option).
|
||||
let mods = event.mods.binding();
|
||||
let mods = {
|
||||
let mut mods_binding = event.mods.binding();
|
||||
if crate::platform::is_darwin() {
|
||||
let keep_alt = match opts.macos_option_as_alt {
|
||||
OptionAsAlt::False => false,
|
||||
OptionAsAlt::True => true,
|
||||
OptionAsAlt::Left => event.mods.sides.alt == ModSide::Left,
|
||||
OptionAsAlt::Right => event.mods.sides.alt == ModSide::Right,
|
||||
};
|
||||
if !keep_alt {
|
||||
mods_binding.alt = false;
|
||||
}
|
||||
}
|
||||
mods_binding
|
||||
};
|
||||
|
||||
// This copies xterm's `ModifyOtherKeys` function that returns
|
||||
// whether modify other keys should be encoded for the given
|
||||
@@ -465,8 +488,18 @@ fn legacy<W: Write>(writer: &mut W, event: &KeyEvent, opts: &Options) -> io::Res
|
||||
return writer.write_all(&[0x1B, byte]);
|
||||
}
|
||||
|
||||
// (On macOS, command+keys do not encode text. On Linux we continue to
|
||||
// encode text because it is typical.)
|
||||
// If we are on macOS, command+keys do not encode text. It isn't
|
||||
// typical for command+keys on macOS to ever encode text. They
|
||||
// don't in native text inputs (i.e. TextEdit) and they also don't
|
||||
// in other native terminals (Terminal.app officially but also
|
||||
// iTerm2).
|
||||
//
|
||||
// For Linux, we continue to encode text because it is typical.
|
||||
// For example on Gnome Console Super+b will encode a "b" character
|
||||
// with legacy encoding.
|
||||
if crate::platform::is_macos() && all_mods.super_ {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
writer.write_all(utf8.as_bytes())
|
||||
}
|
||||
@@ -474,7 +507,7 @@ fn legacy<W: Write>(writer: &mut W, event: &KeyEvent, opts: &Options) -> io::Res
|
||||
fn legacy_alt_prefix(
|
||||
event: &KeyEvent,
|
||||
binding_mods: key::Mods,
|
||||
_mods: key::Mods,
|
||||
mods: key::Mods,
|
||||
opts: &Options,
|
||||
) -> Option<u8> {
|
||||
// This only takes effect with alt pressed
|
||||
@@ -482,7 +515,25 @@ fn legacy_alt_prefix(
|
||||
return None;
|
||||
}
|
||||
|
||||
// (macOS option-as-alt handling does not apply on Linux.)
|
||||
// On macOS, we only handle option like alt in certain
|
||||
// circumstances. Otherwise, macOS does a unicode translation
|
||||
// and we allow that to happen.
|
||||
if crate::platform::is_macos() {
|
||||
match opts.macos_option_as_alt {
|
||||
OptionAsAlt::False => return None,
|
||||
OptionAsAlt::Left => {
|
||||
if mods.sides.alt == ModSide::Right {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
OptionAsAlt::Right => {
|
||||
if mods.sides.alt == ModSide::Left {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
OptionAsAlt::True => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, we require utf8 to already have the byte represented.
|
||||
let utf8 = event.utf8.as_bytes();
|
||||
@@ -3159,3 +3210,216 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The Zig tests that only run on Darwin (`isDarwin()` / `os.tag == .macos`
|
||||
/// skip otherwise). They run on every host here by overriding the platform
|
||||
/// predicates (see [`crate::platform`]).
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod macos_tests {
|
||||
use super::*;
|
||||
use crate::{Mods, platform::with_darwin};
|
||||
|
||||
fn enc_legacy(ev: &KeyEvent, opts: &Options) -> Vec<u8> {
|
||||
let mut w = Vec::new();
|
||||
legacy(&mut w, ev, opts).unwrap();
|
||||
w
|
||||
}
|
||||
|
||||
fn enc_kitty(ev: &KeyEvent, opts: &Options) -> Vec<u8> {
|
||||
let mut w = Vec::new();
|
||||
kitty(&mut w, ev, opts).unwrap();
|
||||
w
|
||||
}
|
||||
|
||||
fn kitty_opts(option_as_alt: OptionAsAlt) -> Options {
|
||||
Options {
|
||||
kitty_flags: KittyFlags {
|
||||
disambiguate: true,
|
||||
report_all: true,
|
||||
report_alternates: true,
|
||||
report_associated: true,
|
||||
..KittyFlags::DISABLED
|
||||
},
|
||||
macos_option_as_alt: option_as_alt,
|
||||
..Options::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kitty_report_associated_with_alt_text_on_macos_with_option() {
|
||||
with_darwin(true, || {
|
||||
let ev = KeyEvent {
|
||||
key: Key::KeyW,
|
||||
mods: Mods {
|
||||
alt: true,
|
||||
..Mods::default()
|
||||
},
|
||||
utf8: "∑",
|
||||
unshifted_codepoint: 119,
|
||||
..KeyEvent::default()
|
||||
};
|
||||
assert_eq!(
|
||||
enc_kitty(&ev, &kitty_opts(OptionAsAlt::False)),
|
||||
"\x1b[119;3;8721u".as_bytes()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kitty_report_associated_with_alt_text_on_macos_with_alt() {
|
||||
with_darwin(true, || {
|
||||
// With Alt modifier
|
||||
let ev = KeyEvent {
|
||||
key: Key::KeyW,
|
||||
mods: Mods {
|
||||
alt: true,
|
||||
..Mods::default()
|
||||
},
|
||||
utf8: "∑",
|
||||
unshifted_codepoint: 119,
|
||||
..KeyEvent::default()
|
||||
};
|
||||
assert_eq!(
|
||||
enc_kitty(&ev, &kitty_opts(OptionAsAlt::True)),
|
||||
b"\x1b[119;3u"
|
||||
);
|
||||
|
||||
// Without Alt modifier
|
||||
let ev = KeyEvent {
|
||||
key: Key::KeyW,
|
||||
utf8: "∑",
|
||||
unshifted_codepoint: 119,
|
||||
..KeyEvent::default()
|
||||
};
|
||||
assert_eq!(
|
||||
enc_kitty(&ev, &kitty_opts(OptionAsAlt::True)),
|
||||
"\x1b[119;;8721u".as_bytes()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_alt_x_macos() {
|
||||
with_darwin(true, || {
|
||||
let ev = KeyEvent {
|
||||
key: Key::KeyC,
|
||||
utf8: "≈",
|
||||
unshifted_codepoint: u32::from('c'),
|
||||
mods: Mods {
|
||||
alt: true,
|
||||
..Mods::default()
|
||||
},
|
||||
..KeyEvent::default()
|
||||
};
|
||||
let opts = Options {
|
||||
alt_esc_prefix: true,
|
||||
macos_option_as_alt: OptionAsAlt::True,
|
||||
..Options::default()
|
||||
};
|
||||
assert_eq!(enc_legacy(&ev, &opts), b"\x1bc");
|
||||
|
||||
// With option-as-alt off, macOS keeps the unicode translation.
|
||||
let opts = Options {
|
||||
macos_option_as_alt: OptionAsAlt::False,
|
||||
..opts
|
||||
};
|
||||
assert_eq!(enc_legacy(&ev, &opts), "≈".as_bytes());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_shift_alt_period_macos() {
|
||||
with_darwin(true, || {
|
||||
let ev = KeyEvent {
|
||||
key: Key::Period,
|
||||
utf8: ">",
|
||||
unshifted_codepoint: u32::from('.'),
|
||||
mods: Mods {
|
||||
alt: true,
|
||||
shift: true,
|
||||
..Mods::default()
|
||||
},
|
||||
..KeyEvent::default()
|
||||
};
|
||||
let opts = Options {
|
||||
alt_esc_prefix: true,
|
||||
macos_option_as_alt: OptionAsAlt::True,
|
||||
..Options::default()
|
||||
};
|
||||
assert_eq!(enc_legacy(&ev, &opts), b"\x1b>");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_alt_digit_with_modify_other_state_2_and_macos_option_as_alt_false() {
|
||||
with_darwin(true, || {
|
||||
let ev = KeyEvent {
|
||||
key: Key::Digit8,
|
||||
mods: Mods {
|
||||
alt: true,
|
||||
..Mods::default()
|
||||
},
|
||||
consumed_mods: Mods {
|
||||
alt: true,
|
||||
..Mods::default()
|
||||
},
|
||||
// common translation of option+8 with European keyboard layouts
|
||||
utf8: "[",
|
||||
..KeyEvent::default()
|
||||
};
|
||||
let opts = Options {
|
||||
modify_other_keys_state_2: true,
|
||||
macos_option_as_alt: OptionAsAlt::False,
|
||||
..Options::default()
|
||||
};
|
||||
assert_eq!(enc_legacy(&ev, &opts), b"[");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_super_only_on_macos_with_text() {
|
||||
with_darwin(true, || {
|
||||
let ev = KeyEvent {
|
||||
key: Key::KeyB,
|
||||
utf8: "b",
|
||||
mods: Mods {
|
||||
super_: true,
|
||||
..Mods::default()
|
||||
},
|
||||
..KeyEvent::default()
|
||||
};
|
||||
assert_eq!(enc_legacy(&ev, &Options::default()), b"");
|
||||
});
|
||||
// On other platforms the text is still encoded.
|
||||
with_darwin(false, || {
|
||||
let ev = KeyEvent {
|
||||
key: Key::KeyB,
|
||||
utf8: "b",
|
||||
mods: Mods {
|
||||
super_: true,
|
||||
..Mods::default()
|
||||
},
|
||||
..KeyEvent::default()
|
||||
};
|
||||
assert_eq!(enc_legacy(&ev, &Options::default()), b"b");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_super_and_other_mods_on_macos_with_text() {
|
||||
with_darwin(true, || {
|
||||
let ev = KeyEvent {
|
||||
key: Key::KeyB,
|
||||
utf8: "B",
|
||||
mods: Mods {
|
||||
super_: true,
|
||||
shift: true,
|
||||
..Mods::default()
|
||||
},
|
||||
..KeyEvent::default()
|
||||
};
|
||||
assert_eq!(enc_legacy(&ev, &Options::default()), b"");
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user