diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-12 19:13:52 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-12 19:13:52 +0200 |
| commit | 84f2c5c785132dd1641caf30e87be7b84a7e5791 (patch) | |
| tree | 58f33ac56e06fb21055bc6f4686bb99bbd54113f | |
| parent | b565deda8d6bab7435689c9a964ba3586faaf0e7 (diff) | |
| download | quickshell-84f2c5c785132dd1641caf30e87be7b84a7e5791.tar.gz quickshell-84f2c5c785132dd1641caf30e87be7b84a7e5791.zip | |
fix(window-switcher): focus after the overlay is really gone
Picking a window focused nothing, and focus returned to whatever had it
before. The overlay holds keyboard focus exclusively, and the compositor
will not move window focus out from under that grab: the dispatch is
accepted, reports ok, and is then ignored. Nothing in the log says so,
which is why this looked like a dispatch that had worked.
Closing first in the same turn is not enough either, because close() only
clears a property and the layer surface survives until the frame after,
so the dispatch still lands inside the grab. The target is now remembered
and focused once the surface is actually gone.
Tested in both directions, since the case that exposed it was
cross-monitor: DP-1 to a window on DP-3, and back.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYg4wYHq5XNbiVmMeKRb1S
| -rw-r--r-- | window-switcher/Switcher.qml | 29 |
1 files changed, 25 insertions, 4 deletions
diff --git a/window-switcher/Switcher.qml b/window-switcher/Switcher.qml index 2e911b7..960cb58 100644 --- a/window-switcher/Switcher.qml +++ b/window-switcher/Switcher.qml @@ -29,6 +29,29 @@ Scope { // since the last showing would otherwise be missing from the grid. onOpenChanged: if (open) Windows.refresh(); + // A focus dispatched while the overlay is up is accepted and then ignored: + // the layer surface holds keyboard focus exclusively, and the compositor + // will not move window focus out from under that grab. Closing in the same + // turn does not help either, because close() only clears a property and the + // surface survives until the frame after. So the target is remembered and + // focused once the grab is really gone. + property var pendingFocus: null + + function focusAfterClose(toplevel) { + root.pendingFocus = toplevel; + root.close(); + focusTimer.restart(); + } + + Timer { + id: focusTimer + interval: 60 + onTriggered: { + if (root.pendingFocus) Windows.focus(root.pendingFocus); + root.pendingFocus = null; + } + } + // A config with no visible window exits, reporting nothing. This 1x1 // click-through window is what holds the shell open while the overlay // is hidden, which is almost always. @@ -71,10 +94,8 @@ Scope { function commit() { const list = Windows.windows; - if (selectedIndex >= 0 && selectedIndex < list.length) { - Windows.focus(list[selectedIndex]); - root.close(); - } + if (selectedIndex >= 0 && selectedIndex < list.length) + root.focusAfterClose(list[selectedIndex]); } function move(delta) { |
