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

@@ -1306,7 +1306,7 @@ impl Surface {
mods,
consumed_mods,
composing: imp.im_composing.get(),
utf8: &utf8,
utf8: utf8.as_bytes(),
unshifted_codepoint: keyval_unicode_unshifted,
}) {
Ok(e) => e,
@@ -3673,7 +3673,7 @@ mod imp {
let (text, _, _) = ctx.preedit_string();
// Update our preedit state in Ghostty core
if let Err(err) = surface.preedit_callback(Some(&text)) {
if let Err(err) = surface.preedit_callback(Some(text.as_bytes())) {
log::warn!("error in preedit callback err={err}");
}
}
@@ -3755,7 +3755,7 @@ mod imp {
mods: Mods::default(),
consumed_mods: Mods::default(),
composing: false,
utf8: str,
utf8: str.as_bytes(),
..KeyEvent::default()
}) {
log::warn!("error in key callback err={err}");