diff --git a/src/apprt/gtk/Tab.zig b/src/apprt/gtk/Tab.zig index 3595bb977..1a0762719 100644 --- a/src/apprt/gtk/Tab.zig +++ b/src/apprt/gtk/Tab.zig @@ -35,7 +35,7 @@ elem: Surface.Container.Elem, // We'll update this every time a Surface gains focus, so that we have it // when we switch to another Tab. Then when we switch back to this tab, we // can easily re-focus that terminal. -focus_child: *Surface, +focus_child: ?*Surface, pub fn create(alloc: Allocator, window: *Window, parent_: ?*CoreSurface) !*Tab { var tab = try alloc.create(Tab); @@ -52,7 +52,7 @@ pub fn init(self: *Tab, window: *Window, parent_: ?*CoreSurface) !void { .label_text = undefined, .box = undefined, .elem = undefined, - .focus_child = undefined, + .focus_child = null, }; // Create a Box in which we'll later keep either Surface or Split. diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index f846c6b28..458c1452a 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -45,6 +45,9 @@ context_menu: *c.GtkWidget, /// not used, this is null and unused. toast_overlay: ?*c.GtkWidget, +/// See adwTabOverviewOpen for why we have this. +adw_tab_overview_focus_timer: ?c.guint = null, + pub fn create(alloc: Allocator, app: *App) !*Window { // Allocate a fixed pointer for our window. We try to minimize // allocations but windows and other GUI requirements are so minimal @@ -122,6 +125,14 @@ pub fn init(self: *Window, app: *App) !void { null, c.G_CONNECT_DEFAULT, ); + _ = c.g_signal_connect_data( + tab_overview, + "notify::open", + c.G_CALLBACK(&adwTabOverviewOpen), + self, + null, + c.G_CONNECT_DEFAULT, + ); break :overview tab_overview; } else null; @@ -368,6 +379,10 @@ fn initActions(self: *Window) void { pub fn deinit(self: *Window) void { c.gtk_widget_unparent(@ptrCast(self.context_menu)); + + if (self.adw_tab_overview_focus_timer) |timer| { + _ = c.g_source_remove(timer); + } } /// Returns true if this window should use an Adwaita window. @@ -477,7 +492,8 @@ pub fn toggleWindowDecorations(self: *Window) void { /// Grabs focus on the currently selected tab. pub fn focusCurrentTab(self: *Window) void { const tab = self.notebook.currentTab() orelse return; - const gl_area = @as(*c.GtkWidget, @ptrCast(tab.focus_child.gl_area)); + const surface = tab.focus_child orelse return; + const gl_area = @as(*c.GtkWidget, @ptrCast(surface.gl_area)); _ = c.gtk_widget_grab_focus(gl_area); } @@ -516,6 +532,51 @@ fn gtkNewTabFromOverview(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) ?*c.AdwT return c.adw_tab_view_get_page(self.notebook.adw_tab_view, @ptrCast(@alignCast(tab.box))); } +fn adwTabOverviewOpen( + object: *c.GObject, + _: *c.GParamSpec, + ud: ?*anyopaque, +) void { + const tab_overview: *c.AdwTabOverview = @ptrCast(@alignCast(object)); + + // We only care about when the tab overview is closed. + if (c.adw_tab_overview_get_open(tab_overview) == 1) { + return; + } + + // On tab overview close, focus is sometimes lost. This is an + // upstream issue in libadwaita[1]. When this is resolved we + // can put a runtime version check here to avoid this workaround. + // + // Our workaround is to start a timer after 500ms to refocus + // the currently selected tab. We choose 500ms because the adw + // animation is 400ms. + // + // [1]: https://gitlab.gnome.org/GNOME/libadwaita/-/issues/670 + const window: *Window = @ptrCast(@alignCast(ud.?)); + + // If we have an old timer remove it + if (window.adw_tab_overview_focus_timer) |timer| { + _ = c.g_source_remove(timer); + } + + // Restart our timer + window.adw_tab_overview_focus_timer = c.g_timeout_add( + 500, + @ptrCast(&adwTabOverviewFocusTimer), + window, + ); +} + +fn adwTabOverviewFocusTimer( + self: *Window, +) callconv(.C) c.gboolean { + self.focusCurrentTab(); + + // Remove the timer + return 0; +} + fn gtkRefocusTerm(v: *c.GtkWindow, ud: ?*anyopaque) callconv(.C) bool { _ = v; log.debug("refocus term request", .{}); @@ -761,7 +822,8 @@ fn gtkActionReset( /// Returns the surface to use for an action. fn actionSurface(self: *Window) ?*CoreSurface { const tab = self.notebook.currentTab() orelse return null; - return &tab.focus_child.core_surface; + const surface = tab.focus_child orelse return null; + return &surface.core_surface; } fn userdataSelf(ud: *anyopaque) *Window {