From 531d4a480ed01ef549ec670db9c33e62c718fb48 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 16 Jul 2025 20:49:53 -0700 Subject: [PATCH] apprt/gtk-ng: hook up all the bindings for the config errors dialog --- src/apprt/gtk-ng/class/application.zig | 23 ++++-- src/apprt/gtk-ng/class/config.zig | 76 +++++++++++++++++++ ...ig_errors.zig => config_errors_dialog.zig} | 40 +++++++++- src/apprt/gtk-ng/class/window.zig | 18 +++-- .../gtk-ng/ui/1.5/config-errors-dialog.blp | 3 +- 5 files changed, 144 insertions(+), 16 deletions(-) rename src/apprt/gtk-ng/class/{config_errors.zig => config_errors_dialog.zig} (63%) diff --git a/src/apprt/gtk-ng/class/application.zig b/src/apprt/gtk-ng/class/application.zig index ef1c76366..511c68eee 100644 --- a/src/apprt/gtk-ng/class/application.zig +++ b/src/apprt/gtk-ng/class/application.zig @@ -21,6 +21,7 @@ const adw_version = @import("../adw_version.zig"); const gtk_version = @import("../gtk_version.zig"); const GhosttyConfig = @import("config.zig").GhosttyConfig; const GhosttyWindow = @import("window.zig").GhosttyWindow; +const ConfigErrorsDialog = @import("config_errors_dialog.zig").ConfigErrorsDialog; const log = std.log.scoped(.gtk_ghostty_application); @@ -320,6 +321,11 @@ pub const GhosttyApplication = extern struct { fn startup(self: *GhosttyApplication) callconv(.C) void { log.debug("startup", .{}); + gio.Application.virtual_methods.startup.call( + Class.parent, + self.as(Parent), + ); + // Setup our event loop self.startupXev(); @@ -331,10 +337,15 @@ pub const GhosttyApplication = extern struct { log.warn("TODO", .{}); }; - gio.Application.virtual_methods.startup.call( - Class.parent, - self.as(Parent), - ); + // If we have any config diagnostics from loading, then we + // show the diagnostics dialog. We show this one as a general + // modal (not to any specific window) because we don't even + // know if the window will load. + const priv = self.private(); + if (priv.config.hasDiagnostics()) { + const dialog: *ConfigErrorsDialog = .new(priv.config); + dialog.present(null); + } } /// Configure libxev to use a specific backend. @@ -474,8 +485,8 @@ pub const GhosttyApplication = extern struct { self.as(Parent), ); - const win = GhosttyWindow.new(self); - gtk.Window.present(win.as(gtk.Window)); + // const win = GhosttyWindow.new(self); + // gtk.Window.present(win.as(gtk.Window)); } fn finalize(self: *GhosttyApplication) callconv(.C) void { diff --git a/src/apprt/gtk-ng/class/config.zig b/src/apprt/gtk-ng/class/config.zig index b9c42edc2..1342dfb50 100644 --- a/src/apprt/gtk-ng/class/config.zig +++ b/src/apprt/gtk-ng/class/config.zig @@ -29,6 +29,36 @@ pub const GhosttyConfig = extern struct { .private = .{ .Type = Private, .offset = &Private.offset }, }); + pub const properties = struct { + pub const @"diagnostics-buffer" = gobject.ext.defineProperty( + "diagnostics-buffer", + Self, + ?*gtk.TextBuffer, + .{ + .nick = "Dignostics Buffer", + .blurb = "A TextBuffer that contains the diagnostics.", + .default = null, + .accessor = .{ + .getter = Self.diagnosticsBuffer, + }, + }, + ); + + pub const @"has-diagnostics" = gobject.ext.defineProperty( + "has-diagnostics", + Self, + bool, + .{ + .nick = "has-diagnostics", + .blurb = "Whether the configuration has diagnostics.", + .default = false, + .accessor = .{ + .getter = Self.hasDiagnostics, + }, + }, + ); + }; + const Private = struct { config: Config, @@ -55,6 +85,48 @@ pub const GhosttyConfig = extern struct { return &self.private().config; } + /// Get the mutable configuration. This is usually NOT recommended + /// because any changes to the config won't be propagated to anyone + /// with a reference to this object. If you know what you're doing, then + /// you can use this. + pub fn getMut(self: *Self) *Config { + return &self.private().config; + } + + /// Returns whether this configuration has any diagnostics. + pub fn hasDiagnostics(self: *Self) bool { + const config = self.get(); + return !config._diagnostics.empty(); + } + + /// Reads the diagnostics of this configuration as a TextBuffer, + /// or returns null if there are no diagnostics. + pub fn diagnosticsBuffer(self: *Self) ?*gtk.TextBuffer { + const config = self.get(); + if (config._diagnostics.empty()) return null; + + const text_buf: *gtk.TextBuffer = .new(null); + errdefer text_buf.unref(); + + var buf: [4095:0]u8 = undefined; + var fbs = std.io.fixedBufferStream(&buf); + for (config._diagnostics.items()) |diag| { + fbs.reset(); + diag.write(fbs.writer()) catch |err| { + log.warn( + "error writing diagnostic to buffer err={}", + .{err}, + ); + continue; + }; + + text_buf.insertAtCursor(&buf, @intCast(fbs.pos)); + text_buf.insertAtCursor("\n", 1); + } + + return text_buf; + } + fn finalize(self: *Self) callconv(.C) void { self.private().config.deinit(); @@ -91,6 +163,10 @@ pub const GhosttyConfig = extern struct { fn init(class: *Class) callconv(.C) void { gobject.Object.virtual_methods.finalize.implement(class, &finalize); + gobject.ext.registerProperties(class, &.{ + properties.@"diagnostics-buffer", + properties.@"has-diagnostics", + }); } }; }; diff --git a/src/apprt/gtk-ng/class/config_errors.zig b/src/apprt/gtk-ng/class/config_errors_dialog.zig similarity index 63% rename from src/apprt/gtk-ng/class/config_errors.zig rename to src/apprt/gtk-ng/class/config_errors_dialog.zig index fdf16602b..7824c437b 100644 --- a/src/apprt/gtk-ng/class/config_errors.zig +++ b/src/apprt/gtk-ng/class/config_errors_dialog.zig @@ -5,11 +5,11 @@ const gtk = @import("gtk"); const gresource = @import("../build/gresource.zig"); const adw_version = @import("../adw_version.zig"); -const GhosttyConfig = @import("config.zig").GhosttyConfig; +const Config = @import("config.zig").GhosttyConfig; const log = std.log.scoped(.gtk_ghostty_window); -pub const GhosttyConfigErrors = extern struct { +pub const ConfigErrorsDialog = extern struct { const Self = @This(); parent_instance: Parent, @@ -19,18 +19,38 @@ pub const GhosttyConfigErrors = extern struct { adw.MessageDialog; pub const getGObjectType = gobject.ext.defineClass(Self, .{ + .name = "GhosttyConfigErrorsDialog", .instanceInit = &init, .classInit = &Class.init, .parent_class = &Class.parent, .private = .{ .Type = Private, .offset = &Private.offset }, }); + pub const properties = struct { + pub const config = gobject.ext.defineProperty( + "config", + Self, + ?*Config, + .{ + .nick = "config", + .blurb = "The configuration that this dialog is showing errors for.", + .default = null, + .accessor = gobject.ext.privateFieldAccessor( + Self, + Private, + &Private.offset, + "config", + ), + }, + ); + }; + const Private = struct { - _todo: u8 = 0, + config: ?*Config, var offset: c_int = 0; }; - pub fn new(config: *GhosttyConfig) *Self { + pub fn new(config: *Config) *Self { return gobject.ext.newInstance(Self, .{ .config = config, }); @@ -40,6 +60,13 @@ pub const GhosttyConfigErrors = extern struct { gtk.Widget.initTemplate(self.as(gtk.Widget)); } + pub fn present(self: *Self, parent: ?*gtk.Widget) void { + switch (Parent) { + adw.AlertDialog => self.as(adw.Dialog).present(parent), + else => unreachable, + } + } + pub fn as(win: *Self, comptime T: type) *T { return gobject.ext.as(T, win); } @@ -58,9 +85,14 @@ pub const GhosttyConfigErrors = extern struct { .minor = 5, .name = "config-errors-dialog", }), + else => unreachable, }, ); + + gobject.ext.registerProperties(class, &.{ + properties.config, + }); } pub fn as(class: *Class, comptime T: type) *T { diff --git a/src/apprt/gtk-ng/class/window.zig b/src/apprt/gtk-ng/class/window.zig index df8b99cee..b1bb27cba 100644 --- a/src/apprt/gtk-ng/class/window.zig +++ b/src/apprt/gtk-ng/class/window.zig @@ -20,7 +20,7 @@ pub const GhosttyWindow = extern struct { }); const Private = struct { - _todo: u8 = 0, + _todo: u8, var offset: c_int = 0; }; @@ -28,12 +28,20 @@ pub const GhosttyWindow = extern struct { return gobject.ext.newInstance(Self, .{ .application = app }); } - fn init(win: *GhosttyWindow, _: *Class) callconv(.C) void { - gtk.Widget.initTemplate(win.as(gtk.Widget)); + fn init(self: *GhosttyWindow, _: *Class) callconv(.C) void { + gtk.Widget.initTemplate(self.as(gtk.Widget)); } - pub fn as(win: *Self, comptime T: type) *T { - return gobject.ext.as(T, win); + pub fn as(self: *Self, comptime T: type) *T { + return gobject.ext.as(T, self); + } + + fn private(self: *Self) *Private { + return gobject.ext.impl_helpers.getPrivate( + self, + Private, + Private.offset, + ); } pub const Class = extern struct { diff --git a/src/apprt/gtk-ng/ui/1.5/config-errors-dialog.blp b/src/apprt/gtk-ng/ui/1.5/config-errors-dialog.blp index 2669ef390..17ecac4e4 100644 --- a/src/apprt/gtk-ng/ui/1.5/config-errors-dialog.blp +++ b/src/apprt/gtk-ng/ui/1.5/config-errors-dialog.blp @@ -1,7 +1,7 @@ using Gtk 4.0; using Adw 1; -Adw.AlertDialog config_errors_dialog { +template $GhosttyConfigErrorsDialog: Adw.AlertDialog { heading: _("Configuration Errors"); body: _("One or more configuration errors were found. Please review the errors below, and either reload your configuration or ignore these errors."); @@ -21,6 +21,7 @@ Adw.AlertDialog config_errors_dialog { bottom-margin: 8; left-margin: 8; right-margin: 8; + buffer: bind (template.config as <$GhosttyConfig>).diagnostics-buffer; } }; }