gtk-ng: don't use signals to toggle command palette (#8182)

This commit is contained in:
Mitchell Hashimoto
2025-08-11 12:02:48 -07:00
committed by GitHub
12 changed files with 57 additions and 82 deletions

View File

@@ -55,8 +55,8 @@
.gobject = .{
// https://github.com/jcollie/ghostty-gobject based on zig_gobject
// Temporary until we generate them at build time automatically.
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-07-34-1/ghostty-gobject-0.14.1-2025-08-07-34-1.tar.zst",
.hash = "gobject-0.3.0-Skun7F_XnABQYabYdzLoVbO3bCcJIwxE3NCPs1_fG2ma",
.url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
.hash = "gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM",
.lazy = true,
},

6
build.zig.zon.json generated
View File

@@ -24,10 +24,10 @@
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
},
"gobject-0.3.0-Skun7F_XnABQYabYdzLoVbO3bCcJIwxE3NCPs1_fG2ma": {
"gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM": {
"name": "gobject",
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-07-34-1/ghostty-gobject-0.14.1-2025-08-07-34-1.tar.zst",
"hash": "sha256-43IIiHR5J7PfgG9JXSlGgC6WztC10fXyIhGZfY9xceQ="
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
"hash": "sha256-B0ziLzKud+kdKu5T1BTE9GMh8EPM/KhhhoNJlys5QPI="
},
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
"name": "gtk4_layer_shell",

6
build.zig.zon.nix generated
View File

@@ -122,11 +122,11 @@ in
};
}
{
name = "gobject-0.3.0-Skun7F_XnABQYabYdzLoVbO3bCcJIwxE3NCPs1_fG2ma";
name = "gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM";
path = fetchZigArtifact {
name = "gobject";
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-07-34-1/ghostty-gobject-0.14.1-2025-08-07-34-1.tar.zst";
hash = "sha256-43IIiHR5J7PfgG9JXSlGgC6WztC10fXyIhGZfY9xceQ=";
url = "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst";
hash = "sha256-B0ziLzKud+kdKu5T1BTE9GMh8EPM/KhhhoNJlys5QPI=";
};
}
{

2
build.zig.zon.txt generated
View File

@@ -27,7 +27,7 @@ https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d6
https://deps.files.ghostty.org/zig_js-12205a66d423259567764fa0fc60c82be35365c21aeb76c5a7dc99698401f4f6fefc.tar.gz
https://deps.files.ghostty.org/ziglyph-b89d43d1e3fb01b6074bc1f7fc980324b04d26a5.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-07-34-1/ghostty-gobject-0.14.1-2025-08-07-34-1.tar.zst
https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst
https://github.com/mbadolato/iTerm2-Color-Schemes/archive/3cbeca99efa10beba24b0efe86331736f09f9ed1.tar.gz
https://github.com/mitchellh/libxev/archive/7f803181b158a10fec8619f793e3b4df515566cb.tar.gz
https://github.com/mitchellh/zig-objc/archive/c9e917a4e15a983b672ca779c7985d738a2d517c.tar.gz

View File

@@ -31,9 +31,9 @@
},
{
"type": "archive",
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-07-34-1/ghostty-gobject-0.14.1-2025-08-07-34-1.tar.zst",
"dest": "vendor/p/gobject-0.3.0-Skun7F_XnABQYabYdzLoVbO3bCcJIwxE3NCPs1_fG2ma",
"sha256": "e3720888747927b3df806f495d2946802e96ced0b5d1f5f22211997d8f7171e4"
"url": "https://github.com/jcollie/ghostty-gobject/releases/download/0.14.1-2025-08-09-37-1/ghostty-gobject-0.14.1-2025-08-09-37-1.tar.zst",
"dest": "vendor/p/gobject-0.3.0-Skun7AngnABC2BPiaoobs6YSSzSgMuEIcjb2rYrRyaAM",
"sha256": "074ce22f32ae77e91d2aee53d414c4f46321f043ccfca861868349972b3940f2"
},
{
"type": "archive",

View File

@@ -133,7 +133,7 @@ pub const Application = extern struct {
/// If non-null, we're currently showing a config errors dialog.
/// This is a WeakRef because the dialog can close on its own
/// outside of our own lifecycle and that's okay.
config_errors_dialog: WeakRef(ConfigErrorsDialog) = .{},
config_errors_dialog: WeakRef(ConfigErrorsDialog) = .empty,
/// glib source for our signal handler.
signal_source: ?c_uint = null,

View File

@@ -174,13 +174,20 @@ pub const CommandPalette = extern struct {
}
}
fn searchStopped(_: *gtk.SearchEntry, self: *CommandPalette) callconv(.c) void {
// ESC was pressed - close the palette
fn close(self: *CommandPalette) void {
const priv = self.private();
_ = priv.dialog.close();
}
fn dialogClosed(_: *adw.Dialog, self: *CommandPalette) callconv(.c) void {
self.unref();
}
fn searchStopped(_: *gtk.SearchEntry, self: *CommandPalette) callconv(.c) void {
// ESC was pressed - close the palette
self.close();
}
fn searchActivated(_: *gtk.SearchEntry, self: *CommandPalette) callconv(.c) void {
// If Enter is pressed, activate the selected entry
const priv = self.private();
@@ -198,11 +205,9 @@ pub const CommandPalette = extern struct {
pub fn toggle(self: *CommandPalette, window: *Window) void {
const priv = self.private();
// If the dialog has been shown, close it and unref ourselves so all of
// our memory is reclaimed.
// If the dialog has been shown, close it.
if (priv.dialog.as(gtk.Widget).getRealized() != 0) {
_ = priv.dialog.close();
self.unref();
self.close();
return;
}
@@ -218,22 +223,17 @@ pub const CommandPalette = extern struct {
fn activated(self: *CommandPalette, pos: c_uint) void {
const priv = self.private();
// Use priv.model and not priv.source here to use the list of *visible* results
const object_ = priv.model.as(gio.ListModel).getObject(pos);
defer if (object_) |object| object.unref();
// Close before running the action in order to avoid being replaced by
// another dialog (such as the change title dialog). If that occurs then
// the command palette dialog won't be counted as having closed properly
// and cannot receive focus when reopened.
_ = priv.dialog.close();
// We are always done with the command palette when this finishes, even
// if there were errors.
defer self.unref();
// Use priv.model and not priv.source here to use the list of *visible* results
const object = priv.model.as(gio.ListModel).getObject(pos) orelse return;
defer object.unref();
const cmd = gobject.ext.cast(Command, object) orelse return;
self.close();
const cmd = gobject.ext.cast(Command, object_ orelse return) orelse return;
const action = cmd.getAction() orelse return;
// Signal that an an action has been selected. Signals are synchronous
@@ -277,6 +277,7 @@ pub const CommandPalette = extern struct {
class.bindTemplateChildPrivate("source", .{});
// Template Callbacks
class.bindTemplateCallback("closed", &dialogClosed);
class.bindTemplateCallback("notify_config", &propConfig);
class.bindTemplateCallback("search_stopped", &searchStopped);
class.bindTemplateCallback("search_activated", &searchActivated);

View File

@@ -119,7 +119,7 @@ pub const SplitTree = extern struct {
/// Last focused surface in the tree. We need this to handle various
/// tree change states.
last_focused: WeakRef(Surface) = .{},
last_focused: WeakRef(Surface) = .empty,
/// The source that we use to rebuild the tree. This is also
/// used to debounce updates.

View File

@@ -361,19 +361,6 @@ pub const Surface = extern struct {
void,
);
};
/// Emitted when this surface requests that the command palette be
/// toggled.
pub const @"toggle-command-palette" = struct {
pub const name = "toggle-command-palette";
pub const connect = impl.connect;
const impl = gobject.ext.defineSignal(
name,
Self,
&.{},
void,
);
};
};
const Private = struct {
@@ -564,13 +551,8 @@ pub const Surface = extern struct {
}
pub fn toggleCommandPalette(self: *Self) bool {
signals.@"toggle-command-palette".impl.emit(
self,
null,
.{},
null,
);
return true;
// TODO: pass the surface with the action
return self.as(gtk.Widget).activateAction("win.toggle-command-palette", null) != 0;
}
/// Set the current progress report state.
@@ -2423,7 +2405,6 @@ pub const Surface = extern struct {
signals.@"present-request".impl.register(.{});
signals.@"toggle-fullscreen".impl.register(.{});
signals.@"toggle-maximize".impl.register(.{});
signals.@"toggle-command-palette".impl.register(.{});
// Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose);

View File

@@ -28,6 +28,7 @@ const Surface = @import("surface.zig").Surface;
const Tab = @import("tab.zig").Tab;
const DebugWarning = @import("debug_warning.zig").DebugWarning;
const CommandPalette = @import("command_palette.zig").CommandPalette;
const WeakRef = @import("../weak_ref.zig").WeakRef;
const log = std.log.scoped(.gtk_ghostty_window);
@@ -249,7 +250,7 @@ pub const Window = extern struct {
tab_overview_focus_timer: ?c_uint = null,
/// A weak reference to a command palette.
command_palette: gobject.WeakRef = std.mem.zeroes(gobject.WeakRef),
command_palette: WeakRef(CommandPalette) = .empty,
// Template bindings
tab_overview: *adw.TabOverview,
@@ -343,6 +344,7 @@ pub const Window = extern struct {
.{ "paste", actionPaste, null },
.{ "reset", actionReset, null },
.{ "clear", actionClear, null },
// TODO: accept the surface that toggled the command palette
.{ "toggle-command-palette", actionToggleCommandPalette, null },
};
@@ -714,13 +716,6 @@ pub const Window = extern struct {
self,
.{},
);
_ = Surface.signals.@"toggle-command-palette".connect(
surface,
*Self,
surfaceToggleCommandPalette,
self,
.{},
);
// If we've never had a surface initialize yet, then we register
// this signal. Its theoretically possible to launch multiple surfaces
@@ -1070,10 +1065,14 @@ pub const Window = extern struct {
fn dispose(self: *Self) callconv(.c) void {
const priv = self.private();
priv.command_palette.set(null);
if (priv.config) |v| {
v.unref();
priv.config = null;
}
priv.tab_bindings.setSource(null);
gtk.Widget.disposeTemplate(
@@ -1529,15 +1528,6 @@ pub const Window = extern struct {
// We react to the changes in the propMaximized callback
}
/// React to a signal from a surface requesting that the command palette
/// be toggled.
fn surfaceToggleCommandPalette(
_: *Surface,
self: *Self,
) callconv(.c) void {
self.toggleCommandPalette();
}
fn surfaceInit(
surface: *Surface,
self: *Self,
@@ -1719,12 +1709,15 @@ pub const Window = extern struct {
}
/// Toggle the command palette.
///
/// TODO: accept the surface that toggled the command palette as a parameter
fn toggleCommandPalette(self: *Window) void {
const priv = self.private();
// Get a reference to a command palette. First check the weak reference
// that we save to see if we already have stored. If we don't then
// that we save to see if we already have one stored. If we don't then
// create a new one.
const command_palette = gobject.ext.cast(CommandPalette, priv.command_palette.get()) orelse command_palette: {
const command_palette = priv.command_palette.get() orelse command_palette: {
// Create a fresh command palette.
const command_palette = CommandPalette.new();
@@ -1747,14 +1740,14 @@ pub const Window = extern struct {
.{},
);
// Save a weak reference to the command palette. We use a weak reference to avoid
// reference counting cycles that might cause problems later.
priv.command_palette.set(command_palette);
break :command_palette command_palette;
};
defer command_palette.unref();
// Save a weak reference to the command palette. We use a weak reference to avoid
// reference counting cycles that might cause problems later.
priv.command_palette.set(command_palette.as(gobject.Object));
// Tell the command palette to toggle itself. If the dialog gets
// presented (instead of hidden) it will be modal over our window.
command_palette.toggle(self);
@@ -1772,6 +1765,8 @@ pub const Window = extern struct {
_: ?*glib.Variant,
self: *Window,
) callconv(.c) void {
// TODO: accept the surface that toggled the command palette as a
// parameter
self.toggleCommandPalette();
}

View File

@@ -4,6 +4,7 @@ using Adw 1;
Adw.Dialog dialog {
content-width: 700;
closed => $closed();
Adw.ToolbarView {
top-bar-style: flat;

View File

@@ -10,6 +10,8 @@ pub fn WeakRef(comptime T: type) type {
ref: gobject.WeakRef = std.mem.zeroes(gobject.WeakRef),
pub const empty: Self = .{};
/// Set the weak reference to the given object. This will not
/// increase the reference count of the object.
pub fn set(self: *Self, v_: ?*T) void {
@@ -23,14 +25,9 @@ pub fn WeakRef(comptime T: type) type {
/// Get a strong reference to the object, or null if the object
/// has been finalized. This increases the reference count by one.
pub fn get(self: *Self) ?*T {
// The GIR of g_weak_ref_get has a bug where the optional
// is not encoded. Or, it may be a bug in zig-gobject.
const obj_: ?*gobject.Object = @ptrCast(self.ref.get());
const obj = obj_ orelse return null;
// We can't use `as` because `as` guarantees conversion and
// that can't be statically guaranteed.
return gobject.ext.cast(T, obj);
return gobject.ext.cast(T, self.ref.get() orelse return null);
}
};
}
@@ -38,7 +35,7 @@ pub fn WeakRef(comptime T: type) type {
test WeakRef {
const testing = std.testing;
var ref: WeakRef(gtk.TextBuffer) = .{};
var ref: WeakRef(gtk.TextBuffer) = .empty;
const obj: *gtk.TextBuffer = .new(null);
ref.set(obj);
ref.get().?.unref(); // The "?" asserts non-null