fix: Windows core build, embedded init, byte-exact paths, version info

Closes the remaining core/lib/termio/GTK rows (final-Z2.md):
- ghostty-core compiles for Windows (Unix-only exec fields and file
  modes gated); checked with cargo check --target x86_64-pc-windows-gnu.
- Embedded init: ensure_locale, SIGPIPE ignored in ghostty_init for
  C/Swift hosts, Apple crash tags; the argv passed to ghostty_init feeds
  ghostty_config_load_cli_args; ghostty_config_load_file accepts
  non-UTF-8 paths.
- OSC 7 pwd, embedded surface cwd/command/env/initial input,
  ghostty_surface_text and GTK file drops keep raw bytes.
- XTVERSION, TERM_PROGRAM_VERSION, the About dialog and crash metadata
  use ghostty_build_info instead of hardcoded versions.

Test Plan:
- cargo test --workspace (3327 passed)
- cargo clippy --workspace --all-targets -- -D warnings
This commit is contained in:
ddidderr committed 2026-10-12 08:14:02 +02:00
1 parent 8519f1ba1b
commit ebefad5031
30 files changed
+863 -80

No files matched your search

+1
View File
@@ -23,6 +23,7 @@ ghostty-config.workspace = true
ghostty-input.workspace = true
ghostty-datastruct.workspace = true
ghostty-inspector.workspace = true
ghostty-build-info.workspace = true
ghostty-os.workspace = true
ghostty-vt.workspace = true
log = "0.4"
+11
View File
@@ -69,6 +69,17 @@ fn main() {
println!("cargo:rerun-if-changed=resources");
println!("cargo:rerun-if-changed=build.rs");
// Source tarballs ship the prebuilt GResource bundle so packagers need
// neither blueprint-compiler nor glib-compile-resources (Zig:
// `GhosttyDist.Resource` for `ghostty_resources.{c,h}`). As in Zig, it is
// never used in a git checkout.
println!("cargo:rerun-if-changed=dist/ghostty.gresource");
let prebuilt = manifest.join("dist/ghostty.gresource");
if prebuilt.is_file() && !manifest.join("../../.git").exists() {
std::fs::copy(&prebuilt, out.join("ghostty.gresource")).unwrap();
return;
}
let mut xml = String::new();
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gresources>\n");
+38 -9
View File
@@ -1512,6 +1512,12 @@ impl Surface {
clipboard::paste(self, text);
}
/// [`Surface::paste`] for raw bytes (e.g. dropped file names that are
/// not valid UTF-8 must reach the pty unchanged).
pub fn paste_bytes(&self, bytes: &[u8]) {
clipboard::paste_bytes(self, bytes);
}
/// Focus this surface. This properly focuses the input part of
/// our surface. (Shadows `WidgetExt::grab_focus` on purpose, like Zig.)
pub fn grab_focus(&self) {
@@ -2622,6 +2628,26 @@ mod clipboard {
}
}
/// Paste explicit bytes. Valid UTF-8 takes the normal text path; other
/// bytes use the byte-preserving paste (the confirmation dialog, which
/// is text based, carries them with the reversible byte escape).
pub(super) fn paste_bytes(surface: &Surface, bytes: &[u8]) {
if let Ok(text) = std::str::from_utf8(bytes) {
paste(surface, text);
return;
}
let Some(core) = surface.core() else { return };
match core.complete_paste_bytes(bytes, false) {
Ok(()) => {}
Err(ClipboardError::UnsafePaste | ClipboardError::UnauthorizedPaste) => {
let text = ghostty_os::osbytes::bytes_to_config_string(bytes);
show_clipboard_confirmation(surface, ClipboardRequest::Paste, &text);
}
Err(err) => log::warn!("failed to complete clipboard request err={err}"),
}
}
/// Get the specific type of clipboard for a widget.
fn get(widget: &gtk::Widget, clipboard: Clipboard) -> gdk::Clipboard {
match clipboard {
@@ -2661,7 +2687,13 @@ mod clipboard {
// Get our text
let text = buf.text(&buf.start_iter(), &buf.end_iter(), false);
if let Err(err) = core.complete_clipboard_request(req, &text, true) {
let result = if matches!(req, ClipboardRequest::Paste) {
// Undo the byte escape used for non-UTF-8 pastes.
core.complete_paste_bytes(&ghostty_os::osbytes::config_str_to_bytes(&text), true)
} else {
core.complete_clipboard_request(req, &text, true)
};
if let Err(err) = result {
log::warn!("failed to complete clipboard request: {err}");
}
});
@@ -3375,17 +3407,14 @@ mod imp {
let mut w = ghostty_os::ShellEscapeWriter::new(&mut escaped);
for file in list.files() {
let Some(path) = file.path() else { continue };
let path = path.to_string_lossy().into_owned();
if let Err(err) = w
.write_all(path.as_bytes())
.and_then(|()| w.write_all(b"\n"))
{
// Raw path bytes: non-UTF-8 names must survive.
let path = ghostty_os::osbytes::os_bytes(path.as_os_str());
if let Err(err) = w.write_all(&path).and_then(|()| w.write_all(b"\n")) {
log::error!("unable to write path to buffer: {err}");
}
}
}
let string = String::from_utf8_lossy(&escaped).into_owned();
this.paste(&string);
this.paste_bytes(&escaped);
return true;
}
@@ -3397,7 +3426,7 @@ mod imp {
{
let mut w = ghostty_os::ShellEscapeWriter::new(&mut escaped);
if let Err(err) = w
.write_all(path.to_string_lossy().as_bytes())
.write_all(&ghostty_os::osbytes::os_bytes(path.as_os_str()))
.and_then(|()| w.write_all(b"\n"))
{
log::error!("unable to write path to buffer: {err}");
@@ -113,11 +113,11 @@ mod imp {
return;
};
if data.exit_code == 0 {
banner.set_title("Command succeeded");
banner.set_title(&ghostty_os::i18n::tr("Command succeeded"));
this.add_css_class("normal");
this.remove_css_class("abnormal");
} else {
banner.set_title("Command failed");
banner.set_title(&ghostty_os::i18n::tr("Command failed"));
this.remove_css_class("normal");
this.add_css_class("abnormal");
}
+2 -2
View File
@@ -1662,7 +1662,7 @@ impl Window {
.application_name(name)
.developer_name(tr("Ghostty Developers"))
.application_icon(icon)
.version(env!("CARGO_PKG_VERSION"))
.version(ghostty_build_info::VERSION_STRING)
.issue_url("https://github.com/ghostty-org/ghostty/issues")
.website(website)
.build();
@@ -1675,7 +1675,7 @@ impl Window {
("program-name", &name),
("logo-icon-name", &icon),
("title", &tr("About Ghostty")),
("version", &env!("CARGO_PKG_VERSION")),
("version", &ghostty_build_info::VERSION_STRING),
("website", &website),
],
);