From ca4e38ff03b8e45edce0b3817c678ead264a40d1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 10 Aug 2025 13:35:26 -0700 Subject: [PATCH] apprt/gtk-ng: split close confirmation --- .../class/close_confirmation_dialog.zig | 4 + src/apprt/gtk-ng/class/split_tree.zig | 96 ++++++++++++++----- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/src/apprt/gtk-ng/class/close_confirmation_dialog.zig b/src/apprt/gtk-ng/class/close_confirmation_dialog.zig index 210533c1c..3debafbb5 100644 --- a/src/apprt/gtk-ng/class/close_confirmation_dialog.zig +++ b/src/apprt/gtk-ng/class/close_confirmation_dialog.zig @@ -133,6 +133,7 @@ pub const CloseConfirmationDialog = extern struct { const C = Common(Self, Private); pub const as = C.as; pub const ref = C.ref; + pub const refSink = C.refSink; pub const unref = C.unref; const private = C.private; @@ -179,12 +180,14 @@ pub const Target = enum(c_int) { app, tab, window, + surface, pub fn title(self: Target) [*:0]const u8 { return switch (self) { .app => i18n._("Quit Ghostty?"), .tab => i18n._("Close Tab?"), .window => i18n._("Close Window?"), + .surface => i18n._("Close Split?"), }; } @@ -193,6 +196,7 @@ pub const Target = enum(c_int) { .app => i18n._("All terminal sessions will be terminated."), .tab => i18n._("All terminal sessions in this tab will be terminated."), .window => i18n._("All terminal sessions in this window will be terminated."), + .surface => i18n._("The currently running process in this split will be terminated."), }; } diff --git a/src/apprt/gtk-ng/class/split_tree.zig b/src/apprt/gtk-ng/class/split_tree.zig index e547fa6d7..b70f0e2f8 100644 --- a/src/apprt/gtk-ng/class/split_tree.zig +++ b/src/apprt/gtk-ng/class/split_tree.zig @@ -130,6 +130,10 @@ pub const SplitTree = extern struct { /// propTree function for a lot more details. rebuild_pending: bool, + /// Used to store state about a pending surface close for the + /// close dialog. + pending_close: ?Surface.Tree.Node.Handle, + pub var offset: c_int = 0; }; @@ -138,6 +142,10 @@ pub const SplitTree = extern struct { // Initialize our actions self.initActions(); + + // Initialize some basic state + const priv = self.private(); + priv.pending_close = null; } fn initActions(self: *Self) void { @@ -511,32 +519,70 @@ pub const SplitTree = extern struct { .window, .tab => return, // Remove the surface from the tree. - .surface => { - // TODO: close confirmation - - // Find the surface in the tree. - const tree = self.getTree() orelse return; - const handle: Surface.Tree.Node.Handle = handle: { - var it = tree.iterator(); - while (it.next()) |entry| { - if (entry.view == surface) break :handle entry.handle; - } - - return; - }; - - // Remove it from the tree. - var new_tree = tree.remove( - Application.default().allocator(), - handle, - ) catch |err| { - log.warn("unable to remove surface from tree: {}", .{err}); - return; - }; - defer new_tree.deinit(); - self.setTree(&new_tree); - }, + .surface => {}, } + + const core = surface.core() orelse return; + + // Reset our pending close state + const priv = self.private(); + priv.pending_close = null; + + // Find the surface in the tree to verify this is valid and + // set our pending close handle. + priv.pending_close = handle: { + const tree = self.getTree() orelse return; + var it = tree.iterator(); + while (it.next()) |entry| { + if (entry.view == surface) { + break :handle entry.handle; + } + } + + return; + }; + + // If we don't need to confirm then just close immediately. + if (!core.needsConfirmQuit()) { + closeConfirmationClose( + null, + self, + ); + return; + } + + // Show a confirmation dialog + const dialog: *CloseConfirmationDialog = .new(.surface); + _ = CloseConfirmationDialog.signals.@"close-request".connect( + dialog, + *Self, + closeConfirmationClose, + self, + .{}, + ); + dialog.present(self.as(gtk.Widget)); + } + + fn closeConfirmationClose( + _: ?*CloseConfirmationDialog, + self: *Self, + ) callconv(.c) void { + // Get the handle we're closing + const priv = self.private(); + const handle = priv.pending_close orelse return; + priv.pending_close = null; + + // Remove it from the tree. + const old_tree = self.getTree() orelse return; + var new_tree = old_tree.remove( + Application.default().allocator(), + handle, + ) catch |err| { + log.warn("unable to remove surface from tree: {}", .{err}); + return; + }; + defer new_tree.deinit(); + self.setTree(&new_tree); } fn propSurfaceFocused(