refactor(config): parse config values from raw bytes like Zig

Zig parses config values (cli/args.zig, Config.zig field parsers,
config/string.zig) from raw byte slices, and each field type decides
whether it requires UTF-8 (InvalidValue otherwise) or stores bytes. The
port escape-encoded raw bytes into Strings at the line reader and let
fields parse the escaped text, so fields could observe encoding
artifacts; successive audits kept finding such leaks (RV5-RV8).

Field parsers now receive the original bytes: text fields validate UTF-8
where Zig does, byte/path fields store bytes (EncodedOs/OsString). This
resolves all 9 RV8 findings (docs/rewrite/notes/review-rv8-codex.md),
each with a regression test: Windows path conversion fails with
BadPathName instead of substituting U+FFFD; Windows process launch uses
strict UTF-8 like Zig's Command; the WTF-8 decoder accepts paired-
surrogate encodings like Zig; literal U+FDD0 Unicode key triggers;
selection-word-chars and clipboard-codepoint-map reject invalid UTF-8;
font style / codepoint-map family names use original bytes; C title
actions forward bytes; D-Bus new-window/Flatpak conversions match Zig.
Design, sweep and validation: docs/rewrite/notes/final-bytes-refactor.md.

Also moves the bundled iTerm2-Color-Schemes license out of
resources/themes/ so +list-themes no longer lists it as a theme
(output is now byte-identical to Ghostty 1.3.1).

Done by Codex (gpt-6-astra), reviewed and verified by the orchestrator.

Test Plan:
- cargo test --workspace --no-fail-fast (3428 passed, exit 0)
- just check-macos (both), just check-windows, just check-wasm
- +show-config --default --docs, +list-keybinds, +list-actions --docs,
  +list-themes --plain compared with /usr/bin/ghostty
This commit is contained in:
ddidderr committed 2026-10-12 11:57:01 +02:00
1 parent 70c250cf2d
commit 6f9b3129e1
100 files changed
+2310 -1054

No files matched your search

+14 -14
View File
@@ -459,10 +459,7 @@ impl Surface {
// don't leak GHOSTTY_LOG to any subprocesses
env.remove("GHOSTTY_LOG");
let mut ecfg = exec::Config::new(
env,
ghostty_os::osbytes::EncodedOs::from_encoded(config.term.clone()),
);
let mut ecfg = exec::Config::new(env, config.term.clone());
ecfg.command = command.clone();
ecfg.env_override = config.env.clone();
ecfg.shell_integration = config.shell_integration;
@@ -607,7 +604,7 @@ impl Surface {
rt_app.perform_action(
Target::Surface(id),
RtAction::SetTitle(SetTitle {
title: ghostty_os::osbytes::config_str_to_text(title),
title: title.clone(),
}),
);
} else if config.xdg_terminal_exec
@@ -620,7 +617,7 @@ impl Surface {
rt_app.perform_action(
Target::Surface(id),
RtAction::SetTitle(SetTitle {
title: ghostty_os::osbytes::config_str_to_text(&cmd.string()),
title: ghostty_os::osbytes::EncodedOs::from_encoded(cmd.string()),
}),
);
} else if let Some(cmd) = &command {
@@ -636,7 +633,7 @@ impl Surface {
rt_app.perform_action(
Target::Surface(id),
RtAction::SetTitle(SetTitle {
title: ghostty_os::osbytes::config_str_to_text(first),
title: first.clone(),
}),
);
}
@@ -872,8 +869,8 @@ impl Surface {
}
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
let title = String::from_utf8_lossy(&buf[..end]).into_owned();
log::debug!("changing title \"{title}\"");
let title = ghostty_os::osbytes::EncodedOs::from_bytes(&buf[..end]);
log::debug!("changing title {title:?}");
self.perform(RtAction::SetTitle(SetTitle { title }));
}
@@ -885,11 +882,14 @@ impl Surface {
let title = self.rt_surface.get_title();
let data = match style {
ghostty_apprt::surface::ReportTitleStyle::Csi21T => {
format!("\x1b]l{}\x1b\\", title.unwrap_or_default())
}
ghostty_apprt::surface::ReportTitleStyle::Csi21T => [
b"\x1b]l".as_slice(),
&title.unwrap_or_default().to_bytes(),
b"\x1b\\",
]
.concat(),
};
self.queue_io(IoMessage::write_req(data.as_bytes()));
self.queue_io(IoMessage::write_req(data.as_slice()));
}
Message::ColorChange(change) => {
@@ -1306,7 +1306,7 @@ impl Surface {
// configured title.
if let Some(title) = &config.title {
self.perform(RtAction::SetTitle(SetTitle {
title: ghostty_os::osbytes::config_str_to_text(title),
title: title.clone(),
}));
}