gtk: +new-window now respects --title

This commit is contained in:
Jeffrey C. Ollie
2026-02-19 15:50:42 -06:00
parent 6961c2265e
commit ec0f9ef416
3 changed files with 102 additions and 17 deletions

View File

@@ -2215,7 +2215,7 @@ const Action = struct {
// was a delay in the event loop before we created a Window.
self.private().requested_window = true;
const win = Window.new(self);
const win = Window.new(self, config_overrides);
initAndShowWindow(self, win, parent, config_overrides);
}

View File

@@ -80,6 +80,18 @@ pub const Window = extern struct {
);
};
pub const @"config-overrides" = struct {
pub const name = "config-overrides";
const impl = gobject.ext.defineProperty(
name,
Self,
?*ConfigOverrides,
.{
.accessor = C.privateObjFieldAccessor("config_overrides"),
},
);
};
pub const debug = struct {
pub const name = "debug";
const impl = gobject.ext.defineProperty(
@@ -232,6 +244,9 @@ pub const Window = extern struct {
/// The configuration that this surface is using.
config: ?*Config = null,
/// Configuration overrides.
config_overrides: ?*ConfigOverrides = null,
/// State and logic for windowing protocol for a window.
winproto: winprotopkg.Window,
@@ -267,10 +282,30 @@ pub const Window = extern struct {
pub var offset: c_int = 0;
};
pub fn new(app: *Application) *Self {
return gobject.ext.newInstance(Self, .{
pub fn new(app: *Application, config_overrides_: ?*ConfigOverrides) *Self {
const win = gobject.ext.newInstance(Self, .{
.application = app,
});
const priv: *Private = win.private();
if (config_overrides_) |v| {
priv.config_overrides = v.ref();
const config_overrides = v.get();
// If the config overrides have a title set, we set that immediately
// so that any applications inspecting the window states see an
// immediate title set when the window appears, rather than waiting
// possibly a few event loop ticks for it to sync from the surface.
if (config_overrides.isSet(.title)) {
const title_ = config_overrides.get(.title);
if (title_) |title| {
win.as(gtk.Window).setTitle(title);
}
}
win.as(gobject.Object).notifyByPspec(properties.@"config-overrides".impl.param_spec);
}
return win;
}
fn init(self: *Self, _: *Class) callconv(.c) void {
@@ -279,10 +314,14 @@ pub const Window = extern struct {
// If our configuration is null then we get the configuration
// from the application.
const priv = self.private();
if (priv.config == null) {
const config = config: {
if (priv.config) |config| break :config config.get();
const app = Application.default();
priv.config = app.getConfig();
}
const config = app.getConfig();
priv.config = config;
break :config config.get();
};
// We initialize our windowing protocol to none because we can't
// actually initialize this until we get realized.
@@ -306,17 +345,25 @@ pub const Window = extern struct {
self.initActionMap();
// Start states based on config.
if (priv.config) |config_obj| {
const config = config_obj.get();
if (config.maximize) self.as(gtk.Window).maximize();
if (config.fullscreen != .false) self.as(gtk.Window).fullscreen();
if (config.maximize) self.as(gtk.Window).maximize();
if (config.fullscreen != .false) self.as(gtk.Window).fullscreen();
// If we have an explicit title set, we set that immediately
// so that any applications inspecting the window states see
// an immediate title set when the window appears, rather than
// waiting possibly a few event loop ticks for it to sync from
// the surface.
if (config.title) |v| self.as(gtk.Window).setTitle(v);
// If we have an explicit title set, we set that immediately
// so that any applications inspecting the window states see
// an immediate title set when the window appears, rather than
// waiting possibly a few event loop ticks for it to sync from
// the surface.
const title_ = title: {
if (priv.config_overrides) |co| {
const config_overrides = co.get();
if (config_overrides.isSet(.title)) {
break :title config_overrides.get(.title);
}
}
break :title config.title;
};
if (title_) |title| {
self.as(gtk.Window).setTitle(title);
}
// We always sync our appearance at the end because loading our
@@ -1151,6 +1198,37 @@ pub const Window = extern struct {
});
}
fn closureTitle(
_: *Self,
config_: ?*Config,
config_overrides_: ?*ConfigOverrides,
title_: ?[*:0]const u8,
) callconv(.c) ?[*:0]const u8 {
config: {
if (config_overrides_) |v| {
const config_overrides = v.get();
if (config_overrides.isSet(.title)) {
if (config_overrides.get(.title)) |title| {
return glib.ext.dupeZ(u8, title);
}
// The `title` has explicitly been set to `null`, skip
// checking the normal config for it's title setting.
break :config;
}
}
if (config_) |v| {
const config = v.get();
if (config.title) |title| {
return glib.ext.dupeZ(u8, title);
}
}
}
if (title_) |title| {
return glib.ext.dupeZ(u8, std.mem.span(title));
}
return null;
}
fn closureSubtitle(
_: *Self,
config_: ?*Config,
@@ -1179,6 +1257,11 @@ pub const Window = extern struct {
priv.config = null;
}
if (priv.config_overrides) |v| {
v.unref();
priv.config_overrides = null;
}
priv.tab_bindings.setSource(null);
gtk.Widget.disposeTemplate(
@@ -2019,6 +2102,7 @@ pub const Window = extern struct {
gobject.ext.registerProperties(class, &.{
properties.@"active-surface".impl,
properties.config.impl,
properties.@"config-overrides".impl,
properties.debug.impl,
properties.@"headerbar-visible".impl,
properties.@"quick-terminal".impl,
@@ -2057,6 +2141,7 @@ pub const Window = extern struct {
class.bindTemplateCallback("notify_quick_terminal", &propQuickTerminal);
class.bindTemplateCallback("notify_scale_factor", &propScaleFactor);
class.bindTemplateCallback("titlebar_style_is_tabs", &closureTitlebarStyleIsTab);
class.bindTemplateCallback("computed_title", &closureTitle);
class.bindTemplateCallback("computed_subtitle", &closureSubtitle);
// Virtual methods

View File

@@ -40,7 +40,7 @@ template $GhosttyWindow: Adw.ApplicationWindow {
visible: bind template.headerbar-visible;
title-widget: Adw.WindowTitle {
title: bind template.title;
title: bind $computed_title(template.config, template.config-overrides, template.title) as <string>;
// Blueprint auto-formatter won't let me split this into multiple
// lines. Let me explain myself. All parameters to a closure are used
// as notifications to recompute the value of the closure. All