Files
ghostty-rs/docs/rewrite/notes/windows-WIN.md
T
ddidderr 3c8fae1089 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
2026-10-12 06:02:28 +02:00

106 lines
7.1 KiB
Markdown

# Windows port (agent WIN)
Windows-specific Zig code ported behind `cfg(windows)`; Linux code paths are
unchanged. Windows is compile-checked only (`cargo check --target
x86_64-pc-windows-gnu`), nothing can be run here.
## Verifying
```
RUSTC_WRAPPER= PKG_CONFIG_ALLOW_CROSS=1 PKG_CONFIG_PATH=<dir with stub fontconfig.pc> \
CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar \
cargo check --target x86_64-pc-windows-gnu --tests \
-p ghostty-vt -p ghostty-unicode -p ghostty-datastruct -p ghostty-input \
-p ghostty-os -p ghostty-config -p ghostty-terminal -p ghostty-termio
```
* `RUSTC_WRAPPER=` is needed because the `kache` rustc wrapper from
`~/.cargo/config.toml` is picked up by `cc-rs` as a compiler launcher and
breaks the mingw C builds (onig_sys, freetype-sys, harfbuzz-sys).
* mingw (`x86_64-w64-mingw32-gcc/g++`, cmake) exists, so the C crates build:
`onig_sys` natively, `freetype-sys` / `harfbuzz-sys` via their `bundled`
feature (enabled only in the `cfg(windows)` dependency table of
ghostty-font; Zig also builds them from source).
* `yeslogic-fontconfig-sys` still wants `pkg-config` for fontconfig (Zig's
default backend there is `fontconfig_freetype`, built from source). For
`cargo check` a stub `fontconfig.pc` + `PKG_CONFIG_ALLOW_CROSS=1` is enough
(no linking happens). A real Windows link needs fontconfig from vcpkg/MSYS2.
* Windows clippy: `cargo clippy --target x86_64-pc-windows-gnu --no-deps
-p ghostty-os -p ghostty-termio -p ghostty-crash -p ghostty-config
-p ghostty-terminal -p ghostty-input --all-targets -- -D warnings` is clean.
## What was ported
| Zig | Rust |
|-----|------|
| `os/windows.zig` | `ghostty-os/src/windows.rs` (`windows-sys` re-export, `PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE`, `to_wide_nul`, `computer_name`) |
| `os/pipe.zig` (CreatePipe) | `ghostty-os/src/pipe.rs` (returns `OwnedHandle`s) |
| `os/homedir.zig` | `homedir.rs`: `HOMEDRIVE`+`HOMEPATH` (`home_windows`, tested), `expand_home` is identity |
| `os/env.zig` | `;` delimiter; `EnvMap` is ASCII case-insensitive on Windows (Zig's EnvMap hash context); `filter_snap_paths` no-op |
| `os/file.zig` | `alloc_tmp_dir` = `%TMP%` only (None if unset); rlimit helpers are unix only |
| `os/path.zig` | `;` delimiter, `\` separator, every file is executable |
| `os/hostname.zig` | `GetComputerNameA` |
| `os/open.zig` | `rundll32 url.dll,FileProtocolHandler` (merged with M1's `open_command`) |
| `os/xdg.zig` | `LOCALAPPDATA` fallback for the XDG env var |
| `os/resourcesdir.zig` | sentinel `terminfo/ghostty.terminfo` |
| `os/desktop.zig` | `DesktopEnvironment::Windows`, `launched_from_desktop` uses `std::process::id` |
| `os/i18n.zig` | `init` is a no-op, `tr` returns the msgid (gettext is not built on Windows) |
| `os/temp_dir` | `BCryptGenRandom` for the name, no unix mode |
| `os/passwd.zig` (`@compileError` on Windows) | `ghostty_os::passwd` stub returning an empty `Entry`; `flatpak::is_flatpak()` is `false` |
| `pty.zig` `WindowsPty` | `ghostty-termio/src/pty_windows.rs` (named pipe for input, anonymous pipe for output, `CreatePseudoConsole`/`ResizePseudoConsole`/`ClosePseudoConsole`, non-inheritable handles, drop order as `deinit`) |
| `Command.zig` Windows branch | `command_windows.rs` (`CreateProcessW` + `STARTUPINFOEXW` + proc-thread attribute list for the pseudo console, NUL stdio, cwd, env block, `wait` = `WaitForSingleObject`+`GetExitCodeProcess`, `terminate`) and the pure `windows_args.rs` (`windows_create_command_line`, `create_windows_env_block`, `windows_shell_args`) |
| `termio/Exec.zig` Windows branches | `exec_windows.rs` (read thread = blocking `ReadFile` on the output pipe, quit pipe polled with `PeekNamedPipe`, interrupted with `CancelIoEx`; process exit via the process handle; `cmd.exe` default shell and `%WINDIR%\System32\cmd.exe /C` for shell commands; `TerminateProcess` on stop) |
| `Thread.zig` loop (libxev IOCP) | `thread_windows.rs`: `WaitForMultipleObjects` over mailbox event, stop event, process handle + deadline timeout |
| `xev.Async` | `wakeup_windows.rs`: auto-reset event |
| `config/Config.zig` Windows branches | `load_cli_args` has no argc fast path, `probable_cli_environment` is false, default shell `cmd.exe` + `%HOMEDRIVE%%HOMEPATH%` working dir, `~/` not expanded in `path.rs` |
| `input/keycodes.zig` | native column 3 (win) on Windows |
| `terminal/kitty/graphics_image.zig` | shared-memory medium returns `UnsupportedMedium`, tmp dir = `%TMP%` |
| `crash/sentry.zig` | `init` is a no-op on Windows (as Zig), `signal_windows.rs` provides only `random_bytes` |
Module selection in termio is `#[cfg_attr(windows, path = "..._windows.rs")]`
in `lib.rs` (pty, command, exec, wakeup) so the Linux files are untouched;
`thread.rs` only gets `#[cfg(unix)]` on its poll loop and a `#[cfg(windows)]
mod windows_loop` child module.
## Dependency gating
* ghostty-os: `gettext-rs`, `nix`, `rustix`, `zbus` -> `cfg(not(any(wasm32, windows)))`;
`windows-sys` -> `cfg(windows)`. Unix-only modules (`flatpak`, `passwd`,
`systemd`) are `cfg(unix)`; `flatpak` / `passwd` have tiny Windows stand-ins.
* ghostty-termio: `libc`, `nix`, `rustix` (+ dev `zbus`, `rustix`) -> `cfg(unix)`;
`windows-sys` -> `cfg(windows)`. Linux integration tests got `#![cfg(unix)]`.
* ghostty-terminal: `rustix` (shm) -> not wasm/windows.
* ghostty-crash: `libc` -> `cfg(unix)`.
* ghostty-font: bundled freetype/harfbuzz on Windows (see above).
## Tests
Platform independent (run natively): `windows_args` (10 tests: command-line
quoting rules incl. a CommandLineToArgvW round-trip reference splitter, env
block layout/UTF-16/invalid unicode, `%WINDIR%` shell args), `path_join_with`
Windows flavour, `home_windows`. Windows-only (compiled, not runnable here):
ConPTY size, pipe round-trip, `cmd.exe /c exit 7`, wakeup event, wait timeout
rounding, `exec_command` Windows variants, `hostname.exe` PATH lookup.
Unix-specific existing tests are `cfg(unix)`-gated.
## Deviations / gaps
* No termios timer on Windows (Zig doesn't run it either); `focus_gained` is a
no-op (Zig would `@panic` there).
* Input writes go through a writer thread doing blocking overlapped
`WriteFile`s (no libxev). Output uses plain blocking `ReadFile`.
* Windows `exec::Config` omits `rt_pre_exec_info`, `rt_post_fork_info`,
`create_scope`, `restore_rlimit` (cgroup/rlimit are Linux-only);
ghostty-core's surface setup will need `cfg` around them when it gets a
Windows apprt.
* `Command::wait(block)` ignores `block` (blocks, like Zig). The thread
handle from `CreateProcessW` is closed (Zig leaks it).
* Fontconfig discovery is kept on Windows (needs a real fontconfig to link).
* Not touched: `cli/ssh-cache/DiskCache` file locking, `cli/boo|list_colors|
list_keybinds|edit_config` Windows branches (ghostty-cli), `benchmark/
CodepointWidth`, `config/Config.zig` `probableCliEnvironment` env-based
extras. ghostty-core/apprt-gtk are not Windows-checkable (GTK).
* Unrelated breakage seen while verifying (other agents): `ghostty-config`
`config.rs` uses `ghostty_os::launched_from_desktop` under `cfg!(macos)`
which fails on wasm32; `ghostty-os/tests/common/flatpak_mock.rs` fails
`clippy::needless_pass_by_value`; `ghostty-font` coretext_math clippy.