apprt/gtk-ng: reload config

This commit is contained in:
Mitchell Hashimoto
2025-07-27 13:46:24 -07:00
parent ccde429bde
commit 53c7b8922f
2 changed files with 111 additions and 39 deletions

View File

@ -508,6 +508,8 @@ pub const Application = extern struct {
.progress_report => return Action.progressReport(target, value),
.reload_config => try Action.reloadConfig(self, target, value),
.render => Action.render(target),
.ring_bell => Action.ringBell(target),
@ -530,7 +532,6 @@ pub const Application = extern struct {
.equalize_splits,
.goto_split,
.open_config,
.reload_config,
.inspector,
.desktop_notification,
.present_terminal,
@ -573,29 +574,6 @@ pub const Application = extern struct {
return true;
}
/// Reload the configuration for the application and propagate it
/// across the entire application and all terminals.
pub fn reloadConfig(self: *Self) !void {
const alloc = self.allocator();
// Read our new config. We can always deinit this because
// we'll clone and store it if libghostty accepts it and
// emits a `config_change` action.
var config = try CoreConfig.load(alloc);
defer config.deinit();
// Notify the app that we've updated.
const priv = self.private();
try priv.core_app.updateConfig(priv.rt_app, &config);
}
/// Returns the configuration for this application.
///
/// The reference count is increased.
pub fn getConfig(self: *Self) *Config {
return self.private().config.ref();
}
/// Returns the core app associated with this application. This is
/// not a reference-counted type so you should not store this.
pub fn core(self: *Self) *CoreApp {
@ -662,6 +640,31 @@ pub const Application = extern struct {
}
}
//---------------------------------------------------------------
// Properties
/// Returns the configuration for this application.
///
/// The reference count is increased.
pub fn getConfig(self: *Self) *Config {
return self.private().config.ref();
}
/// Set the configuration for this application. The reference count
/// is increased on the new configuration and the old one is
/// unreferenced.
///
/// If the config has errors this may show the config errors dialog.
fn setConfig(self: *Self, config: *Config) void {
const priv = self.private();
priv.config.unref();
priv.config = config.ref();
self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
// Show our errors if we have any
self.showConfigErrorsDialog();
}
//---------------------------------------------------------------
// Libghostty Callbacks
@ -794,9 +797,10 @@ pub const Application = extern struct {
// For action names:
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
const actions = .{
.{ "quit", actionQuit, null },
.{ "new-window", actionNewWindow, null },
.{ "new-window-command", actionNewWindow, as_variant_type },
.{ "quit", actionQuit, null },
.{ "reload-config", actionReloadConfig, null },
};
const action_map = self.as(gio.ActionMap);
@ -961,7 +965,12 @@ pub const Application = extern struct {
const priv = self.private();
priv.config_errors_dialog.set(null);
self.reloadConfig() catch |err| {
// Reload our config as if the app reloaded.
Action.reloadConfig(
self,
.app,
.{},
) catch |err| {
// If we fail to reload the configuration, then we want the
// user to know it. For now we log but we should show another
// GUI.
@ -1016,6 +1025,17 @@ pub const Application = extern struct {
dialog.present(null);
}
fn actionReloadConfig(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Self,
) callconv(.c) void {
const priv = self.private();
priv.core_app.performAction(self.rt(), .reload_config) catch |err| {
log.warn("error reloading config err={}", .{err});
};
}
fn actionQuit(
_: *gio.SimpleAction,
_: ?*glib.Variant,
@ -1138,21 +1158,11 @@ const Action = struct {
// Wrap our config in a GObject. This will clone it.
const alloc = self.allocator();
const config_obj: *Config = try .new(alloc, new_config);
errdefer config_obj.unref();
defer config_obj.unref();
switch (target) {
// TODO: when we implement surfaces in gtk-ng
.surface => @panic("TODO"),
.app => {
// Set it on our private
const priv = self.private();
priv.config.unref();
priv.config = config_obj;
// Show our errors if we have any
self.showConfigErrorsDialog();
},
.surface => |core| core.rt_surface.surface.setConfig(config_obj),
.app => self.setConfig(config_obj),
}
}
@ -1219,6 +1229,19 @@ const Action = struct {
parent: ?*CoreSurface,
) !void {
const win = Window.new(self, parent);
// Setup a binding so that whenever our config changes so does the
// window. There's never a time when the window config should be out
// of sync with the application config.
_ = gobject.Object.bindProperty(
self.as(gobject.Object),
"config",
win.as(gobject.Object),
"config",
.{},
);
// Show the window
gtk.Window.present(win.as(gtk.Window));
}
@ -1263,6 +1286,47 @@ const Action = struct {
};
}
/// Reload the configuration for the application and propagate it
/// across the entire application and all terminals.
pub fn reloadConfig(
self: *Application,
target: apprt.Target,
opts: apprt.action.ReloadConfig,
) !void {
// Tell systemd that reloading has started.
systemd.notify.reloading();
// When we exit this function tell systemd that reloading has finished.
defer systemd.notify.ready();
// Get our config object.
const config: *Config = config: {
// Soft-reloading applies conditional logic to the existing loaded
// config so we return that as-is (but take a reference).
if (opts.soft) {
break :config self.private().config.ref();
}
// Hard reload, load a new config completely.
const alloc = self.allocator();
var config = try CoreConfig.load(alloc);
defer config.deinit();
break :config try .new(alloc, &config);
};
defer config.unref();
// Update the proper target. This will trigger a `confige_change`
// apprt action which will propagate the config properly to our
// property system.
switch (target) {
.app => try self.core().updateConfig(
self.rt(),
config.get(),
),
.surface => |core| try core.updateConfig(config.get()),
}
}
pub fn render(target: apprt.Target) void {
switch (target) {
.app => {},

View File

@ -1193,6 +1193,14 @@ pub const Surface = extern struct {
return self.private().pwd;
}
/// Change the configuration for this surface.
pub fn setConfig(self: *Self, config: *Config) void {
const priv = self.private();
if (priv.config) |c| c.unref();
priv.config = config.ref();
self.as(gobject.Object).notifyByPspec(properties.config.impl.param_spec);
}
fn propConfig(
self: *Self,
_: *gobject.ParamSpec,