From faa30c4d487425d52d9d7a0af06431ef1550d031 Mon Sep 17 00:00:00 2001 From: Josh Symonds Date: Sat, 9 May 2026 11:35:35 -0700 Subject: [PATCH] bar: fix fullscreen-detection race during cross-monitor focus moves (#2373) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hasFullscreenToplevelOnScreen's fast-path used `NiriService.currentOutput === screenName` as a proxy for "the active toplevel is on screenName". Those are two independent state channels (niri's workspace-activated events drive currentOutput; wlr-foreign-toplevel-management drives ToplevelManager.activeToplevel) and they don't update atomically. When focus crosses from a fullscreen toplevel on monitor A to a non-fullscreen toplevel on monitor B, currentOutput flips to B before activeToplevel updates away from A's still-fullscreen-and-activated window. For one tick, B's bar evaluates `active.fullscreen && active.activated && currentOutput === B` → true, hides itself, then flips back when activeToplevel finally updates. Visible as a one-frame bar flicker on focus changes between monitors when one has a fullscreen window. Replace the proxy with _toplevelOnScreen(active, screenName), the same helper the X11 fallback path uses 25 lines below. The check now inspects the toplevel's actual outputs instead of trusting a separate state signal, so the race can't fire. The per-workspace loop below was already correct; it would catch any real fullscreen+activated toplevel on the bar's workspace regardless of focused output. The fast-path was redundant when the assertion held and wrong when it didn't. Co-authored-by: Claude Opus 4.7 (1M context) --- quickshell/Services/CompositorService.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickshell/Services/CompositorService.qml b/quickshell/Services/CompositorService.qml index 617fcfbe..a9f43e23 100644 --- a/quickshell/Services/CompositorService.qml +++ b/quickshell/Services/CompositorService.qml @@ -417,7 +417,7 @@ Singleton { if (isNiri) { const active = ToplevelManager.activeToplevel; - if (active?.fullscreen && active?.activated && NiriService.currentOutput === screenName) + if (active?.fullscreen && active?.activated && _toplevelOnScreen(active, screenName)) return true; const filtered = filterCurrentWorkspace(sortedToplevels, screenName);