diff --git a/src/apprt/gtk/class/window.zig b/src/apprt/gtk/class/window.zig index eede346f3..2a1a5435a 100644 --- a/src/apprt/gtk/class/window.zig +++ b/src/apprt/gtk/class/window.zig @@ -221,7 +221,7 @@ pub const Window = extern struct { quick_terminal: bool = false, /// Timeout source to react to this window becoming (in)active. - timeout: ?c_uint = null, + handle_active_state_source: ?c_uint = null, /// The window decoration override. If this is not set then we'll /// inherit whatever the config has. This allows overriding the @@ -858,10 +858,13 @@ pub const Window = extern struct { } } - fn onTimeout(ud: ?*anyopaque) callconv(.c) c_int { + /// Callback to handle this window becoming active or inactive. + /// Triggered by propIsActive with a timeout to debounce temporary + /// changes in active state. + fn handleActiveState(ud: ?*anyopaque) callconv(.c) c_int { const self: *Self = @ptrCast(@alignCast(ud orelse return 0)); const priv = self.private(); - priv.timeout = null; + priv.handle_active_state_source = null; // Hide quick-terminal if set to autohide if (self.isQuickTerminal()) { @@ -1113,12 +1116,29 @@ pub const Window = extern struct { // Use a timeout callback to wait for focus state to settle, // because depending on the windowing backend the window might // become inactive and immediately active again. This happens - // e.g. on Wayland when opening a context menu. - if (priv.timeout == null) priv.timeout = glib.timeoutAdd( - 100, - onTimeout, - self, - ); + // e.g. on Wayland when opening a context menu or a submenu + // inside a context menu. + if (priv.handle_active_state_source == null) { + priv.handle_active_state_source = glib.timeoutAddFull( + // Use priority of an idle callback instead of the higher + // default timeout priority. This allows us to use a shorter + // timeout duration. + glib.PRIORITY_DEFAULT_IDLE, + // 50ms was chosen to be conservative. From testing we know + // that, depending on the backend and system performance, a + // shorter timeout or just an idle callback can be enough for + // the focus to settle. On the other hand a delay of e.g. 10ms + // does not work reliably on some slow systems. The downside + // of a high value is that some operations in handleActiveState, + // e.g. hiding the quick-terminal, will be visibly delayed. + // However, 50ms should barely be noticeable. We can change + // this in the future if necessary. + 50, + handleActiveState, + self, + null, + ); + } } fn propGdkSurfaceDims( @@ -1237,11 +1257,11 @@ pub const Window = extern struct { fn dispose(self: *Self) callconv(.c) void { const priv = self.private(); - if (priv.timeout) |v| { + if (priv.handle_active_state_source) |v| { if (glib.Source.remove(v) == 0) { - log.warn("unable to remove timeout source", .{}); + log.warn("unable to remove handle active state source", .{}); } - priv.timeout = null; + priv.handle_active_state_source = null; } priv.command_palette.set(null);