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.
Add a title field to Terminal, mirroring the existing pwd field.
The title is set via setTitle/getTitle and tracks the most recent
value written by OSC 0/2 sequences. The stream handler now persists
the title in terminal state in addition to forwarding it to the
surface. The field is cleared on full reset.
Previously the window_title action was silently ignored in the
readonly stream handler. Add a set_window_title callback to the
Effects struct so callers can be notified when a window title is
set via OSC 2. Follows the same pattern as bell and write_pty
where the callback is optional and defaults to null in readonly
mode.
Add a generic write_pty effect callback to the stream terminal
handler, allowing callers to receive pty response data. Use it to
implement request_mode and request_mode_unknown (DECRQM), which
encode the mode state as a DECRPM response and write it back
through the callback. Previously these were silently ignored.
The write_pty data is stack-allocated and only valid for the
duration of the call.
Rename stream_readonly.zig to stream_terminal.zig and its exported
types from ReadonlyStream/ReadonlyHandler to TerminalStream. The
"readonly" name is now wrong since the handler now supports
settable effects callbacks. The new name better reflects that this
is a stream handler for updating terminal state.
Add an Effects struct to the readonly stream Handler that allows
callers to provide optional callbacks for side effects like bell.
Previously, the bell action was silently ignored along with other
query/response actions. Now it is handled separately and dispatched
through the effects callback if one is provided.
Add a test that verifies bell with a null callback (default readonly
behavior) does not crash, and that a provided callback is invoked
the correct number of times.
## What
Skip the `expandHomeUnix` test on Windows with `SkipZigTest`.
`expandHomeUnix` is a Unix-internal function that is never called on
Windows. The public `expandHome` already returns the path unchanged on
Windows (added upstream in cccdb0d2a). But the unit test calls
`expandHomeUnix` directly, which invokes `home()` and expects Unix-style
forward-slash separators, so it fails on Windows.
## How
Two lines:
```zig
if (builtin.os.tag == .windows) return error.SkipZigTest;
```
## Verified
- `zig build test-lib-vt` passes on Windows (exit 0)
- No behavior change on Linux/macOS
## What I Learnt
- When upstream adds a platform dispatch for production code (like
`expandHome` returning unchanged on Windows), the unit tests for
internal platform-specific functions (like `expandHomeUnix`) may still
need a skip guard.
- Zig doesn't have something like Go's `//go:build` but damn... comptime
is insane, like supercharged C# `#if`
On Windows, shared libraries (DLLs) require an import library (.lib) for
linking, and the DLL itself is placed in bin/ rather than lib/ by the
Zig build. The CMake wrapper was missing IMPORTED_IMPLIB on the shared
imported target, causing link failures, and assumed the shared library
was always in lib/.
Add GHOSTTY_VT_IMPLIB for the import library name, set IMPORTED_IMPLIB
on the ghostty-vt target, and fix the shared library path to use bin/ on
Windows. Install the DLL and PDB to bin/ and the import library to lib/
following standard Windows conventions. Apply the same fixes to
ghostty-vt-config.cmake.in for the find_package path.
The "Run Example" step in the build-examples-cmake-windows job
hangs, so remove it entirely. The build step is still run so
compilation is verified, but the examples are no longer executed
on Windows.
The SIMD C++ files reference __ubsan_handle_* symbols when compiled
in debug mode, but we do not link or bundle the ubsan runtime on
MSVC. This matches what the highway and simdutf packages already do
in their own build files.
expandHomeUnix is a Unix-internal function that is never called on
Windows. The public expandHome function returns the path unchanged
on Windows since ~/ is not a standard Windows idiom. The test calls
expandHomeUnix directly, which invokes home() and expects Unix-style
forward-slash separators.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SIMD C++ files use C++17 features (std::optional, std::size).
With Zig's bundled libc++ these are available implicitly, but MSVC
headers guard C++17 features behind the standard version
(_HAS_CXX17). Without an explicit -std=c++17 flag, clang defaults
to a lower standard and the MSVC <optional> header does not define
std::optional.
When compiling C++ files, Zig unconditionally passes -nostdinc++ and,
if link_libcpp is set, adds its bundled libc++/libc++abi include paths
as replacements (see Compilation.zig). On MSVC targets this conflicts
with the MSVC C++ runtime headers (vcruntime_typeinfo.h,
vcruntime_exception.h, etc.), causing compilation failures in SIMD
C++ code.
The fix is to use linkLibC instead of linkLibCpp on MSVC. Zig always
passes -nostdinc to strip default search paths, but LibCDirs.detect
re-adds the MSVC SDK include directories, which contain both C and
C++ standard library headers. This gives us proper access to MSVC's
own <optional>, <iterator>, <cstddef>, etc. without the libc++
conflicts.
For the package builds (highway, simdutf, utfcpp) this means
switching from linkLibCpp to linkLibC on MSVC. For SharedDeps and
GhosttyZig, linkLibC is already called separately, so we just skip
linkLibCpp.
Zig's bundled libc++/libc++abi conflicts with the MSVC C++ runtime
headers (vcruntime_typeinfo.h, vcruntime_exception.h, etc.) when
targeting native-native-msvc. This caused compilation failures in
the SIMD C++ code due to -nostdinc++ suppressing MSVC headers and
libc++ types clashing with MSVC runtime types.
Skip linkLibCpp() for MSVC targets across all packages (highway,
simdutf, utfcpp) and the main build (SharedDeps, GhosttyZig) since
MSVC provides its own C++ standard library natively. Also add
missing <iterator> and <cstddef> includes that were previously
pulled in transitively through libc++ headers but are not
guaranteed by MSVC's headers.
Zig defaults to the GNU ABI on Windows, which produces COFF objects
with invalid COMDAT sections in compiler_rt that the MSVC linker
rejects (LNK1143), and uses GNU conventions like ___chkstk_ms that
are unavailable in the MSVC CRT.
Default to the MSVC ABI when no explicit ABI is requested, following
the same pattern as the existing macOS target override. This ensures
compiler_rt produces valid COFF and the generated code uses
MSVC-compatible symbols. Users can still explicitly request the GNU
ABI via -Dtarget.
Also disable bundling ubsan_rt on Windows (its /exclude-symbols
directives are MSVC-incompatible) and add ntdll and kernel32 as
transitive link dependencies for the static library.
Three issues when linking the static library with the MSVC linker:
Use the LLVM backend on Windows to produce valid COFF objects.
The self-hosted backend generates compiler_rt objects with invalid
COMDAT sections that the MSVC linker rejects (LNK1143).
Disable bundling ubsan_rt on Windows. Zig's ubsan runtime emits
/exclude-symbols linker directives that MSVC does not understand
(LNK4229).
Add ntdll and kernel32 as transitive link dependencies for the
static library on Windows. The Zig standard library uses NT API
functions (NtClose, NtCreateSection, etc.) that consumers must
link.
Zig's compiler_rt produces COFF objects with invalid COMDAT
sections that the MSVC linker rejects (LNK1143), and its ubsan_rt
emits /exclude-symbols directives that MSVC does not understand
(LNK4229). Skip bundling both in the static library on Windows
since the MSVC CRT provides the needed builtins (memcpy, memset,
etc.). The shared library continues to bundle compiler_rt as it
needs to be self-contained.
Zig's ubsan runtime emits /exclude-symbols linker directives that
are incompatible with the MSVC linker, causing LNK4229 warnings and
LNK1143 errors. Disable bundling ubsan_rt on Windows while keeping
compiler_rt which provides essential symbols like memcpy, memset,
memmove, and ___chkstk_ms.
The previous check used target.result.abi == .msvc which never
matched because Zig defaults to the gnu ABI on Windows.
Zig's ubsan instrumentation emits ELF-style /exclude-symbols linker
directives into the compiled object files, causing LNK4229 warnings
with the MSVC linker. The bundled compiler_rt also produces COMDAT
sections that are incompatible with MSVC, causing fatal LNK1143.
Disable sanitize_c entirely on the root module for MSVC targets and
skip bundling both compiler_rt and ubsan_rt since MSVC provides its
own runtime.
Zig's bundled compiler_rt and ubsan_rt produce object files with
ELF-style linker directives (/exclude-symbols) and COMDAT sections
that are incompatible with the MSVC linker, causing LNK1143 and
LNK4229 errors when linking the static library.
MSVC provides its own compiler runtime so bundling Zig's versions
is unnecessary. Skip bundling both runtimes when the target ABI is
MSVC.
The cmake examples were failing at runtime on Windows CI for two
reasons.
The static library was installed as "libghostty-vt.a" on all
platforms, but on Windows the DLL import library is also placed in
zig-out/lib/ as "ghostty-vt.lib". The CMakeLists.txt expected the
platform-native name "ghostty-vt.lib" for the static lib, so it
picked up the tiny DLL import lib instead, silently producing a
dynamically-linked executable. That executable then failed at
runtime because the DLL was not on PATH.
Fix this by installing the static library as "ghostty-vt-static.lib"
on Windows to avoid the name collision, and updating CMakeLists.txt
to match. For the shared (DLL) example, add zig-out/bin to PATH in
the CI run step so the DLL can be found at runtime.
Add a "Run Example" step to the build-examples-cmake-windows job
so that each CMake example is executed after it is built, verifying
the resulting binaries actually work. The executable name is derived
from the matrix directory name by replacing hyphens with underscores,
matching the project convention.
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 when stdout is redirected via
captureStdOut() because ftruncate maps INVALID_PARAMETER to
FileTooBig. Streaming mode skips truncation entirely since stdout
is inherently a sequential stream.
Replace scandir with opendir/readdir plus qsort in framegen since
scandir is a POSIX extension not available on Windows.
On Windows, shared libraries (DLLs) require an import library (.lib)
for linking, and the DLL itself is placed in bin/ rather than lib/ by
the Zig build. The CMake wrapper was missing IMPORTED_IMPLIB on the
shared imported target, causing link failures, and assumed the shared
library was always in lib/.
Add GHOSTTY_VT_IMPLIB for the import library name, set IMPORTED_IMPLIB
on the ghostty-vt target, and fix the shared library path to use bin/
on Windows. Install the DLL and PDB to bin/ and the import library to
lib/ following standard Windows conventions. Apply the same fixes to
ghostty-vt-config.cmake.in for the find_package path.
Our Windows build has been broken for a _long_ time. It hasn't actually
worked and our CI was falsely passing when it was actually failing to
build/test. This PR fixes that and fixes the issues it found so
`libghostty-vt` can build and pass tests.
**This is only for libghostty!** I'd still like to expand our _test_
coverage to all of Ghostty for Windows but libghostty is more important
for that platform in the short term and it's an incremental piece of
work.
A couple windows compatibility issues fixed:
- `terminal.Page` uses `VirtualAlloc` on Windows (thanks @deblasis)
- Our rgb.txt loading was not resilient to CRLF endings
The X11 color map parser in x11_color.zig uses @embedFile to load
rgb.txt at comptime, then splits on \n. On Windows, git may check
out rgb.txt with CRLF line endings, leaving a trailing \r on each
line. This caused color names to be stored as e.g. "white\r" instead
of "white", so all X11 color lookups failed at runtime.
Strip trailing \r from each line before parsing. Also mark rgb.txt
as -text in .gitattributes to prevent line ending conversion in
future checkouts.
Extract the platform-specific page backing memory allocation into
AllocPosix and AllocWindows structs behind a PageAlloc comptime
switch. Previously, POSIX mmap calls were inlined at each call
site. This adds a Windows VirtualAlloc implementation and routes
all allocation through PageAlloc.alloc/free, making the backing
memory strategy consistent and easier to extend.
Rename build-windows to build-libghostty-vt-windows to reflect that
it only builds and tests libghostty-vt for now, and move it next to
the other build-libghostty-vt jobs.
Replace the manual PowerShell zig download/install with mlugg/setup-zig,
which auto-detects the version from build.zig.zon and handles caching.
Upgrade the runner from windows-2022 to windows-2025. Remove the
generated-script-to-swallow-errors pattern in favor of direct zig
build commands.
- Our `checkGhosttyH` calls need to be guarded on building Ghostty app
which has it
- Move FileFormatter to its own file to avoid poisoning test refs with
Config.zig which pulls in the world
- Move WindowPaddingBalance to renderer to avoid pulling in Config.zig
- Add a `zig build test-lib-vt` CI job
The colors_get function used structSizedFieldFits to guard the
palette copy, which required the entire palette array to fit in the
provided size. This prevented partial palette writes when the caller
passed a truncated sized struct, since the guard failed even though
the inner code already handled partial copies correctly. Remove the
outer guard so the existing partial-copy logic applies.
The setopt_from_terminal test expected alt_esc_prefix to be false on
a fresh terminal, but the mode definition in modes.zig sets its
default to true. Update the test expectation to match.
Add a new CI job that runs `zig build test-lib-vt` to test the
lib-vt build step. The job mirrors the existing test job structure
with the same nix/cachix setup and skip conditions. It is also
added to the required checks list.
The inline else switch in each C API getter expands the .invalid
case, which has OutType void. When called with .invalid and a null
out pointer, the @ptrCast(@alignCast(out)) panics before getTyped
can return early.
Handle .invalid explicitly in the outer switch of every getter to
short-circuit before the pointer cast. This affects build_info,
cell, row, terminal, osc, and render (three getters).
Previously WindowPaddingBalance was defined inside Config.zig, which
meant tests for renderer sizing had to pull in the full config
dependency. Move the enum into renderer/size.zig as PaddingBalance
and re-export it from Config so the public API is unchanged. This
lets size.zig tests run without depending on Config.
This is a continuation of the solid work done by @jcollie in PR #7864. I
checked with him if I could take over to continue the implementation.
His changes of last year have been adapted to be compatible with the
current GTK implementation. Aside from just "making it work", I also
dived into the portals and OpenURI implementation and made some
improvements there.
Notable improvements were:
- Improved lifecycle management of glib resources in the OpenURI
implementation
- More forgiving error handling in OpenURI implementation by adding more
fallbacks
- Fixed some memory leaks
- Less memory allocations in Portals implementation
- Added tests for building the Portals request path
Fixes#5991