apprt/gtk-ng: hook up Tab signals to surface

pull/8165/head
Mitchell Hashimoto 2025-08-06 13:48:50 -07:00
parent 3b4c33afe0
commit bc731c0ff6
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
3 changed files with 102 additions and 22 deletions

View File

@ -1318,6 +1318,11 @@ pub const Surface = extern struct {
return self.private().pwd; return self.private().pwd;
} }
/// Returns the focus state of this surface.
pub fn getFocused(self: *Self) bool {
return self.private().focused;
}
/// Change the configuration for this surface. /// Change the configuration for this surface.
pub fn setConfig(self: *Self, config: *Config) void { pub fn setConfig(self: *Self, config: *Config) void {
const priv = self.private(); const priv = self.private();
@ -1654,6 +1659,7 @@ pub const Surface = extern struct {
priv.focused = true; priv.focused = true;
priv.im_context.as(gtk.IMContext).focusIn(); priv.im_context.as(gtk.IMContext).focusIn();
_ = glib.idleAddOnce(idleFocus, self.ref()); _ = glib.idleAddOnce(idleFocus, self.ref());
self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec);
} }
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void { fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
@ -1661,6 +1667,7 @@ pub const Surface = extern struct {
priv.focused = false; priv.focused = false;
priv.im_context.as(gtk.IMContext).focusOut(); priv.im_context.as(gtk.IMContext).focusOut();
_ = glib.idleAddOnce(idleFocus, self.ref()); _ = glib.idleAddOnce(idleFocus, self.ref());
self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec);
} }
/// The focus callback must be triggered on an idle loop source because /// The focus callback must be triggered on an idle loop source because

View File

@ -139,7 +139,6 @@ pub const Tab = extern struct {
// Template bindings // Template bindings
split_tree: *SplitTree, split_tree: *SplitTree,
surface: *Surface,
pub var offset: c_int = 0; pub var offset: c_int = 0;
}; };
@ -147,12 +146,10 @@ pub const Tab = extern struct {
/// Set the parent of this tab page. This only affects the first surface /// Set the parent of this tab page. This only affects the first surface
/// ever created for a tab. If a surface was already created this does /// ever created for a tab. If a surface was already created this does
/// nothing. /// nothing.
pub fn setParent( pub fn setParent(self: *Self, parent: *CoreSurface) void {
self: *Self, if (self.getActiveSurface()) |surface| {
parent: *CoreSurface, surface.setParent(parent);
) void { }
const priv = self.private();
priv.surface.setParent(parent);
} }
fn init(self: *Self, _: *Class) callconv(.c) void { fn init(self: *Self, _: *Class) callconv(.c) void {
@ -175,10 +172,6 @@ pub const Tab = extern struct {
.{}, .{},
); );
// TODO: Eventually this should be set dynamically based on the
// current active surface.
priv.surface_bindings.setSource(priv.surface.as(gobject.Object));
// We need to do this so that the title initializes properly, // We need to do this so that the title initializes properly,
// I think because its a dynamic getter. // I think because its a dynamic getter.
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec); self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec);
@ -194,14 +187,62 @@ pub const Tab = extern struct {
priv.split_tree.setTree(&tree); priv.split_tree.setTree(&tree);
} }
fn connectSurfaceHandlers(
self: *Self,
tree: *const Surface.Tree,
) void {
var it = tree.iterator();
while (it.next()) |entry| {
const surface = entry.view;
_ = Surface.signals.@"close-request".connect(
surface,
*Self,
surfaceCloseRequest,
self,
.{},
);
_ = gobject.Object.signals.notify.connect(
surface,
*Self,
propSurfaceFocused,
self,
.{ .detail = "focused" },
);
}
}
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 // Properties
/// Get the currently active surface. See the "active-surface" property. /// Get the currently active surface. See the "active-surface" property.
/// This does not ref the value. /// This does not ref the value.
pub fn getActiveSurface(self: *Self) *Surface { pub fn getActiveSurface(self: *Self) ?*Surface {
const priv = self.private(); const tree = self.getSurfaceTree() orelse return null;
return priv.surface; var it = tree.iterator();
while (it.next()) |entry| {
if (entry.view.getFocused()) return entry.view;
}
return null;
} }
/// Get the surface tree of this tab. /// Get the surface tree of this tab.
@ -219,7 +260,7 @@ pub const Tab = extern struct {
/// Returns true if this tab needs confirmation before quitting based /// Returns true if this tab needs confirmation before quitting based
/// on the various Ghostty configurations. /// on the various Ghostty configurations.
pub fn getNeedsConfirmQuit(self: *Self) bool { pub fn getNeedsConfirmQuit(self: *Self) bool {
const surface = self.getActiveSurface(); const surface = self.getActiveSurface() orelse return false;
const core_surface = surface.core() orelse return false; const core_surface = surface.core() orelse return false;
return core_surface.needsConfirmQuit(); return core_surface.needsConfirmQuit();
} }
@ -283,6 +324,20 @@ pub const Tab = extern struct {
} }
} }
fn splitTreeWillChange(
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 propSplitTree( fn propSplitTree(
_: *SplitTree, _: *SplitTree,
_: *gobject.ParamSpec, _: *gobject.ParamSpec,
@ -291,6 +346,27 @@ pub const Tab = extern struct {
self.as(gobject.Object).notifyByPspec(properties.@"surface-tree".impl.param_spec); self.as(gobject.Object).notifyByPspec(properties.@"surface-tree".impl.param_spec);
} }
fn propActiveSurface(
_: *Self,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
const priv = self.private();
priv.surface_bindings.setSource(null);
if (self.getActiveSurface()) |surface| {
priv.surface_bindings.setSource(surface.as(gobject.Object));
}
}
fn propSurfaceFocused(
surface: *Surface,
_: *gobject.ParamSpec,
self: *Self,
) callconv(.c) void {
if (!surface.getFocused()) return;
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec);
}
const C = Common(Self, Private); const C = Common(Self, Private);
pub const as = C.as; pub const as = C.as;
pub const ref = C.ref; pub const ref = C.ref;
@ -324,10 +400,10 @@ pub const Tab = extern struct {
// Bindings // Bindings
class.bindTemplateChildPrivate("split_tree", .{}); class.bindTemplateChildPrivate("split_tree", .{});
class.bindTemplateChildPrivate("surface", .{});
// Template Callbacks // Template Callbacks
class.bindTemplateCallback("surface_close_request", &surfaceCloseRequest); class.bindTemplateCallback("tree_will_change", &splitTreeWillChange);
class.bindTemplateCallback("notify_active_surface", &propActiveSurface);
class.bindTemplateCallback("notify_tree", &propSplitTree); class.bindTemplateCallback("notify_tree", &propSplitTree);
// Signals // Signals

View File

@ -5,16 +5,13 @@ template $GhosttyTab: Box {
"tab", "tab",
] ]
notify::active-surface => $notify_active_surface();
orientation: vertical; orientation: vertical;
hexpand: true; hexpand: true;
vexpand: true; vexpand: true;
// A tab currently just contains a surface directly. When we introduce
// splits we probably want to replace this with the split widget type.
$GhosttySurface surface {
close-request => $surface_close_request();
}
$GhosttySplitTree split_tree { $GhosttySplitTree split_tree {
notify::tree => $notify_tree(); notify::tree => $notify_tree();
tree-will-change => $tree_will_change();
} }
} }