fix: C action buffer lengths, byte-faithful callbacks, runtime finalize
Resolves the 8 gaps in docs/rewrite/notes/review-rv10-codex.md (final-Z13.md): - P1 memory safety: the C key-table action (and hovered-link/open-URL payloads) could advertise a length larger than the NUL-truncated allocation. A counted_bytes helper now passes the full bytes plus a trailing NUL with len == byte count, like Zig's backing slices; all C pointer+len structs were audited and tested to read exactly len. - ghostty-bench args follow cli/args.zig (ordered duplicates, empty value resets, first bad value errors, parseBool spellings); the C benchmark tokenizer uses the ArgIteratorGeneral port. - OSC 7 derived titles, desktop notifications and OSC 8 hover links forward raw bytes to the runtime; GTK converts only for display. - C diagnostic getter exports original bytes; escaped surrogates report Utf8CannotEncodeSurrogateHalf; config float formatters print `nan`. - Config::finalize_for(AppRuntime): GTK-only finalization (gtk-single- instance=detect) and termio's cgroup hooks apply only to the GTK runtime, so libghostty keeps `detect` like Zig. Test Plan: - cargo test --workspace --no-fail-fast (exit 0) - cargo test -p ghostty-lib --features test-platform - just check-wasm (macOS/Windows clippy re-run after the concurrent terminal changes land)
This commit is contained in:
1 parent
7b3fffded3
commit
da9599140b
21 files changed
+479
-133
No files matched your search
@@ -3818,6 +3818,47 @@ fn shell_from_env(value: &std::ffi::OsStr) -> Command {
|
||||
Command::Shell(ghostty_os::osbytes::EncodedOs::from_os(value))
|
||||
}
|
||||
|
||||
/// The application runtime (Zig `build_config.app_runtime`). It only
|
||||
/// affects finalization (GTK-only defaults).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AppRuntime {
|
||||
/// No apprt: the embedded library (and wasm).
|
||||
None,
|
||||
/// GTK, the default on Linux and BSD.
|
||||
Gtk,
|
||||
}
|
||||
|
||||
const RUNTIME_DEFAULT: u8 = if cfg!(all(
|
||||
unix,
|
||||
not(target_vendor = "apple"),
|
||||
not(target_family = "wasm")
|
||||
)) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
static APP_RUNTIME: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(RUNTIME_DEFAULT);
|
||||
|
||||
/// The process-wide runtime used by [`Config::finalize`] (and by the
|
||||
/// load/reload paths which finalize internally).
|
||||
#[must_use]
|
||||
pub fn app_runtime() -> AppRuntime {
|
||||
match APP_RUNTIME.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
1 => AppRuntime::Gtk,
|
||||
_ => AppRuntime::None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the process-wide runtime. The embedded library selects
|
||||
/// [`AppRuntime::None`] so `gtk-single-instance = detect` stays `detect`.
|
||||
pub fn set_app_runtime(runtime: AppRuntime) {
|
||||
APP_RUNTIME.store(
|
||||
u8::from(runtime == AppRuntime::Gtk),
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// The value of the config key in the form the C API exposes it (Zig:
|
||||
/// `c_get.get`). `None` if there is no such key.
|
||||
@@ -4478,6 +4519,12 @@ impl Config {
|
||||
/// Call this once after you are done setting configuration. This
|
||||
/// is idempotent but will waste memory if called multiple times.
|
||||
pub fn finalize(&mut self) -> Result<(), Error> {
|
||||
self.finalize_for(app_runtime())
|
||||
}
|
||||
|
||||
/// [`Config::finalize`] for an explicit application runtime (Zig's
|
||||
/// `build_config.app_runtime`, a build option there).
|
||||
pub fn finalize_for(&mut self, runtime: AppRuntime) -> Result<(), Error> {
|
||||
// We always load the theme first because it may set other fields
|
||||
// in our config.
|
||||
if let Some(theme) = self.theme.clone() {
|
||||
@@ -4543,19 +4590,22 @@ impl Config {
|
||||
wd.finalize();
|
||||
self.working_directory = Some(wd);
|
||||
|
||||
// Apprt-specific defaults (GTK)
|
||||
match self.gtk_single_instance {
|
||||
GtkSingleInstance::True | GtkSingleInstance::False => {}
|
||||
// Apprt-specific defaults
|
||||
match runtime {
|
||||
AppRuntime::None => {}
|
||||
AppRuntime::Gtk => match self.gtk_single_instance {
|
||||
GtkSingleInstance::True | GtkSingleInstance::False => {}
|
||||
|
||||
// For detection, we assume single instance unless we're
|
||||
// in a CLI environment, then we disable single instance.
|
||||
GtkSingleInstance::Detect => {
|
||||
self.gtk_single_instance = if probable_cli {
|
||||
GtkSingleInstance::False
|
||||
} else {
|
||||
GtkSingleInstance::True
|
||||
};
|
||||
}
|
||||
// For detection, we assume single instance unless we're
|
||||
// in a CLI environment, then we disable single instance.
|
||||
GtkSingleInstance::Detect => {
|
||||
self.gtk_single_instance = if probable_cli {
|
||||
GtkSingleInstance::False
|
||||
} else {
|
||||
GtkSingleInstance::True
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Default our click interval
|
||||
@@ -5433,6 +5483,19 @@ mod tests {
|
||||
assert!(cfg.conditional_set.contains(&conditional::Key::Theme));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gtk_single_instance_detect_only_finalizes_for_gtk() {
|
||||
let mut cfg = Config::default();
|
||||
cfg.load_iter(&mut it(&["--gtk-single-instance=detect"]))
|
||||
.unwrap();
|
||||
let mut none = cfg.clone();
|
||||
none.finalize_for(AppRuntime::None).unwrap();
|
||||
assert_eq!(none.gtk_single_instance, GtkSingleInstance::Detect);
|
||||
let mut gtk = cfg;
|
||||
gtk.finalize_for(AppRuntime::Gtk).unwrap();
|
||||
assert_ne!(gtk.gtk_single_instance, GtkSingleInstance::Detect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compatibility_gtk_single_instance_desktop() {
|
||||
let mut cfg = Config::default();
|
||||
|
||||
Reference in new issue
Block a user