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

This commit is contained in:
Mitchell Hashimoto
2025-08-06 13:48:50 -07:00
parent 3b4c33afe0
commit bc731c0ff6
3 changed files with 102 additions and 22 deletions

View File

@@ -1318,6 +1318,11 @@ pub const Surface = extern struct {
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.
pub fn setConfig(self: *Self, config: *Config) void {
const priv = self.private();
@@ -1654,6 +1659,7 @@ pub const Surface = extern struct {
priv.focused = true;
priv.im_context.as(gtk.IMContext).focusIn();
_ = glib.idleAddOnce(idleFocus, self.ref());
self.as(gobject.Object).notifyByPspec(properties.focused.impl.param_spec);
}
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
@@ -1661,6 +1667,7 @@ pub const Surface = extern struct {
priv.focused = false;
priv.im_context.as(gtk.IMContext).focusOut();
_ = 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

View File

@@ -139,7 +139,6 @@ pub const Tab = extern struct {
// Template bindings
split_tree: *SplitTree,
surface: *Surface,
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
/// ever created for a tab. If a surface was already created this does
/// nothing.
pub fn setParent(
self: *Self,
parent: *CoreSurface,
) void {
const priv = self.private();
priv.surface.setParent(parent);
pub fn setParent(self: *Self, parent: *CoreSurface) void {
if (self.getActiveSurface()) |surface| {
surface.setParent(parent);
}
}
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,
// I think because its a dynamic getter.
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);
}
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
/// Get the currently active surface. See the "active-surface" property.
/// This does not ref the value.
pub fn getActiveSurface(self: *Self) *Surface {
const priv = self.private();
return priv.surface;
pub fn getActiveSurface(self: *Self) ?*Surface {
const tree = self.getSurfaceTree() orelse return null;
var it = tree.iterator();
while (it.next()) |entry| {
if (entry.view.getFocused()) return entry.view;
}
return null;
}
/// 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
/// on the various Ghostty configurations.
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;
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(
_: *SplitTree,
_: *gobject.ParamSpec,
@@ -291,6 +346,27 @@ pub const Tab = extern struct {
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);
pub const as = C.as;
pub const ref = C.ref;
@@ -324,10 +400,10 @@ pub const Tab = extern struct {
// Bindings
class.bindTemplateChildPrivate("split_tree", .{});
class.bindTemplateChildPrivate("surface", .{});
// 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);
// Signals

View File

@@ -5,16 +5,13 @@ template $GhosttyTab: Box {
"tab",
]
notify::active-surface => $notify_active_surface();
orientation: vertical;
hexpand: 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 {
notify::tree => $notify_tree();
tree-will-change => $tree_will_change();
}
}