diff --git a/include/ghostty.h b/include/ghostty.h index bcd88251b..0c9b840e7 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -680,6 +680,12 @@ typedef struct { uintptr_t len; } ghostty_action_open_url_s; +// apprt.surface.Message.ChildExited +typedef struct { + uint32_t exit_code; + uint64_t timetime_ms; +} ghostty_surface_message_childexited_s; + // apprt.Action.Key typedef enum { GHOSTTY_ACTION_QUIT, @@ -731,6 +737,7 @@ typedef enum { GHOSTTY_ACTION_REDO, GHOSTTY_ACTION_CHECK_FOR_UPDATES, GHOSTTY_ACTION_OPEN_URL, + GHOSTTY_ACTION_SHOW_CHILD_EXITED } ghostty_action_tag_e; typedef union { @@ -759,6 +766,7 @@ typedef union { ghostty_action_reload_config_s reload_config; ghostty_action_config_change_s config_change; ghostty_action_open_url_s open_url; + ghostty_surface_message_childexited_s child_exited; } ghostty_action_u; typedef struct { diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 0fdea1760..f78585c9a 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -579,6 +579,8 @@ extension Ghostty { case GHOSTTY_ACTION_SIZE_LIMIT: fallthrough case GHOSTTY_ACTION_QUIT_TIMER: + fallthrough + case GHOSTTY_SHOW_CHILD_EXITED: Ghostty.logger.info("known but unimplemented action action=\(action.tag.rawValue)") return false default: diff --git a/src/Surface.zig b/src/Surface.zig index a4a8d46df..6e58ab5a5 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1018,6 +1018,14 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void { return; }; + _ = self.rt_app.performAction( + .{ .surface = self }, + .show_child_exited, + info, + ) catch |err| { + log.err("error trying to show native child exited GUI err={}", .{err}); + }; + return; } @@ -1044,6 +1052,14 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void { t.screen.kitty_keyboard.set(.set, .{}); } + _ = self.rt_app.performAction( + .{ .surface = self }, + .show_child_exited, + info, + ) catch |err| { + log.err("error trying to show native child exited GUI err={}", .{err}); + }; + // Waiting after command we stop here. The terminal is updated, our // state is updated, and now its up to the user to decide what to do. if (self.config.wait_after_command) return; diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 1c3c7c72c..201d27e31 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -272,6 +272,9 @@ pub const Action = union(Key) { /// apprt. open_url: OpenUrl, + /// Show a native GUI notification that the child process has exited. + show_child_exited: apprt.surface.Message.ChildExited, + /// Sync with: ghostty_action_tag_e pub const Key = enum(c_int) { quit, @@ -323,6 +326,7 @@ pub const Action = union(Key) { redo, check_for_updates, open_url, + show_child_exited, }; /// Sync with: ghostty_action_u diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index bdb2f0f24..a3a6ec411 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -521,6 +521,7 @@ pub fn performAction( .ring_bell => try self.ringBell(target), .toggle_command_palette => try self.toggleCommandPalette(target), .open_url => self.openUrl(value), + .show_child_exited => try self.showChildExited(target, value), // Unimplemented .close_all_windows, @@ -846,6 +847,13 @@ fn toggleCommandPalette(_: *App, target: apprt.Target) !void { } } +fn showChildExited(_: *App, target: apprt.Target, value: apprt.surface.Message.ChildExited) !void { + switch (target) { + .app => {}, + .surface => |surface| try surface.rt_surface.showChildExited(value), + } +} + fn quitTimer(self: *App, mode: apprt.action.QuitTimer) void { switch (mode) { .start => self.startQuitTimer(), diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index d16083d5a..7ea11bc17 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -2503,3 +2503,20 @@ fn gtkStreamError(media_file: *gtk.MediaFile, _: *gobject.ParamSpec, _: ?*anyopa fn gtkStreamEnded(media_file: *gtk.MediaFile, _: *gobject.ParamSpec, _: ?*anyopaque) callconv(.c) void { media_file.unref(); } + +pub fn showChildExited(self: *Surface, _: apprt.surface.Message.ChildExited) (error{})!void { + if (!adw_version.supportsBanner()) return; + + const warning_box = gtk.Box.new(.vertical, 0); + + warning_box.as(gtk.Widget).setHalign(.fill); + warning_box.as(gtk.Widget).setValign(.end); + + const warning_text = i18n._("⚠️ Process exited. Press any key to close the terminal."); + const banner = adw.Banner.new(warning_text); + banner.setRevealed(1); + + warning_box.append(banner.as(gtk.Widget)); + + self.overlay.addOverlay(warning_box.as(gtk.Widget)); +} diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index 9254b2fd5..1cd53b66a 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -98,7 +98,7 @@ pub const Message = union(enum) { // This enum is a placeholder for future title styles. }; - pub const ChildExited = struct { + pub const ChildExited = extern struct { exit_code: u32, runtime_ms: u64, };