feat(windows): port Ghostty's Windows code paths

Ports the Windows branches of the Zig code behind cfg(windows):
ConPTY pty (CreatePseudoConsole, pipes, resize), CreateProcessW with the
pseudo-console attribute list, env blocks and command-line quoting, a
pipe read thread with CancelIoEx and a WaitForMultipleObjects IO loop,
plus os helpers (home dir, env, paths, temp dirs, hostname, XDG via
LOCALAPPDATA) and config/input/terminal/crash Windows branches.

Compile-checked with `cargo check --target x86_64-pc-windows-gnu` for
vt, unicode, datastruct, input, os, config, terminal and termio (setup
in docs/rewrite/notes/windows-WIN.md); cannot run here.
Platform-independent pieces (quoting with a CommandLineToArgvW
round-trip, env block encoding) have unit tests.

Test Plan:
- cargo test -p ghostty-termio windows_args
- cargo check --target x86_64-pc-windows-gnu -p ghostty-termio
This commit is contained in:
ddidderr committed 2026-10-12 06:02:28 +02:00
1 parent 84f665cdfb
commit 76771bed0d
8 files changed
+2402

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
//! Windows API helpers (`os/windows.zig`). Zig re-exports a handful of
//! kernel32 items and declares the ConPTY externs that `std` lacks; here the
//! `windows-sys` crate provides all of those, so this module only holds the
//! small shared helpers plus the constants Zig computes by hand.
/// Re-export so dependants can name Windows API items through one path.
pub use windows_sys as sys;
/// `ProcThreadAttributeNumber.ProcThreadAttributePseudoConsole`.
const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE_NUMBER: u32 = 22;
const PROC_THREAD_ATTRIBUTE_NUMBER: u32 = 0x0000_FFFF;
const PROC_THREAD_ATTRIBUTE_THREAD: u32 = 0x0001_0000;
const PROC_THREAD_ATTRIBUTE_INPUT: u32 = 0x0002_0000;
const PROC_THREAD_ATTRIBUTE_ADDITIVE: u32 = 0x0004_0000;
/// Corresponds to the `ProcThreadAttributeValue` define in WinBase.h.
pub const fn proc_thread_attribute_value(
number: u32,
thread: bool,
input: bool,
additive: bool,
) -> u32 {
(number & PROC_THREAD_ATTRIBUTE_NUMBER)
| if thread {
PROC_THREAD_ATTRIBUTE_THREAD
} else {
0
}
| if input {
PROC_THREAD_ATTRIBUTE_INPUT
} else {
0
}
| if additive {
PROC_THREAD_ATTRIBUTE_ADDITIVE
} else {
0
}
}
/// `PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE` (`windows.exp`).
pub const PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE: u32 = proc_thread_attribute_value(
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE_NUMBER,
false,
true,
false,
);
/// `CREATE_UNICODE_ENVIRONMENT`.
pub const CREATE_UNICODE_ENVIRONMENT: u32 = 0x0000_0400;
/// `EXTENDED_STARTUPINFO_PRESENT`.
pub const EXTENDED_STARTUPINFO_PRESENT: u32 = 0x0008_0000;
/// `FILE_FLAG_FIRST_PIPE_INSTANCE`.
pub const FILE_FLAG_FIRST_PIPE_INSTANCE: u32 = 0x0008_0000;
/// `STATUS_PENDING` / `STILL_ACTIVE`.
pub const STILL_ACTIVE: u32 = 0x0000_0103;
/// UTF-8 to a NUL-terminated UTF-16 buffer (`utf8ToUtf16LeAllocZ`).
pub fn to_wide_nul(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}
/// The NetBIOS name of this computer (`GetComputerNameA`), if available.
#[allow(unsafe_code)]
pub fn computer_name() -> Option<String> {
use windows_sys::Win32::System::WindowsProgramming::GetComputerNameA;
let mut buf = [0u8; 257];
let mut size = 256u32;
// SAFETY: `buf` is valid for `size` + 1 bytes and `size` is a valid
// in/out pointer.
if unsafe { GetComputerNameA(buf.as_mut_ptr(), &raw mut size) } == 0 {
return None;
}
Some(String::from_utf8_lossy(&buf[..size as usize]).into_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pseudoconsole_attribute_value() {
// Matches the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE define (0x20016).
assert_eq!(PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, 0x0002_0016);
assert_eq!(
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
windows_sys::Win32::System::Threading::PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
);
}
#[test]
fn wide_is_nul_terminated() {
assert_eq!(to_wide_nul("ab"), vec![b'a'.into(), b'b'.into(), 0]);
}
}