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:
ddidderr committed 2026-10-12 13:09:04 +02:00
1 parent 95fe02b55a
commit f2ccb04aa4
21 files changed
+479 -133

No files matched your search

+3 -3
View File
@@ -358,7 +358,7 @@ impl Surface {
/// Show a desktop notification, rate limited (Zig
/// `showDesktopNotification`).
pub(crate) fn show_desktop_notification(&self, title: &str, body: &str) {
pub(crate) fn show_desktop_notification(&self, title: &[u8], body: &[u8]) {
let now = Instant::now();
// Set a limit of one desktop notification per second so that the OS
@@ -391,8 +391,8 @@ impl Surface {
self.app.last_notification_time.set(Some(now));
self.app.last_notification_digest.set(new_digest);
self.perform(RtAction::DesktopNotification(DesktopNotification {
title: title.to_owned(),
body: body.to_owned(),
title: ghostty_os::osbytes::EncodedOs::from_bytes(title),
body: ghostty_os::osbytes::EncodedOs::from_bytes(body),
}));
}
+3 -1
View File
@@ -514,7 +514,9 @@ impl Surface {
// we reset state
self.perform(RtAction::MouseShape(mouse_shape));
self.perform(RtAction::MouseOverLink(
ghostty_apprt::action::MouseOverLink { url: String::new() },
ghostty_apprt::action::MouseOverLink {
url: ghostty_os::osbytes::EncodedOs::new(),
},
));
self.queue_render();
}
+1 -3
View File
@@ -960,9 +960,7 @@ impl Surface {
return Ok(());
}
let title = String::from_utf8_lossy(notification.title()).into_owned();
let body = String::from_utf8_lossy(notification.body()).into_owned();
self.show_desktop_notification(&title, &body);
self.show_desktop_notification(notification.title(), notification.body());
}
Message::RendererHealth(health) => self.update_renderer_health(health),
+6 -5
View File
@@ -1801,7 +1801,9 @@ impl Surface {
trim: false,
});
(
Some(MouseOverLink { url: str }),
Some(MouseOverLink {
url: ghostty_os::osbytes::EncodedOs::from_plain_text(&str),
}),
cfg.link_previews == LinkPreviews::True,
)
}
@@ -1814,9 +1816,8 @@ impl Surface {
break 'link (None, false);
};
(
// Display-only text for the status bar.
Some(MouseOverLink {
url: String::from_utf8_lossy(&uri).into_owned(),
url: ghostty_os::osbytes::EncodedOs::from_bytes(&uri),
}),
cfg.link_previews != LinkPreviews::False,
)
@@ -1848,7 +1849,7 @@ impl Surface {
out.actions
.push(RtAction::MouseShape(st.terminal.mouse_shape));
out.actions.push(RtAction::MouseOverLink(MouseOverLink {
url: String::new(),
url: ghostty_os::osbytes::EncodedOs::new(),
}));
out.render = true;
}
@@ -1930,7 +1931,7 @@ impl Surface {
let shape = self.lock_terminal().mouse_shape;
self.perform(RtAction::MouseShape(shape));
self.perform(RtAction::MouseOverLink(MouseOverLink {
url: String::new(),
url: ghostty_os::osbytes::EncodedOs::new(),
}));
self.queue_render();
}