The DA1 trampoline was converting C feature codes into a local
stack buffer and returning a slice pointing into it. This is
unsound because the slice outlives the stack frame once the
trampoline returns, leaving reportDeviceAttributes reading
invalid memory.
Move the scratch buffer into the wrapper effects struct so that
its lifetime extends beyond the trampoline call, keeping the
returned slice valid for the caller.
Add a comprehensive "Effects" section to the terminal module
documentation in terminal.h explaining the callback system that
lets embedding applications react to terminal-initiated events
(bell, title changes, pty writes, device queries, etc.). The
section includes a reference table of all available effects and
their triggers, plus @snippet references to the new example.
Add c-vt-effects example project demonstrating how to register
write_pty, bell, and title_changed callbacks, attach userdata,
and feed VT data that triggers each effect.
Assign handler.effects as a struct literal instead of setting fields
individually. This lets the compiler catch missing fields if new
effects are added to the Effects struct.
Also sort the callback function typedefs in vt/terminal.h
alphabetically (Bell, ColorScheme, DeviceAttributes, Enquiry, Size,
TitleChanged, WritePty, Xtversion).
Rename device_status.h to device.h and add C-compatible structs for
device attributes (DA1/DA2/DA3) responses. The new header includes
defines for all known conformance levels, DA1 feature codes, and DA2
device type identifiers.
Add a GhosttyTerminalDeviceAttributesFn callback that C consumers can
set via GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES. The callback follows
the existing bool + out-pointer pattern used by color_scheme and size
callbacks. When the callback is unset or returns false, the trampoline
returns a default VT220 response (conformance level 62, ANSI color).
The DA1 primary features use a fixed [64]uint16_t inline array with a
num_features count rather than a pointer, so the entire struct is
value-typed and can be safely copied without lifetime concerns.
Change device_status.ColorScheme from a plain Zig enum to
lib.Enum so it uses c_int backing when targeting the C ABI.
Add a color_scheme callback to the C terminal effects, following
the bool + out-pointer pattern used by the size callback. The
trampoline converts between the C calling convention and the
internal stream handler color_scheme effect, returning null when
no callback is set.
Add device_status.h header with GhosttyColorScheme enum and wire
it through terminal.h as GHOSTTY_TERMINAL_OPT_COLOR_SCHEME (= 7)
with GhosttyTerminalColorSchemeFn.
Add GHOSTTY_TERMINAL_OPT_SIZE so C consumers can respond to
XTWINOPS size queries (CSI 14/16/18 t). The callback receives a
GhosttySizeReportSize out-pointer and returns true if the size is
available, or false to silently ignore the query. The trampoline
converts the bool + out-pointer pattern to the optional that the
Zig handler expects.
Add GHOSTTY_TERMINAL_OPT_TITLE_CHANGED so C consumers are notified
when the terminal title changes via OSC 0 or OSC 2 sequences. The
callback has the same fire-and-forget shape as bell.
Add GHOSTTY_TERMINAL_OPT_ENQUIRY and GHOSTTY_TERMINAL_OPT_XTVERSION
so C consumers can respond to ENQ (0x05) and XTVERSION (CSI > q)
queries. Both callbacks return a GhosttyString rather than using
out-pointers.
Introduce GhosttyString in types.h as a borrowed byte string
(ptr + len) backed by lib.String on the Zig side. This will be
reusable for future callbacks that need to return string data.
Without an xtversion callback the trampoline returns an empty
string, which causes the handler to report the default
"libghostty" version. Without an enquiry callback no response
is sent.
Test that the write_pty callback receives correct DECRQM response
data and userdata, that queries are silently ignored without a
callback, and that setting null clears the callback. Test that
the bell callback fires on single and multiple BEL characters
with correct userdata, and that BEL without a callback is safe.
Add GHOSTTY_TERMINAL_OPT_BELL so C consumers can receive bell
notifications during VT processing. The bell trampoline follows
the same pattern as write_pty.
Move the C function pointer typedefs (WritePtyFn, BellFn) into
the Effects struct namespace to keep callback types co-located
with their storage and trampolines.
Add a typed option setter ghostty_terminal_set() following the
existing setopt pattern used by the key encoder and render state
APIs. This is the first step toward exposing stream_terminal
Handler.Effects through the C API.
The initial implementation includes a write_pty callback and a
shared userdata pointer. The write_pty callback is invoked
synchronously during ghostty_terminal_vt_write() when the terminal
needs to send a response back to the pty, such as DECRQM mode
reports or device status responses.
Trampolines are always installed at terminal creation time and
no-op when no C callback is set, so callers can configure
callbacks at any point without reinitializing the stream. The C
callback state is grouped into an internal Effects struct on the
TerminalWrapper to simplify adding more callbacks in the future.
> [!WARNING]
> Review/approve this AFTER #11807 and #11810 (this PR includes their
commits)
## Summary
### **And `run test ghostty-test` finally runs on Windows! 🎉almost
there!**
- Skip `linkLibCpp()` on MSVC for dcimgui, spirv-cross, and harfbuzz
(same fix already applied upstream to highway, simdutf, utfcpp, glslang,
SharedDeps, GhosttyZig)
- Fix freetype C enum signedness: MSVC translates C enums as signed
`int`, while GCC/Clang uses unsigned `int`. Add `@intCast` at call sites
and `@bitCast` for bit-shift operations on glyph format tags.
## Context
Zig unconditionally passes `-nostdinc++` and adds its bundled
libc++/libc++abi include paths, which conflict with MSVC's own C++
runtime headers. The MSVC SDK directories (added via `linkLibC`) already
contain both C and C++ headers, so `linkLibCpp` is not needed.
The freetype enum issue is a different facet of the same MSVC vs
GCC/Clang divide: `FT_Render_Mode` and `FT_Glyph_Format` are C enums
that get different signedness on different compilers.
## Stack
Stacked on 015-windows/fix-ssize-t-msvc.
## Test plan
### Cross-platform results (`zig build test` / `zig build
-Dapp-runtime=none test` on Windows)
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** (015, a35f84db3) | FAIL - 48/51, 1 failed (compile
ghostty-test) | PASS - 86/86, 2655/2678, 23 skipped | PASS - 160/160,
2655/2662, 7 skipped |
| **AFTER** (016, ce9930051) | FAIL - 49/51, 2630/2654 tests passed, 1
failed, 23 skipped | PASS - 86/86, 2655/2678, 23 skipped | PASS -
160/160, 2655/2662, 7 skipped |
### Windows: what changed (48 -> 49 steps, tests now run)
**Fixed by this PR:**
- `compile test ghostty-test` - was `3 errors` (libcxxabi conflicts +
freetype type mismatches) -> `success`
- `run test ghostty-test` - now actually runs: 2630 passed, 23 skipped,
1 failed
**Remaining test failure (pre-existing, unrelated):**
- `ghostty.h MouseShape` - `checkGhosttyHEnum` cannot find
`GHOSTTY_MOUSE_SHAPE_*` constants in the translate-c output. This is a
translate-c issue with how MSVC enum constants are exposed, not related
to C++ linking or enum signedness.
### Linux/macOS: no regressions
Identical pass counts and test results before and after.
## Discussion
### Grep wider: other unconditional linkLibCpp calls
`pkg/breakpad/build.zig` still calls `linkLibCpp()` unconditionally but
is behind sentry and not in the Windows build path. Noted for
completeness.
### Freetype enum signedness
The freetype Zig bindings define `RenderMode = enum(c_uint)` and
`Encoding = enum(u31)`. On MSVC, C enums are `int` (signed), so the
translated C functions expect `c_int` parameters. The fix adds
`@intCast` to convert between signed and unsigned at call sites. This is
safe because the enum values are small positive integers that fit in
both types.
Also, not sure if there's a better way to make this change more
elegantly. The comments are replicated in each instance, probably
overkill but I have seen this same pattern elsewhere in the codebase.
## What I Learnt
- More of the same
> [!WARNING]
> Review/approve this AFTER #11807 (this PR includes its commits)
92% progress with the fixes!
## Summary
- Add a conditional `ssize_t` typedef for MSVC in `include/ghostty.h`
- MSVC's `<sys/types.h>` does not define `ssize_t` (it is a POSIX type),
which causes the `translate-c` build step to fail when translating
`ghostty.h` on Windows
- Uses `SSIZE_T` from `<BaseTsd.h>`, the standard Windows SDK equivalent
## Context
The `translate-c` step translates `ghostty.h` to Zig for test
compilation. On MSVC, it fails with 3 errors on `ssize_t` (used in
`ghostty_action_move_tab_s`, `ghostty_action_search_total_s`,
`ghostty_action_search_selected_s`).
The `#ifdef _MSC_VER` guard means this only affects MSVC builds.
`BaseTsd.h` is a standard Windows SDK header and `SSIZE_T` is a signed
pointer-sized integer, matching POSIX `ssize_t` and Zig's `isize`. This
pattern is used by libuv, curl, and other cross-platform C projects.
## Test plan
### Cross-platform results (`zig build test` / `zig build
-Dapp-runtime=none test` on Windows)
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** (d5aef6e84) | FAIL - 47/51 steps, 1 failed | PASS - 86/86,
2655/2678 tests, 23 skipped | PASS - 160/160, 2655/2662 tests, 7 skipped
|
| **AFTER** (a35f84db3) | FAIL - 48/51 steps, 1 failed | PASS - 86/86,
2655/2678 tests, 23 skipped | PASS - 160/160, 2655/2662 tests, 7 skipped
|
### Windows: what changed (47 -> 48 steps, translate-c fixed)
**Fixed by this PR:**
- `translate-c` - was `3 errors` (unknown type name 'ssize_t' at lines
582, 842, 847) -> `success`
**Remaining failure (pre-existing, unrelated):**
- `compile test ghostty-test` - 3 errors in libcxxabi
(`std::get_new_handler` not found, `type_info` redefinition). This is
Zig's bundled libc++ ABI conflicting with MSVC headers when compiling
the test binary. It was previously masked by the translate-c failure
blocking this step.
### Linux/macOS: no regressions
Identical pass counts and test results before and after.
## What Have I Learnt
- I tried fixing this issue the old way, googling and stuff, I
eventually figured out but it took me way more than I am prepared to
share. Yikes.
## Summary
**Getting there!** Goal for today/tomorrow is to get it all green.
This one is easy:
- Gate `HAVE_UNISTD_H` and `HAVE_FCNTL_H` behind a non-Windows check
since these headers do not exist with MSVC
- Freetype's gzip module includes zlib headers which conditionally
include `unistd.h` based on this define
## Context
Same pattern as the zlib fix (010-* branch from my fork). Freetype
passes `-DHAVE_UNISTD_H` unconditionally, which causes zlib's `zconf.h`
to try including `unistd.h` when freetype compiles its gzip support. The
fix follows the same approach used in `pkg/zlib/build.zig` (line 36-38).
## Stack
Stacked on 013-windows/fix-helpgen-framegen.
## Test plan
### Cross-platform results (`zig build test` / `zig build
-Dapp-runtime=none test` on Windows)
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** (f9d3b1aaf) | FAIL - 44/51 steps, 2 failed | PASS - 86/86,
2655/2678 tests, 23 skipped | PASS - 160/160, 2655/2662 tests, 7 skipped
|
| **AFTER** (d5aef6e84) | FAIL - 47/51 steps, 1 failed | PASS - 86/86,
2655/2678 tests, 23 skipped | PASS - 160/160, 2655/2662 tests, 7 skipped
|
### Windows: what changed (44 to 47 steps, 2 to 1 failure)
**Fixed by this PR:**
- `compile lib freetype` - was `2 errors` (unistd.h/fcntl.h not found)
-> `success`
- 3 additional steps that depended on freetype now succeed
**Remaining failure (pre-existing, tracked separately):**
- `translate-c` - 3 errors (`ssize_t` unknown in ghostty.h on MSVC)
### Linux/macOS: no regressions
Identical pass counts and test results before and after.
## Discussion
### Other build files with the same pattern
`pkg/fontconfig/build.zig` and `pkg/harfbuzz/build.zig` also pass
`-DHAVE_UNISTD_H` and/or `-DHAVE_FCNTL_H` unconditionally. They are not
in the Windows build path today, but will need the same fix when they
are.
## What I Learnt
More of the same
MSVC translates C enums as signed int, while GCC/Clang uses unsigned
int. The freetype Zig bindings hardcode c_uint for enum backing types,
causing type mismatches when compiling with MSVC target.
Fix by adding @intCast at call sites where enum values are passed to
C functions, and @bitCast for the glyph format tag extraction where
bit-shift operations require unsigned integers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Zig unconditionally passes -nostdinc++ and adds its bundled
libc++/libc++abi include paths, which conflict with MSVC's own C++
runtime headers. The MSVC SDK directories (added via linkLibC)
already contain both C and C++ headers, so linkLibCpp is not needed.
This is the same fix already applied upstream to highway, simdutf,
utfcpp, glslang, SharedDeps, and GhosttyZig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MSVC's <sys/types.h> does not define ssize_t (it is a POSIX type).
This causes the translate-c build step to fail when translating
ghostty.h on Windows. Use SSIZE_T from <BaseTsd.h> which is the
Windows SDK equivalent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
> [!WARNING]
> Review/approve this AFTER #11798, #11800 and #11801 (this PR stacks on
top of rhem... ergo, it includes their commits)
> Don't cheat! Start from the oldest one! 😄 I know these are almost
one-liners but I am doing this mostly for documentation and karma
points. BTW, Github needs to level up this wankflow like a lot... IMHO
## Summary
- Use `writerStreaming()` instead of `writer()` for stdout in helpgen
and main_build_data (`ftruncate` on pipes fails on Windows with
`INVALID_PARAMETER` mapped to `FileTooBig`)
- Replace POSIX `scandir` with `opendir`/`readdir` plus `qsort` in
framegen since `scandir` is not available on Windows
## Context
This fix was previously applied upstream by Mitchell (f4998c6ab) and
reverted 15 minutes later (0fdddd5bc). The reason for the revert is not
clear. Around the same time, a CI step was added to execute cmake
examples on Windows, which was later removed (b723f2a43) with the note
"hangs, so remove it entirely". Whether the revert is related to the
hang or had a separate reason, we don't know.
What we do know:
- Both `helpgen` and `framegen` run during normal builds on Windows (via
`SharedDeps`), not just during dist packaging. Claude had told me the
opposite before but "don't trust and verify".
- Without this fix, both tools fail: helpgen with `FileTooBig`
(ftruncate on pipes), framegen with `scandir` undeclared
- The fix does not regress Linux or macOS
## Stack
Stacked on 012-windows/fix-glslang-msvc.
## Test plan
### Cross-platform results (`zig build test` / `zig build
-Dapp-runtime=none test` on Windows)
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** (74c6ffe78) | FAIL - 39/51 steps, 4 failed | PASS - 86/86,
2655/2678 tests, 23 skipped | PASS - 160/160, 2655/2662 tests, 7 skipped
|
| **AFTER** (f9d3b1aaf) | FAIL - 44/51 steps, 2 failed | PASS - 86/86,
2655/2678 tests, 23 skipped | PASS - 160/160, 2655/2662 tests, 7 skipped
|
### Windows: what changed (39 > 44 steps, 4 > 2 failures)
**Fixed by this PR:**
- `run exe helpgen` -> was `failure` (FileTooBig from ftruncate on
stdout pipe) -> `success`
- `compile exe framegen` -> was `1 errors` (scandir undeclared) ->.
`success`
**Remaining failures (pre-existing, fixed by later PRs in stack):**
- `translate-c` -> 3 errors (`ssize_t` unknown in ghostty.h on MSVC)
- `compile lib freetype` -> 2 errors (`unistd.h` not found)
### Linux/macOS: no regressions
Identical pass counts and test results before and after.
## Discussion points
### "Grep wider" other `stdout().writer()` callsites
There are 15+ other `stdout().writer(&buf)` callsites in the codebase.
Build-time generators that capture stdout (webgen, mdgen, unicode
generators) would have the same `ftruncate` issue if they ran on
Windows. Currently they don't appear in the Windows build graph, but
worth noting for future Windows work.
### `writerStreaming()` vs `writer()`
`writer()` calls `ftruncate` on flush/end to set the file size, which
fails on pipes (stdout captured by the build system).
`writerStreaming()` skips the truncate since the output goes to a pipe,
not a seekable file. This is the correct API for this use case on all
platforms, not just Windows.
## What I Learnt
- When upstream has applied and reverted something, state what you
observe rather than speculating about their reasoning. Let the reviewer
fill in context you don't have.
- "Grep wider" (testing pattern): `stdout().writer()` appears in 17
files. Only 2 are fixed here because only 2 are in the current Windows
build path. But the pattern exists more broadly.
- I feel like I am training my replacements. I mean, I am a parent, it
rhymes.
- I feel like my replacements are training me. It rhymes as well.
> [!WARNING]
> Review/approve this AFTER #11798 and #11800 (this PR stacks on top of
rhem... ergo, it includes their commits)
> Don't cheat! Start from the oldest one! 😄 I know these are almost
one-liners but I am doing this mostly for documentation and karma
points.
## Summary
- Conditionally skip `linkLibCpp()` on MSVC since Zig's bundled libc++
headers conflict with MSVC's own C++ runtime
- Add `-std=c++17` flag for C++17 features (std::variant,
std::filesystem, inline variables) that glslang requires
## Context
The exact same `linkLibCpp` fix was applied to `simdutf` and `highway`
in commits 3d581eb92 and b4c529a82 but glslang was missed. Without this
fix, glslang fails with 297 compilation errors on MSVC.
Thanks Claude for the forensic digging. A carpenter should always be
thankful for his tools. Even if they are borrowed, maybe even more so.
## Stack
Stacked on 011-windows/fix-oniguruma-msvc.
## Discussion points
**`-std=c++17` scope:** Currently added unconditionally for all targets.
Tested on all three platforms with no regressions, but since this is
specifically fixing a Windows/MSVC issue, it could be gated behind
`target.result.abi == .msvc`. Donno. The reason it works unconditionally
is that Zig's bundled clang already defaults to C++17 on non-MSVC
targets, so the flag is a no-op there. Open to either approach.
**Other packages with bare `linkLibCpp()`:** The same `linkLibCpp` guard
has been applied to `simdutf`, `highway`, `utfcpp`, and now `glslang`.
However, `spirv-cross`, `dcimgui`, `harfbuzz`, and `breakpad` still have
unconditional `linkLibCpp()` calls. These may need the same treatment
when they become buildable on MSVC (some are currently blocked by other
issues like freetype's `unistd.h`). Worth tracking as a follow-up?
## Test plan
### test-lib-vt
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** | 3791/3839 passed, 48 skipped | 3791/3839 passed, 48
skipped | 3807/3839 passed, 32 skipped |
| **AFTER** | 3791/3839 passed, 48 skipped | 3791/3839 passed, 48
skipped | 3807/3839 passed, 32 skipped |
| **Delta** | no change | no change | no change |
### all tests (`zig build test` / `zig build -Dapp-runtime=none test` on
Windows)
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** | FAIL — 38/51 build steps, 5 failed | 2655/2678 passed, 23
skipped (86/86 steps) | 2655/2662 passed, 7 skipped (160/160 steps) |
| **AFTER** | FAIL — 39/51 build steps, 4 failed | 2655/2678 passed, 23
skipped (86/86 steps) | 2655/2662 passed, 7 skipped (160/160 steps) |
| **Delta** | +1 build step (glslang unblocked) | no change | no change
|
- Zero regressions on any platform
- Windows improved: glslang now compiles (38 -> 39 steps, 5 -> 4
failures)
- Remaining 4 Windows failures (`helpgen`, `framegen`, `freetype`,
`translate-c`) are addressed by other PRs in the stack
## What I Learnt
- **MSVC's clang doesn't default to C++17.** Zig's bundled clang uses
C++17 by default on Linux/Mac, but when targeting MSVC, the C++ standard
needs to be specified explicitly. Without `-std=c++17`, features like
`std::variant`, `std::filesystem`, and `inline` variables are gated
behind `_HAS_CXX17` and won't compile.
- **`linkLibCpp` conflicts with MSVC headers.** Zig's `linkLibCpp`
passes `-nostdinc++` and adds its own libc++/libc++abi headers, which
collide with the C++ headers already provided by the MSVC SDK through
`linkLibC`. On MSVC, you don't need `linkLibCpp` at all since the SDK
includes both C and C++ headers. I think yesterday we dealt with
something similar. Windows is fun. 🫠 Unironically and chronically.
- **Grep wider.** The `linkLibCpp` guard was already applied to simdutf,
highway, and utfcpp but missed glslang. When a fix follows a repeated
pattern across packages, search the whole codebase before declaring it
complete.
> [!WARNING]
> Review/approve this AFTER #11798 (this PR stacks on top of it... ergo,
it includes its commits)
## Summary
- Conditionally disable POSIX-only header defines (`alloca.h`,
`sys/times.h`, `sys/time.h`, `unistd.h`) on Windows since they do not
exist with MSVC
- Enable `USE_CRNL_AS_LINE_TERMINATOR` on Windows for correct line
endings
## Context
Oniguruma's `config.h` template had all POSIX header availability
defines hardcoded to `true`. On MSVC, these headers don't exist, causing
24 compilation errors (all `alloca.h` file not found).
Uses a comptime `is_windows` constant to flip the config values, same
pattern as PR #11798 (zlib).
## Stack
Stacked on 010-windows/fix-zlib-msvc.
## Test plan
### test-lib-vt
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** | 3791/3839 passed, 48 skipped | 3791/3839 passed, 48
skipped | 3807/3839 passed, 32 skipped |
| **AFTER** | 3791/3839 passed, 48 skipped | 3791/3839 passed, 48
skipped | 3807/3839 passed, 32 skipped |
| **Delta** | no change | no change | no change |
### all tests (`zig build test` / `zig build -Dapp-runtime=none test` on
Windows)
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** | FAIL — 37/51 steps, 6 failed | 2655/2678 passed, 23
skipped (86/86 steps) | 2655/2662 passed, 7 skipped (160/160 steps) |
| **AFTER** | FAIL — 38/51 steps, 5 failed | 2655/2678 passed, 23
skipped (86/86 steps) | 2655/2662 passed, 7 skipped (160/160 steps) |
| **Delta** | +1 step, -1 failure (oniguruma unblocked) | no change | no
change |
- Zero regressions on any platform
- Windows improved: oniguruma now compiles (37 -> 38 steps, 6 -> 5
failures)
- Remaining 5 Windows failures (`translate-c`/ssize_t, `helpgen`,
`framegen`, `glslang`, `harfbuzz` via freetype) are addressed by other
PRs in the stack
## What I Learnt
- comptime, man. It's the small things.
## Summary
- Gate `Z_HAVE_UNISTD_H` behind a non-Windows check since `unistd.h`
does not exist with MSVC
- Add `_CRT_SECURE_NO_DEPRECATE` and `_CRT_NONSTDC_NO_DEPRECATE` for
MSVC to suppress deprecation errors for standard C functions that zlib
uses
## Context
Part of the effort to get `zig build -Dapp-runtime=none test` passing on
Windows. This unblocks freetype, harfbuzz, libpng, and dcimgui which all
depend on zlib.
My research shows that we should default to msvc in ci with zig build
ran without `-Dratget`.
## Stack
This is branch 010 in the stacked branches series (soon on Netflix).
Independent fix, no dependencies on other branches.
## Test plan
### test-lib-vt
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** | 3791/3839 passed, 48 skipped | 3791/3839 passed, 48
skipped | 3807/3839 passed, 32 skipped |
| **AFTER** | 3791/3839 passed, 48 skipped | 3791/3839 passed, 48
skipped | 3807/3839 passed, 32 skipped |
| **Delta** | no change | no change | no change |
### all tests (`zig build test` / `zig build -Dapp-runtime=none test` on
Windows)
| | Windows | Linux | Mac |
|---|---|---|---|
| **BEFORE** | FAIL — 35/51 build steps, 6 failed | 2655/2678 passed, 23
skipped (86/86 steps) | 2655/2662 passed, 7 skipped (160/160 steps) |
| **AFTER** | FAIL — 37/51 build steps, 6 failed | 2655/2678 passed, 23
skipped (86/86 steps) | 2655/2662 passed, 7 skipped (160/160 steps) |
| **Delta** | +2 build steps (zlib + png unblocked) | no change | no
change |
- Zero regressions on any platform
- Windows improved: zlib and png now compile (35 -> 37 steps)
- Remaining 6 Windows build failures (`ssize_t`, `helpgen`, `framegen`,
`harfbuzz`, `dcimgui`) are addressed by other PRs in the stack
## What I Learnt
- Always run tests with `--summary all` to get actual pass/skip/fail
counts. Without it, zig just exits 0 or 1 and you have no numbers to
compare. "You get confident if you got the numbers."
- Build dependencies cascade: fixing zlib also unblocked png (which
depends on it), giving us +2 build steps from a one-file change.
Gate HAVE_UNISTD_H and HAVE_FCNTL_H behind a non-Windows check since
these headers do not exist with MSVC. Freetype includes zlib headers
which conditionally include unistd.h based on this define.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use writerStreaming() instead of writer() for stdout in helpgen and
main_build_data. The positional writer calls setEndPos/ftruncate in
end(), which fails on Windows because ftruncate on pipes maps
INVALID_PARAMETER to FileTooBig.
Replace scandir with opendir/readdir plus qsort in framegen since
scandir is a POSIX extension not available on Windows.
This was previously applied and reverted upstream (f4998c6ab, 0fdddd5bc)
as collateral from an unrelated example-execution hang that has since
been resolved.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Apply the same MSVC fixes used for simdutf and highway: conditionally
skip linkLibCpp on MSVC since Zig's bundled libc++ headers conflict
with MSVC's own C++ runtime, and add -std=c++17 for C++17 features
like std::variant and inline variables that glslang requires.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Conditionally disable POSIX-only header defines (alloca.h, sys/times.h,
sys/time.h, unistd.h) on Windows since they do not exist with MSVC.
Enable USE_CRNL_AS_LINE_TERMINATOR on Windows for correct line endings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gate Z_HAVE_UNISTD_H behind a non-Windows check since unistd.h does
not exist on Windows. Add _CRT_SECURE_NO_DEPRECATE and
_CRT_NONSTDC_NO_DEPRECATE for MSVC to suppress deprecation errors
for standard C functions that zlib uses.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Windows tests and builds are now passing reliably. Remove the
continue-on-error safety net so failures are visible immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# What
PR #11756 added IMPORTED_IMPLIB pointing to the .lib import library, but
the
import library is not listed in the OUTPUT directive of the
`add_custom_command`
that runs zig build. The file is produced as a side-effect of the build.
This works with the Visual Studio generator (which is lenient about
undeclared outputs) but fails with Ninja:
ninja: error: 'zig-out/lib/ghostty-vt.lib', needed by 'ghostling',
missing and no known rule to make it
The fix adds "${ZIG_OUT_DIR}/lib/${GHOSTTY_VT_IMPLIB}" to the OUTPUT
list. No
behavioral change. The file was already being built, Ninja just needs to
know
about it.
## but_why.gif
I am cleaning up https://github.com/ghostty-org/ghostling/pull/6 and I
realise that in order to get rid of the CMake workarounds we had before
#11756, this change is necessary.
# POC
I set up a branch pointing at my fork with a POC and it builds, this is
the cleaned up CMakeList
https://github.com/deblasis/winghostling/blob/test/cmake-implib-no-workaround/CMakeLists.txt
Ninja requires all produced files to be listed as explicit outputs of
custom commands. The zig build produces a .lib import library alongside
the DLL, but it was not listed in the OUTPUT directive. This causes
Ninja to fail with "missing and no known rule to make it" when
IMPORTED_IMPLIB references the .lib file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## What
On Windows, calling `free()` on memory allocated by libghostty crashes
because Zig and MSVC use separate heaps.
This adds `ghostty_free()` so consumers can free library-allocated
memory safely on all platforms.
## Why
When Zig builds a DLL on Windows with `link_libc = true`, it does not
link the Windows C runtime (`ucrtbase.dll`). Instead it uses its own
libc built on top of `KERNEL32.dll`. So `builtin.link_libc` is true and
`c_allocator` is selected, but Zig's `malloc` and MSVC's `malloc` are
different implementations with different heaps. 💥
On Linux/macOS this is not a problem because Zig links the system libc
and everyone shares the same heap. On Windows, `free(buf)` from MSVC
tries to free memory from Zig's heap and you get a debug assertion
failure or undefined behavior.
The `format_alloc` docs said "the buffer can be freed with `free()`" but
that is only true when the library and consumer share the same C
runtime, which is not the case on Windows.
## How
- Add `ghostty_free(allocator, ptr, len)` that frees through the same
allocator that did the allocation
- Update `format_alloc` docs to point to `ghostty_free()` instead of
`free()`
- Update all 3 examples to use `ghostty_free(NULL, buf, len)`
The signature takes an allocator because raw buffers (unlike objects
like terminals or formatters) do not store their allocator internally.
The caller already has all three values: the allocator they passed, the
pointer, and the length they got back.
I went back and forth on the naming. Other options I considered:
`ghostty_alloc_free(allocator, ptr, len)` or returning a `GhosttyBuffer`
wrapper with its own `_free`. Happy to change the naming if there is a
preference.
No impact on Linux/macOS. `ghostty_free()` works correctly there too, it
just happens to call the same `free()` the consumer would have called
anyway.
## Verified
- `zig build test-lib-vt` passes on Windows, macOS arm64, Linux x86_64
(exit 0)
- `zig build test` passes on Windows (2575/2619 passed, 1 pre-existing
font sprite failure) and macOS (exit 0)
- cmake shared example builds, links, and runs correctly on Windows with
`ghostty_free()` (no more heap crash)
## What I Learnt
- What I wrote in Why
- Zig allocators require the length to free (no hidden metadata headers
like C's malloc). This is a deliberate design choice for explicit
control.
- The standard pattern for C libraries on Windows is "whoever allocates,
frees" (like `curl_free()`, `SDL_free()`). This avoids cross-runtime
heap issues entirely.
Add a ghostty_alloc function that pairs with the existing
ghostty_free, giving embedders a symmetric malloc/free-style
API for buffer allocation through the libghostty allocator
interface. Returns NULL on allocation failure.
Extract the inline free_alloc function from main.zig into a new
allocator.zig module in the C API layer. The function is renamed
to alloc_free in main.zig (and free in allocator.zig) for
consistency with the other C API naming conventions. Add tests
for null pointer, allocated memory, and null allocator fallback.
Renames `ReadonlyStream` to `TerminalStream` and introduces an
effects-based callback system so that the stream handler can optionally
respond to queries and side effects (bell, title changes, device
attributes, device status, size reports, XTVERSION, ENQ, DECRQM, kitty
keyboard queries).
The default behavior is still read-only, callers have to opt-in to
setting callbacks to function pointers.
This doesn't handle every possible side effect yet, e.g. this doesn't
include clipboards, pwd reporting, and others. But this covers the
important ones.
This PR is Zig only, the C version of this will come later.
The default firmware_version for Secondary device attributes is 0,
but the test expected a value of 10. Update the test expectation to
match the actual default.
Add a device_attributes effect callback to the stream_terminal
Handler. The callback returns a device_attributes.Attributes
struct which the handler encodes and writes back to the pty.
Add Attributes.encode which dispatches to the correct sub-type
encoder based on the request type (primary, secondary, tertiary).
In readonly mode the callback is null so all DA queries are
silently ignored, matching the previous behavior where
device_attributes was in the ignored actions list.
Tests cover all three DA types with default attributes, custom
attributes, and readonly mode.
Introduce a dedicated device_attributes.zig module that consolidates
all device attribute types and encoding logic. This moves
DeviceAttributeReq out of ansi.zig and adds structured response
types for DA1 (primary), DA2 (secondary), and DA3 (tertiary) with
self-encoding methods.
Primary DA uses a ConformanceLevel enum covering VT100-series
per-model values and VT200+ conformance levels, plus a Feature
enum with all known xterm DA1 attribute codes (132-col, printer,
sixel, color, clipboard, etc.) as a simple slice. Secondary DA
uses a DeviceType enum matching the xterm decTerminalID values.
Tertiary DA encodes the DECRPTUI unit ID as a u32 formatted to
8 hex digits.
This is preparatory work for exposing device attributes through
the stream_terminal Effects callback system.
Previously device_status was in the ignored "no terminal-modifying
effect" group in stream_terminal.zig. This ports it to use the
Effects pattern, handling all three DSR request types.
Operating status and cursor position are handled entirely within
stream_terminal since they only need terminal state and write_pty.
Cursor position respects origin mode and scrolling region offsets.
Color scheme adds a new color_scheme effect callback that returns
a ColorScheme enum (light/dark). The handler encodes the response
internally, keeping protocol knowledge in the terminal layer. A
new ColorScheme type is added to device_status.zig so the terminal
layer does not depend on apprt.
Previously the ENQ (0x05) action was ignored in stream_terminal,
listed in the no-op group alongside other unhandled queries. The
real implementation in termio/stream_handler writes a configurable
response string back to the pty.
Add an enquiry callback to Effects following the same query-style
pattern as xtversion: the callback returns the raw response bytes
and the handler owns writing them to the pty via writePty. When no
callback is set (readonly mode), ENQ is silently ignored. Empty
responses are also ignored. The response is capped at 256 bytes
using a stack buffer with sentinel conversion for writePty.
## What
Two fixes for tests that fail on Windows due to Unix-specific
assumptions.
1. The "cache directory paths" test in xdg.zig hardcodes Unix paths like
`/Users/test/.cache` in expected values. The function under test uses
`std.fs.path.join` which produces native separators, so the expectations
need to match. Fixed by using `std.fs.path.join` for expected values
too, with a platform-appropriate mock home path.
2. Two shell integration tests for `setupXdgDataDirs` hardcode Unix path
separators (`:`) and Unix default paths (`/usr/local/share:/usr/share`).
These are not applicable on Windows where the delimiter is `;` and
`XDG_DATA_DIRS` is not a standard concept. Skipped on Windows with
`SkipZigTest`.
## Why skip instead of fix for the shell integration tests?
`setupXdgDataDirs` is used by fish, elvish, and nushell. On Windows,
`XDG_DATA_DIRS` is not standard. The equivalent would be `%ProgramData%`
(what Go's `adrg/xdg`, Python's `platformdirs`, and others map to).
Fixing this properly means adding a Windows-appropriate default, which
is a separate change. (How do you guys deal with these situations? Do
you create issues on the spot as reminders or do you wait for the
requirement to emerge by itself when the time comes?
Worth noting: the production code on line 664 of `shell_integration.zig`
hardcodes the fallback to `"/usr/local/share:/usr/share"` with `:`
separators, while `prependEnv` correctly uses `std.fs.path.delimiter`
(`;` on Windows). If a shell that uses this runs on Windows, you would
get mixed separators. Tracked separately.
## Verified
- `zig build test-lib-vt` passes on Windows (exit 0)
- No behavior change on Linux/macOS (xdg.zig fix produces same paths,
shell_integration skip only triggers on Windows)
## What I Learnt
- `std.fs.path.join` uses the native path separator, so tests that
hardcode `/` in expected paths will fail on Windows even if the
production code is correct. Better to use `path.join` in test
expectations too.
- The XDG Base Directory spec is Unix-only but cross-platform libraries
have converged on mappings. Ghostty maps to `%LOCALAPPDATA%` which
matches common conventions. The missing piece is `XDG_DATA_DIRS` which
has no Windows default and falls through to Unix paths.
Add a `size` callback to the stream_terminal Effects struct that
returns a size_report.Size geometry snapshot for XTWINOPS size
queries (CSI 14/16/18 t). The handler owns all protocol encoding
using the existing size_report.encode, keeping VT knowledge out
of effect consumers. This follows the same pattern as the xtversion
effect: the callback supplies data, the handler formats the reply
and calls write_pty.
CSI 21 t (title report) is handled internally from terminal state
since the title is already available via terminal.getTitle() and
does not require an external callback.
Add an xtversion callback to the Effects struct so that
stream_terminal can respond to XTVERSION queries. The callback
returns the version string to embed in the DCS response. If the
callback is unset or returns an empty string, the response defaults
to "libghostty". The response is formatted and written back via the
existing write_pty effect.
Previously kitty_keyboard_query was listed as a no-op in the
readonly stream handler. This implements it using the write_pty
effect callback so that the current kitty keyboard flags are
reported back via the pty, matching the behavior in the full
stream handler.
The effect callback no longer receives the title string directly.
Instead, the handler stores the title in terminal state via setTitle
before invoking the callback, so consumers query it through
handler.terminal.getTitle(). This removes the redundant parameter
and keeps the effect signature consistent with the new terminal
title field. Tests now verify terminal state directly rather than
tracking the title through the callback.