diff --git a/example/wasm-vt/index.html b/example/wasm-vt/index.html
index 81a033737..76c9b9bbd 100644
--- a/example/wasm-vt/index.html
+++ b/example/wasm-vt/index.html
@@ -156,7 +156,7 @@
// Look up a field's offset and DataView setter from the type layout JSON.
function fieldInfo(structName, fieldName) {
- const field = typeLayout[structName].fields[fieldName];
+ const field = typeLayout.types[structName].fields[fieldName];
return field;
}
@@ -166,9 +166,16 @@
switch (field.type) {
case 'u8': case 'bool': view.setUint8(field.offset, value); break;
case 'u16': view.setUint16(field.offset, value, true); break;
- case 'u32': case 'enum': view.setUint32(field.offset, value, true); break;
+ case 'u32': view.setUint32(field.offset, value, true); break;
case 'u64': view.setBigUint64(field.offset, BigInt(value), true); break;
- default: throw new Error(`Unsupported field type: ${field.type}`);
+ default: {
+ const type = typeLayout.types[field.type];
+ if (type?.kind === 'enum' && type.underlying === 'i32') {
+ view.setInt32(field.offset, value, true);
+ break;
+ }
+ throw new Error(`Unsupported field type: ${field.type}`);
+ }
}
}
@@ -186,15 +193,14 @@
.replace(/\\\\/g, '\\');
}
- // GHOSTTY_FORMATTER_FORMAT_PLAIN = 0
- const GHOSTTY_FORMATTER_FORMAT_PLAIN = 0;
- // GHOSTTY_SUCCESS = 0
- const GHOSTTY_SUCCESS = 0;
-
function run() {
const outputDiv = document.getElementById('output');
try {
+ const GHOSTTY_FORMATTER_FORMAT_PLAIN =
+ typeLayout.types.GhosttyFormatterFormat.values.PLAIN;
+ const GHOSTTY_SUCCESS =
+ typeLayout.types.GhosttyResult.values.SUCCESS;
const cols = parseInt(document.getElementById('cols').value, 10);
const rows = parseInt(document.getElementById('rows').value, 10);
const vtText = parseEscapes(document.getElementById('vtInput').value);
@@ -224,7 +230,7 @@
wasmInstance.exports.ghostty_wasm_free_u8_array(dataPtr, vtBytes.length);
// Create a plain-text formatter
- const FMT_OPTS_SIZE = typeLayout['GhosttyFormatterTerminalOptions'].size;
+ const FMT_OPTS_SIZE = typeLayout.types.GhosttyFormatterTerminalOptions.size;
const fmtOptsPtr = wasmInstance.exports.ghostty_wasm_alloc_u8_array(FMT_OPTS_SIZE);
new Uint8Array(getBuffer(), fmtOptsPtr, FMT_OPTS_SIZE).fill(0);
const fmtOptsView = new DataView(getBuffer(), fmtOptsPtr, FMT_OPTS_SIZE);
@@ -235,12 +241,12 @@
// Set the nested sized-struct `size` fields for extra and extra.screen
const extraOffset = fieldInfo('GhosttyFormatterTerminalOptions', 'extra').offset;
- const extraSize = typeLayout['GhosttyFormatterTerminalExtra'].size;
+ const extraSize = typeLayout.types.GhosttyFormatterTerminalExtra.size;
const extraSizeField = fieldInfo('GhosttyFormatterTerminalExtra', 'size');
fmtOptsView.setUint32(extraOffset + extraSizeField.offset, extraSize, true);
const screenOffset = fieldInfo('GhosttyFormatterTerminalExtra', 'screen').offset;
- const screenSize = typeLayout['GhosttyFormatterScreenExtra'].size;
+ const screenSize = typeLayout.types.GhosttyFormatterScreenExtra.size;
const screenSizeField = fieldInfo('GhosttyFormatterScreenExtra', 'size');
fmtOptsView.setUint32(extraOffset + screenOffset + screenSizeField.offset, screenSize, true);
diff --git a/include/ghostty/vt/osc.h b/include/ghostty/vt/osc.h
index 9409ebc73..f7626383c 100644
--- a/include/ghostty/vt/osc.h
+++ b/include/ghostty/vt/osc.h
@@ -63,6 +63,9 @@ typedef enum GHOSTTY_ENUM_TYPED {
GHOSTTY_OSC_COMMAND_CONEMU_XTERM_EMULATION = 20,
GHOSTTY_OSC_COMMAND_CONEMU_COMMENT = 21,
GHOSTTY_OSC_COMMAND_KITTY_TEXT_SIZING = 22,
+ GHOSTTY_OSC_COMMAND_KITTY_CLIPBOARD_PROTOCOL = 23,
+ GHOSTTY_OSC_COMMAND_KITTY_DND_PROTOCOL = 24,
+ GHOSTTY_OSC_COMMAND_CONTEXT_SIGNAL = 25,
GHOSTTY_OSC_COMMAND_TYPE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE,
} GhosttyOscCommandType;
diff --git a/include/ghostty/vt/types.h b/include/ghostty/vt/types.h
index c66ee89d2..bc1c6a69b 100644
--- a/include/ghostty/vt/types.h
+++ b/include/ghostty/vt/types.h
@@ -332,29 +332,40 @@ typedef struct {
#endif
/**
- * Return a pointer to a null-terminated JSON string describing the
- * layout of every C API struct for the current target.
+ * Return the versioned libghostty-vt C type manifest for the current target.
*
- * This is primarily useful for language bindings that can't easily
- * set C struct fields and need to do so via byte offsets. For example,
- * WebAssembly modules can't share struct definitions with the host.
+ * The manifest defines all the public types available in the linked
+ * build. The types contain their layouts, enum values, union fields, and more.
+ *
+ * Language bindings, such as WebAssembly hosts, should obtain offsets,
+ * sizes, alignments, array shapes, enum constants, and tagged-union arms from
+ * this manifest rather than hardcoding them. Consumers should reject unknown
+ * schema versions and verify the descriptors they require at initialization.
*
* Example (abbreviated):
* @code{.json}
* {
- * "GhosttyMouseEncoderSize": {
- * "size": 40,
- * "align": 8,
- * "fields": {
- * "size": { "offset": 0, "size": 8, "type": "u64" },
- * "screen_width": { "offset": 8, "size": 4, "type": "u32" },
- * "screen_height": { "offset": 12, "size": 4, "type": "u32" },
- * "cell_width": { "offset": 16, "size": 4, "type": "u32" },
- * "cell_height": { "offset": 20, "size": 4, "type": "u32" },
- * "padding_top": { "offset": 24, "size": 4, "type": "u32" },
- * "padding_bottom": { "offset": 28, "size": 4, "type": "u32" },
- * "padding_right": { "offset": 32, "size": 4, "type": "u32" },
- * "padding_left": { "offset": 36, "size": 4, "type": "u32" }
+ * "schema": 1,
+ * "abi": {
+ * "target": "wasm32", "os": "freestanding", "environment": "none",
+ * "pointer_size": 4, "usize_size": 4, "endian": "little"
+ * },
+ * "types": {
+ * "GhosttyRenderStateData": {
+ * "kind": "enum", "size": 4, "align": 4,
+ * "underlying": "i32", "prefix": "GHOSTTY_RENDER_STATE_DATA_",
+ * "values": { "INVALID": 0, "DIRTY": 3, "MAX_VALUE": 2147483647 }
+ * },
+ * "GhosttyStyleColor": {
+ * "kind": "struct", "size": 16, "align": 8,
+ * "fields": {
+ * "tag": { "offset": 0, "size": 4,
+ * "type": "GhosttyStyleColorTag" },
+ * "value": { "offset": 8, "size": 8,
+ * "type": "GhosttyStyleColorValue", "tag": "tag",
+ * "arms": { "NONE": null, "PALETTE": "palette",
+ * "RGB": "rgb" } }
+ * }
* }
* }
* }
diff --git a/src/lib/union.zig b/src/lib/union.zig
index 63a34a8cb..728b13304 100644
--- a/src/lib/union.zig
+++ b/src/lib/union.zig
@@ -15,14 +15,18 @@ const Target = @import("target.zig").Target;
/// be an enum created with the `Enum` function in this library, so that
/// automatic C ABI compatibility is ensured.
///
-/// The `Padding` type is a type that is always added to the C union
-/// with the key `_padding`. This should be set to a type that has the size
-/// and alignment needed to pad the C union to the expected size. This
-/// should never change to ensure ABI compatibility.
+/// `options.padding` is a type that is always added to the C union with the key
+/// `_padding`. It should have the size and alignment needed to pad the C union
+/// to the expected size and should never change to ensure ABI compatibility.
+///
+/// Each tag has an optional field in `options.field_renames` that may rename its
+/// C value field. Multiple tags may map to the same field when their C value
+/// types are identical. Tags with no payload are omitted. This is used
+/// for metadata and should match the C headers directly.
pub fn TaggedUnion(
comptime target: Target,
comptime Union: type,
- comptime Padding: type,
+ comptime options: TaggedUnionOptions(Union),
) type {
return struct {
comptime {
@@ -30,7 +34,7 @@ pub fn TaggedUnion(
.zig => {},
// For ABI compatibility, we expect that this is our union size.
- .c => if (@sizeOf(CValue) != @sizeOf(Padding)) {
+ .c => if (@sizeOf(CValue) != @sizeOf(options.padding)) {
@compileLog(@sizeOf(CValue));
@compileError("TaggedUnion CValue size does not match expected fixed size");
},
@@ -49,11 +53,25 @@ pub fn TaggedUnion(
.c => extern struct {
tag: Tag,
value: CValue,
+
+ /// Returns the public C value-union field name for `tag`, or
+ /// null when the tag has no value field. This is metadata only;
+ /// it does not rename fields in `CValue`.
+ pub fn cFieldRename(comptime tag: Tag) ?[]const u8 {
+ const tag_name = @tagName(tag);
+ const value = @field(@unionInit(Union, tag_name, undefined), tag_name);
+
+ if (@field(options.field_renames, tag_name)) |name| return name;
+ if (@sizeOf(@TypeOf(value)) == 0) return null;
+
+ return tag_name;
+ }
},
};
/// The C ABI compatible union value type.
pub const CValue = cvalue: {
+ @setEvalBranchQuota(10_000);
switch (target) {
.zig => break :cvalue extern struct {},
.c => {},
@@ -83,8 +101,8 @@ pub fn TaggedUnion(
}
names[tag_fields.len] = "_padding";
- types[tag_fields.len] = Padding;
- attrs[tag_fields.len] = .{ .@"align" = @alignOf(Padding) };
+ types[tag_fields.len] = options.padding;
+ attrs[tag_fields.len] = .{ .@"align" = @alignOf(options.padding) };
break :cvalue @Union(.@"extern", null, &names, &types, &attrs);
};
@@ -125,6 +143,30 @@ pub fn TaggedUnion(
};
}
+/// Options for generating the C representation of a tagged union.
+pub fn TaggedUnionOptions(comptime Union: type) type {
+ const Tag = @typeInfo(Union).@"union".tag_type.?;
+ const tag_fields = @typeInfo(Tag).@"enum".fields;
+ const FieldRenames: type = field_renames: {
+ const default_rename: ?[]const u8 = null;
+
+ var names: [tag_fields.len][]const u8 = undefined;
+ var types: [tag_fields.len]type = undefined;
+ var attrs: [tag_fields.len]std.builtin.Type.StructField.Attributes = undefined;
+
+ for (tag_fields, 0..) |field, i| {
+ names[i] = field.name;
+ types[i] = ?[]const u8;
+ attrs[i] = .{ .default_value_ptr = &default_rename };
+ }
+
+ break :field_renames @Struct(.auto, null, &names, &types, &attrs);
+ };
+ return struct {
+ padding: type,
+ field_renames: FieldRenames = .{},
+ };
+}
test "TaggedUnion: matching size" {
const Tag = enum(c_int) { a, b };
const U = TaggedUnion(
@@ -133,7 +175,7 @@ test "TaggedUnion: matching size" {
a: u32,
b: u64,
},
- u64,
+ .{ .padding = u64 },
);
try testing.expectEqual(8, @sizeOf(U.CValue));
@@ -146,7 +188,7 @@ test "TaggedUnion: padded size" {
union(Tag) {
a: u32,
},
- u64,
+ .{ .padding = u64 },
);
try testing.expectEqual(8, @sizeOf(U.CValue));
@@ -157,9 +199,39 @@ test "TaggedUnion: c conversion" {
const U = TaggedUnion(.c, union(Tag) {
a: u32,
b: u64,
- }, u64);
+ }, .{ .padding = u64 });
const c = U.cval(.{ .a = 42 });
try testing.expectEqual(Tag.a, c.tag);
try testing.expectEqual(42, c.value.a);
}
+
+test "TaggedUnion: custom C value fields" {
+ const Tag = enum(c_int) { a, b, none };
+ const Union = union(Tag) {
+ a: u32,
+ b: u32,
+ none,
+ };
+ const U = TaggedUnion(.c, Union, .{
+ .padding = u64,
+ .field_renames = .{
+ .a = "value",
+ .b = "value",
+ },
+ });
+
+ try testing.expect(!@hasField(U.CValue, "value"));
+ try testing.expect(@hasField(U.CValue, "a"));
+ try testing.expect(@hasField(U.CValue, "b"));
+ try testing.expect(@hasField(U.CValue, "none"));
+ try testing.expectEqualStrings("value", U.C.cFieldRename(.a).?);
+ try testing.expect(U.C.cFieldRename(.none) == null);
+
+ const a = U.cval(.{ .a = 42 });
+ try testing.expectEqual(Tag.a, a.tag);
+ try testing.expectEqual(42, a.value.a);
+
+ const none = U.cval(.none);
+ try testing.expectEqual(Tag.none, none.tag);
+}
diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig
index 1e974cdd5..b935b505f 100644
--- a/src/terminal/Terminal.zig
+++ b/src/terminal/Terminal.zig
@@ -2590,7 +2590,7 @@ pub const ScrollViewport = union(Tag) {
@This(),
// Padding: largest variant is isize (8 bytes on 64-bit).
// Use [2]u64 (16 bytes) for future expansion.
- [2]u64,
+ .{ .padding = [2]u64 },
);
pub const C = c_union.C;
pub const CValue = c_union.CValue;
diff --git a/src/terminal/c/osc.zig b/src/terminal/c/osc.zig
index 8eca343c2..2ecc9c975 100644
--- a/src/terminal/c/osc.zig
+++ b/src/terminal/c/osc.zig
@@ -12,6 +12,9 @@ pub const Parser = ?*osc.Parser;
/// C: GhosttyOscCommand
pub const Command = ?*osc.Command;
+/// C: GhosttyOscCommandType
+pub const CommandType = osc.Command.Key;
+
pub fn new(
alloc_: ?*const CAllocator,
result: *Parser,
@@ -44,7 +47,7 @@ pub fn end(parser_: Parser, terminator: u8) callconv(lib.calling_conv) Command {
return parser_.?.end(terminator);
}
-pub fn commandType(command_: Command) callconv(lib.calling_conv) osc.Command.Key {
+pub fn commandType(command_: Command) callconv(lib.calling_conv) CommandType {
const command = command_ orelse return .invalid;
return command.*;
}
diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig
index 7dcca5f02..55d488158 100644
--- a/src/terminal/c/terminal.zig
+++ b/src/terminal/c/terminal.zig
@@ -182,7 +182,7 @@ pub const UnknownSequence = union(Tag) {
// A future borrowed CSI payload may need parameter, separator, and
// intermediate arrays. Reserve 128 bytes so that representation and
// other structured sequence types can be added without an ABI break.
- [16]u64,
+ .{ .padding = [16]u64 },
);
pub const C = c_union.C;
pub const CValue = c_union.CValue;
diff --git a/src/terminal/c/types.zig b/src/terminal/c/types.zig
index 681556666..88759ef1c 100644
--- a/src/terminal/c/types.zig
+++ b/src/terminal/c/types.zig
@@ -1,29 +1,49 @@
-//! Comptime-generated metadata describing the layout of all C API
-//! extern structs for the current target.
+//! Comptime-generated ABI metadata for the public libghostty-vt C types.
//!
-//! This is embedded in the binary as a const string and exposed via
-//! `ghostty_type_json` so that WASM (and other FFI) consumers can
-//! build structs without hardcoding byte offsets.
+//! The manifest is embedded in the library and returned by
+//! `ghostty_type_json`. It is intended for FFI consumers that cannot use the
+//! C headers directly, most notably WebAssembly hosts.
const std = @import("std");
+const builtin = @import("builtin");
+const build_options = @import("terminal_options");
const lib = @import("../lib.zig");
-const color = @import("../color.zig");
-const sgr = @import("../sgr.zig");
-const color_c = @import("color.zig");
-const mouse_event = @import("mouse_event.zig");
-const point = @import("../point.zig");
-const size_report = @import("size_report.zig");
-const terminal = @import("terminal.zig");
+const color = @import("../color.zig");
+const clipboard = @import("../clipboard.zig");
+const device_status = @import("../device_status.zig");
+const focus_pkg = @import("../focus.zig");
+const formatter_pkg = @import("../formatter.zig");
+const modes_pkg = @import("../modes.zig");
+const mouse_pkg = @import("../mouse.zig");
+const point = @import("../point.zig");
+const Selection = @import("../Selection.zig");
+const sgr = @import("../sgr.zig");
+
+const input_config = @import("../../input/config.zig");
+const input_key = @import("../../input/key.zig");
+const input_mouse = @import("../../input/mouse.zig");
+
+const build_info = @import("build_info.zig");
+const cell = @import("cell.zig");
+const color_c = @import("color.zig");
const formatter = @import("formatter.zig");
-const selection = @import("selection.zig");
-const selection_gesture = @import("selection_gesture.zig");
-const render = @import("render.zig");
-const style_c = @import("style.zig");
-const kitty_graphics = @import("kitty_graphics.zig");
-const mouse_encode = @import("mouse_encode.zig");
const grid_ref = @import("grid_ref.zig");
const io = @import("io.zig");
+const key_encode = @import("key_encode.zig");
+const kitty_graphics = @import("kitty_graphics.zig");
+const mouse_encode = @import("mouse_encode.zig");
+const mouse_event = @import("mouse_event.zig");
+const osc = @import("osc.zig");
+const render = @import("render.zig");
+const result = @import("result.zig");
+const row = @import("row.zig");
+const selection = @import("selection.zig");
+const selection_gesture = @import("selection_gesture.zig");
+const size_report = @import("size_report.zig");
+const snapshot = @import("snapshot.zig");
+const style = @import("style.zig");
const sys = @import("sys.zig");
+const terminal = @import("terminal.zig");
/// C: GhosttySurfacePosition
pub const SurfacePosition = extern struct {
@@ -37,318 +57,802 @@ pub const Codepoints = extern struct {
len: usize = 0,
};
-/// All C API structs and their Ghostty C names.
-pub const structs: std.StaticStringMap(StructInfo) = structs: {
- @setEvalBranchQuota(10_000);
- break :structs .initComptime(.{
- .{ "GhosttyAllocator", StructInfo.init(lib.alloc.Allocator) },
- .{ "GhosttyAllocatorVtable", StructInfo.init(lib.alloc.VTable) },
- .{ "GhosttyBuffer", StructInfo.init(lib.Buffer) },
- .{ "GhosttyClipboardContent", StructInfo.init(terminal.ClipboardContent) },
- .{ "GhosttyClipboardWrite", StructInfo.init(terminal.ClipboardWrite) },
- .{ "GhosttyCodepoints", StructInfo.init(Codepoints) },
- .{ "GhosttyColorPaletteMask", StructInfo.init(color_c.PaletteMask) },
- .{ "GhosttyColorRgb", StructInfo.init(color.RGB.C) },
- .{ "GhosttyColorX11Entry", StructInfo.init(color_c.X11Entry) },
- .{ "GhosttyDeviceAttributes", StructInfo.init(terminal.DeviceAttributes) },
- .{ "GhosttyDeviceAttributesPrimary", StructInfo.init(terminal.DeviceAttributes.Primary) },
- .{ "GhosttyDeviceAttributesSecondary", StructInfo.init(terminal.DeviceAttributes.Secondary) },
- .{ "GhosttyDeviceAttributesTertiary", StructInfo.init(terminal.DeviceAttributes.Tertiary) },
- .{ "GhosttyFormatterTerminalOptions", StructInfo.init(formatter.TerminalOptions) },
- .{ "GhosttySelection", StructInfo.init(selection.CSelection) },
- .{ "GhosttyTerminalSelectWordOptions", StructInfo.init(selection.SelectWordOptions) },
- .{ "GhosttyTerminalSelectWordBetweenOptions", StructInfo.init(selection.SelectWordBetweenOptions) },
- .{ "GhosttyTerminalSelectLineOptions", StructInfo.init(selection.SelectLineOptions) },
- .{ "GhosttyTerminalSelectionFormatOptions", StructInfo.init(selection.FormatOptions) },
- .{ "GhosttyFormatterTerminalExtra", StructInfo.init(formatter.TerminalOptions.Extra) },
- .{ "GhosttyFormatterScreenExtra", StructInfo.init(formatter.ScreenOptions.Extra) },
- .{ "GhosttyGridRef", StructInfo.init(grid_ref.CGridRef) },
- .{ "GhosttyKittyGraphicsPlacementRenderInfo", StructInfo.init(kitty_graphics.PlacementRenderInfo) },
- .{ "GhosttyMouseEncoderSize", StructInfo.init(mouse_encode.Size) },
- .{ "GhosttyMousePosition", StructInfo.init(mouse_event.Position) },
- .{ "GhosttyPoint", StructInfo.init(point.Point.C) },
- .{ "GhosttyPointCoordinate", StructInfo.init(point.Coordinate) },
- .{ "GhosttyReader", StructInfo.init(io.Reader) },
- .{ "GhosttyRenderStateColors", StructInfo.init(render.Colors) },
- .{ "GhosttyRenderStateCursor", StructInfo.init(render.Cursor) },
- .{ "GhosttyRenderStateRowSelection", StructInfo.init(render.RowSelection) },
- .{ "GhosttySelectionGestureBehaviors", StructInfo.init(selection_gesture.Behaviors) },
- .{ "GhosttySelectionGestureGeometry", StructInfo.init(selection_gesture.Geometry) },
- .{ "GhosttySgrAttribute", StructInfo.init(sgr.Attribute.C) },
- .{ "GhosttySgrUnknown", StructInfo.init(sgr.Attribute.Unknown.C) },
- .{ "GhosttySizeReportSize", StructInfo.init(size_report.Size) },
- .{ "GhosttyString", StructInfo.init(lib.String) },
- .{ "GhosttySurfacePosition", StructInfo.init(SurfacePosition) },
- .{ "GhosttyStyle", StructInfo.init(style_c.Style) },
- .{ "GhosttyStyleColor", StructInfo.init(style_c.Color) },
- .{ "GhosttySysImage", StructInfo.init(sys.Image) },
- .{ "GhosttyTerminalDesktopNotification", StructInfo.init(terminal.DesktopNotification) },
- .{ "GhosttyTerminalModeConfig", StructInfo.init(terminal.ModeConfig) },
- .{ "GhosttyTerminalProgressReport", StructInfo.init(terminal.ProgressReport) },
- .{ "GhosttyTerminalScrollbar", StructInfo.init(terminal.TerminalScrollbar) },
- .{ "GhosttyTerminalScrollViewport", StructInfo.init(terminal.ScrollViewport) },
- .{ "GhosttyTerminalUnknownSequence", StructInfo.init(terminal.UnknownSequence.C) },
- .{ "GhosttyTerminalUnknownStringSequence", StructInfo.init(terminal.UnknownStringSequence) },
- .{ "GhosttyWriter", StructInfo.init(io.Writer) },
- });
+const TypeDecl = struct {
+ name: []const u8,
+ T: type,
+ kind: Kind,
+ prefix: []const u8 = "",
+ sentinel_suffix: []const u8 = "MAX_VALUE",
+ alias_type: []const u8 = "",
+ tagged_union: ?TaggedUnion = null,
+ union_field_names_T: ?type = null,
+
+ const Kind = enum {
+ @"struct",
+ @"union",
+ @"enum",
+ alias,
+ @"opaque",
+ };
+
+ const TaggedUnion = struct {
+ tag_field: []const u8,
+ value_field: []const u8,
+ arm_source: ArmSource,
+
+ const ArmSource = enum {
+ fields,
+ generated,
+ };
+ };
+
+ fn initStruct(comptime name: []const u8, comptime T: type) TypeDecl {
+ return .{ .name = name, .T = T, .kind = .@"struct" };
+ }
+
+ fn initTaggedStruct(
+ comptime name: []const u8,
+ comptime T: type,
+ comptime tag_field: []const u8,
+ comptime value_field: []const u8,
+ comptime arm_source: TaggedUnion.ArmSource,
+ ) TypeDecl {
+ return .{
+ .name = name,
+ .T = T,
+ .kind = .@"struct",
+ .tagged_union = .{
+ .tag_field = tag_field,
+ .value_field = value_field,
+ .arm_source = arm_source,
+ },
+ };
+ }
+
+ fn initUnion(
+ comptime name: []const u8,
+ comptime T: type,
+ comptime field_names_T: ?type,
+ ) TypeDecl {
+ return .{
+ .name = name,
+ .T = T,
+ .kind = .@"union",
+ .union_field_names_T = field_names_T,
+ };
+ }
+
+ fn initEnum(
+ comptime name: []const u8,
+ comptime T: type,
+ comptime prefix: []const u8,
+ ) TypeDecl {
+ return .{ .name = name, .T = T, .kind = .@"enum", .prefix = prefix };
+ }
+
+ fn initEnumSentinel(
+ comptime name: []const u8,
+ comptime T: type,
+ comptime prefix: []const u8,
+ comptime sentinel_suffix: []const u8,
+ ) TypeDecl {
+ return .{
+ .name = name,
+ .T = T,
+ .kind = .@"enum",
+ .prefix = prefix,
+ .sentinel_suffix = sentinel_suffix,
+ };
+ }
+
+ fn initAlias(
+ comptime name: []const u8,
+ comptime T: type,
+ comptime alias_type: []const u8,
+ ) TypeDecl {
+ return .{ .name = name, .T = T, .kind = .alias, .alias_type = alias_type };
+ }
+
+ fn initOpaque(comptime name: []const u8) TypeDecl {
+ return .{ .name = name, .T = *anyopaque, .kind = .@"opaque" };
+ }
};
-/// The comptime-generated JSON string of all structs.
+/// Public C types. Names and enum prefixes are intentionally explicit because
+/// Zig identifiers are not the C API contract.
+const type_decls = [_]TypeDecl{
+ .initStruct("GhosttyAllocator", lib.alloc.Allocator),
+ .initStruct("GhosttyAllocatorVtable", lib.alloc.VTable),
+ .initStruct("GhosttyBuffer", lib.Buffer),
+ .initStruct("GhosttyCellsView", cell.CellsView),
+ .initStruct("GhosttyClipboardContent", terminal.ClipboardContent),
+ .initStruct("GhosttyClipboardWrite", terminal.ClipboardWrite),
+ .initStruct("GhosttyCodepoints", Codepoints),
+ .initStruct("GhosttyColorPaletteMask", color_c.PaletteMask),
+ .initStruct("GhosttyColorRgb", color.RGB.C),
+ .initStruct("GhosttyColorX11Entry", color_c.X11Entry),
+ .initStruct("GhosttyDeviceAttributes", terminal.DeviceAttributes),
+ .initStruct("GhosttyDeviceAttributesPrimary", terminal.DeviceAttributes.Primary),
+ .initStruct("GhosttyDeviceAttributesSecondary", terminal.DeviceAttributes.Secondary),
+ .initStruct("GhosttyDeviceAttributesTertiary", terminal.DeviceAttributes.Tertiary),
+ .initStruct("GhosttyFormatterScreenExtra", formatter.ScreenOptions.Extra),
+ .initStruct("GhosttyFormatterTerminalExtra", formatter.TerminalOptions.Extra),
+ .initStruct("GhosttyFormatterTerminalOptions", formatter.TerminalOptions),
+ .initStruct("GhosttyGridRef", grid_ref.CGridRef),
+ .initStruct("GhosttyKittyGraphicsPlacementRenderInfo", kitty_graphics.PlacementRenderInfo),
+ .initStruct("GhosttyMouseEncoderSize", mouse_encode.Size),
+ .initStruct("GhosttyMousePosition", mouse_event.Position),
+ .initTaggedStruct("GhosttyPoint", point.Point.C, "tag", "value", .generated),
+ .initStruct("GhosttyPointCoordinate", point.Coordinate),
+ .initUnion("GhosttyPointValue", point.Point.CValue, point.Point.C),
+ .initStruct("GhosttyReader", io.Reader),
+ .initStruct("GhosttyRenderStateColors", render.Colors),
+ .initStruct("GhosttyRenderStateCursor", render.Cursor),
+ .initStruct("GhosttyRenderStateRowSelection", render.RowSelection),
+ .initStruct("GhosttySelection", selection.CSelection),
+ .initStruct("GhosttySelectionGestureBehaviors", selection_gesture.Behaviors),
+ .initStruct("GhosttySelectionGestureGeometry", selection_gesture.Geometry),
+ .initTaggedStruct("GhosttySgrAttribute", sgr.Attribute.C, "tag", "value", .generated),
+ .initStruct("GhosttySgrUnknown", sgr.Attribute.Unknown.C),
+ .initUnion("GhosttySgrAttributeValue", sgr.Attribute.CValue, sgr.Attribute.C),
+ .initStruct("GhosttySizeReportSize", size_report.Size),
+ .initStruct("GhosttyString", lib.String),
+ .initStruct("GhosttySurfacePosition", SurfacePosition),
+ .initStruct("GhosttyStyle", style.Style),
+ .initTaggedStruct("GhosttyStyleColor", style.Color, "tag", "value", .fields),
+ .initUnion("GhosttyStyleColorValue", style.ColorValue, null),
+ .initStruct("GhosttySysImage", sys.Image),
+ .initStruct("GhosttyTerminalDesktopNotification", terminal.DesktopNotification),
+ .initStruct("GhosttyTerminalModeConfig", terminal.ModeConfig),
+ .initStruct("GhosttyTerminalProgressReport", terminal.ProgressReport),
+ .initStruct("GhosttyTerminalScrollbar", terminal.TerminalScrollbar),
+ .initTaggedStruct("GhosttyTerminalScrollViewport", terminal.ScrollViewport, "tag", "value", .generated),
+ .initUnion(
+ "GhosttyTerminalScrollViewportValue",
+ terminal.ZigTerminal.ScrollViewport.CValue,
+ terminal.ZigTerminal.ScrollViewport.C,
+ ),
+ .initStruct("GhosttyTerminalSelectLineOptions", selection.SelectLineOptions),
+ .initStruct("GhosttyTerminalSelectWordBetweenOptions", selection.SelectWordBetweenOptions),
+ .initStruct("GhosttyTerminalSelectWordOptions", selection.SelectWordOptions),
+ .initStruct("GhosttyTerminalSelectionFormatOptions", selection.FormatOptions),
+ .initTaggedStruct("GhosttyTerminalUnknownSequence", terminal.UnknownSequence.C, "tag", "value", .generated),
+ .initStruct("GhosttyTerminalUnknownStringSequence", terminal.UnknownStringSequence),
+ .initUnion(
+ "GhosttyTerminalUnknownSequenceValue",
+ terminal.UnknownSequence.CValue,
+ terminal.UnknownSequence.C,
+ ),
+ .initStruct("GhosttyWriter", io.Writer),
+
+ .initEnumSentinel("GhosttyResult", result.Result, "GHOSTTY_", "RESULT_MAX_VALUE"),
+ .initEnum("GhosttyBuildInfo", build_info.BuildInfo, "GHOSTTY_BUILD_INFO_"),
+ .initEnumSentinel("GhosttyOptimizeMode", build_info.OptimizeMode, "GHOSTTY_OPTIMIZE_", "MODE_MAX_VALUE"),
+ .initEnumSentinel("GhosttyCellContentTag", cell.ContentTag, "GHOSTTY_CELL_CONTENT_", "TAG_MAX_VALUE"),
+ .initEnum("GhosttyCellData", cell.CellData, "GHOSTTY_CELL_DATA_"),
+ .initEnum("GhosttyCellSemanticContent", cell.SemanticContent, "GHOSTTY_CELL_SEMANTIC_"),
+ .initEnum("GhosttyCellWide", cell.Wide, "GHOSTTY_CELL_WIDE_"),
+ .initEnum("GhosttyClipboardLocation", clipboard.Location, "GHOSTTY_CLIPBOARD_LOCATION_"),
+ .initEnum("GhosttyClipboardWriteResult", clipboard.WriteResult, "GHOSTTY_CLIPBOARD_WRITE_RESULT_"),
+ .initEnum("GhosttyColorScheme", device_status.ColorScheme, "GHOSTTY_COLOR_SCHEME_"),
+ .initEnum("GhosttyFocusEvent", focus_pkg.Event, "GHOSTTY_FOCUS_"),
+ .initEnum("GhosttyFormatterFormat", formatter_pkg.Format, "GHOSTTY_FORMATTER_FORMAT_"),
+ .initEnum("GhosttyKey", input_key.Key, "GHOSTTY_KEY_"),
+ .initEnum("GhosttyKeyAction", input_key.Action, "GHOSTTY_KEY_ACTION_"),
+ .initEnum("GhosttyKeyEncoderOption", key_encode.Option, "GHOSTTY_KEY_ENCODER_OPT_"),
+ .initEnum("GhosttyKittyGraphicsData", kitty_graphics.Data, "GHOSTTY_KITTY_GRAPHICS_DATA_"),
+ .initEnum("GhosttyKittyGraphicsImageData", kitty_graphics.ImageData, "GHOSTTY_KITTY_IMAGE_DATA_"),
+ .initEnum("GhosttyKittyGraphicsPlacementData", kitty_graphics.PlacementData, "GHOSTTY_KITTY_GRAPHICS_PLACEMENT_DATA_"),
+ .initEnum("GhosttyKittyGraphicsPlacementIteratorOption", kitty_graphics.PlacementIteratorOption, "GHOSTTY_KITTY_GRAPHICS_PLACEMENT_ITERATOR_OPTION_"),
+ .initEnum("GhosttyKittyImageCompression", kitty_graphics.ImageCompression, "GHOSTTY_KITTY_IMAGE_COMPRESSION_"),
+ .initEnum("GhosttyKittyImageFormat", kitty_graphics.ImageFormat, "GHOSTTY_KITTY_IMAGE_FORMAT_"),
+ .initEnum("GhosttyKittyPlacementLayer", kitty_graphics.PlacementLayer, "GHOSTTY_KITTY_PLACEMENT_LAYER_"),
+ .initEnum("GhosttyModeReportState", modes_pkg.Report.State, "GHOSTTY_MODE_REPORT_"),
+ .initEnum("GhosttyMouseAction", input_mouse.Action, "GHOSTTY_MOUSE_ACTION_"),
+ .initEnum("GhosttyMouseButton", input_mouse.Button, "GHOSTTY_MOUSE_BUTTON_"),
+ .initEnum("GhosttyMouseEncoderOption", mouse_encode.Option, "GHOSTTY_MOUSE_ENCODER_OPT_"),
+ .initEnum("GhosttyMouseFormat", mouse_pkg.Format, "GHOSTTY_MOUSE_FORMAT_"),
+ .initEnum("GhosttyMouseTrackingMode", mouse_pkg.Event, "GHOSTTY_MOUSE_TRACKING_"),
+ .initEnum("GhosttyOptionAsAlt", input_config.OptionAsAlt, "GHOSTTY_OPTION_AS_ALT_"),
+ .initEnum("GhosttyOscCommandData", osc.CommandData, "GHOSTTY_OSC_DATA_"),
+ .initEnumSentinel(
+ "GhosttyOscCommandType",
+ osc.CommandType,
+ "GHOSTTY_OSC_COMMAND_",
+ "TYPE_MAX_VALUE",
+ ),
+ .initEnum("GhosttyPointTag", point.Tag, "GHOSTTY_POINT_TAG_"),
+ .initEnum("GhosttyRenderStateCursorVisualStyle", render.CursorVisualStyle, "GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_"),
+ .initEnum("GhosttyRenderStateData", render.Data, "GHOSTTY_RENDER_STATE_DATA_"),
+ .initEnum("GhosttyRenderStateDirty", render.Dirty, "GHOSTTY_RENDER_STATE_DIRTY_"),
+ .initEnum("GhosttyRenderStateOption", render.SetOption, "GHOSTTY_RENDER_STATE_OPTION_"),
+ .initEnum("GhosttyRenderStateRowCellsData", render.RowCellsData, "GHOSTTY_RENDER_STATE_ROW_CELLS_DATA_"),
+ .initEnum("GhosttyRenderStateRowData", render.RowData, "GHOSTTY_RENDER_STATE_ROW_DATA_"),
+ .initEnum("GhosttyRenderStateRowOption", render.RowOption, "GHOSTTY_RENDER_STATE_ROW_OPTION_"),
+ .initEnum("GhosttyRowData", row.RowData, "GHOSTTY_ROW_DATA_"),
+ .initEnum("GhosttyRowSemanticPrompt", row.SemanticPrompt, "GHOSTTY_ROW_SEMANTIC_"),
+ .initEnum("GhosttySelectionAdjust", Selection.Adjustment, "GHOSTTY_SELECTION_ADJUST_"),
+ .initEnum("GhosttySelectionGestureAutoscroll", selection_gesture.Autoscroll, "GHOSTTY_SELECTION_GESTURE_AUTOSCROLL_"),
+ .initEnum("GhosttySelectionGestureBehavior", selection_gesture.Behavior, "GHOSTTY_SELECTION_GESTURE_BEHAVIOR_"),
+ .initEnum("GhosttySelectionGestureData", selection_gesture.Data, "GHOSTTY_SELECTION_GESTURE_DATA_"),
+ .initEnum("GhosttySelectionGestureEventOption", selection_gesture.EventOption, "GHOSTTY_SELECTION_GESTURE_EVENT_OPT_"),
+ .initEnum("GhosttySelectionGestureEventType", selection_gesture.EventType, "GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_"),
+ .initEnum("GhosttySelectionOrder", Selection.Order, "GHOSTTY_SELECTION_ORDER_"),
+ .initEnum("GhosttySgrAttributeTag", sgr.Attribute.Tag, "GHOSTTY_SGR_ATTR_"),
+ .initEnum("GhosttySgrUnderline", sgr.Attribute.Underline, "GHOSTTY_SGR_UNDERLINE_"),
+ .initEnumSentinel("GhosttySizeReportStyle", size_report.Style, "GHOSTTY_SIZE_REPORT_", "STYLE_MAX_VALUE"),
+ .initEnum("GhosttySnapshotDecoderData", snapshot.DecoderData, "GHOSTTY_SNAPSHOT_DECODER_DATA_"),
+ .initEnum("GhosttySnapshotDecoderOption", snapshot.DecoderOption, "GHOSTTY_SNAPSHOT_DECODER_OPT_"),
+ .initEnumSentinel("GhosttyStyleColorTag", style.ColorTag, "GHOSTTY_STYLE_COLOR_", "TAG_MAX_VALUE"),
+ .initEnum("GhosttySysLogLevel", sys.LogLevel, "GHOSTTY_SYS_LOG_LEVEL_"),
+ .initEnum("GhosttySysOption", sys.Option, "GHOSTTY_SYS_OPT_"),
+ .initEnum("GhosttyTerminalCompressionMode", terminal.CompressionMode, "GHOSTTY_TERMINAL_COMPRESSION_MODE_"),
+ .initEnum("GhosttyTerminalCompressionResult", terminal.CompressionResult, "GHOSTTY_TERMINAL_COMPRESSION_RESULT_"),
+ .initEnum("GhosttyTerminalCursorStyle", terminal.TerminalCursorStyle, "GHOSTTY_TERMINAL_CURSOR_STYLE_"),
+ .initEnum("GhosttyTerminalData", terminal.TerminalData, "GHOSTTY_TERMINAL_DATA_"),
+ .initEnum("GhosttyTerminalOption", terminal.Option, "GHOSTTY_TERMINAL_OPT_"),
+ .initEnum("GhosttyTerminalProgressState", terminal.ProgressState, "GHOSTTY_TERMINAL_PROGRESS_STATE_"),
+ .initEnum("GhosttyTerminalScreen", terminal.TerminalScreen, "GHOSTTY_TERMINAL_SCREEN_"),
+ .initEnum("GhosttyTerminalScrollViewportTag", terminal.ZigTerminal.ScrollViewport.Tag, "GHOSTTY_SCROLL_VIEWPORT_"),
+ .initEnum("GhosttyTerminalUnknownSequenceTag", terminal.UnknownSequence.Tag, "GHOSTTY_TERMINAL_UNKNOWN_SEQUENCE_"),
+
+ .initAlias("GhosttyCell", u64, "u64"),
+ .initAlias("GhosttyColorPaletteIndex", u8, "u8"),
+ .initAlias("GhosttyKittyKeyFlags", u8, "u8"),
+ .initAlias("GhosttyMode", u16, "u16"),
+ .initAlias("GhosttyMods", u16, "u16"),
+ .initAlias("GhosttyRow", u64, "u64"),
+ .initAlias("GhosttyStyleId", u16, "u16"),
+
+ .initOpaque("GhosttyFormatter"),
+ .initOpaque("GhosttyKeyEncoder"),
+ .initOpaque("GhosttyKeyEvent"),
+ .initOpaque("GhosttyKittyGraphics"),
+ .initOpaque("GhosttyKittyGraphicsImage"),
+ .initOpaque("GhosttyKittyGraphicsPlacementIterator"),
+ .initOpaque("GhosttyMouseEncoder"),
+ .initOpaque("GhosttyMouseEvent"),
+ .initOpaque("GhosttyOscCommand"),
+ .initOpaque("GhosttyOscParser"),
+ .initOpaque("GhosttyRenderState"),
+ .initOpaque("GhosttyRenderStateRowCells"),
+ .initOpaque("GhosttyRenderStateRowIterator"),
+ .initOpaque("GhosttySelectionGesture"),
+ .initOpaque("GhosttySelectionGestureEvent"),
+ .initOpaque("GhosttySgrParser"),
+ .initOpaque("GhosttySnapshotDecoder"),
+ .initOpaque("GhosttyTerminal"),
+ .initOpaque("GhosttyTrackedGridRef"),
+};
+
+comptime {
+ @setEvalBranchQuota(100_000);
+ for (type_decls, 0..) |decl, i| {
+ for (type_decls[0..i]) |previous| {
+ if (std.mem.eql(u8, decl.name, previous.name))
+ @compileError("duplicate public ABI type: " ++ decl.name);
+ }
+ }
+}
+
pub const json: [:0]const u8 = json: {
- @setEvalBranchQuota(200_000);
+ @setEvalBranchQuota(1_000_000);
var counter: std.Io.Writer.Discarding = .init(&.{});
- jsonWriteAll(&counter.writer) catch unreachable;
+ Json.writeAll(&counter.writer) catch unreachable;
var buf: [counter.count:0]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
- jsonWriteAll(&writer) catch unreachable;
+ Json.writeAll(&writer) catch unreachable;
const final = buf;
break :json final[0..writer.end :0];
};
-/// Returns a pointer to the comptime-generated JSON string describing
-/// the layout of all C API extern structs, and writes its length to `len`.
-/// Exported as `ghostty_type_json` for FFI consumers.
pub fn get_json() callconv(lib.calling_conv) [*:0]const u8 {
return json.ptr;
}
-/// Meta information about a struct that we expose to ease writing
-/// bindings in some languages, particularly WASM where we can't
-/// easily share struct definitions and need to hardcode byte offsets.
-pub const StructInfo = struct {
- name: []const u8,
- size: usize,
- @"align": usize,
- fields: []const FieldInfo,
+const Json = struct {
+ fn writeAll(writer: *std.Io.Writer) std.Io.Writer.Error!void {
+ var jws: std.json.Stringify = .{ .writer = writer };
+ try jws.beginObject();
+ try jws.objectField("schema");
+ try jws.write(1);
- pub const FieldInfo = struct {
- name: []const u8,
- offset: usize,
- size: usize,
- type: []const u8,
- };
+ try jws.objectField("abi");
+ try jws.beginObject();
+ try jws.objectField("target");
+ try jws.write(@tagName(builtin.target.cpu.arch));
+ try jws.objectField("os");
+ try jws.write(@tagName(builtin.target.os.tag));
+ try jws.objectField("environment");
+ try jws.write(@tagName(builtin.target.abi));
+ try jws.objectField("pointer_size");
+ try jws.write(@sizeOf(*anyopaque));
+ try jws.objectField("usize_size");
+ try jws.write(@sizeOf(usize));
+ try jws.objectField("endian");
+ try jws.write(@tagName(builtin.target.cpu.arch.endian()));
+ try jws.endObject();
- pub fn init(comptime T: type) StructInfo {
- comptime {
- const fields = @typeInfo(T).@"struct".fields;
- const field_infos: [fields.len]FieldInfo = blk: {
- var infos: [fields.len]FieldInfo = undefined;
- for (fields, 0..) |field, i| infos[i] = .{
- .name = field.name,
- .offset = @offsetOf(T, field.name),
- .size = @sizeOf(field.type),
- .type = typeName(field.type),
- };
- break :blk infos;
- };
+ try jws.objectField("library_version");
+ try jws.write(build_options.version_string);
+ try jws.objectField("commit");
+ if (build_options.version_build) |commit| try jws.write(commit) else try jws.write(null);
+ try jws.objectField("dirty");
+ try jws.write(null);
- return .{
- .name = @typeName(T),
- .size = @sizeOf(T),
- .@"align" = @alignOf(T),
- .fields = &field_infos,
- };
+ try jws.objectField("types");
+ try jws.beginObject();
+ inline for (type_decls) |decl| {
+ try jws.objectField(decl.name);
+ try writeType(decl, &jws);
}
+ try jws.endObject();
+ try jws.endObject();
}
- pub fn jsonStringify(
- self: *const StructInfo,
- jws: anytype,
+ fn writeType(comptime decl: TypeDecl, jws: *std.json.Stringify) std.Io.Writer.Error!void {
+ try jws.beginObject();
+ try jws.objectField("kind");
+ try jws.write(@tagName(decl.kind));
+
+ switch (decl.kind) {
+ .@"struct", .@"union" => {
+ try writeSizeAlign(decl.T, jws);
+ try jws.objectField("fields");
+ try jws.beginObject();
+ if (decl.union_field_names_T != null) {
+ try writeMappedUnionFields(decl, jws);
+ } else {
+ const fields = switch (decl.kind) {
+ .@"struct" => @typeInfo(decl.T).@"struct".fields,
+ .@"union" => @typeInfo(decl.T).@"union".fields,
+ else => unreachable,
+ };
+ inline for (fields) |field| {
+ try writeField(
+ decl,
+ field.name,
+ field.type,
+ if (decl.kind == .@"union") 0 else @offsetOf(decl.T, field.name),
+ jws,
+ );
+ }
+ }
+ try jws.endObject();
+ },
+ .@"enum" => {
+ try writeSizeAlign(c_int, jws);
+ try jws.objectField("underlying");
+ try jws.write("i32");
+ try jws.objectField("prefix");
+ try jws.write(decl.prefix);
+ try jws.objectField("values");
+ try jws.beginObject();
+ inline for (@typeInfo(decl.T).@"enum".fields) |field| {
+ try writeEnumObjectField(decl.name, field.name, jws);
+ try jws.write(field.value);
+ }
+ try jws.objectField(decl.sentinel_suffix);
+ try jws.write(std.math.maxInt(c_int));
+ try jws.endObject();
+ },
+ .alias => {
+ try writeSizeAlign(decl.T, jws);
+ try jws.objectField("type");
+ try jws.write(decl.alias_type);
+ },
+ .@"opaque" => try writeSizeAlign(*anyopaque, jws),
+ }
+ try jws.endObject();
+ }
+
+ fn writeField(
+ comptime decl: TypeDecl,
+ comptime name: []const u8,
+ comptime T: type,
+ comptime offset: usize,
+ jws: *std.json.Stringify,
) std.Io.Writer.Error!void {
+ try jws.objectField(name);
try jws.beginObject();
+ try jws.objectField("offset");
+ try jws.write(offset);
try jws.objectField("size");
- try jws.write(self.size);
- try jws.objectField("align");
- try jws.write(self.@"align");
- try jws.objectField("fields");
- try jws.beginObject();
- for (self.fields) |field| {
- try jws.objectField(field.name);
- try jws.beginObject();
- try jws.objectField("offset");
- try jws.write(field.offset);
- try jws.objectField("size");
- try jws.write(field.size);
- try jws.objectField("type");
- try jws.write(field.type);
- try jws.endObject();
+ try jws.write(@sizeOf(T));
+ try writeFieldType(decl.name, name, T, jws);
+ if (decl.tagged_union) |tagged| {
+ if (std.mem.eql(u8, name, tagged.value_field)) {
+ try jws.objectField("tag");
+ try jws.write(tagged.tag_field);
+ try writeTaggedArms(decl, tagged, jws);
+ }
}
try jws.endObject();
+ }
+
+ fn writeMappedUnionFields(
+ comptime decl: TypeDecl,
+ jws: *std.json.Stringify,
+ ) std.Io.Writer.Error!void {
+ const FieldNames = decl.union_field_names_T.?;
+ const Tag = @FieldType(FieldNames, "tag");
+ const tag_fields = @typeInfo(Tag).@"enum".fields;
+
+ inline for (tag_fields, 0..) |field, i| {
+ const name = comptime FieldNames.cFieldRename(@field(Tag, field.name)) orelse continue;
+ if (comptime mappedUnionFieldIsDuplicate(decl, i, name)) continue;
+ try writeField(decl, name, @FieldType(decl.T, field.name), 0, jws);
+ }
+
+ if (@hasField(decl.T, "_padding"))
+ try writeField(decl, "_padding", @FieldType(decl.T, "_padding"), 0, jws);
+ }
+
+ fn mappedUnionFieldIsDuplicate(
+ comptime decl: TypeDecl,
+ comptime index: usize,
+ comptime name: []const u8,
+ ) bool {
+ const FieldNames = decl.union_field_names_T.?;
+ const Tag = @FieldType(FieldNames, "tag");
+ const tag_fields = @typeInfo(Tag).@"enum".fields;
+ const T = @FieldType(decl.T, tag_fields[index].name);
+
+ inline for (tag_fields[0..index]) |previous| {
+ const previous_name = FieldNames.cFieldRename(@field(Tag, previous.name)) orelse continue;
+ if (std.mem.eql(u8, previous_name, name)) {
+ if (@FieldType(decl.T, previous.name) != T)
+ @compileError("tagged union metadata maps different field types to " ++ name);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ fn writeSizeAlign(comptime T: type, jws: *std.json.Stringify) std.Io.Writer.Error!void {
+ try jws.objectField("size");
+ try jws.write(@sizeOf(T));
+ try jws.objectField("align");
+ try jws.write(@alignOf(T));
+ }
+
+ fn writeFieldType(
+ comptime owner: []const u8,
+ comptime field_name: []const u8,
+ comptime T: type,
+ jws: *std.json.Stringify,
+ ) std.Io.Writer.Error!void {
+ if (comptime std.mem.eql(u8, owner, "GhosttyCellsView") and
+ std.mem.eql(u8, field_name, "ptr"))
+ {
+ try writePointerTypeNamed(T, true, "GhosttyCell", jws);
+ return;
+ }
+ if (comptime std.mem.eql(u8, owner, "GhosttyGridRef") and
+ std.mem.eql(u8, field_name, "node"))
+ {
+ try writePointerTypeNamed(T, true, "opaque", jws);
+ return;
+ }
+
+ if (comptime fieldTypeOverride(owner, field_name)) |name| {
+ try jws.objectField("type");
+ try jws.write(name);
+ return;
+ }
+
+ switch (@typeInfo(T)) {
+ .array => |info| {
+ try jws.objectField("type");
+ try jws.write("array");
+ try jws.objectField("elem");
+ try jws.write(publicTypeName(info.child));
+ try jws.objectField("count");
+ try jws.write(info.len);
+ },
+ .optional => |info| try writePointerType(info.child, true, jws),
+ .pointer => try writePointerType(T, false, jws),
+ else => {
+ try jws.objectField("type");
+ try jws.write(publicTypeName(T));
+ },
+ }
+ }
+
+ fn writePointerType(
+ comptime T: type,
+ comptime nullable: bool,
+ jws: *std.json.Stringify,
+ ) std.Io.Writer.Error!void {
+ const info = @typeInfo(T).pointer;
+ try jws.objectField("type");
+ try jws.write("pointer");
+ try jws.objectField("elem");
+ try jws.write(publicTypeName(info.child));
+ try jws.objectField("const");
+ try jws.write(info.is_const);
+ if (nullable) {
+ try jws.objectField("nullable");
+ try jws.write(true);
+ }
+ }
+
+ fn writePointerTypeNamed(
+ comptime T: type,
+ comptime nullable: bool,
+ comptime elem: []const u8,
+ jws: *std.json.Stringify,
+ ) std.Io.Writer.Error!void {
+ const pointer_type = switch (@typeInfo(T)) {
+ .optional => |info| info.child,
+ .pointer => T,
+ else => unreachable,
+ };
+ const info = @typeInfo(pointer_type).pointer;
+ try jws.objectField("type");
+ try jws.write("pointer");
+ try jws.objectField("elem");
+ try jws.write(elem);
+ try jws.objectField("const");
+ try jws.write(info.is_const);
+ if (nullable) {
+ try jws.objectField("nullable");
+ try jws.write(true);
+ }
+ }
+
+ fn fieldTypeOverride(comptime owner: []const u8, comptime field_name: []const u8) ?[]const u8 {
+ if (std.mem.eql(u8, field_name, "value")) {
+ if (std.mem.eql(u8, owner, "GhosttyPoint")) return "GhosttyPointValue";
+ if (std.mem.eql(u8, owner, "GhosttySgrAttribute")) return "GhosttySgrAttributeValue";
+ if (std.mem.eql(u8, owner, "GhosttyStyleColor")) return "GhosttyStyleColorValue";
+ if (std.mem.eql(u8, owner, "GhosttyTerminalScrollViewport")) return "GhosttyTerminalScrollViewportValue";
+ if (std.mem.eql(u8, owner, "GhosttyTerminalUnknownSequence")) return "GhosttyTerminalUnknownSequenceValue";
+ }
+ if (std.mem.eql(u8, owner, "GhosttyStyleColorValue") and std.mem.eql(u8, field_name, "palette"))
+ return "GhosttyColorPaletteIndex";
+ if (std.mem.eql(u8, owner, "GhosttySgrAttributeValue")) {
+ if (std.mem.eql(u8, field_name, "underline")) return "GhosttySgrUnderline";
+ if (std.mem.endsWith(u8, field_name, "_8") or std.mem.endsWith(u8, field_name, "_256"))
+ return "GhosttyColorPaletteIndex";
+ }
+ if (std.mem.eql(u8, owner, "GhosttyTerminalModeConfig") and std.mem.eql(u8, field_name, "mode"))
+ return "GhosttyMode";
+ return null;
+ }
+
+ fn writeTaggedArms(
+ comptime decl: TypeDecl,
+ comptime tagged: TypeDecl.TaggedUnion,
+ jws: *std.json.Stringify,
+ ) std.Io.Writer.Error!void {
+ const Tag = @FieldType(decl.T, tagged.tag_field);
+ try jws.objectField("arms");
+ try jws.beginObject();
+ inline for (@typeInfo(Tag).@"enum".fields) |field| {
+ try writeEnumObjectField(publicTypeName(Tag), field.name, jws);
+ if (comptime taggedArm(decl, field.name)) |arm| try jws.write(arm) else try jws.write(null);
+ }
try jws.endObject();
}
-};
-fn jsonWriteAll(writer: *std.Io.Writer) std.Io.Writer.Error!void {
- var jws: std.json.Stringify = .{ .writer = writer };
- try jws.beginObject();
- for (structs.keys(), structs.values()) |name, *info| {
- try jws.objectField(name);
- try info.jsonStringify(&jws);
+ fn taggedArm(comptime decl: TypeDecl, comptime tag_name: []const u8) ?[]const u8 {
+ const tagged = decl.tagged_union.?;
+ const Tag = @FieldType(decl.T, tagged.tag_field);
+ if (tagged.arm_source == .generated)
+ return decl.T.cFieldRename(@field(Tag, tag_name));
+
+ const Value = @FieldType(decl.T, tagged.value_field);
+ if (!@hasField(Value, tag_name)) return null;
+ return if (@sizeOf(@FieldType(Value, tag_name)) == 0) null else tag_name;
}
- try jws.endObject();
-}
-fn typeName(comptime T: type) []const u8 {
- return switch (@typeInfo(T)) {
- .bool => "bool",
- .float => |info| switch (info.bits) {
- 32 => "f32",
- 64 => "f64",
- else => @compileError("unsupported float size"),
- },
- .int => |info| switch (info.signedness) {
- .signed => switch (info.bits) {
+ fn publicTypeName(comptime T: type) []const u8 {
+ inline for (type_decls) |decl| switch (decl.kind) {
+ .@"struct", .@"union", .@"enum" => if (T == decl.T) return decl.name,
+ .alias, .@"opaque" => {},
+ };
+ return switch (@typeInfo(T)) {
+ .bool => "bool",
+ .float => |info| switch (info.bits) {
+ 32 => "f32",
+ 64 => "f64",
+ else => "opaque",
+ },
+ .int => |info| intName(info.signedness, info.bits),
+ .comptime_int => "comptime_int",
+ .void => "void",
+ .@"opaque" => "opaque",
+ .@"fn" => "function",
+ .@"enum" => "enum",
+ .@"struct" => "struct",
+ .@"union" => "union",
+ else => "opaque",
+ };
+ }
+
+ fn intName(comptime signedness: std.builtin.Signedness, comptime bits: u16) []const u8 {
+ return switch (signedness) {
+ .signed => switch (bits) {
8 => "i8",
16 => "i16",
32 => "i32",
64 => "i64",
- else => @compileError("unsupported signed int size"),
+ else => "opaque",
},
- .unsigned => switch (info.bits) {
+ .unsigned => switch (bits) {
8 => "u8",
16 => "u16",
32 => "u32",
64 => "u64",
- else => @compileError("unsupported unsigned int size"),
+ else => "opaque",
},
- },
- .@"enum" => "enum",
- .@"struct" => "struct",
- .pointer => "pointer",
- .array => "array",
- else => "opaque",
- };
-}
+ };
+ }
-test "json parses" {
- const parsed = try std.json.parseFromSlice(
- std.json.Value,
- std.testing.allocator,
- json,
- .{},
- );
+ fn writeUpperObjectField(comptime name: []const u8, jws: *std.json.Stringify) std.Io.Writer.Error!void {
+ var upper: [name.len]u8 = undefined;
+ for (name, 0..) |c, i| upper[i] = std.ascii.toUpper(c);
+ try jws.objectField(&upper);
+ }
+
+ fn writeEnumObjectField(
+ comptime enum_name: []const u8,
+ comptime zig_name: []const u8,
+ jws: *std.json.Stringify,
+ ) std.Io.Writer.Error!void {
+ if (std.mem.eql(u8, enum_name, "GhosttyKey") and std.mem.startsWith(u8, zig_name, "key_"))
+ return writeUpperObjectField(zig_name["key_".len..], jws);
+ if (std.mem.eql(u8, enum_name, "GhosttySgrAttributeTag")) {
+ if (std.mem.eql(u8, zig_name, "256_underline_color")) return jws.objectField("UNDERLINE_COLOR_256");
+ if (std.mem.eql(u8, zig_name, "8_bg")) return jws.objectField("BG_8");
+ if (std.mem.eql(u8, zig_name, "8_fg")) return jws.objectField("FG_8");
+ if (std.mem.eql(u8, zig_name, "8_bright_bg")) return jws.objectField("BRIGHT_BG_8");
+ if (std.mem.eql(u8, zig_name, "8_bright_fg")) return jws.objectField("BRIGHT_FG_8");
+ if (std.mem.eql(u8, zig_name, "256_bg")) return jws.objectField("BG_256");
+ if (std.mem.eql(u8, zig_name, "256_fg")) return jws.objectField("FG_256");
+ }
+ if (std.mem.eql(u8, enum_name, "GhosttyTerminalOption") and std.mem.eql(u8, zig_name, "size_cb"))
+ return jws.objectField("SIZE");
+ return writeUpperObjectField(zig_name, jws);
+ }
+
+ fn isBuiltinType(name: []const u8) bool {
+ const builtins = [_][]const u8{
+ "array", "bool", "f32", "f64", "function", "i8", "i16", "i32",
+ "i64", "opaque", "pointer", "u8", "u16", "u32", "u64", "void",
+ };
+ for (builtins) |builtin_name| {
+ if (std.mem.eql(u8, name, builtin_name)) return true;
+ }
+ return false;
+ }
+};
+
+test "manifest parses and is versioned" {
+ const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{});
defer parsed.deinit();
-
const root = parsed.value.object;
+ try std.testing.expectEqual(@as(i64, 1), root.get("schema").?.integer);
+ try std.testing.expect(root.contains("abi"));
+ try std.testing.expect(root.contains("library_version"));
+ const manifest_types = root.get("types").?.object;
+ try std.testing.expectEqual(type_decls.len, manifest_types.count());
+ inline for (type_decls) |decl| try std.testing.expect(manifest_types.contains(decl.name));
- // Verify we have every public struct declared by the C API headers.
- const expected_structs = [_][]const u8{
- "GhosttyAllocator",
- "GhosttyAllocatorVtable",
- "GhosttyBuffer",
- "GhosttyClipboardContent",
- "GhosttyClipboardWrite",
- "GhosttyCodepoints",
- "GhosttyColorPaletteMask",
- "GhosttyColorRgb",
- "GhosttyColorX11Entry",
- "GhosttyDeviceAttributes",
- "GhosttyDeviceAttributesPrimary",
- "GhosttyDeviceAttributesSecondary",
- "GhosttyDeviceAttributesTertiary",
- "GhosttyFormatterScreenExtra",
- "GhosttyFormatterTerminalExtra",
- "GhosttyFormatterTerminalOptions",
- "GhosttyGridRef",
- "GhosttyKittyGraphicsPlacementRenderInfo",
- "GhosttyMouseEncoderSize",
- "GhosttyMousePosition",
- "GhosttyPoint",
- "GhosttyPointCoordinate",
- "GhosttyReader",
- "GhosttyRenderStateColors",
- "GhosttyRenderStateCursor",
- "GhosttyRenderStateRowSelection",
- "GhosttySelection",
- "GhosttySelectionGestureBehaviors",
- "GhosttySelectionGestureGeometry",
- "GhosttySgrAttribute",
- "GhosttySgrUnknown",
- "GhosttySizeReportSize",
- "GhosttyString",
- "GhosttyStyle",
- "GhosttyStyleColor",
- "GhosttySurfacePosition",
- "GhosttySysImage",
- "GhosttyTerminalDesktopNotification",
- "GhosttyTerminalModeConfig",
- "GhosttyTerminalProgressReport",
- "GhosttyTerminalScrollbar",
- "GhosttyTerminalScrollViewport",
- "GhosttyTerminalSelectLineOptions",
- "GhosttyTerminalSelectWordBetweenOptions",
- "GhosttyTerminalSelectWordOptions",
- "GhosttyTerminalSelectionFormatOptions",
- "GhosttyTerminalUnknownSequence",
- "GhosttyTerminalUnknownStringSequence",
- "GhosttyWriter",
- };
- try std.testing.expectEqual(expected_structs.len, root.count());
- for (expected_structs) |name| {
- try std.testing.expect(root.contains(name));
- }
-
- const clipboard_content = root.get("GhosttyClipboardContent").?.object;
- const clipboard_content_fields = clipboard_content.get("fields").?.object;
- try std.testing.expect(clipboard_content_fields.contains("mime"));
- try std.testing.expect(clipboard_content_fields.contains("data"));
-
- const clipboard_write = root.get("GhosttyClipboardWrite").?.object;
- const clipboard_write_fields = clipboard_write.get("fields").?.object;
- try std.testing.expect(clipboard_write_fields.contains("size"));
- try std.testing.expect(clipboard_write_fields.contains("location"));
- try std.testing.expect(clipboard_write_fields.contains("contents"));
- try std.testing.expect(clipboard_write_fields.contains("contents_len"));
-
- const unknown_sequence = root.get("GhosttyTerminalUnknownSequence").?.object;
- const unknown_sequence_fields = unknown_sequence.get("fields").?.object;
- try std.testing.expect(unknown_sequence_fields.contains("tag"));
- try std.testing.expect(unknown_sequence_fields.contains("value"));
-
- const unknown_string = root.get("GhosttyTerminalUnknownStringSequence").?.object;
- const unknown_string_fields = unknown_string.get("fields").?.object;
- try std.testing.expect(unknown_string_fields.contains("truncated"));
- try std.testing.expect(unknown_string_fields.contains("content"));
-
- const render_cursor_fields = root.get("GhosttyRenderStateCursor").?.object
+ const cursor_fields = manifest_types.get("GhosttyRenderStateCursor").?.object
.get("fields").?.object;
- try std.testing.expect(render_cursor_fields.contains("size"));
- try std.testing.expect(render_cursor_fields.contains("viewport_has_value"));
- try std.testing.expect(render_cursor_fields.contains("viewport_x"));
- try std.testing.expect(render_cursor_fields.contains("viewport_y"));
- try std.testing.expect(render_cursor_fields.contains("wide_tail"));
- try std.testing.expect(render_cursor_fields.contains("visible"));
- try std.testing.expect(render_cursor_fields.contains("blinking"));
- try std.testing.expect(render_cursor_fields.contains("password_input"));
- try std.testing.expect(render_cursor_fields.contains("visual_style"));
-
- const reader_fields = root.get("GhosttyReader").?.object
- .get("fields").?.object;
- try std.testing.expect(reader_fields.contains("read"));
- try std.testing.expect(reader_fields.contains("userdata"));
-
- const writer_fields = root.get("GhosttyWriter").?.object
- .get("fields").?.object;
- try std.testing.expect(writer_fields.contains("write"));
- try std.testing.expect(writer_fields.contains("userdata"));
-
- try std.testing.expect(!root.contains("GhosttyTerminalOptions"));
+ try std.testing.expect(cursor_fields.contains("size"));
+ try std.testing.expect(cursor_fields.contains("viewport_has_value"));
+ try std.testing.expect(cursor_fields.contains("viewport_x"));
+ try std.testing.expect(cursor_fields.contains("viewport_y"));
+ try std.testing.expect(cursor_fields.contains("wide_tail"));
+ try std.testing.expect(cursor_fields.contains("visible"));
+ try std.testing.expect(cursor_fields.contains("blinking"));
+ try std.testing.expect(cursor_fields.contains("password_input"));
+ try std.testing.expect(cursor_fields.contains("visual_style"));
}
-test "struct sizes are non-zero" {
- const parsed = try std.json.parseFromSlice(
- std.json.Value,
- std.testing.allocator,
- json,
- .{},
- );
+test "manifest describes enums, arrays, and tagged unions" {
+ const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{});
defer parsed.deinit();
+ const manifest_types = parsed.value.object.get("types").?.object;
- var it = parsed.value.object.iterator();
- while (it.next()) |entry| {
- const struct_info = entry.value_ptr.object;
- const size = struct_info.get("size").?.integer;
- try std.testing.expect(size > 0);
+ const data = manifest_types.get("GhosttyRenderStateData").?.object;
+ try std.testing.expectEqualStrings("enum", data.get("kind").?.string);
+ try std.testing.expectEqual(@as(i64, @intFromEnum(render.Data.dirty)), data.get("values").?.object.get("DIRTY").?.integer);
+
+ const colors = manifest_types.get("GhosttyRenderStateColors").?.object;
+ const palette = colors.get("fields").?.object.get("palette").?.object;
+ try std.testing.expectEqualStrings("array", palette.get("type").?.string);
+ try std.testing.expectEqualStrings("GhosttyColorRgb", palette.get("elem").?.string);
+ try std.testing.expectEqual(@as(i64, 256), palette.get("count").?.integer);
+
+ const style_color = manifest_types.get("GhosttyStyleColor").?.object;
+ const value = style_color.get("fields").?.object.get("value").?.object;
+ try std.testing.expectEqualStrings("GhosttyStyleColorValue", value.get("type").?.string);
+ try std.testing.expectEqualStrings("tag", value.get("tag").?.string);
+ try std.testing.expectEqualStrings("palette", value.get("arms").?.object.get("PALETTE").?.string);
+ try std.testing.expect(value.get("arms").?.object.get("NONE").? == .null);
+}
+
+test "manifest uses public enum names" {
+ const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{});
+ defer parsed.deinit();
+ const manifest_types = parsed.value.object.get("types").?.object;
+
+ const cell_content = manifest_types.get("GhosttyCellContentTag").?.object.get("values").?.object;
+ try std.testing.expect(cell_content.contains("TAG_MAX_VALUE"));
+ try std.testing.expect(!cell_content.contains("MAX_VALUE"));
+
+ const sgr_values = manifest_types.get("GhosttySgrAttributeTag").?.object.get("values").?.object;
+ try std.testing.expectEqual(@as(i64, 9), sgr_values.get("UNDERLINE_COLOR_256").?.integer);
+ try std.testing.expectEqual(@as(i64, 23), sgr_values.get("BG_8").?.integer);
+ try std.testing.expect(!sgr_values.contains("256_UNDERLINE_COLOR"));
+
+ const sgr_arms = manifest_types.get("GhosttySgrAttribute").?.object
+ .get("fields").?.object.get("value").?.object.get("arms").?.object;
+ try std.testing.expectEqualStrings("underline_color_256", sgr_arms.get("UNDERLINE_COLOR_256").?.string);
+ try std.testing.expectEqualStrings("bg_8", sgr_arms.get("BG_8").?.string);
+
+ const terminal_values = manifest_types.get("GhosttyTerminalOption").?.object.get("values").?.object;
+ try std.testing.expectEqual(@as(i64, 6), terminal_values.get("SIZE").?.integer);
+ try std.testing.expect(!terminal_values.contains("SIZE_CB"));
+
+ const osc_values = manifest_types.get("GhosttyOscCommandType").?.object.get("values").?.object;
+ try std.testing.expectEqual(@as(i64, 22), osc_values.get("KITTY_TEXT_SIZING").?.integer);
+ try std.testing.expectEqual(@as(i64, 23), osc_values.get("KITTY_CLIPBOARD_PROTOCOL").?.integer);
+ try std.testing.expectEqual(@as(i64, 24), osc_values.get("KITTY_DND_PROTOCOL").?.integer);
+ try std.testing.expectEqual(@as(i64, 25), osc_values.get("CONTEXT_SIGNAL").?.integer);
+ try std.testing.expect(osc_values.contains("TYPE_MAX_VALUE"));
+ try std.testing.expect(!osc_values.contains("MAX_VALUE"));
+
+ const key_values = manifest_types.get("GhosttyKey").?.object.get("values").?.object;
+ try std.testing.expect(key_values.contains("A"));
+ try std.testing.expect(!key_values.contains("KEY_A"));
+
+ const mode_values = manifest_types.get("GhosttyModeReportState").?.object.get("values").?.object;
+ try std.testing.expectEqual(@as(i64, 0), mode_values.get("NOT_RECOGNIZED").?.integer);
+ try std.testing.expectEqual(@as(i64, 4), mode_values.get("PERMANENTLY_RESET").?.integer);
+}
+
+test "manifest named references resolve" {
+ const parsed = try std.json.parseFromSlice(std.json.Value, std.testing.allocator, json, .{});
+ defer parsed.deinit();
+ const manifest_types = parsed.value.object.get("types").?.object;
+
+ var type_iterator = manifest_types.iterator();
+ while (type_iterator.next()) |type_entry| {
+ const descriptor = type_entry.value_ptr.object;
+ const fields_value = descriptor.get("fields") orelse continue;
+ var field_iterator = fields_value.object.iterator();
+ while (field_iterator.next()) |field_entry| {
+ const field = field_entry.value_ptr.object;
+ const field_type = field.get("type").?.string;
+ if (!Json.isBuiltinType(field_type))
+ try std.testing.expect(manifest_types.contains(field_type));
+
+ if (field.get("elem")) |elem_value| {
+ const elem = elem_value.string;
+ if (!Json.isBuiltinType(elem))
+ try std.testing.expect(manifest_types.contains(elem));
+ }
+
+ if (field.get("arms")) |arms_value| {
+ const union_descriptor = manifest_types.get(field_type).?.object;
+ const union_fields = union_descriptor.get("fields").?.object;
+ const tag_field_name = field.get("tag").?.string;
+ const tag_type_name = fields_value.object.get(tag_field_name).?.object.get("type").?.string;
+ const tag_values = manifest_types.get(tag_type_name).?.object.get("values").?.object;
+ var arm_iterator = arms_value.object.iterator();
+ while (arm_iterator.next()) |arm_entry| {
+ try std.testing.expect(tag_values.contains(arm_entry.key_ptr.*));
+ if (arm_entry.value_ptr.* == .null) continue;
+ try std.testing.expect(union_fields.contains(arm_entry.value_ptr.string));
+ }
+ }
+ }
}
}
diff --git a/src/terminal/point.zig b/src/terminal/point.zig
index f6bf25c39..5937576cf 100644
--- a/src/terminal/point.zig
+++ b/src/terminal/point.zig
@@ -71,7 +71,15 @@ pub const Point = union(Tag) {
@This(),
// Padding: largest variant is Coordinate (u16 + u32 = 6 bytes).
// Use [2]u64 (16 bytes) for future expansion.
- [2]u64,
+ .{
+ .padding = [2]u64,
+ .field_renames = .{
+ .active = "coordinate",
+ .viewport = "coordinate",
+ .screen = "coordinate",
+ .history = "coordinate",
+ },
+ },
);
pub const C = c_union.C;
pub const CValue = c_union.CValue;
diff --git a/src/terminal/sgr.zig b/src/terminal/sgr.zig
index e9b9354c3..3fecd4469 100644
--- a/src/terminal/sgr.zig
+++ b/src/terminal/sgr.zig
@@ -161,7 +161,18 @@ pub const Attribute = union(Tag) {
// Largest variant is Unknown.C: 2 pointers + 2 usize = 32 bytes on 64-bit.
// We use [8]u64 (64 bytes) to allow room for future expansion while
// maintaining ABI compatibility.
- [8]u64,
+ .{
+ .padding = [8]u64,
+ .field_renames = .{
+ .@"256_underline_color" = "underline_color_256",
+ .@"8_bg" = "bg_8",
+ .@"8_fg" = "fg_8",
+ .@"8_bright_bg" = "bright_bg_8",
+ .@"8_bright_fg" = "bright_fg_8",
+ .@"256_bg" = "bg_256",
+ .@"256_fg" = "fg_256",
+ },
+ },
);
pub const Value = c_union.Value;
pub const C = c_union.C;
diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig
index afbfa719c..105cd1e4f 100644
--- a/src/terminal/stream.zig
+++ b/src/terminal/stream.zig
@@ -236,7 +236,7 @@ pub const Action = union(Key) {
@This(),
// TODO: Before shipping an ABI-compatible libghostty, verify this.
// This was just arbitrarily chosen for now.
- [16]u64,
+ .{ .padding = [16]u64 },
);
pub const Tag = c_union.Tag;
pub const Value = c_union.Value;