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 1327e63811
commit c8113345a5
19 files changed
+277 -144

No files matched your search

+12 -3
View File
@@ -1679,7 +1679,7 @@ impl Surface {
/// The core surface will NOT reset the preedit state on charCallback or
/// keyCallback and we rely completely on the apprt implementation to
/// track the preedit state correctly.
pub fn preedit_callback(&self, preedit: Option<&str>) -> Result<(), CoreError> {
pub fn preedit_callback(&self, preedit: Option<&[u8]>) -> Result<(), CoreError> {
let cfg = self.cfg();
let mut st = self.lock_state();
@@ -1704,7 +1704,16 @@ impl Surface {
return Ok(());
};
// We convert the UTF-8 text to codepoints.
// We convert the UTF-8 text to codepoints. Like Zig's
// `Utf8View.init(text)`, invalid UTF-8 is an error: the prior preedit
// is already cleared and marked dirty, and no new preedit is set.
let text = match std::str::from_utf8(text) {
Ok(t) => t,
Err(e) => {
drop(st);
return Err(Box::new(e));
}
};
let mut codepoints: Vec<PreeditCodepoint> = Vec::new();
for cp in text.chars() {
let width = usize::from(ghostty_unicode::table().get(u32::from(cp)).width);
@@ -1993,7 +2002,7 @@ impl CoreSurface for Surface {
Surface::key_callback(self, event)
}
fn preedit_callback(&self, text: Option<&str>) -> Result<(), CoreError> {
fn preedit_callback(&self, text: Option<&[u8]>) -> Result<(), CoreError> {
let _crash = self.crash_guard();
Surface::preedit_callback(self, text)
}