From 3b4c33afe08024870dcf521532451b0cadb5d321 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 6 Aug 2025 12:41:57 -0700 Subject: [PATCH] apprt/gtk-ng: connect surface signals --- src/apprt/gtk-ng/class/split_tree.zig | 2 +- src/apprt/gtk-ng/class/tab.zig | 42 +++++ src/apprt/gtk-ng/class/window.zig | 213 ++++++++++++++++---------- src/apprt/gtk-ng/ui/1.5/tab.blp | 4 +- 4 files changed, 181 insertions(+), 80 deletions(-) diff --git a/src/apprt/gtk-ng/class/split_tree.zig b/src/apprt/gtk-ng/class/split_tree.zig index 3cd03e810..7e3f7d92b 100644 --- a/src/apprt/gtk-ng/class/split_tree.zig +++ b/src/apprt/gtk-ng/class/split_tree.zig @@ -81,7 +81,7 @@ pub const SplitTree = extern struct { /// The new value is given as the signal parameter. The old value /// can still be retrieved from the tree property. pub const @"tree-will-change" = struct { - pub const name = "tree-change"; + pub const name = "tree-will-change"; pub const connect = impl.connect; const impl = gobject.ext.defineSignal( name, diff --git a/src/apprt/gtk-ng/class/tab.zig b/src/apprt/gtk-ng/class/tab.zig index 0619b2de8..b8711873f 100644 --- a/src/apprt/gtk-ng/class/tab.zig +++ b/src/apprt/gtk-ng/class/tab.zig @@ -74,6 +74,26 @@ pub const Tab = extern struct { ); }; + pub const @"surface-tree" = struct { + pub const name = "surface-tree"; + const impl = gobject.ext.defineProperty( + name, + Self, + ?*Surface.Tree, + .{ + .nick = "Surface Tree", + .blurb = "The surface tree that is contained in this tab.", + .accessor = gobject.ext.typedAccessor( + Self, + ?*Surface.Tree, + .{ + .getter = getSurfaceTree, + }, + ), + }, + ); + }; + pub const title = struct { pub const name = "title"; pub const get = impl.get; @@ -184,6 +204,18 @@ pub const Tab = extern struct { return priv.surface; } + /// Get the surface tree of this tab. + pub fn getSurfaceTree(self: *Self) ?*Surface.Tree { + const priv = self.private(); + return priv.split_tree.getTree(); + } + + /// Get the split tree widget that is in this tab. + pub fn getSplitTree(self: *Self) *SplitTree { + const priv = self.private(); + return priv.split_tree; + } + /// Returns true if this tab needs confirmation before quitting based /// on the various Ghostty configurations. pub fn getNeedsConfirmQuit(self: *Self) bool { @@ -251,6 +283,14 @@ pub const Tab = extern struct { } } + fn propSplitTree( + _: *SplitTree, + _: *gobject.ParamSpec, + self: *Self, + ) callconv(.c) void { + self.as(gobject.Object).notifyByPspec(properties.@"surface-tree".impl.param_spec); + } + const C = Common(Self, Private); pub const as = C.as; pub const ref = C.ref; @@ -278,6 +318,7 @@ pub const Tab = extern struct { gobject.ext.registerProperties(class, &.{ properties.@"active-surface".impl, properties.config.impl, + properties.@"surface-tree".impl, properties.title.impl, }); @@ -287,6 +328,7 @@ pub const Tab = extern struct { // Template Callbacks class.bindTemplateCallback("surface_close_request", &surfaceCloseRequest); + class.bindTemplateCallback("notify_tree", &propSplitTree); // Signals signals.@"close-request".impl.register(.{}); diff --git a/src/apprt/gtk-ng/class/window.zig b/src/apprt/gtk-ng/class/window.zig index 6881ee052..3d7143953 100644 --- a/src/apprt/gtk-ng/class/window.zig +++ b/src/apprt/gtk-ng/class/window.zig @@ -22,6 +22,7 @@ const Common = @import("../class.zig").Common; const Config = @import("config.zig").Config; const Application = @import("application.zig").Application; const CloseConfirmationDialog = @import("close_confirmation_dialog.zig").CloseConfirmationDialog; +const SplitTree = @import("split_tree.zig").SplitTree; const Surface = @import("surface.zig").Surface; const Tab = @import("tab.zig").Tab; const DebugWarning = @import("debug_warning.zig").DebugWarning; @@ -408,6 +409,24 @@ pub const Window = extern struct { .{ .sync_create = true }, ); + // Bind signals + const split_tree = tab.getSplitTree(); + _ = SplitTree.signals.@"tree-will-change".connect( + split_tree, + *Self, + tabSplitTreeWillChange, + self, + .{}, + ); + + // Run an initial notification for the surface tree so we can setup + // initial state. + tabSplitTreeWillChange( + split_tree, + split_tree.getTree(), + self, + ); + return page; } @@ -637,6 +656,102 @@ pub const Window = extern struct { self.private().toast_overlay.addToast(toast); } + fn connectSurfaceHandlers( + self: *Self, + tree: *const Surface.Tree, + ) void { + const priv = self.private(); + var it = tree.iterator(); + while (it.next()) |entry| { + const surface = entry.view; + _ = Surface.signals.@"close-request".connect( + surface, + *Self, + surfaceCloseRequest, + self, + .{}, + ); + _ = Surface.signals.@"present-request".connect( + surface, + *Self, + surfacePresentRequest, + self, + .{}, + ); + _ = Surface.signals.@"clipboard-write".connect( + surface, + *Self, + surfaceClipboardWrite, + self, + .{}, + ); + _ = Surface.signals.menu.connect( + surface, + *Self, + surfaceMenu, + self, + .{}, + ); + _ = Surface.signals.@"toggle-fullscreen".connect( + surface, + *Self, + surfaceToggleFullscreen, + self, + .{}, + ); + _ = Surface.signals.@"toggle-maximize".connect( + surface, + *Self, + surfaceToggleMaximize, + 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 + // before init so we could register this on multiple and that is not + // a problem because we'll check the flag again in each handler. + if (!priv.surface_init) { + _ = Surface.signals.init.connect( + surface, + *Self, + surfaceInit, + self, + .{}, + ); + } + } + } + + /// Disconnect all the surface handlers for the given tree. This should + /// be called whenever a tree is no longer present in the window, e.g. + /// when a tab is detached or the tree changes. + fn disconnectSurfaceHandlers( + self: *Self, + tree: *const Surface.Tree, + ) void { + var it = tree.iterator(); + while (it.next()) |entry| { + const surface = entry.view; + _ = gobject.signalHandlersDisconnectMatched( + surface.as(gobject.Object), + .{ .data = true }, + 0, + 0, + null, + null, + self, + ); + } + } + //--------------------------------------------------------------- // Properties @@ -1134,8 +1249,6 @@ pub const Window = extern struct { _: c_int, self: *Self, ) callconv(.c) void { - const priv = self.private(); - // Get the attached page which must be a Tab object. const child = page.getChild(); const tab = gobject.ext.cast(Tab, child) orelse return; @@ -1168,71 +1281,8 @@ pub const Window = extern struct { // behavior is consistent with macOS and the previous GTK apprt, // but that behavior was all implicit and not documented, so here // I am. - // - // TODO: When we have a split tree we'll want to attach to that. - const surface = tab.getActiveSurface(); - _ = Surface.signals.@"close-request".connect( - surface, - *Self, - surfaceCloseRequest, - self, - .{}, - ); - _ = Surface.signals.@"present-request".connect( - surface, - *Self, - surfacePresentRequest, - self, - .{}, - ); - _ = Surface.signals.@"clipboard-write".connect( - surface, - *Self, - surfaceClipboardWrite, - self, - .{}, - ); - _ = Surface.signals.menu.connect( - surface, - *Self, - surfaceMenu, - self, - .{}, - ); - _ = Surface.signals.@"toggle-fullscreen".connect( - surface, - *Self, - surfaceToggleFullscreen, - self, - .{}, - ); - _ = Surface.signals.@"toggle-maximize".connect( - surface, - *Self, - surfaceToggleMaximize, - 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 - // before init so we could register this on multiple and that is not - // a problem because we'll check the flag again in each handler. - if (!priv.surface_init) { - _ = Surface.signals.init.connect( - surface, - *Self, - surfaceInit, - self, - .{}, - ); + if (tab.getSurfaceTree()) |tree| { + self.connectSurfaceHandlers(tree); } } @@ -1255,17 +1305,10 @@ pub const Window = extern struct { self, ); - // Remove all the signals that have this window as the userdata. - const surface = tab.getActiveSurface(); - _ = gobject.signalHandlersDisconnectMatched( - surface.as(gobject.Object), - .{ .data = true }, - 0, - 0, - null, - null, - self, - ); + // Remove the tree handlers + if (tab.getSurfaceTree()) |tree| { + self.disconnectSurfaceHandlers(tree); + } } fn tabViewCreateWindow( @@ -1464,6 +1507,20 @@ pub const Window = extern struct { } } + fn tabSplitTreeWillChange( + split_tree: *SplitTree, + new_tree: ?*const Surface.Tree, + self: *Self, + ) callconv(.c) void { + if (split_tree.getTree()) |old_tree| { + self.disconnectSurfaceHandlers(old_tree); + } + + if (new_tree) |tree| { + self.connectSurfaceHandlers(tree); + } + } + fn actionAbout( _: *gio.SimpleAction, _: ?*glib.Variant, diff --git a/src/apprt/gtk-ng/ui/1.5/tab.blp b/src/apprt/gtk-ng/ui/1.5/tab.blp index fc20962e4..8e6aee6cf 100644 --- a/src/apprt/gtk-ng/ui/1.5/tab.blp +++ b/src/apprt/gtk-ng/ui/1.5/tab.blp @@ -14,5 +14,7 @@ template $GhosttyTab: Box { close-request => $surface_close_request(); } - $GhosttySplitTree split_tree {} + $GhosttySplitTree split_tree { + notify::tree => $notify_tree(); + } }