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 { 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 { pub fn cgroup(self: *Self) ?[]const u8 {

View File

@ -542,8 +542,8 @@ pub const Application = extern struct {
value: apprt.Action.Value(action), value: apprt.Action.Value(action),
) !bool { ) !bool {
switch (action) { switch (action) {
.close_tab => Action.close(target, .tab), .close_tab => return Action.closeTab(target),
.close_window => Action.close(target, .window), .close_window => return Action.closeWindow(target),
.config_change => try Action.configChange( .config_change => try Action.configChange(
self, self,
@ -1582,13 +1582,23 @@ pub const Application = extern struct {
/// All apprt action handlers /// All apprt action handlers
const Action = struct { const Action = struct {
pub fn close( pub fn closeTab(target: apprt.Target) bool {
target: apprt.Target,
scope: Surface.CloseScope,
) void {
switch (target) { switch (target) {
.app => {}, .app => return false,
.surface => |v| v.rt_surface.surface.close(scope), .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 // 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. /// 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 {
@ -636,18 +652,8 @@ pub const SplitTree = extern struct {
fn surfaceCloseRequest( fn surfaceCloseRequest(
surface: *Surface, surface: *Surface,
scope: *const Surface.CloseScope,
self: *Self, self: *Self,
) callconv(.c) void { ) 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; const core = surface.core() orelse return;
// Reset our pending close state // Reset our pending close state

View File

@ -275,7 +275,7 @@ pub const Surface = extern struct {
const impl = gobject.ext.defineSignal( const impl = gobject.ext.defineSignal(
name, name,
Self, Self,
&.{*const CloseScope}, &.{},
void, void,
); );
}; };
@ -1047,11 +1047,11 @@ pub const Surface = extern struct {
//--------------------------------------------------------------- //---------------------------------------------------------------
// Libghostty Callbacks // Libghostty Callbacks
pub fn close(self: *Self, scope: CloseScope) void { pub fn close(self: *Self) void {
signals.@"close-request".impl.emit( signals.@"close-request".impl.emit(
self, self,
null, null,
.{&scope}, .{},
null, null,
); );
} }
@ -1749,7 +1749,7 @@ pub const Surface = extern struct {
self: *Self, self: *Self,
) callconv(.c) void { ) callconv(.c) void {
// This closes the surface with no confirmation. // This closes the surface with no confirmation.
self.close(.{ .surface = false }); self.close();
} }
fn contextMenuClosed( fn contextMenuClosed(
@ -2709,25 +2709,6 @@ pub const Surface = extern struct {
pub const bindTemplateCallback = C.Class.bindTemplateCallback; 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. /// Simple dimensions struct for the surface used by various properties.
pub const Size = extern struct { pub const Size = extern struct {
width: u32, width: u32,

View File

@ -208,6 +208,7 @@ pub const Tab = extern struct {
// For action names: // For action names:
// https://docs.gtk.org/gio/type_func.Action.name_is_valid.html // https://docs.gtk.org/gio/type_func.Action.name_is_valid.html
const actions = .{ const actions = .{
.{ "close", actionClose, null },
.{ "ring-bell", actionRingBell, null }, .{ "ring-bell", actionRingBell, null },
}; };
@ -262,9 +263,8 @@ 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() orelse return false; const tree = self.getSplitTree();
const core_surface = surface.core() orelse return false; return tree.getNeedsConfirmQuit();
return core_surface.needsConfirmQuit();
} }
/// Get the tab page holding this tab, if any. /// 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); 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( fn actionRingBell(
_: *gio.SimpleAction, _: *gio.SimpleAction,
_: ?*glib.Variant, _: ?*glib.Variant,

View File

@ -683,13 +683,6 @@ pub const Window = extern struct {
var it = tree.iterator(); var it = tree.iterator();
while (it.next()) |entry| { while (it.next()) |entry| {
const surface = entry.view; const surface = entry.view;
_ = Surface.signals.@"close-request".connect(
surface,
*Self,
surfaceCloseRequest,
self,
.{},
);
_ = Surface.signals.@"present-request".connect( _ = Surface.signals.@"present-request".connect(
surface, surface,
*Self, *Self,
@ -1458,25 +1451,6 @@ pub const Window = extern struct {
self.addToast(i18n._("Cleared clipboard")); 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( fn surfaceMenu(
_: *Surface, _: *Surface,
self: *Self, self: *Self,