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
@@ -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),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ fn osc8_link_hover_and_ctrl_click_opens() {
|
||||
// hover with ctrl over "here" (cols 3..6)
|
||||
h.move_to(4, 0, Some(ctrl()));
|
||||
let url = h.wait_action("mouse over link", |a| match a {
|
||||
Action::MouseOverLink(l) if !l.url.is_empty() => Some(l.url.clone()),
|
||||
Action::MouseOverLink(l) if !l.url.is_empty() => Some(l.url.to_text_lossy()),
|
||||
_ => None,
|
||||
});
|
||||
assert_eq!(url, "http://osc8.example/x");
|
||||
@@ -324,7 +324,7 @@ fn regex_url_ctrl_click_opens_and_plain_click_does_not() {
|
||||
h.move_to(30, 2, Some(none()));
|
||||
h.move_to(20, 0, Some(ctrl()));
|
||||
h.wait_action("hover", |a| match a {
|
||||
Action::MouseOverLink(l) if l.url.contains("ghostty.org") => Some(()),
|
||||
Action::MouseOverLink(l) if l.url.to_text_lossy().contains("ghostty.org") => Some(()),
|
||||
_ => None,
|
||||
});
|
||||
h.click(20, 0, L, ctrl());
|
||||
|
||||
@@ -229,11 +229,24 @@ fn osc52_read_allow_and_deny() {
|
||||
assert!(h.rt.confirmations.borrow().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_notification_keeps_invalid_utf8_bytes() {
|
||||
let h = harness!(Harness::recorder(r"\033]777;notify;T\377;B\377\007", &[]));
|
||||
let (t, b) = h.wait_action("osc777", |a| match a {
|
||||
Action::DesktopNotification(n) => Some((n.title.to_bytes(), n.body.to_bytes())),
|
||||
_ => None,
|
||||
});
|
||||
assert_eq!(
|
||||
(t.as_slice(), b.as_slice()),
|
||||
(b"T\xff".as_slice(), b"B\xff".as_slice())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn desktop_notifications() {
|
||||
let h = harness!(Harness::recorder(r"\033]9;just a body\007", &[]));
|
||||
let (t, b) = h.wait_action("osc9", |a| match a {
|
||||
Action::DesktopNotification(n) => Some((n.title.clone(), n.body.clone())),
|
||||
Action::DesktopNotification(n) => Some((n.title.to_text_lossy(), n.body.to_text_lossy())),
|
||||
_ => None,
|
||||
});
|
||||
assert_eq!(b, "just a body");
|
||||
@@ -244,7 +257,7 @@ fn desktop_notifications() {
|
||||
&[]
|
||||
));
|
||||
let (t, b) = h.wait_action("osc777", |a| match a {
|
||||
Action::DesktopNotification(n) => Some((n.title.clone(), n.body.clone())),
|
||||
Action::DesktopNotification(n) => Some((n.title.to_text_lossy(), n.body.to_text_lossy())),
|
||||
_ => None,
|
||||
});
|
||||
assert_eq!((t.as_str(), b.as_str()), ("My Title", "My Body"));
|
||||
|
||||
Reference in new issue
Block a user