apprt/gtk-ng: clean up close handling of all types

This cleans up our close handling of all types (surfaces, tabs, windows).
Surfaces no longer emit their scope; their scope is always just the
surface itself. For tab and window scope we use widget actions.

This makes `close_tab` work properly (previously broken).
pull/8233/head
Mitchell Hashimoto 2025-08-14 10:01:00 -07:00
parent 3eda14e2d6
commit 83d1bdcfcb
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
6 changed files with 59 additions and 71 deletions

View File

@ -32,7 +32,8 @@ pub fn rtApp(self: *Self) *ApprtApp {
}
pub fn close(self: *Self, process_active: bool) void {
self.surface.close(.{ .surface = process_active });
_ = process_active;
self.surface.close();
}
pub fn cgroup(self: *Self) ?[]const u8 {

View File

@ -542,8 +542,8 @@ pub const Application = extern struct {
value: apprt.Action.Value(action),
) !bool {
switch (action) {
.close_tab => Action.close(target, .tab),
.close_window => Action.close(target, .window),
.close_tab => return Action.closeTab(target),
.close_window => return Action.closeWindow(target),
.config_change => try Action.configChange(
self,
@ -1582,13 +1582,23 @@ pub const Application = extern struct {
/// All apprt action handlers
const Action = struct {
pub fn close(
target: apprt.Target,
scope: Surface.CloseScope,
) void {
pub fn closeTab(target: apprt.Target) bool {
switch (target) {
.app => {},
.surface => |v| v.rt_surface.surface.close(scope),
.app => return false,
.surface => |core| {
const surface = core.rt_surface.surface;
return surface.as(gtk.Widget).activateAction("tab.close", null) != 0;
},
}
}
pub fn closeWindow(target: apprt.Target) bool {
switch (target) {
.app => return false,
.surface => |core| {
const surface = core.rt_surface.surface;
return surface.as(gtk.Widget).activateAction("win.close", null) != 0;
},
}
}

View File

@ -407,6 +407,22 @@ pub const SplitTree = extern struct {
//---------------------------------------------------------------
// Properties
/// Returns true if this split tree needs confirmation before quitting based
/// on the various Ghostty configurations.
pub fn getNeedsConfirmQuit(self: *Self) bool {
const tree = self.getTree() orelse return false;
var it = tree.iterator();
while (it.next()) |entry| {
if (entry.view.core()) |core| {
if (core.needsConfirmQuit()) {
return true;
}
}
}
return false;
}
/// Get the currently active surface. See the "active-surface" property.
/// This does not ref the value.
pub fn getActiveSurface(self: *Self) ?*Surface {
@ -636,18 +652,8 @@ pub const SplitTree = extern struct {
fn surfaceCloseRequest(
surface: *Surface,
scope: *const Surface.CloseScope,
self: *Self,
) callconv(.c) void {
switch (scope.*) {
// Handled upstream... this will probably go away for widget
// actions eventually.
.window, .tab => return,
// Remove the surface from the tree.
.surface => {},
}
const core = surface.core() orelse return;
// Reset our pending close state

View File

@ -275,7 +275,7 @@ pub const Surface = extern struct {
const impl = gobject.ext.defineSignal(
name,
Self,
&.{*const CloseScope},
&.{},
void,
);
};
@ -1047,11 +1047,11 @@ pub const Surface = extern struct {
//---------------------------------------------------------------
// Libghostty Callbacks
pub fn close(self: *Self, scope: CloseScope) void {
pub fn close(self: *Self) void {
signals.@"close-request".impl.emit(
self,
null,
.{&scope},
.{},
null,
);
}
@ -1749,7 +1749,7 @@ pub const Surface = extern struct {
self: *Self,
) callconv(.c) void {
// This closes the surface with no confirmation.
self.close(.{ .surface = false });
self.close();
}
fn contextMenuClosed(
@ -2709,25 +2709,6 @@ pub const Surface = extern struct {
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
};
/// The scope of a close request.
pub const CloseScope = union(enum) {
/// Close the surface. The boolean determines if there is a
/// process active.
surface: bool,
/// Close the tab. We can't know if there are processes active
/// for the entire tab scope so listeners must query the app.
tab,
/// Close the window.
window,
pub const getGObjectType = gobject.ext.defineBoxed(
CloseScope,
.{ .name = "GhosttySurfaceCloseScope" },
);
};
/// Simple dimensions struct for the surface used by various properties.
pub const Size = extern struct {
width: u32,

View File

@ -208,6 +208,7 @@ pub const Tab = extern struct {
// For action names:
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
const actions = .{
.{ "close", actionClose, null },
.{ "ring-bell", actionRingBell, null },
};
@ -262,9 +263,8 @@ 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() orelse return false;
const core_surface = surface.core() orelse return false;
return core_surface.needsConfirmQuit();
const tree = self.getSplitTree();
return tree.getNeedsConfirmQuit();
}
/// Get the tab page holding this tab, if any.
@ -344,6 +344,22 @@ pub const Tab = extern struct {
self.as(gobject.Object).notifyByPspec(properties.@"active-surface".impl.param_spec);
}
fn actionClose(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *Self,
) callconv(.c) void {
const tab_view = ext.getAncestor(
adw.TabView,
self.as(gtk.Widget),
) orelse return;
const page = tab_view.getPage(self.as(gtk.Widget));
// Delegate to our parent to handle this, since this will emit
// a close-page signal that the parent can intercept.
tab_view.closePage(page);
}
fn actionRingBell(
_: *gio.SimpleAction,
_: ?*glib.Variant,

View File

@ -683,13 +683,6 @@ pub const Window = extern struct {
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,
@ -1458,25 +1451,6 @@ pub const Window = extern struct {
self.addToast(i18n._("Cleared clipboard"));
}
fn surfaceCloseRequest(
_: *Surface,
scope: *const Surface.CloseScope,
self: *Self,
) callconv(.c) void {
switch (scope.*) {
// Handled directly by the tab. If the surface is the last
// surface then the tab will emit its own signal to request
// closing itself.
.surface => return,
// Also handled directly by the tab.
.tab => return,
// The only one we care about!
.window => self.as(gtk.Window).close(),
}
}
fn surfaceMenu(
_: *Surface,
self: *Self,