diff --git a/include/ghostty.h b/include/ghostty.h index 571cbd904..7c81dbe77 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -505,6 +505,7 @@ typedef enum { GHOSTTY_ACTION_NEW_SPLIT, GHOSTTY_ACTION_CLOSE_ALL_WINDOWS, GHOSTTY_ACTION_TOGGLE_FULLSCREEN, + GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW, GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS, GHOSTTY_ACTION_GOTO_TAB, GHOSTTY_ACTION_GOTO_SPLIT, diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index f0128e2f7..5b2efad3e 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -484,6 +484,8 @@ extension Ghostty { case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS: fallthrough + case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW: + fallthrough case GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS: fallthrough case GHOSTTY_ACTION_PRESENT_TERMINAL: diff --git a/src/Surface.zig b/src/Surface.zig index a37f0a7e8..28d66d25d 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3830,6 +3830,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool {}, ), + .toggle_tab_overview => try self.rt_app.performAction( + .{ .surface = self }, + .toggle_tab_overview, + {}, + ), + .toggle_secure_input => try self.rt_app.performAction( .{ .surface = self }, .secure_input, diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 70c189c8f..9ed89b5a3 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -87,6 +87,9 @@ pub const Action = union(Key) { /// Toggle fullscreen mode. toggle_fullscreen: Fullscreen, + /// Toggle tab overview. + toggle_tab_overview, + /// Toggle whether window directions are shown. toggle_window_decorations, @@ -171,6 +174,7 @@ pub const Action = union(Key) { new_split, close_all_windows, toggle_fullscreen, + toggle_tab_overview, toggle_window_decorations, goto_tab, goto_split, diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 57667afb1..fb31f7c2b 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -194,6 +194,7 @@ pub const App = struct { .toggle_split_zoom, .present_terminal, .close_all_windows, + .toggle_tab_overview, .toggle_window_decorations, .goto_tab, .inspector, diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 45031324a..294954bd4 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -372,6 +372,7 @@ pub fn performAction( .mouse_visibility => self.setMouseVisibility(target, value), .mouse_shape => try self.setMouseShape(target, value), .mouse_over_link => self.setMouseOverLink(target, value), + .toggle_tab_overview => self.toggleTabOverview(target), .toggle_window_decorations => self.toggleWindowDecorations(target), .quit_timer => self.quitTimer(value), @@ -534,6 +535,23 @@ fn toggleFullscreen( } } +fn toggleTabOverview(_: *App, target: apprt.Target) void { + switch (target) { + .app => {}, + .surface => |v| { + const window = v.rt_surface.container.window() orelse { + log.info( + "toggleTabOverview invalid for container={s}", + .{@tagName(v.rt_surface.container)}, + ); + return; + }; + + window.toggleTabOverview(); + }, + } +} + fn toggleWindowDecorations( _: *App, target: apprt.Target, diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 80bbd0944..ff8735ff9 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -35,6 +35,10 @@ window: *c.GtkWindow, /// GtkHeaderBar depending on if adw is enabled and linked. header: ?*c.GtkWidget, +/// The tab overview for the window. This is possibly null since there is no +/// taboverview without a AdwApplicationWindow (libadwaita >= 1.4.0). +tab_overview: ?*c.GtkWidget, + /// The notebook (tab grouping) for this window. /// can be either c.GtkNotebook or c.AdwTabView. notebook: Notebook, @@ -68,6 +72,7 @@ pub fn init(self: *Window, app: *App) !void { .app = app, .window = undefined, .header = null, + .tab_overview = null, .notebook = undefined, .context_menu = undefined, .toast_overlay = undefined, @@ -114,7 +119,7 @@ pub fn init(self: *Window, app: *App) !void { const box = c.gtk_box_new(c.GTK_ORIENTATION_VERTICAL, 0); // If we are using an AdwWindow then we can support the tab overview. - const tab_overview_: ?*c.GtkWidget = if (self.isAdwWindow()) overview: { + self.tab_overview = if (self.isAdwWindow()) overview: { const tab_overview = c.adw_tab_overview_new(); c.adw_tab_overview_set_enable_new_tab(@ptrCast(tab_overview), 1); _ = c.g_signal_connect_data( @@ -152,14 +157,15 @@ pub fn init(self: *Window, app: *App) !void { c.gtk_widget_set_tooltip_text(btn, "Main Menu"); c.gtk_menu_button_set_icon_name(@ptrCast(btn), "open-menu-symbolic"); c.gtk_menu_button_set_menu_model(@ptrCast(btn), @ptrCast(@alignCast(app.menu))); - if (self.isAdwWindow()) - c.adw_header_bar_pack_end(@ptrCast(header), btn) - else - c.gtk_header_bar_pack_end(@ptrCast(header), btn); + if (self.isAdwWindow()) { + if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable; + c.adw_header_bar_pack_end(@ptrCast(header), btn); + } else c.gtk_header_bar_pack_end(@ptrCast(header), btn); } // If we're using an AdwWindow then we can support the tab overview. - if (tab_overview_) |tab_overview| { + if (self.tab_overview) |tab_overview| { + if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable; assert(self.isAdwWindow()); const btn = c.gtk_toggle_button_new(); @@ -235,7 +241,8 @@ pub fn init(self: *Window, app: *App) !void { }; // If we have a tab overview then we can set it on our notebook. - if (tab_overview_) |tab_overview| { + if (self.tab_overview) |tab_overview| { + if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable; assert(self.notebook == .adw_tab_view); c.adw_tab_overview_set_view(@ptrCast(tab_overview), self.notebook.adw_tab_view); } @@ -256,7 +263,8 @@ pub fn init(self: *Window, app: *App) !void { // Our actions for the menu initActions(self); - if (self.hasAdwToolbar()) { + if (self.isAdwWindow()) { + if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable; const toolbar_view: *c.AdwToolbarView = @ptrCast(c.adw_toolbar_view_new()); const header_widget: *c.GtkWidget = @ptrCast(@alignCast(self.header.?)); @@ -289,7 +297,7 @@ pub fn init(self: *Window, app: *App) !void { // Set our application window content. The content depends on if // we're using an AdwTabOverview or not. - if (tab_overview_) |tab_overview| { + if (self.tab_overview) |tab_overview| { c.adw_tab_overview_set_child( @ptrCast(tab_overview), @ptrCast(@alignCast(toolbar_view)), @@ -391,18 +399,9 @@ pub fn deinit(self: *Window) void { /// paths that are not enabled. inline fn isAdwWindow(self: *Window) bool { return (comptime adwaita.versionAtLeast(1, 4, 0)) and - adwaita.enabled(&self.app.config) and - self.app.config.@"gtk-titlebar" and - adwaita.versionAtLeast(1, 4, 0); -} - -/// This must be `inline` so that the comptime check noops conditional -/// paths that are not enabled. -inline fn hasAdwToolbar(self: *Window) bool { - return ((comptime adwaita.versionAtLeast(1, 4, 0)) and adwaita.enabled(&self.app.config) and adwaita.versionAtLeast(1, 4, 0) and - self.app.config.@"gtk-titlebar"); + self.app.config.@"gtk-titlebar"; } /// Add a new tab to this window. @@ -458,6 +457,15 @@ pub fn gotoTab(self: *Window, n: usize) void { } } +/// Toggle tab overview (if present) +pub fn toggleTabOverview(self: *Window) void { + if (self.tab_overview) |tab_overview_widget| { + if (comptime !adwaita.versionAtLeast(1, 4, 0)) unreachable; + const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(tab_overview_widget)); + c.adw_tab_overview_set_open(tab_overview, 1 - c.adw_tab_overview_get_open(tab_overview)); + } +} + /// Toggle fullscreen for this window. pub fn toggleFullscreen(self: *Window) void { const is_fullscreen = c.gtk_window_is_fullscreen(self.window); diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 45ec24126..f9921a87e 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -298,6 +298,10 @@ pub const Action = union(enum) { /// Go to the tab with the specific number, 1-indexed. goto_tab: usize, + /// Toggle the tab overview. + /// This only works with libadwaita enabled currently. + toggle_tab_overview: void, + /// Create a new split in the given direction. The new split will appear in /// the direction given. new_split: SplitDirection, @@ -607,6 +611,7 @@ pub const Action = union(enum) { .next_tab, .last_tab, .goto_tab, + .toggle_tab_overview, .new_split, .goto_split, .toggle_split_zoom,