Add three sized structs that let callers fetch all image, placement, or
rendering metadata in a single call instead of many individual queries.
This is an optimization for environments with high per-call overhead
such as FFI or Cgo.
GhosttyKittyGraphicsImageInfo is returned via image_get() with the new
GHOSTTY_KITTY_IMAGE_DATA_INFO data kind. It bundles id, number, width,
height, format, compression, data pointer, and data length.
GhosttyKittyGraphicsPlacementInfo is returned via placement_get() with
the new GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO data kind. It bundles
image id, placement id, virtual flag, offsets, source rect, columns,
rows, and z-index.
GhosttyKittyGraphicsPlacementRenderInfo is returned by the new
ghostty_kitty_graphics_placement_render_info() function, which combines
pixel size, grid size, viewport position, and resolved source rectangle.
This one requires image and terminal handles so it does not fit the
existing _get() pattern and is a dedicated function.
All three use the sized-struct ABI pattern with GHOSTTY_INIT_SIZED for
forward compatibility.
Add three sized structs that let callers fetch all image, placement,
or rendering metadata in a single call instead of many individual
queries. This is an optimization for environments with high per-call
overhead such as FFI or Cgo.
GhosttyKittyGraphicsImageInfo is returned via image_get() with the
new GHOSTTY_KITTY_IMAGE_DATA_INFO data kind. It bundles id, number,
width, height, format, compression, data pointer, and data length.
GhosttyKittyGraphicsPlacementInfo is returned via placement_get()
with the new GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_INFO data kind.
It bundles image id, placement id, virtual flag, offsets, source
rect, columns, rows, and z-index.
GhosttyKittyGraphicsPlacementRenderInfo is returned by the new
ghostty_kitty_graphics_placement_render_info() function, which
combines pixel size, grid size, viewport position, and resolved
source rectangle. This one requires image and terminal handles so
it does not fit the existing _get() pattern and is a dedicated
function.
All three use the sized-struct ABI pattern with GHOSTTY_INIT_SIZED
for forward compatibility.
Fixes#12151
When `emit_lib_vt` is true, the `// Tests` block was still
evaluated, pulling in the full ghostty-test dependency graph
(freetype, zlib, dcimgui, etc.). This causes the Nix
`libghostty-vt` package to fail when `doCheck` is enabled,
since those system libraries aren't available in the sandbox.
Guard the block with `if (!config.emit_lib_vt)`, following
the existing pattern at line 179.
In C ABI builds, the Zig std.log default writes to stderr which is not
appropriate for a library. Override std_options.logFn with a custom sink
that dispatches to an embedder-provided callback, or silently discards
when none is registered.
Add GHOSTTY_SYS_OPT_LOG to ghostty_sys_set() following the existing
decode_png pattern. The callback receives the log level as a
GhosttySysLogLevel enum, scope and message as separate byte slices,
giving embedders full control over formatting and routing.
Export ghostty_sys_log_stderr as a built-in convenience callback that
writes to stderr using std.debug.lockStderrWriter for thread-safe
output. Embedders who want the old behavior can install it at startup
with a single ghostty_sys_set call.
In C ABI builds, the Zig std.log default writes to stderr which is
not appropriate for a library. Override std_options.logFn with a
custom sink that dispatches to an embedder-provided callback, or
silently discards when none is registered.
Add GHOSTTY_SYS_OPT_LOG to ghostty_sys_set() following the existing
decode_png pattern. The callback receives the log level as a
GhosttySysLogLevel enum, scope and message as separate byte slices,
giving embedders full control over formatting and routing.
Export ghostty_sys_log_stderr as a built-in convenience callback that
writes to stderr using std.debug.lockStderrWriter for thread-safe
output. Embedders who want the old behavior can install it at startup
with a single ghostty_sys_set call.
Add a ghostty_vt_add_target() CMake function that lets downstream
projects build libghostty-vt for a specific Zig target triple. The
function encapsulates zig discovery, build-type-to-optimize mapping, the
zig build invocation, and output path conventions so consumers do not
need to duplicate any of that logic. It creates named IMPORTED targets
(e.g. ghostty-vt-static-linux-amd64) that work alongside the existing
native ghostty-vt and ghostty-vt-static targets.
The build-type mapping is factored into a shared _GHOSTTY_ZIG_OPT_FLAG
variable used by both the native build and the new function.
A new example/c-vt-cmake-cross/ demonstrates end-to-end cross-
compilation using zig cc as the C compiler, auto-detecting a cross
target based on the host OS.
Add a ghostty_vt_add_target() CMake function that lets downstream
projects build libghostty-vt for a specific Zig target triple. The
function encapsulates zig discovery, build-type-to-optimize mapping,
the zig build invocation, and output path conventions so consumers
do not need to duplicate any of that logic. It creates named IMPORTED
targets (e.g. ghostty-vt-static-linux-amd64) that work alongside the
existing native ghostty-vt and ghostty-vt-static targets.
The build-type mapping is factored into a shared _GHOSTTY_ZIG_OPT_FLAG
variable used by both the native build and the new function.
The static library targets now propagate c++ as a link dependency on
non-Windows platforms, fixing link failures when consumers use static
linking with the default SIMD-enabled build.
A new example/c-vt-cmake-cross/ demonstrates end-to-end cross-
compilation using zig cc as the C compiler, auto-detecting a cross
target based on the host OS.
Keep libghostty-vt.pc as the shared/default pkg-config module so
`pkg-config --static libghostty-vt` continues to emit the historical
`-lghostty-vt` flags. This preserves the old behavior for consumers that
still want it, even though that form remains ambiguous on macOS when
both the dylib and archive are installed in the same directory.
Add a separate libghostty-vt-static.pc module for consumers that need an
unambiguous static link. Its `Libs:` entry points directly at the
installed archive so macOS does not resolve the request to the dylib.
Update the Nix packaging to rewrite the new static module into the `dev`
output, use it in the static-link smoke test, and add a compatibility
check that covers both pkg-config entry points.
Keep libghostty-vt.pc as the shared/default pkg-config module so
`pkg-config --static libghostty-vt` continues to emit the historical
`-lghostty-vt` flags. This preserves the old behavior for consumers
that still want it, even though that form remains ambiguous on macOS
when both the dylib and archive are installed in the same directory.
Add a separate libghostty-vt-static.pc module for consumers that need
an unambiguous static link. Its `Libs:` entry points directly at the
installed archive so macOS does not resolve the request to the dylib.
Update the Nix packaging to rewrite the new static module into the `dev`
output, use it in the static-link smoke test, and add a compatibility
check that covers both pkg-config entry points.
Fixes#11990
Previously only slashes were replaced with hyphens in the branch name
used as the semver pre-release identifier. Branch names containing dots
(e.g. dependabot branches like
"cachix/install-nix-action-31.10.4") would cause an InvalidVersion error
because std.SemanticVersion only allows alphanumeric characters and
hyphens in pre-release identifiers.
Replace all non-alphanumeric, non-hyphen characters instead of only
slashes.
Fixes#11990
Previously only slashes were replaced with hyphens in the branch
name used as the semver pre-release identifier. Branch names
containing dots (e.g. dependabot branches like
"cachix/install-nix-action-31.10.4") would cause an InvalidVersion
error because std.SemanticVersion only allows alphanumeric
characters and hyphens in pre-release identifiers.
Replace all non-alphanumeric, non-hyphen characters instead of
only slashes.
This is a regression from #9983. When resetting to default, we shouldn't
use the representation of the icon, which will prevent the icon from
updating after system settings change.
1. Delete `macos-icon` config if it exists and reload.
2. Go to **System Settings -> Appearance** and change **Icon & widget
style** to any one other than Default, and observe the app icon.
<img width="228" height="179" alt="image"
src="https://github.com/user-attachments/assets/e53274f8-b679-4d6f-8e0b-edfd7d17811d"
/>
> A temporary workaround to this issue is to reload the config.
This pr resets the `NSDockTile.contentView`, which will let AppKit
revert back to `Ghostty.icon`.
https://github.com/user-attachments/assets/06ab0519-225b-45e1-85a5-a22832a36177
Pre-C23, the C standard allows compilers to choose any integer type
that can represent all enum values, so small enums could be backed
by char or short. This breaks ABI compatibility with the Zig side,
which backs these enums with c_int.
Define GHOSTTY_ENUM_MAX_VALUE as INT_MAX in types.h and add it as
the last entry in every enum in include/ghostty/vt/. This forces
the compiler to use int as the backing type, matching c_int on all
targets. INT_MAX is used rather than a fixed constant because enum
constants must be representable as int; values above INT_MAX are a
constraint violation in standard C.
Document this convention in AGENTS.md.
Pre-C23, the C standard allows compilers to choose any integer type
that can represent all enum values, so small enums could be backed
by char or short. This breaks ABI compatibility with the Zig side,
which backs these enums with c_int.
Define GHOSTTY_ENUM_MAX_VALUE as INT_MAX in types.h and add it as
the last entry in every enum in include/ghostty/vt/. This forces
the compiler to use int as the backing type, matching c_int on all
targets. INT_MAX is used rather than a fixed constant because enum
constants must be representable as int; values above INT_MAX are a
constraint violation in standard C.
Document this convention in AGENTS.md.
The 👻 Ghost Tab Issue
https://github.com/user-attachments/assets/cb91cd85-4a08-4c16-9efb-1a9ab30fc2bc
Previous failure scenario (User perspective):
1. Open a new tab
2. Instantly trigger close other tabs (eg. through custom user keyboard
shortcut)
3. Now you will see an empty Ghost Tab (Only a window bar with empty
content)
The previous failure mode is:
1. Create a tab or window now in `newTab(...)` / `newWindow(...)`.
2. Queue its initial show/focus work with `DispatchQueue.main.async`.
3. Close that tab or window with `closeTabImmediately()` /
`closeWindowImmediately()` before the queued callback runs.
4. The queued callback still runs anyway and calls `showWindow(...)` /
`makeKeyAndOrderFront(...)` on stale state.
5. The tab can be resurrected as a half-closed blank ghost tab.
The fix:
- Store deferred presentation work in a cancellable DispatchWorkItem and
cancel it from the close paths before AppKit finishes tearing down the
tab or window.
- This prevents the stale show/focus callback from running after close.
## AI Usage
I used GPT 5.4 to find the initial issue and fix it. I cleaned up and
narrowed down the commit afterwards.
-----
Additional Notes:
I use `cmd+o` to `close_tab:other`
https://github.com/jamylak/dotfiles/blob/main/ghostty/config#L106C1-L106C34
Try it for your self if you want to reproduce, just do a quick `cmd+t`
`cmd+o` and you will see
This produces a `ghostty-vt.xcframework` for `zig build -Demit-lib-vt`
when the host is macOS and the target is Apple platforms. Our CI has
been updated to release this via tip channels (GH releases and our blob
storage), too.
The xcframework contains binaries for macOS Universal (x86_64 +
aarch64), iOS, and iOS simulator.
I've added a Swift example we run in CI to verify this works. Users can
also drag and drop the XCFramework directly into Xcode.
## Example
```swift
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "swift-vt-xcframework",
platforms: [.macOS(.v13)],
targets: [
.executableTarget(
name: "swift-vt-xcframework",
dependencies: ["GhosttyVt"],
path: "Sources",
linkerSettings: [
.linkedLibrary("c++"),
]
),
.binaryTarget(
name: "GhosttyVt",
path: "../../zig-out/lib/ghostty-vt.xcframework"
),
]
)
```
```swift
import GhosttyVt
// Create a terminal with a small grid
var terminal: GhosttyTerminal?
var opts = GhosttyTerminalOptions(
cols: 80,
rows: 24,
max_scrollback: 0
)
let result = ghostty_terminal_new(nil, &terminal, opts)
guard result == GHOSTTY_SUCCESS, let terminal else {
fatalError("Failed to create terminal")
}
// Write some VT-encoded content
let text = "Hello from \u{1b}[1mSwift\u{1b}[0m via xcframework!\r\n"
text.withCString { ptr in
ghostty_terminal_vt_write(terminal, ptr, strlen(ptr))
}
// Format the terminal contents as plain text
var fmtOpts = GhosttyFormatterTerminalOptions()
fmtOpts.size = MemoryLayout<GhosttyFormatterTerminalOptions>.size
fmtOpts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN
fmtOpts.trim = true
var formatter: GhosttyFormatter?
let fmtResult = ghostty_formatter_terminal_new(nil, &formatter, terminal, fmtOpts)
guard fmtResult == GHOSTTY_SUCCESS, let formatter else {
fatalError("Failed to create formatter")
}
var buf: UnsafeMutablePointer<UInt8>?
var len: Int = 0
let allocResult = ghostty_formatter_format_alloc(formatter, nil, &buf, &len)
guard allocResult == GHOSTTY_SUCCESS, let buf else {
fatalError("Failed to format")
}
print("Plain text (\(len) bytes):")
print(String(cString: buf))
ghostty_free(nil, buf, len)
ghostty_formatter_free(formatter)
ghostty_terminal_free(terminal)
```
Add R2 upload steps to the source-tarball-lib-vt job in the tip
release workflow, matching the pattern used by the xcframework
job. The tarball is uploaded to the ghostty-tip R2 bucket keyed
by commit hash, making it available at
tip.files.ghostty.org/<commit>/libghostty-vt-source.tar.gz.
Add iOS device and simulator slices to the xcframework, gated on
SDK availability via std.zig.LibCInstallation.findNative. Refactor
AppleLibs from a struct with named fields to an EnumMap keyed by
ApplePlatform so that adding new platforms only requires extending
the enum and its sdk_platforms table.
tvOS, watchOS, and visionOS are listed as not yet supported due to
Zig stdlib limitations (missing PATH_MAX, mcontext fields).
Add a build-lib-vt-xcframework job to the release-tip workflow that
builds the universal xcframework with ReleaseFast, zips it, signs
it with minisign, and uploads it to both the GitHub Release and R2
blob storage. Consumers can pull the xcframework zip from the tip
release or by commit hash from tip.files.ghostty.org.