mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
apprt/gtk-ng: child exited overlay
This commit is contained in:
@ -39,6 +39,7 @@ pub const blueprints: []const Blueprint = &.{
|
||||
.{ .major = 1, .minor = 2, .name = "config-errors-dialog" },
|
||||
.{ .major = 1, .minor = 2, .name = "resize-overlay" },
|
||||
.{ .major = 1, .minor = 2, .name = "surface" },
|
||||
.{ .major = 1, .minor = 3, .name = "surface-child-exited" },
|
||||
.{ .major = 1, .minor = 5, .name = "window" },
|
||||
};
|
||||
|
||||
|
@ -485,6 +485,8 @@ pub const Application = extern struct {
|
||||
|
||||
.set_title => Action.setTitle(target, value),
|
||||
|
||||
.show_child_exited => return Action.showChildExited(target, value),
|
||||
|
||||
.show_gtk_inspector => Action.showGtkInspector(),
|
||||
|
||||
// Unimplemented but todo on gtk-ng branch
|
||||
@ -514,7 +516,6 @@ pub const Application = extern struct {
|
||||
.ring_bell,
|
||||
.toggle_command_palette,
|
||||
.open_url,
|
||||
.show_child_exited,
|
||||
.close_all_windows,
|
||||
.float_window,
|
||||
.toggle_visibility,
|
||||
@ -1141,6 +1142,16 @@ const Action = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn showChildExited(
|
||||
target: apprt.Target,
|
||||
value: apprt.surface.Message.ChildExited,
|
||||
) bool {
|
||||
return switch (target) {
|
||||
.app => false,
|
||||
.surface => |v| v.rt_surface.surface.childExited(value),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn showGtkInspector() void {
|
||||
gtk.Window.setInteractiveDebugging(@intFromBool(true));
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ const Common = @import("../class.zig").Common;
|
||||
const Application = @import("application.zig").Application;
|
||||
const Config = @import("config.zig").Config;
|
||||
const ResizeOverlay = @import("resize_overlay.zig").ResizeOverlay;
|
||||
const ChildExited = @import("surface_child_exited.zig").SurfaceChildExited;
|
||||
const ClipboardConfirmationDialog = @import("clipboard_confirmation_dialog.zig").ClipboardConfirmationDialog;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_surface);
|
||||
@ -57,6 +58,26 @@ pub const Surface = extern struct {
|
||||
);
|
||||
};
|
||||
|
||||
pub const @"child-exited" = struct {
|
||||
pub const name = "child-exited";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
bool,
|
||||
.{
|
||||
.nick = "Child Exited",
|
||||
.blurb = "True when the child process has exited.",
|
||||
.default = false,
|
||||
.accessor = gobject.ext.privateFieldAccessor(
|
||||
Self,
|
||||
Private,
|
||||
&Private.offset,
|
||||
"child_exited",
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
pub const focused = struct {
|
||||
pub const name = "focused";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
@ -284,7 +305,11 @@ pub const Surface = extern struct {
|
||||
/// True when we have a precision scroll in progress
|
||||
precision_scroll: bool = false,
|
||||
|
||||
/// True when the child has exited.
|
||||
child_exited: bool = false,
|
||||
|
||||
// Template binds
|
||||
child_exited_overlay: *ChildExited,
|
||||
drop_target: *gtk.DropTarget,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
@ -634,6 +659,26 @@ pub const Surface = extern struct {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn childExited(
|
||||
self: *Self,
|
||||
data: apprt.surface.Message.ChildExited,
|
||||
) bool {
|
||||
// If we have the noop child exited overlay then we don't do anything
|
||||
// for child exited. The false return will force libghostty to show
|
||||
// the normal text-based message.
|
||||
if (comptime @hasDecl(ChildExited, "noop")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const priv = self.private();
|
||||
priv.child_exited = true;
|
||||
priv.child_exited_overlay.setData(&data);
|
||||
self.as(gobject.Object).notifyByPspec(
|
||||
properties.@"child-exited".impl.param_spec,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn cgroupPath(self: *Self) ?[]const u8 {
|
||||
return self.private().cgroup_path;
|
||||
}
|
||||
@ -1015,6 +1060,14 @@ pub const Surface = extern struct {
|
||||
//---------------------------------------------------------------
|
||||
// Signal Handlers
|
||||
|
||||
fn childExitedClose(
|
||||
_: *ChildExited,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
// This closes the surface with no confirmation.
|
||||
self.close(false);
|
||||
}
|
||||
|
||||
fn dtDrop(
|
||||
_: *gtk.DropTarget,
|
||||
value: *gobject.Value,
|
||||
@ -1762,6 +1815,7 @@ pub const Surface = extern struct {
|
||||
|
||||
fn init(class: *Class) callconv(.C) void {
|
||||
gobject.ext.ensureType(ResizeOverlay);
|
||||
gobject.ext.ensureType(ChildExited);
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
@ -1775,6 +1829,7 @@ pub const Surface = extern struct {
|
||||
class.bindTemplateChildPrivate("gl_area", .{});
|
||||
class.bindTemplateChildPrivate("url_left", .{});
|
||||
class.bindTemplateChildPrivate("url_right", .{});
|
||||
class.bindTemplateChildPrivate("child_exited_overlay", .{});
|
||||
class.bindTemplateChildPrivate("resize_overlay", .{});
|
||||
class.bindTemplateChildPrivate("drop_target", .{});
|
||||
class.bindTemplateChildPrivate("im_context", .{});
|
||||
@ -1802,6 +1857,7 @@ pub const Surface = extern struct {
|
||||
class.bindTemplateCallback("im_commit", &imCommit);
|
||||
class.bindTemplateCallback("url_mouse_enter", &ecUrlMouseEnter);
|
||||
class.bindTemplateCallback("url_mouse_leave", &ecUrlMouseLeave);
|
||||
class.bindTemplateCallback("child_exited_close", &childExitedClose);
|
||||
class.bindTemplateCallback("notify_config", &propConfig);
|
||||
class.bindTemplateCallback("notify_mouse_hover_url", &propMouseHoverUrl);
|
||||
class.bindTemplateCallback("notify_mouse_hidden", &propMouseHidden);
|
||||
@ -1810,6 +1866,7 @@ pub const Surface = extern struct {
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.config.impl,
|
||||
properties.@"child-exited".impl,
|
||||
properties.focused.impl,
|
||||
properties.@"mouse-shape".impl,
|
||||
properties.@"mouse-hidden".impl,
|
||||
|
272
src/apprt/gtk-ng/class/surface_child_exited.zig
Normal file
272
src/apprt/gtk-ng/class/surface_child_exited.zig
Normal file
@ -0,0 +1,272 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const adw = @import("adw");
|
||||
const glib = @import("glib");
|
||||
const gobject = @import("gobject");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const adw_version = @import("../adw_version.zig");
|
||||
const apprt = @import("../../../apprt.zig");
|
||||
const gresource = @import("../build/gresource.zig");
|
||||
const i18n = @import("../../../os/main.zig").i18n;
|
||||
const Common = @import("../class.zig").Common;
|
||||
|
||||
const log = std.log.scoped(.gtk_ghostty_surface_child_exited);
|
||||
|
||||
pub const SurfaceChildExited = if (adw_version.supportsBanner())
|
||||
SurfaceChildExitedBanner
|
||||
else
|
||||
SurfaceChildExitedNoop;
|
||||
|
||||
/// Child exited overlay based on adw.Banner introduced in
|
||||
/// Adwaita 1.3.
|
||||
const SurfaceChildExitedBanner = extern struct {
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = adw.Bin;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttySurfaceChildExited",
|
||||
.instanceInit = &init,
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
.private = .{ .Type = Private, .offset = &Private.offset },
|
||||
});
|
||||
|
||||
pub const properties = struct {
|
||||
pub const data = struct {
|
||||
pub const name = "data";
|
||||
const impl = gobject.ext.defineProperty(
|
||||
name,
|
||||
Self,
|
||||
?*apprt.surface.Message.ChildExited,
|
||||
.{
|
||||
.nick = "Data",
|
||||
.blurb = "The child exit data.",
|
||||
.accessor = gobject.ext.privateFieldAccessor(
|
||||
Self,
|
||||
Private,
|
||||
&Private.offset,
|
||||
"data",
|
||||
),
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
pub const signals = struct {
|
||||
/// Emitted when the banner would like to be closed.
|
||||
pub const @"close-request" = struct {
|
||||
pub const name = "close-request";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const Private = struct {
|
||||
/// The child exited data sent by the apprt.
|
||||
data: ?*apprt.surface.Message.ChildExited = null,
|
||||
|
||||
// Template bindings
|
||||
banner: *adw.Banner,
|
||||
|
||||
pub var offset: c_int = 0;
|
||||
};
|
||||
|
||||
fn init(self: *Self, _: *Class) callconv(.C) void {
|
||||
gtk.Widget.initTemplate(self.as(gtk.Widget));
|
||||
}
|
||||
|
||||
pub fn setData(
|
||||
self: *Self,
|
||||
data_: ?*const apprt.surface.Message.ChildExited,
|
||||
) void {
|
||||
const priv = self.private();
|
||||
if (priv.data) |v| glib.ext.destroy(v);
|
||||
const data = data_ orelse {
|
||||
priv.data = null;
|
||||
return;
|
||||
};
|
||||
|
||||
const ptr = glib.ext.create(apprt.surface.Message.ChildExited);
|
||||
ptr.* = data.*;
|
||||
priv.data = ptr;
|
||||
self.as(gobject.Object).notifyByPspec(properties.data.impl.param_spec);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Signal handlers
|
||||
|
||||
fn propData(
|
||||
self: *Self,
|
||||
_: *gobject.ParamSpec,
|
||||
_: ?*anyopaque,
|
||||
) callconv(.c) void {
|
||||
const priv = self.private();
|
||||
const banner = priv.banner;
|
||||
const data = priv.data orelse {
|
||||
// Not localized on purpose.
|
||||
banner.as(adw.Banner).setTitle("This is a bug in Ghostty. Please report it.");
|
||||
return;
|
||||
};
|
||||
if (data.exit_code == 0) {
|
||||
banner.as(adw.Banner).setTitle(i18n._("Command succeeded"));
|
||||
self.as(gtk.Widget).addCssClass("normal");
|
||||
self.as(gtk.Widget).removeCssClass("abnormal");
|
||||
} else {
|
||||
banner.as(adw.Banner).setTitle(i18n._("Command failed"));
|
||||
self.as(gtk.Widget).removeCssClass("normal");
|
||||
self.as(gtk.Widget).addCssClass("abnormal");
|
||||
}
|
||||
}
|
||||
|
||||
fn closeButtonClicked(
|
||||
_: *adw.Banner,
|
||||
self: *Self,
|
||||
) callconv(.c) void {
|
||||
signals.@"close-request".impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Virtual methods
|
||||
|
||||
fn dispose(self: *Self) callconv(.C) void {
|
||||
gtk.Widget.disposeTemplate(
|
||||
self.as(gtk.Widget),
|
||||
getGObjectType(),
|
||||
);
|
||||
|
||||
gobject.Object.virtual_methods.dispose.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
fn finalize(self: *Self) callconv(.C) void {
|
||||
const priv = self.private();
|
||||
if (priv.data) |v| {
|
||||
glib.ext.destroy(v);
|
||||
priv.data = null;
|
||||
}
|
||||
|
||||
gobject.Object.virtual_methods.finalize.call(
|
||||
Class.parent,
|
||||
self.as(Parent),
|
||||
);
|
||||
}
|
||||
|
||||
const C = Common(Self, Private);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.C) void {
|
||||
gtk.Widget.Class.setTemplateFromResource(
|
||||
class.as(gtk.Widget.Class),
|
||||
comptime gresource.blueprint(.{
|
||||
.major = 1,
|
||||
.minor = 3,
|
||||
.name = "surface-child-exited",
|
||||
}),
|
||||
);
|
||||
|
||||
// Template bindings
|
||||
class.bindTemplateChildPrivate("banner", .{});
|
||||
class.bindTemplateCallback("clicked", &closeButtonClicked);
|
||||
class.bindTemplateCallback("notify_data", &propData);
|
||||
|
||||
// Properties
|
||||
gobject.ext.registerProperties(class, &.{
|
||||
properties.data.impl,
|
||||
});
|
||||
|
||||
// Signals
|
||||
signals.@"close-request".impl.register(.{});
|
||||
|
||||
// Virtual methods
|
||||
gobject.Object.virtual_methods.dispose.implement(class, &dispose);
|
||||
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||
};
|
||||
};
|
||||
|
||||
/// Empty widget that does nothing if we don't have a new enough
|
||||
/// Adwaita version to support the child exited banner.
|
||||
const SurfaceChildExitedNoop = extern struct {
|
||||
/// Can be detected at comptime
|
||||
pub const noop = true;
|
||||
|
||||
const Self = @This();
|
||||
parent_instance: Parent,
|
||||
pub const Parent = gtk.Widget;
|
||||
pub const getGObjectType = gobject.ext.defineClass(Self, .{
|
||||
.name = "GhosttySurfaceChildExited",
|
||||
.classInit = &Class.init,
|
||||
.parent_class = &Class.parent,
|
||||
});
|
||||
|
||||
pub const signals = struct {
|
||||
pub const @"close-request" = struct {
|
||||
pub const name = "close-request";
|
||||
pub const connect = impl.connect;
|
||||
const impl = gobject.ext.defineSignal(
|
||||
name,
|
||||
Self,
|
||||
&.{},
|
||||
void,
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
pub fn setData(
|
||||
self: *Self,
|
||||
_: ?*const apprt.surface.Message.ChildExited,
|
||||
) void {
|
||||
signals.@"close-request".impl.emit(
|
||||
self,
|
||||
null,
|
||||
.{},
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
const C = Common(Self, null);
|
||||
pub const as = C.as;
|
||||
pub const ref = C.ref;
|
||||
pub const unref = C.unref;
|
||||
const private = C.private;
|
||||
|
||||
pub const Class = extern struct {
|
||||
parent_class: Parent.Class,
|
||||
var parent: *Parent.Class = undefined;
|
||||
pub const Instance = Self;
|
||||
|
||||
fn init(class: *Class) callconv(.C) void {
|
||||
_ = class;
|
||||
signals.@"close-request".impl.register(.{});
|
||||
}
|
||||
|
||||
pub const as = C.Class.as;
|
||||
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
|
||||
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
|
||||
};
|
||||
};
|
@ -69,3 +69,18 @@ label.url-overlay.right {
|
||||
.clipboard-confirmation-dialog .clipboard-contents.blurred {
|
||||
filter: blur(5px);
|
||||
}
|
||||
|
||||
/*
|
||||
* Child Exited Overlay
|
||||
*/
|
||||
.child-exited.normal revealer widget {
|
||||
background-color: rgba(38, 162, 105, 0.5);
|
||||
/* after GTK 4.16 is a requirement, switch to the following:
|
||||
/* background-color: color-mix(in srgb, var(--success-bg-color), transparent 50%); */
|
||||
}
|
||||
|
||||
.child-exited.abnormal revealer widget {
|
||||
background-color: rgba(192, 28, 40, 0.5);
|
||||
/* after GTK 4.16 is a requirement, switch to the following:
|
||||
/* background-color: color-mix(in srgb, var(--error-bg-color), transparent 50%); */
|
||||
}
|
||||
|
@ -25,6 +25,12 @@ template $GhosttySurface: Adw.Bin {
|
||||
use-es: false;
|
||||
}
|
||||
|
||||
[overlay]
|
||||
$GhosttySurfaceChildExited child_exited_overlay {
|
||||
visible: bind template.child-exited;
|
||||
close-request => $child_exited_close();
|
||||
}
|
||||
|
||||
[overlay]
|
||||
$GhosttyResizeOverlay resize_overlay {
|
||||
styles [
|
||||
|
22
src/apprt/gtk-ng/ui/1.3/surface-child-exited.blp
Normal file
22
src/apprt/gtk-ng/ui/1.3/surface-child-exited.blp
Normal file
@ -0,0 +1,22 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $GhosttySurfaceChildExited: Adw.Bin {
|
||||
styles [
|
||||
"child-exited",
|
||||
]
|
||||
|
||||
notify::data => $notify_data();
|
||||
|
||||
Adw.Bin {
|
||||
Adw.Banner banner {
|
||||
button-clicked => $clicked();
|
||||
revealed: true;
|
||||
// Not localized on purpose because it should never be seen.
|
||||
title: "This is a bug in Ghostty. Please report it.";
|
||||
button-label: _("Close");
|
||||
halign: fill;
|
||||
valign: end;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const apprt = @import("../apprt.zig");
|
||||
const build_config = @import("../build_config.zig");
|
||||
const App = @import("../App.zig");
|
||||
const Surface = @import("../Surface.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
@ -107,6 +108,18 @@ pub const Message = union(enum) {
|
||||
pub const ChildExited = extern struct {
|
||||
exit_code: u32,
|
||||
runtime_ms: u64,
|
||||
|
||||
/// Make this a valid gobject if we're in a GTK environment.
|
||||
pub const getGObjectType = switch (build_config.app_runtime) {
|
||||
.gtk,
|
||||
.@"gtk-ng",
|
||||
=> @import("gobject").ext.defineBoxed(
|
||||
ChildExited,
|
||||
.{ .name = "GhosttyApprtChildExited" },
|
||||
),
|
||||
|
||||
.none => void,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -82,6 +82,18 @@
|
||||
fun:main
|
||||
}
|
||||
|
||||
{
|
||||
VMware Graphics Driver
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
...
|
||||
fun:vmw_fence_create
|
||||
fun:vmw_ioctl_command
|
||||
fun:vmw_swc_flush
|
||||
fun:svga_context_flush
|
||||
...
|
||||
}
|
||||
|
||||
{
|
||||
GSK Renderer GPU Stuff
|
||||
Memcheck:Leak
|
||||
|
Reference in New Issue
Block a user