Skip to content

fix: reconcile focus-follows-cursor after workspace switch - #2737

Open
chris-010 wants to merge 1 commit into
pop-os:masterfrom
chris-010:fix/ffc-workspace-switch
Open

fix: reconcile focus-follows-cursor after workspace switch#2737
chris-010 wants to merge 1 commit into
pop-os:masterfrom
chris-010:fix/ffc-workspace-switch

Conversation

@chris-010

@chris-010 chris-010 commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

With "focus follows cursor" enabled, switching workspaces left keyboard focus on the newly-active workspace's previously-active window instead of the window under the pointer. Neither holding the pointer still nor moving it within that window corrected it — only a click did.

Focus-follows-cursor is implemented solely as an edge-triggered pointer motion handler that compares the element under the old vs. new pointer position. A workspace switch moves focus without any pointer motion, so the handler never runs, and the switch actions only call Shell::activate(). A click works because the button handler is level-triggered.

This adds State::reconcile_focus_to_pointer(), invoked from the four workspace-switch actions (Workspace, LastWorkspace, NextWorkspace, PreviousWorkspace) on success. When focus-follows-cursor is enabled and the pointer is ungrabbed, it focuses the window under the pointer on the destination workspace, and leaves focus untouched when the pointer is over empty space. MoveToWorkspace/SendToWorkspace are deliberately left alone — the moved window should follow and stay focused.

Fixes: pop-os/cosmic-epoch#3650

Tested on real hardware (Pop!_OS 24.04, COSMIC, Wayland, ThinkPad T490) and in continuous daily use since 2026-07-15.

Why it queries the destination workspace directly, how this relates to #1366 and #2396, and the test runs

It queries the destination workspace's layout directly via Workspace::toplevel_element_under rather than State::element_under: the switch animation is still in progress when the action handler runs, and element_under honours the animation offset, so it would resolve the pointer to the outgoing workspace's window, which Common::refresh_focus then reverts. The full reasoning is in the commit message.

Related issues

Test runs, with the patched build installed as /usr/bin/cosmic-comp:

  • Pointer held still over the left tile, switching to a workspace whose last-focused window is the right tile → focus lands on the window under the pointer.
  • Hover the right tile, switch away, move the pointer left, switch back → focus lands on the left tile.
  • Super+Ctrl+Up/Down (NextWorkspace/PreviousWorkspace) → focus follows the pointer.
  • Pointer over empty space → focus is left untouched.
  • Verified with cursor_follows_focus both disabled and enabled; a plain workspace switch performs no cursor warp.

On the base, precisely. That runtime testing was done on a build of bb584aa, the commit the installed package is built from. For this PR the change was rebased onto master (f97a852), where it applies cleanly and cargo check --release completes without warnings; the runtime checks above were not repeated on that newer base.

  • I have disclosed use of any AI generated code in my commit messages.
  • I understand these changes in full and will be able to respond to review comments.
  • My change is accurately described in the commit message.
  • My contribution is tested and working as described.
  • I have read the Developer Certificate of Origin and certify my contribution under its conditions.

With "focus follows cursor" enabled, switching workspaces left keyboard
focus on the newly-active workspace's previously-active window instead of
the window under the pointer. Neither staying still nor moving the pointer
within that window corrected it -- only a click did.

Focus-follows-cursor is implemented solely as an edge-triggered pointer
motion handler that compares the element under the old vs new pointer
position. A workspace switch moves focus without any pointer motion, so the
handler never runs, and the switch actions only call Shell::activate() and
never consult focus_follows_cursor.

Add State::reconcile_focus_to_pointer(), invoked from the four
workspace-switch actions (Workspace, LastWorkspace, NextWorkspace,
PreviousWorkspace) on success. When focus-follows-cursor is enabled and the
pointer is ungrabbed, it focuses the window under the pointer on the
destination workspace, and leaves focus untouched when the pointer is over
empty space.

It queries the destination workspace's layout directly via
Workspace::toplevel_element_under rather than State::element_under: the
switch animation (WorkspaceDelta::Shortcut) is still in progress when the
action handler runs, and element_under honours the animation offset, so it
would resolve the pointer to the outgoing workspace's window. Focusing that
off-workspace window is then reverted by Common::refresh_focus to the new
workspace's focus-stack top, defeating the reconcile. active_space() is
already the destination workspace (the active index is updated
synchronously), so its toplevel_element_under ignores the animation and
yields the window actually under the pointer; set_focus() then makes it the
workspace's focus-stack top, which refresh_focus subsequently keeps.

MoveToWorkspace/SendToWorkspace are deliberately left untouched: the moved
window should follow and stay focused.

Fixes: pop-os/cosmic-epoch#3650

This change was drafted with AI assistance (Claude Code); I have reviewed
it, understand it in full, and can respond to review.

Signed-off-by: chris-010 <10660568+chris-010@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Focus-follows-cursor does not re-evaluate after switching workspace (stale keyboard focus; only a click fixes it)

1 participant