apprt/gtk-ng: connect surface signals

pull/8165/head
Mitchell Hashimoto 2025-08-06 12:41:57 -07:00
parent a7865d79ea
commit 3b4c33afe0
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
4 changed files with 181 additions and 80 deletions

View File

@ -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,

View File

@ -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(.{});

View File

@ -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,

View File

@ -14,5 +14,7 @@ template $GhosttyTab: Box {
close-request => $surface_close_request();
}
$GhosttySplitTree split_tree {}
$GhosttySplitTree split_tree {
notify::tree => $notify_tree();
}
}