fix: Alt+Tab intermittently leaves focus unchanged - #7
Open
ekropotin wants to merge 1 commit into
Open
Conversation
…ed focus Alt+Tab intermittently left focus unchanged: the overlay opened, the highlight moved, releasing Alt dismissed it, and the window never switched. Two separate causes, each of which produces that same symptom. 1. The switch was undone by the compositor's focus restore. The overlay takes WlrKeyboardFocus.Exclusive, and Hyprland hands keyboard focus back to the previously focused toplevel when the layer surface unmaps -- `closelayer` is followed a few milliseconds later by an activewindow/activewindowv2 naming the window that was focused before it opened. focusSelected() dispatched focus while the overlay was still mapped and dismissed immediately after, so the switch only survived when the spawned hyprctl lost the race against that restore. When it won, the restore overwrote the switch. Measured on Hyprland by ordering the two operations explicitly: dispatch before unmap reverted the switch 6/6, dispatch after it stuck 6/6. focusSelected() now stashes the target, dismisses, and applies it when the restore event arrives, with a timer backstop for the case where the compositor emits no restore (nothing was focused before opening). 2. The pre-selection could point at the window already focused. Ordering came from lastIpcObject.focusHistoryID, a cached snapshot that is not refreshed when focus moves, so it can still name a stale window as rank 0. With a newly created window focused, the live compositor reported it at rank 0 while the plugin's cached copy reported a different window -- isCurrent(rows[0]) then held for the wrong row and selectedIndex stayed 0, so confirming re-focused the current window. The Wayland `activated` flag cannot stand in: it reads false for every toplevel while the overlay holds exclusive keyboard focus. Windows are now ranked by an MRU built from the compositor's own activewindowv2 events, which are observed whether or not the overlay is open. focusHistoryID remains the seed for windows not yet seen focused, where it is accurate. rows[0] is the focused window by construction, so the neighbour is always a real switch target. End to end with three windows: 8/8 single-tap and 6/6 double-tap switches, and 16/16 across two windows. Before the change the same script reverted 6 of 8. sortedWindows() takes an optional rankFn and still defaults to focusRank, so the model stays usable on its own; normalizeAddress() is shared because toplevels expose "0x55..." while the event payload is the bare "55...". Tests cover both.
Author
|
I've just find out, that the MRU half (problem 2) here duplicates #5, and it does it better. The other half is independent and not covered by #5 tho. That matters because the ordering fix alone doesn't resolve the symptom. So I'd rather not rewrite this pre-emptively - I'll wait to see where #5 lands and adjust accordingly. If it is merged, I'll rebase this down to just the unmap fix so the two compose. If it isn't, I'll keep this self-contained. Happy either way, so let me know if you'd prefer one over the other. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Alt+Tabintermittently leaves focus unchanged: the overlay opens, the highlight moves to another window, releasingAltdismisses it — and the window never switches. Repeating it often stays stuck on the same window for several presses before working again.There are two independent causes, and each produces that identical symptom.
1. The switch is undone by the compositor's focus restore
The overlay takes
WlrKeyboardFocus.Exclusive. Hyprland hands keyboard focus back to the previously focused toplevel when that layer surface unmaps — tracing the event socket showscloselayerfollowed ~2 ms later by the restore:focusSelected()dispatched focus while the overlay was still mapped, then calleddismiss(). So the switch only survived when the spawnedhyprctllost the race against the unmap; when it won, the restore overwrote it.Ordering the two operations explicitly, everything else held constant:
focusSelected()now stashes the target, dismisses, and applies it when the restore event arrives — with a timer backstop for the case where no restore is emitted (nothing was focused before opening).2. The pre-selection can point at the window already focused
Ordering came from
lastIpcObject.focusHistoryID. That object is a cached snapshot which is not refreshed when focus moves, so it can still name a stale window as rank 0. With a newly created window focused:hyprctl clientsfh=0fh=1lastIpcObjectas read by the pluginfh=0isCurrent(rows[0])then held for the wrong row,selectedIndexstayed0, and confirming re-focused the current window. Observed as single-tap failing 6/6 while double-tap passed 4/4 — the extra tap advanced off index 0.The Wayland
activatedflag cannot stand in either: it readsfalsefor every toplevel while the overlay holds exclusive keyboard focus.Windows are now ranked by an MRU built from the compositor's own
activewindowv2events, which are observed whether or not the overlay is open.focusHistoryIDremains the seed for windows not yet seen focused, where it is accurate.rows[0]is the focused window by construction, so its neighbour is always a real switch target.Verification
Driven through the real binding path (summon, confirm, then read
hyprctl activewindow):Single-tap now alternates between the two most recent windows and double-tap reaches the third, which is the expected
Alt+Tabbehaviour. No stuck overlays across any run.Notes
sortedWindows()takes an optionalrankFnand still defaults tofocusRank, so the model remains usable and testable on its own.normalizeAddress()is shared because toplevels expose0x55…while theactivewindowv2payload is the bare55….test_model.jscovers both additions; existing assertions are unchanged and still pass.