From 56ccadd7f1de5df4041d475a1af9c051e034ffbc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 15:44:08 -0700 Subject: [PATCH 1/4] core: app needsConfirmQuit to streamline quitting if no active sessions --- include/ghostty.h | 1 + src/App.zig | 10 ++++++++++ src/Surface.zig | 28 +++++++++++++++------------- src/apprt/embedded.zig | 5 +++++ 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 56b1fe5dd..eab70bac2 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -316,6 +316,7 @@ bool ghostty_app_tick(ghostty_app_t); void *ghostty_app_userdata(ghostty_app_t); void ghostty_app_keyboard_changed(ghostty_app_t); void ghostty_app_reload_config(ghostty_app_t); +bool ghostty_app_needs_confirm_quit(ghostty_app_t); ghostty_surface_config_s ghostty_surface_config_new(); diff --git a/src/App.zig b/src/App.zig index 942581e3e..4b88c30e7 100644 --- a/src/App.zig +++ b/src/App.zig @@ -168,6 +168,16 @@ pub fn focusedSurface(self: *const App) ?*Surface { return surface; } +/// Returns true if confirmation is needed to quit the app. It is up to +/// the apprt to call this. +pub fn needsConfirmQuit(self: *const App) bool { + for (self.surfaces.items) |v| { + if (v.core_surface.needsConfirmQuit()) return true; + } + + return false; +} + /// Initialize once and return the font discovery mechanism. This remains /// initialized throughout the lifetime of the application because some /// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit. diff --git a/src/Surface.zig b/src/Surface.zig index 505f572f5..922175e2d 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -504,22 +504,24 @@ pub fn deinit(self: *Surface) void { /// Close this surface. This will trigger the runtime to start the /// close process, which should ultimately deinitialize this surface. pub fn close(self: *Surface) void { - const process_alive = process_alive: { - // If the child has exited then our process is certainly not alive. - // We check this first to avoid the locking overhead below. - if (self.child_exited) break :process_alive false; + self.rt_surface.close(self.needsConfirmQuit()); +} - // If we are configured to not hold open surfaces explicitly, just - // always say there is nothing alive. - if (!self.config.confirm_close_surface) break :process_alive false; +/// True if the surface requires confirmation to quit. This should be called +/// by apprt to determine if the surface should confirm before quitting. +pub fn needsConfirmQuit(self: *Surface) bool { + // If the child has exited then our process is certainly not alive. + // We check this first to avoid the locking overhead below. + if (self.child_exited) return false; - // We have to talk to the terminal. - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); - break :process_alive !self.io.terminal.cursorIsAtPrompt(); - }; + // If we are configured to not hold open surfaces explicitly, just + // always say there is nothing alive. + if (!self.config.confirm_close_surface) return false; - self.rt_surface.close(process_alive); + // We have to talk to the terminal. + self.renderer_state.mutex.lock(); + defer self.renderer_state.mutex.unlock(); + return !self.io.terminal.cursorIsAtPrompt(); } /// Called from the app thread to handle mailbox messages to our specific diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index e3475cc82..e3b8df6e0 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -787,6 +787,11 @@ pub const CAPI = struct { }; } + /// Returns true if the app needs to confirm quitting. + export fn ghostty_app_needs_confirm_quit(v: *App) bool { + return v.core_app.needsConfirmQuit(); + } + /// Returns initial surface options. export fn ghostty_surface_config_new() apprt.Surface.Options { return .{}; From 999d17c49d6899dda8d8cdf74847e772c6abbb2e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 15:44:28 -0700 Subject: [PATCH 2/4] macos: integrate needsConfirmQuit to quit if all windows are at prompt --- macos/Sources/AppDelegate.swift | 3 +++ macos/Sources/Ghostty/AppState.swift | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index 6785c3a30..e412de441 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -102,6 +102,9 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp } } + // If our app says we don't need to confirm, we can exit now. + if (!ghostty.needsConfirmQuit) { return .terminateNow } + // We have some visible window, and all our windows will watch the confirmQuit. confirmQuit = true return .terminateLater diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index c714a4f40..99ddf04ac 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -40,6 +40,12 @@ extension Ghostty { } } + /// True if we need to confirm before quitting. + var needsConfirmQuit: Bool { + guard let app = app else { return false } + return ghostty_app_needs_confirm_quit(app) + } + /// Cached clipboard string for `read_clipboard` callback. private var cached_clipboard_string: String? = nil From 868b66ce749fa5e05045fc5aedd8e4527e54dbfd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 15:47:37 -0700 Subject: [PATCH 3/4] apprt/gtk: integrate app needsConfirmQuit --- src/apprt/gtk.zig | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index b0f845ced..cd04022d7 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -228,6 +228,12 @@ pub const App = struct { } c.g_list_free(list); + // If the app says we don't need to confirm, then we can quit now. + if (!self.core_app.needsConfirmQuit()) { + self.quitNow(); + return; + } + // If we have windows, then we want to confirm that we want to exit. const alert = c.gtk_message_dialog_new( null, @@ -259,20 +265,8 @@ pub const App = struct { c.gtk_widget_show(alert); } - fn gtkQuitConfirmation( - alert: *c.GtkMessageDialog, - response: c.gint, - ud: ?*anyopaque, - ) callconv(.C) void { - _ = ud; - - // Close the alert window - c.gtk_window_destroy(@ptrCast(alert)); - - // If we didn't confirm then we're done - if (response != c.GTK_RESPONSE_YES) return; - - // Force close all open windows + fn quitNow(self: *App) void { + _ = self; const list = c.gtk_window_list_toplevels(); defer c.g_list_free(list); c.g_list_foreach(list, struct { @@ -285,6 +279,23 @@ pub const App = struct { }.callback, null); } + fn gtkQuitConfirmation( + alert: *c.GtkMessageDialog, + response: c.gint, + ud: ?*anyopaque, + ) callconv(.C) void { + const self: *App = @ptrCast(@alignCast(ud orelse return)); + + // Close the alert window + c.gtk_window_destroy(@ptrCast(alert)); + + // If we didn't confirm then we're done + if (response != c.GTK_RESPONSE_YES) return; + + // Force close all open windows + self.quitNow(); + } + /// This is called by the "activate" signal. This is sent on program /// startup and also when a secondary instance launches and requests /// a new window. From fc90a4a05235b2980b67d67a4cb030eb2a3a2bcf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 11 Sep 2023 15:53:20 -0700 Subject: [PATCH 4/4] apprt/gtk: do not ask for confirm window close if all surfaces idle --- src/apprt/gtk.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/apprt/gtk.zig b/src/apprt/gtk.zig index cd04022d7..54e805dd1 100644 --- a/src/apprt/gtk.zig +++ b/src/apprt/gtk.zig @@ -594,6 +594,13 @@ const Window = struct { log.debug("window close request", .{}); const self = userdataSelf(ud.?); + // If none of our surfaces need confirmation, we can just exit. + for (self.app.core_app.surfaces.items) |surface| { + if (surface.window == self) { + if (surface.core_surface.needsConfirmQuit()) break; + } + } else return false; + // Setup our basic message const alert = c.gtk_message_dialog_new( self.window,