fix(input): carry key and preedit text as bytes like Zig

Zig's KeyEvent.utf8 and the embedded API's key/preedit text are byte
slices passed through to key encoding and the pty; the port converted
them lossily to UTF-8.
- KeyEvent.utf8 is &[u8] end to end (ghostty-input encoder, core,
  ghostty-lib C API, libghostty-vt key events, GTK callers). Where Zig
  validates with Utf8View (alternates, modify-other-keys, ctrl CSI-u,
  associated text, binding unicode lookup) invalid text takes the same
  "no UTF-8" fallback.
- Invalid preedit UTF-8 behaves like Zig's `try Utf8View.init`: the
  prior preedit is cleared and marked dirty, the callback returns an
  error (logged by the C API) and no preedit is set.
- The inspector key widget shows invalid key text lossily, display only.

Test Plan:
- cargo test --workspace --no-fail-fast (3407 passed)
- cargo test -p ghostty-lib --features test-platform (C-API key text
  0xff 0xfe reaches the pty unchanged)
- just check-macos (both), just check-windows, just check-wasm
This commit is contained in:
ddidderr committed 2026-10-12 11:01:17 +02:00
1 parent 5f16c24760
commit db954441fa
19 files changed
+277 -144

No files matched your search

+3 -7
View File
@@ -27,8 +27,8 @@ pub struct KeyEventWrapper {
}
impl KeyEventWrapper {
/// Borrow as the Rust [`KeyEvent`]. Zig's `utf8` is a byte slice, ours is
/// `&str`: invalid UTF-8 is truncated to its longest valid prefix.
/// Borrow as the Rust [`KeyEvent`]. `utf8` is a byte slice (as in Zig) and is
/// passed through unchanged, even if it is not valid UTF-8.
///
/// # Safety
/// The caller-provided UTF-8 bytes must still be alive.
@@ -39,17 +39,13 @@ impl KeyEventWrapper {
// SAFETY: per the contract of `ghostty_key_event_set_utf8`.
unsafe { std::slice::from_raw_parts(self.utf8, self.utf8_len) }
};
let text = match std::str::from_utf8(bytes) {
Ok(s) => s,
Err(e) => std::str::from_utf8(&bytes[..e.valid_up_to()]).unwrap_or(""),
};
KeyEvent {
action: self.action,
key: self.key,
mods: self.mods,
consumed_mods: self.consumed_mods,
composing: self.composing,
utf8: text,
utf8: bytes,
unshifted_codepoint: self.unshifted_codepoint,
}
}
@@ -282,7 +282,7 @@ fn wasm_key_encode_example() {
action,
key,
mods,
utf8,
utf8: utf8.as_bytes(),
unshifted_codepoint: unshifted as u32,
..KeyEvent::default()
};