Skip to content

fix(engine): mitigate focus corruption from boundary raise races#355

Open
qeude wants to merge 1 commit intoacsandmann:mainfrom
qeude:fix/focus-state-boundary-raise-race
Open

fix(engine): mitigate focus corruption from boundary raise races#355
qeude wants to merge 1 commit intoacsandmann:mainfrom
qeude:fix/focus-state-boundary-raise-race

Conversation

@qeude
Copy link
Copy Markdown

@qeude qeude commented May 6, 2026

Summary

This PR mitigates a bug where keyboard focus can become stuck or behave erratically when repeatedly moving focus at layout boundaries (e.g., holding down the focus-left key in a scrolling layout).

Contributing factor: When a directional focus command hits a boundary, the engine previously fell back by queuing a raise request for every visible tiled window. Rapidly sending focus commands floods the RaiseManager queue, causing focus confirmations from the OS to arrive out of order. These delayed WindowFocused events can then interfere with the engine's current focus state.

Changes

  • Boundary fallback (move_focus_internal): When there is no adjacent focus target, only keep focus on the current selection. Previously the engine returned all visible windows as raise_windows, causing an unbounded queue backlog when the focus key is held at the edge. Now returns raise_windows: vec![].
  • Stale event guard (WindowFocused): Before accepting a WindowFocused event, verify the reported window is still present in the active workspace/layout. Stale/delayed focus confirmations for windows that have left the layout are now dropped instead of corrupting focused_window.

Scope

This mitigates the focus corruption described in #285. Further work may be needed to fully eliminate races between delayed OS confirmations and subsequent focus commands (e.g., tracking a focus generation number to ignore confirmations older than the last explicit MoveFocus).

Files changed

  • src/layout_engine/engine.rs (+12 / −6)

When MoveFocus hits a boundary in a scrolling layout, the engine was
falling back and returning ALL visible tiled windows as raise_windows.
Holding down the focus key floods RaiseManager with sequences, each
raising N windows. The queue backs up, so OS focus confirmations
(WindowFocused) arrive out of order and can interfere with the engine's
current focus state.

- Boundary fallback: only focus the selected window, don't raise every
  visible tiled window. This stops the raise queue from exploding when
  a focus key is held at the edge.

- WindowFocused guard: ignore the event if the window is not present in
  the active workspace/layout. Stale/delayed focus confirmations for
  windows that have left the layout are dropped instead of corrupting
  focused_window.

This mitigates the focus corruption described in acsandmann#285. Further work may
be needed to fully eliminate races between delayed OS confirmations and
subsequent focus commands.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant