From e76a151b42f7f988e9a78c581db822d7b687e032 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 16 Jul 2025 12:46:15 -0700 Subject: [PATCH] apprt/gtk-ng: GhosttyConfig --- src/apprt/gtk-ng.zig | 6 ++ src/apprt/gtk-ng/class/application.zig | 3 - src/apprt/gtk-ng/class/config.zig | 107 +++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/apprt/gtk-ng/class/config.zig diff --git a/src/apprt/gtk-ng.zig b/src/apprt/gtk-ng.zig index 19d450a54..ae57dd6fa 100644 --- a/src/apprt/gtk-ng.zig +++ b/src/apprt/gtk-ng.zig @@ -7,3 +7,9 @@ pub const resourcesDir = internal_os.resourcesDir; // The exported API, custom for the apprt. pub const GhosttyApplication = @import("gtk-ng/class/application.zig").GhosttyApplication; +pub const GhosttyWindow = @import("gtk-ng/class/window.zig").GhosttyWindow; +pub const GhosttyConfig = @import("gtk-ng/class/config.zig").GhosttyConfig; + +test { + @import("std").testing.refAllDecls(@This()); +} diff --git a/src/apprt/gtk-ng/class/application.zig b/src/apprt/gtk-ng/class/application.zig index e713d1de2..07dc365a3 100644 --- a/src/apprt/gtk-ng/class/application.zig +++ b/src/apprt/gtk-ng/class/application.zig @@ -464,9 +464,6 @@ pub const GhosttyApplication = extern struct { } fn activate(self: *GhosttyApplication) callconv(.C) void { - // This is called when the application is activated, but we - // don't need to do anything here since we handle activation - // in the `run` method. log.debug("activate", .{}); // Call the parent activate method. diff --git a/src/apprt/gtk-ng/class/config.zig b/src/apprt/gtk-ng/class/config.zig new file mode 100644 index 000000000..74ab14923 --- /dev/null +++ b/src/apprt/gtk-ng/class/config.zig @@ -0,0 +1,107 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const adw = @import("adw"); +const gobject = @import("gobject"); +const gtk = @import("gtk"); + +const configpkg = @import("../../../config.zig"); +const Config = configpkg.Config; + +const log = std.log.scoped(.gtk_ghostty_config); + +/// Wraps a `Ghostty.Config` object in a GObject so it can be reference +/// counted. When this object is freed, the underlying config is also freed. +/// +/// It is highly recommended to NOT take a reference to this object, +/// since configuration takes up a lot of memory (relatively). Instead, +/// receivers of this should usually create a `DerivedConfig` struct from +/// this, copy any memory they require, and own that structure instead. +/// +/// This can also expose helpers to access configuration in ways that +/// may be more egonomic to GTK primitives. +pub const GhosttyConfig = extern struct { + const Self = @This(); + parent_instance: Parent, + pub const Parent = gobject.Object; + pub const getGObjectType = gobject.ext.defineClass(Self, .{ + .classInit = &Class.init, + .parent_class = &Class.parent, + .private = .{ .Type = Private, .offset = &Private.offset }, + }); + + const Private = struct { + config: Config, + + var offset: c_int = 0; + }; + + /// Create a new GhosttyConfig from a loaded configuration. + /// + /// This clones the given configuration, so it is safe for the + /// caller to free the original configuration after this call. + pub fn new(alloc: Allocator, config: *const Config) Allocator.Error!*Self { + const self = gobject.ext.newInstance(Self, .{}); + errdefer self.unref(); + + const priv = self.private(); + priv.config = try config.clone(alloc); + + return self; + } + + /// Get the wrapped configuration. It's unsafe to store this or access + /// it in any way that may live beyond the lifetime of this object. + pub fn getConfig(self: *Self) *const Config { + return self.private().config; + } + + fn finalize(self: *Self) callconv(.C) void { + self.private().config.deinit(); + + gobject.Object.virtual_methods.finalize.call( + Class.parent, + self.as(Parent), + ); + } + + pub fn as(self: *Self, comptime T: type) *T { + return gobject.ext.as(T, self); + } + + pub fn ref(self: *Self) *Self { + return @ptrCast(@alignCast(gobject.Object.ref(self.as(gobject.Object)))); + } + + pub fn unref(self: *Self) void { + gobject.Object.unref(self.as(gobject.Object)); + } + + fn private(self: *Self) *Private { + return gobject.ext.impl_helpers.getPrivate( + self, + Private, + Private.offset, + ); + } + + pub const Class = extern struct { + parent_class: Parent.Class, + var parent: *Parent.Class = undefined; + pub const Instance = Self; + + fn init(class: *Class) callconv(.C) void { + gobject.Object.virtual_methods.finalize.implement(class, &finalize); + } + }; +}; + +// This test verifies our memory management works as expected. Since +// we use the testing allocator any leaks are detected. +test "GhosttyConfig" { + const testing = std.testing; + const alloc = testing.allocator; + var config: Config = try .default(alloc); + defer config.deinit(); + const obj: *GhosttyConfig = try .new(alloc, &config); + obj.unref(); +}