diff --git a/build.zig.zon b/build.zig.zon index ee283870b..55a693496 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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, }, diff --git a/build.zig.zon.json b/build.zig.zon.json index 190fc1cd2..24f1053ba 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -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", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index 3708d61ed..380bafaeb 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -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="; }; } { diff --git a/build.zig.zon.txt b/build.zig.zon.txt index 2f3a9cab9..14bb0e8df 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -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 diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 56f4dc6a4..d50371f5f 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -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", diff --git a/src/apprt/gtk-ng/class/application.zig b/src/apprt/gtk-ng/class/application.zig index eda2a49eb..4a14434fa 100644 --- a/src/apprt/gtk-ng/class/application.zig +++ b/src/apprt/gtk-ng/class/application.zig @@ -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, diff --git a/src/apprt/gtk-ng/class/command_palette.zig b/src/apprt/gtk-ng/class/command_palette.zig index ee10989b7..8b7bb328c 100644 --- a/src/apprt/gtk-ng/class/command_palette.zig +++ b/src/apprt/gtk-ng/class/command_palette.zig @@ -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); diff --git a/src/apprt/gtk-ng/class/split_tree.zig b/src/apprt/gtk-ng/class/split_tree.zig index 3018afdb4..5eb0a5472 100644 --- a/src/apprt/gtk-ng/class/split_tree.zig +++ b/src/apprt/gtk-ng/class/split_tree.zig @@ -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. diff --git a/src/apprt/gtk-ng/class/surface.zig b/src/apprt/gtk-ng/class/surface.zig index 05393bd4f..8487b24b0 100644 --- a/src/apprt/gtk-ng/class/surface.zig +++ b/src/apprt/gtk-ng/class/surface.zig @@ -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); diff --git a/src/apprt/gtk-ng/class/window.zig b/src/apprt/gtk-ng/class/window.zig index f3e8ee129..eb41b61d0 100644 --- a/src/apprt/gtk-ng/class/window.zig +++ b/src/apprt/gtk-ng/class/window.zig @@ -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(); } diff --git a/src/apprt/gtk-ng/ui/1.5/command-palette.blp b/src/apprt/gtk-ng/ui/1.5/command-palette.blp index 0ccae1f0a..473fb1f06 100644 --- a/src/apprt/gtk-ng/ui/1.5/command-palette.blp +++ b/src/apprt/gtk-ng/ui/1.5/command-palette.blp @@ -4,6 +4,7 @@ using Adw 1; Adw.Dialog dialog { content-width: 700; + closed => $closed(); Adw.ToolbarView { top-bar-style: flat; diff --git a/src/apprt/gtk-ng/weak_ref.zig b/src/apprt/gtk-ng/weak_ref.zig index 7ee5cf730..f689e45fa 100644 --- a/src/apprt/gtk-ng/weak_ref.zig +++ b/src/apprt/gtk-ng/weak_ref.zig @@ -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