diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/apprt/gtk/class.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/apprt/gtk/class.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/apprt/gtk/class.zig | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/apprt/gtk/class.zig b/vendor/libghostty-vt/src/apprt/gtk/class.zig new file mode 100644 index 00000000..942666cf --- /dev/null +++ b/vendor/libghostty-vt/src/apprt/gtk/class.zig @@ -0,0 +1,367 @@ +//! This files contains all the GObject classes for the GTK apprt +//! along with helpers to work with them. + +const std = @import("std"); +const glib = @import("glib"); +const gobject = @import("gobject"); +const gtk = @import("gtk"); + +const ext = @import("ext.zig"); +pub const Application = @import("class/application.zig").Application; +pub const Window = @import("class/window.zig").Window; +pub const Config = @import("class/config.zig").Config; +pub const Surface = @import("class/surface.zig").Surface; + +/// Common methods for all GObject classes we create. +pub fn Common( + comptime Self: type, + comptime Private: ?type, +) type { + return struct { + /// Upcast our type to a parent type or interface. This will fail at + /// compile time if the cast isn't 100% safe. For unsafe casts, + /// use `gobject.ext.cast` instead. We don't have a helper for that + /// because its uncommon and unsafe behavior should be noisier. + pub fn as(self: *Self, comptime T: type) *T { + return gobject.ext.as(T, self); + } + + /// Increase the reference count of the object. + pub fn ref(self: *Self) *Self { + return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object)))); + } + + /// If the reference count is 1 and the object is floating, clear the + /// floating attribute. Otherwise, increase the reference count by 1. + pub fn refSink(self: *Self) *Self { + return @ptrCast(@alignCast(gobject.Object.refSink(self.as(gobject.Object)))); + } + + /// Decrease the reference count of the object. + pub fn unref(self: *Self) void { + gobject.Object.unref(self.as(gobject.Object)); + } + + /// Access the private data of the object. This should be forwarded + /// via a non-pub const usually. + pub const private = if (Private) |P| (struct { + fn private(self: *Self) *P { + return gobject.ext.impl_helpers.getPrivate( + self, + P, + P.offset, + ); + } + }).private else {}; + + /// Get the class for the object. + /// + /// This _seems_ ugly and unsafe but this is how GObject + /// works under the hood. From the [GObject Type System + /// Concepts](https://docs.gtk.org/gobject/concepts.html) documentation: + /// + /// Every object must define two structures: its class structure + /// and its instance structure. All class structures must contain + /// as first member a GTypeClass structure. All instance structures + /// must contain as first member a GTypeInstance structure. + /// … + /// These constraints allow the type system to make sure that + /// every object instance (identified by a pointer to the object’s + /// instance structure) contains in its first bytes a pointer to the + /// object’s class structure. + /// … + /// The C standard mandates that the first field of a C structure is + /// stored starting in the first byte of the buffer used to hold the + /// structure’s fields in memory. This means that the first field of + /// an instance of an object B is A’s first field which in turn is + /// GTypeInstance‘s first field which in turn is g_class, a pointer + /// to B’s class structure. + /// + /// This means that to access the class structure for an object you cast it + /// to `*gobject.TypeInstance` and then access the `f_g_class` field. + /// + /// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L555-571 + /// https://gitlab.gnome.org/GNOME/glib/-/blob/2c08654b62d52a31c4e4d13d7d85e12b989e72be/gobject/gtype.h#L2673 + /// + pub fn getClass(self: *Self) ?*Self.Class { + const type_instance: *gobject.TypeInstance = @ptrCast(self); + return @ptrCast(type_instance.f_g_class orelse return null); + } + + /// Define a virtual method. The `Self.Class` type must have a field + /// named `name` which is a function pointer in the following form: + /// + /// ?*const fn (*Self) callconv(.c) void + /// + /// The virtual method may take additional parameters and specify + /// a non-void return type. The parameters and return type must be + /// valid for the C calling convention. + pub fn defineVirtualMethod( + comptime name: [:0]const u8, + ) type { + return struct { + pub fn call( + class: anytype, + object: *ClassInstance(@TypeOf(class)), + params: anytype, + ) (fn_info.return_type orelse void) { + const func = @field( + gobject.ext.as(Self.Class, class), + name, + ).?; + @call(.auto, func, .{ + gobject.ext.as(Self, object), + } ++ params); + } + + pub fn implement( + class: anytype, + implementation: *const ImplementFunc(@TypeOf(class)), + ) void { + @field(gobject.ext.as( + Self.Class, + class, + ), name) = @ptrCast(implementation); + } + + /// The type info of the virtual method. + const fn_info = fn_info: { + // This is broken down like this so its slightly more + // readable. We expect a field named "name" on the Class + // with the rough type of `?*const fn` and we need the + // function info. + const Field = @FieldType(Self.Class, name); + const opt = @typeInfo(Field).optional; + const ptr = @typeInfo(opt.child).pointer; + break :fn_info @typeInfo(ptr.child).@"fn"; + }; + + /// The instance type for a class. + fn ClassInstance(comptime T: type) type { + return @typeInfo(T).pointer.child.Instance; + } + + /// The function type for implementations. This is the same type + /// as the virtual method but the self parameter points to the + /// target instead of the original class. + fn ImplementFunc(comptime T: type) type { + var params: [fn_info.params.len]std.builtin.Type.Fn.Param = undefined; + @memcpy(¶ms, fn_info.params); + params[0].type = *ClassInstance(T); + return @Type(.{ .@"fn" = .{ + .calling_convention = fn_info.calling_convention, + .is_generic = fn_info.is_generic, + .is_var_args = fn_info.is_var_args, + .return_type = fn_info.return_type, + .params = ¶ms, + } }); + } + }; + } + + /// A helper that creates a property that reads and writes a + /// private field with only shallow copies. This is good for primitives + /// such as bools, numbers, etc. + pub fn privateShallowFieldAccessor( + comptime name: []const u8, + ) gobject.ext.Accessor( + Self, + @FieldType(Private.?, name), + ) { + return gobject.ext.privateFieldAccessor( + Self, + Private.?, + &Private.?.offset, + name, + ); + } + + /// A helper that can be used to create a property that reads and + /// writes a private boxed gobject field type. + /// + /// Reading the property will result in allocating a pointer and + /// setting it will free the previous pointer. + /// + /// The object class (Self) must still free the private field + /// in finalize! + pub fn privateBoxedFieldAccessor( + comptime name: []const u8, + ) gobject.ext.Accessor( + Self, + @FieldType(Private.?, name), + ) { + return .{ + .getter = &struct { + fn get(self: *Self, value: *gobject.Value) void { + gobject.ext.Value.set( + value, + @field(private(self), name), + ); + } + }.get, + .setter = &struct { + fn set(self: *Self, value: *const gobject.Value) void { + const priv = private(self); + if (@field(priv, name)) |v| { + ext.boxedFree( + @typeInfo(@TypeOf(v)).pointer.child, + v, + ); + } + + const T = @TypeOf(@field(priv, name)); + @field( + priv, + name, + ) = gobject.ext.Value.dup(value, T); + } + }.set, + }; + } + + /// A helper that can be used to create a property that reads and + /// writes a private field gobject field type (reference counted). + /// + /// Reading the property will result in taking a reference to the + /// value and writing the property will unref the previous value. + /// + /// The object class (Self) must still free the private field + /// in finalize! + pub fn privateObjFieldAccessor( + comptime name: []const u8, + ) gobject.ext.Accessor( + Self, + @FieldType(Private.?, name), + ) { + return .{ + .getter = &struct { + fn get(self: *Self, value: *gobject.Value) void { + gobject.ext.Value.set( + value, + @field(private(self), name), + ); + } + }.get, + .setter = &struct { + fn set(self: *Self, value: *const gobject.Value) void { + const priv = private(self); + if (@field(priv, name)) |v| v.unref(); + + const T = @TypeOf(@field(priv, name)); + @field( + priv, + name, + ) = gobject.ext.Value.dup(value, T); + } + }.set, + }; + } + + /// A helper that can be used to create a property that reads and + /// writes a private `?[:0]const u8` field type. + /// + /// Reading the property will result in a copy of the string + /// and callers are responsible for freeing it. + /// + /// Writing the property will free the previous value and copy + /// the new value into the private field. + /// + /// The object class (Self) must still free the private field + /// in finalize! + pub fn privateStringFieldAccessor( + comptime name: []const u8, + ) gobject.ext.Accessor( + Self, + @FieldType(Private.?, name), + ) { + const S = struct { + fn getter(self: *Self) ?[:0]const u8 { + return @field(private(self), name); + } + + fn setter(self: *Self, value: ?[:0]const u8) void { + const priv = private(self); + if (@field(priv, name)) |v| { + glib.free(@ptrCast(@constCast(v))); + } + + // We don't need to copy this because it was already + // copied by the typedAccessor. + @field(priv, name) = value; + } + }; + + return gobject.ext.typedAccessor( + Self, + ?[:0]const u8, + .{ + .getter = S.getter, + .getter_transfer = .none, + .setter = S.setter, + .setter_transfer = .full, + }, + ); + } + + /// Common class functions. + pub const Class = struct { + pub fn as(class: *Self.Class, comptime T: type) *T { + return gobject.ext.as(T, class); + } + + /// Bind a template child to a private entry in the class. + pub const bindTemplateChildPrivate = if (Private) |P| (struct { + pub fn bindTemplateChildPrivate( + class: *Self.Class, + comptime name: [:0]const u8, + comptime options: gtk.ext.BindTemplateChildOptions, + ) void { + gtk.ext.impl_helpers.bindTemplateChildPrivate( + class, + name, + P, + P.offset, + options, + ); + } + }).bindTemplateChildPrivate else {}; + + /// Bind a function pointer to a template callback symbol. + pub fn bindTemplateCallback( + class: *Self.Class, + comptime name: [:0]const u8, + comptime func: anytype, + ) void { + { + const ptr_ti = @typeInfo(@TypeOf(func)); + if (ptr_ti != .pointer) { + @compileError("bound function must be a pointer type"); + } + if (ptr_ti.pointer.size != .one) { + @compileError("bound function must be a pointer to a function"); + } + + const func_ti = @typeInfo(ptr_ti.pointer.child); + if (func_ti != .@"fn") { + @compileError("bound function must be a function pointer"); + } + if (func_ti.@"fn".return_type == bool) { + // glib booleans are ints and returning a Zig bool type + // I think uses a byte and causes ABI issues. + @compileError("bound function must return c_int instead of bool"); + } + } + + gtk.Widget.Class.bindTemplateCallbackFull( + class.as(gtk.Widget.Class), + name, + @ptrCast(func), + ); + } + }; + }; +} + +test { + @import("std").testing.refAllDecls(@This()); +} |
