GTK: add action to show the GTK inspector

The default keybinds for showing the GTK inspector (`ctrl+shift+i` and
`ctrl+shift+d`) don't work reliably in Ghostty due to the way Ghostty
handles input. You can show the GTK inspector by setting the environment
variable `GTK_DEBUG` to `interactive` before starting Ghostty but that's
not always convenient.

This adds a keybind action that will show the GTK inspector. Due to
API limitations toggling the GTK inspector using the keybind action is
impractical because GTK does not provide a convenient API to determine
if the GTK inspector is already showing. Thus we limit ourselves to
strictly showing the GTK inspector. To close the GTK inspector the user
must click the close button on the GTK inspector window. If the GTK
inspector window is already visible but is hidden, calling the keybind
action will not bring the GTK inspector window to the front.
This commit is contained in:
Jeffrey C. Ollie
2025-05-29 15:45:51 -05:00
parent b2f3c7f309
commit d3cb6d0d41
8 changed files with 38 additions and 1 deletions

View File

@ -653,6 +653,7 @@ typedef enum {
GHOSTTY_ACTION_INITIAL_SIZE,
GHOSTTY_ACTION_CELL_SIZE,
GHOSTTY_ACTION_INSPECTOR,
GHOSTTY_ACTION_SHOW_GTK_INSPECTOR,
GHOSTTY_ACTION_RENDER_INSPECTOR,
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
GHOSTTY_ACTION_SET_TITLE,

View File

@ -29,7 +29,8 @@ struct TerminalCommandPaletteView: View {
let key = String(cString: c.action_key)
switch (key) {
case "toggle_tab_overview",
"toggle_window_decorations":
"toggle_window_decorations",
"show_gtk_inspector":
return false
default:
return true

View File

@ -445,6 +445,7 @@ pub fn performAction(
.toggle_quick_terminal => _ = try rt_app.performAction(.app, .toggle_quick_terminal, {}),
.toggle_visibility => _ = try rt_app.performAction(.app, .toggle_visibility, {}),
.check_for_updates => _ = try rt_app.performAction(.app, .check_for_updates, {}),
.show_gtk_inspector => _ = try rt_app.performAction(.app, .show_gtk_inspector, {}),
}
}

View File

@ -165,6 +165,9 @@ pub const Action = union(Key) {
/// Control whether the inspector is shown or hidden.
inspector: Inspector,
/// Show the GTK inspector.
show_gtk_inspector,
/// The inspector for the given target has changes and should be
/// rendered at the next opportunity.
render_inspector,
@ -284,6 +287,7 @@ pub const Action = union(Key) {
initial_size,
cell_size,
inspector,
show_gtk_inspector,
render_inspector,
desktop_notification,
set_title,

View File

@ -250,6 +250,7 @@ pub const App = struct {
.reset_window_size,
.ring_bell,
.check_for_updates,
.show_gtk_inspector,
=> {
log.info("unimplemented action={}", .{action});
return false;

View File

@ -481,6 +481,7 @@ pub fn performAction(
.config_change => self.configChange(target, value.config),
.reload_config => try self.reloadConfig(target, value),
.inspector => self.controlInspector(target, value),
.show_gtk_inspector => self.showGTKInspector(),
.desktop_notification => self.showDesktopNotification(target, value),
.set_title => try self.setTitle(target, value),
.pwd => try self.setPwd(target, value),
@ -687,6 +688,12 @@ fn controlInspector(
surface.controlInspector(mode);
}
fn showGTKInspector(
_: *const App,
) void {
gtk.Window.setInteractiveDebugging(@intFromBool(true));
}
fn toggleMaximize(_: *App, target: apprt.Target) void {
switch (target) {
.app => {},
@ -1060,6 +1067,7 @@ fn syncActionAccelerators(self: *App) !void {
try self.syncActionAccelerator("app.open-config", .{ .open_config = {} });
try self.syncActionAccelerator("app.reload-config", .{ .reload_config = {} });
try self.syncActionAccelerator("win.toggle-inspector", .{ .inspector = .toggle });
try self.syncActionAccelerator("app.show-gtk-inspector", .show_gtk_inspector);
try self.syncActionAccelerator("win.toggle-command-palette", .toggle_command_palette);
try self.syncActionAccelerator("win.close", .{ .close_window = {} });
try self.syncActionAccelerator("win.new-window", .{ .new_window = {} });
@ -1655,6 +1663,16 @@ fn gtkActionPresentSurface(
);
}
fn gtkActionShowGTKInspector(
_: *gio.SimpleAction,
_: ?*glib.Variant,
self: *App,
) callconv(.c) void {
self.core_app.performAction(self, .show_gtk_inspector) catch |err| {
log.err("error showing GTK inspector err={}", .{err});
};
}
/// This is called to setup the action map that this application supports.
/// This should be called only once on startup.
fn initActions(self: *App) void {
@ -1673,6 +1691,7 @@ fn initActions(self: *App) void {
.{ "open-config", gtkActionOpenConfig, null },
.{ "reload-config", gtkActionReloadConfig, null },
.{ "present-surface", gtkActionPresentSurface, t },
.{ "show-gtk-inspector", gtkActionShowGTKInspector, null },
};
inline for (actions) |entry| {
const action = gio.SimpleAction.new(entry[0], entry[2]);

View File

@ -397,6 +397,9 @@ pub const Action = union(enum) {
/// keybind = cmd+i=inspector:toggle
inspector: InspectorMode,
/// Show the GTK inspector.
show_gtk_inspector,
/// Open the configuration file in the default OS editor. If your default OS
/// editor isn't configured then this will fail. Currently, any failures to
/// open the configuration will show up only in the logs.
@ -795,6 +798,7 @@ pub const Action = union(enum) {
.toggle_quick_terminal,
.toggle_visibility,
.check_for_updates,
.show_gtk_inspector,
=> .app,
// These are app but can be special-cased in a surface context.

View File

@ -298,6 +298,12 @@ fn actionCommands(action: Action.Key) []const Command {
.description = "Toggle the inspector.",
}},
.show_gtk_inspector => comptime &.{.{
.action = .show_gtk_inspector,
.title = "Show the GTK Inspector",
.description = "Show the GTK inspector.",
}},
.open_config => comptime &.{.{
.action = .open_config,
.title = "Open Config",