//! Inter-process Communication to a running Ghostty instance from a separate //! process. const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; pub const Errors = error{ /// The IPC failed. If a function returns this error, it's expected that /// an a more specific error message will have been written to stderr (or /// otherwise shown to the user in an appropriate way). IPCFailed, }; pub const Target = union(Key) { /// Open up a new window in a custom instance of Ghostty. class: [:0]const u8, /// Detect which instance to open a new window in. detect, // Sync with: ghostty_ipc_target_tag_e pub const Key = enum(c_int) { class, detect, }; // Sync with: ghostty_ipc_target_u pub const CValue = extern union { class: [*:0]const u8, detect: void, }; // Sync with: ghostty_ipc_target_s pub const C = extern struct { key: Key, value: CValue, }; /// Convert to ghostty_ipc_target_s. pub fn cval(self: Target) C { return .{ .key = @as(Key, self), .value = switch (self) { .class => |class| .{ .class = class.ptr }, .detect => .{ .detect = {} }, }, }; } }; pub const Action = union(enum) { // A GUIDE TO ADDING NEW ACTIONS: // // 1. Add the action to the `Key` enum. The order of the enum matters // because it maps directly to the libghostty C enum. For ABI // compatibility, new actions should be added to the end of the enum. // // 2. Add the action and optional value to the Action union. // // 3. If the value type is not void, ensure the value is C ABI // compatible (extern). If it is not, add a `C` decl to the value // and a `cval` function to convert to the C ABI compatible value. // // 4. Update `include/ghostty.h`: add the new key, value, and union // entry. If the value type is void then only the key needs to be // added. Ensure the order matches exactly with the Zig code. /// The arguments to pass to Ghostty as the command. new_window: NewWindow, pub const NewWindow = struct { /// A list of command arguments to launch in the new window. If this is /// `null` the command configured in the config or the user's default /// shell should be launched. /// /// It is an error for this to be non-`null`, but zero length. arguments: ?[][:0]const u8, pub const C = extern struct { /// null terminated list of arguments /// it will be null itself if there are no arguments arguments: ?[*]?[*:0]const u8, pub fn deinit(self: *NewWindow.C, alloc: Allocator) void { if (self.arguments) |arguments| alloc.free(arguments); } }; pub fn cval(self: *NewWindow, alloc: Allocator) Allocator.Error!NewWindow.C { var result: NewWindow.C = undefined; if (self.arguments) |arguments| { result.arguments = try alloc.alloc([*:0]const u8, arguments.len + 1); for (arguments, 0..) |argument, i| result.arguments[i] = argument.ptr; // add null terminator result.arguments[arguments.len] = null; } else { result.arguments = null; } return result; } }; /// Sync with: ghostty_ipc_action_tag_e pub const Key = enum(c_uint) { new_window, }; /// Sync with: ghostty_ipc_action_u pub const CValue = cvalue: { const key_fields = @typeInfo(Key).@"enum".fields; var union_fields: [key_fields.len]std.builtin.Type.UnionField = undefined; for (key_fields, 0..) |field, i| { const action = @unionInit(Action, field.name, undefined); const Type = t: { const Type = @TypeOf(@field(action, field.name)); // Types can provide custom types for their CValue. if (Type != void and @hasDecl(Type, "C")) break :t Type.C; break :t Type; }; union_fields[i] = .{ .name = field.name, .type = Type, .alignment = @alignOf(Type), }; } break :cvalue @Type(.{ .@"union" = .{ .layout = .@"extern", .tag_type = null, .fields = &union_fields, .decls = &.{}, } }); }; /// Sync with: ghostty_ipc_action_s pub const C = extern struct { key: Key, value: CValue, }; comptime { // For ABI compatibility, we expect that this is our union size. // At the time of writing, we don't promise ABI compatibility // so we can change this but I want to be aware of it. assert(@sizeOf(CValue) == switch (@sizeOf(usize)) { 4 => 4, 8 => 8, else => unreachable, }); } /// Returns the value type for the given key. pub fn Value(comptime key: Key) type { inline for (@typeInfo(Action).@"union".fields) |field| { const field_key = @field(Key, field.name); if (field_key == key) return field.type; } unreachable; } /// Convert to ghostty_ipc_action_s. pub fn cval(self: Action, alloc: Allocator) C { const value: CValue = switch (self) { inline else => |v, tag| @unionInit( CValue, @tagName(tag), if (@TypeOf(v) != void and @hasDecl(@TypeOf(v), "cval")) v.cval(alloc) else v, ), }; return .{ .key = @as(Key, self), .value = value, }; } };