mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
Merge pull request #430 from mitchellh/confirm-quit
Confirm quit improvements, both macOS and GTK
This commit is contained in:
@ -316,6 +316,7 @@ bool ghostty_app_tick(ghostty_app_t);
|
|||||||
void *ghostty_app_userdata(ghostty_app_t);
|
void *ghostty_app_userdata(ghostty_app_t);
|
||||||
void ghostty_app_keyboard_changed(ghostty_app_t);
|
void ghostty_app_keyboard_changed(ghostty_app_t);
|
||||||
void ghostty_app_reload_config(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();
|
ghostty_surface_config_s ghostty_surface_config_new();
|
||||||
|
|
||||||
|
@ -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.
|
// We have some visible window, and all our windows will watch the confirmQuit.
|
||||||
confirmQuit = true
|
confirmQuit = true
|
||||||
return .terminateLater
|
return .terminateLater
|
||||||
|
@ -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.
|
/// Cached clipboard string for `read_clipboard` callback.
|
||||||
private var cached_clipboard_string: String? = nil
|
private var cached_clipboard_string: String? = nil
|
||||||
|
|
||||||
|
10
src/App.zig
10
src/App.zig
@ -168,6 +168,16 @@ pub fn focusedSurface(self: *const App) ?*Surface {
|
|||||||
return 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
|
/// Initialize once and return the font discovery mechanism. This remains
|
||||||
/// initialized throughout the lifetime of the application because some
|
/// initialized throughout the lifetime of the application because some
|
||||||
/// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit.
|
/// font discovery mechanisms (i.e. fontconfig) are unsafe to reinit.
|
||||||
|
@ -504,22 +504,24 @@ pub fn deinit(self: *Surface) void {
|
|||||||
/// Close this surface. This will trigger the runtime to start the
|
/// Close this surface. This will trigger the runtime to start the
|
||||||
/// close process, which should ultimately deinitialize this surface.
|
/// close process, which should ultimately deinitialize this surface.
|
||||||
pub fn close(self: *Surface) void {
|
pub fn close(self: *Surface) void {
|
||||||
const process_alive = process_alive: {
|
self.rt_surface.close(self.needsConfirmQuit());
|
||||||
// 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;
|
|
||||||
|
|
||||||
// If we are configured to not hold open surfaces explicitly, just
|
/// True if the surface requires confirmation to quit. This should be called
|
||||||
// always say there is nothing alive.
|
/// by apprt to determine if the surface should confirm before quitting.
|
||||||
if (!self.config.confirm_close_surface) break :process_alive false;
|
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.
|
// If we are configured to not hold open surfaces explicitly, just
|
||||||
self.renderer_state.mutex.lock();
|
// always say there is nothing alive.
|
||||||
defer self.renderer_state.mutex.unlock();
|
if (!self.config.confirm_close_surface) return false;
|
||||||
break :process_alive !self.io.terminal.cursorIsAtPrompt();
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
/// Called from the app thread to handle mailbox messages to our specific
|
||||||
|
@ -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.
|
/// Returns initial surface options.
|
||||||
export fn ghostty_surface_config_new() apprt.Surface.Options {
|
export fn ghostty_surface_config_new() apprt.Surface.Options {
|
||||||
return .{};
|
return .{};
|
||||||
|
@ -228,6 +228,12 @@ pub const App = struct {
|
|||||||
}
|
}
|
||||||
c.g_list_free(list);
|
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.
|
// If we have windows, then we want to confirm that we want to exit.
|
||||||
const alert = c.gtk_message_dialog_new(
|
const alert = c.gtk_message_dialog_new(
|
||||||
null,
|
null,
|
||||||
@ -259,20 +265,8 @@ pub const App = struct {
|
|||||||
c.gtk_widget_show(alert);
|
c.gtk_widget_show(alert);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gtkQuitConfirmation(
|
fn quitNow(self: *App) void {
|
||||||
alert: *c.GtkMessageDialog,
|
_ = self;
|
||||||
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
|
|
||||||
const list = c.gtk_window_list_toplevels();
|
const list = c.gtk_window_list_toplevels();
|
||||||
defer c.g_list_free(list);
|
defer c.g_list_free(list);
|
||||||
c.g_list_foreach(list, struct {
|
c.g_list_foreach(list, struct {
|
||||||
@ -285,6 +279,23 @@ pub const App = struct {
|
|||||||
}.callback, null);
|
}.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
|
/// This is called by the "activate" signal. This is sent on program
|
||||||
/// startup and also when a secondary instance launches and requests
|
/// startup and also when a secondary instance launches and requests
|
||||||
/// a new window.
|
/// a new window.
|
||||||
@ -583,6 +594,13 @@ const Window = struct {
|
|||||||
log.debug("window close request", .{});
|
log.debug("window close request", .{});
|
||||||
const self = userdataSelf(ud.?);
|
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
|
// Setup our basic message
|
||||||
const alert = c.gtk_message_dialog_new(
|
const alert = c.gtk_message_dialog_new(
|
||||||
self.window,
|
self.window,
|
||||||
|
Reference in New Issue
Block a user