After pointer constraints behavior change of 0dce5f125c1445992b5d800d01e228ad21108163, it fixes #3729 there's still a new bug.
Demonstration video:
https://github.com/user-attachments/assets/90861000-72ca-4236-a621-0f1925d0378d
Expected behavior: Pointer lock should be released once the window loses its keyboard focus (e.g. workspace change operation, Mod+HJKL, or Alt+Tab), and should be restored automatically on keyboard focus regain. Otherwise the user won't be able to escape from the lock state unless the app itself actively unlocks the pointer.
Tested sway/hyprland/kwin_wayland/mutter, all does the "expected behavior".
Activity
20You can open the overview or the screenshot UI or switch workspaces or do anything else that causes the pointer to leave the surface though
Tested sway/hyprland/kwin_wayland/mutter, all does the "expected behavior".
What exactly is the expected behavior? Only allow pointer lock when the surface has not only pointer, but keyboard focus too?
Tested sway/hyprland/kwin_wayland/mutter, all does the "expected behavior".
What exactly is the expected behavior? Only allow pointer lock when the surface has not only pointer, but keyboard focus too?
You can open the overview or the screenshot UI or switch workspaces or do anything else that causes the pointer to leave the surface though
Yes but keyboard focus change should also make it unlock.
What exactly is the expected behavior? Only allow pointer lock when the surface has not only pointer, but keyboard focus too?
Surely exactly. Pointer constraints should always follow keyboard focus. This splits into two:
- this issue: get unlocked on keyboard focus loss (ofc. regain the lock on keyboard focus regain)
- #4121: after the "escape the lock" action above, the pointer should not be re-locked only because the pointer moves above the surface (but without keyboard focus).
I guess this applies for both "lock" and "confine" constraint types. And I'm willing to do screenrecords on those "expected" compositors if you need one.
You can open the overview or the screenshot UI or switch workspaces or do anything else that causes the pointer to leave the surface though
Yes but keyboard focus change should also make it unlock.
What exactly is the expected behavior? Only allow pointer lock when the surface has not only pointer, but keyboard focus too?
Surely exactly. Pointer constraints should always follow keyboard focus. This splits into two:
- this issue: get unlocked on keyboard focus loss (ofc. regain the lock on keyboard focus regain)
- #4121: after the "escape the lock" action above, the pointer should not be re-locked only because the pointer moves above the surface (but without keyboard focus).
I guess this applies for both "lock" and "confine" constraint types. And I'm willing to do screenrecords on those "expected" compositors if you need one.
Alright. No need for screenrecords, I believe you if you say you tested these and that's how they work on other compositors.
So do I understand correctly that surfaces that cannot receive keyboard focus (e.g. layer-shell without kb interactivity) can never lock the pointer then?
Alright. No need for screenrecords, I believe you if you say you tested these and that's how they work on other compositors.
So do I understand correctly that surfaces that cannot receive keyboard focus (e.g. layer-shell without kb interactivity) can never lock the pointer then?
Alright. No need for screenrecords, I believe you if you say you tested these and that's how they work on other compositors.
Yea actually screenrecords in #4121 show it. You can notice the constraint is released after I do Mod+HJKL or Alt+Tab things.
So do I understand correctly that surfaces that cannot receive keyboard focus (e.g. layer-shell without kb interactivity) can never lock the pointer then?
I guess so.. For this wait and let me do a test using my demonstration app+wl-paper on those compositors.
Alright. No need for screenrecords, I believe you if you say you tested these and that's how they work on other compositors.
Yea actually screenrecords in #4121 show it. You can notice the constraint is released after I do Mod+HJKL or Alt+Tab things.
So do I understand correctly that surfaces that cannot receive keyboard focus (e.g. layer-shell without kb interactivity) can never lock the pointer then?
I guess so.. For this wait and let me do a test using my demonstration app+wl-paper on those compositors.
I guess so.. For this wait and let me do a test using my demonstration app+wl-paper on those compositors.
- Hyprland:
exclusive,on-demand,noneall do not react to the lock request. - Sway:
exclusive,on-demandreact to the lock request (successfully locks it);nonedoes not. - Mutter: not supporting zwlr-layer-shell
- kwin_wayland:
exclusivereacts to the lock, buton-demandandnonedo not.
So yes.
Test program is also from my https://github.com/bczhc/cursor-lock-demo but with "toggle the lock via mouse click" logic added (originally it's toggled via keyboard) because we're testing none keyboard interactivity here.
I guess so.. For this wait and let me do a test using my demonstration app+wl-paper on those compositors.
- Hyprland:
exclusive,on-demand,noneall do not react to the lock request. - Sway:
exclusive,on-demandreact to the lock request (successfully locks it);nonedoes not. - Mutter: not supporting zwlr-layer-shell
- kwin_wayland:
exclusivereacts to the lock, buton-demandandnonedo not.
So yes.
Test program is also from my https://github.com/bczhc/cursor-lock-demo but with "toggle the lock via mouse click" logic added (originally it's toggled via keyboard) because we're testing none keyboard interactivity here.
Thanks for testing. Guess then maybe_enable_pointer_constraint() should check kb focus, and update_keyboard_focus() should break pointer constraint in addition to grabs. Just need to be careful and check it against the root surface.
Thanks for testing. Guess then maybe_enable_pointer_constraint() should check kb focus, and update_keyboard_focus() should break pointer constraint in addition to grabs. Just need to be careful and check it against the root surface.
I updated #4411 to implement the lifecycle discussed here, including YaLTeR's suggested root-surface check.
Exact head: 2a97e5ee2a481cc42f15c2ef7f8573ee9137a999.
maybe_activate_pointer_constraint()now requires pointer and keyboard focus to resolve to the same root shell surface.update_keyboard_focus()deactivates the constraint when focus leaves that root.- returning keyboard focus restores an existing persistent Locked or Confined constraint; one-shot constraints do not reactivate.
- merely moving the pointer back over an unfocused surface does not reactivate it (#4121).
- layer-shell follows keyboard interactivity:
exclusivecan activate,on-demandactivates after click focus, andnonecannot activate. - cursor hints remain surface-local and deferred, apply at most once, and survive current geometry/output changes without overriding a newer compositor warp.
The exact head passes 27 pointer-constraint tests and all 222 Niri tests. It also includes regressions for lock and confine, layer-shell focus changes, updated regions/input regions, workspace moves, popups/subsurfaces, overview animation, hidden cursor, multiple constraints, and active/inactive destruction.
There is a Smithay prerequisite: the branch pins fe0320bc, one commit on top of Niri's current Smithay 7609ab82, which already contains merged #2107. This is deliberately not the older #2107 head 46a875 that would have regressed Niri's later Smithay fixes. The extra commit separates temporary deactivation from final removal and provides the concrete lifecycle identity needed by Niri; it still needs a separate upstream Smithay review before the Niri PR can drop the fork.
@bczhc, if possible, please test the exact head with your original demo and mouse-toggle layer variant, especially Locked/Confined, Alt+Tab/Mod+HJKL leave-and-return, and the exclusive/on-demand/none layer matrix.
I updated #4411 to implement the lifecycle discussed here, including YaLTeR's suggested root-surface check.
Exact head: 2a97e5ee2a481cc42f15c2ef7f8573ee9137a999.
maybe_activate_pointer_constraint()now requires pointer and keyboard focus to resolve to the same root shell surface.update_keyboard_focus()deactivates the constraint when focus leaves that root.- returning keyboard focus restores an existing persistent Locked or Confined constraint; one-shot constraints do not reactivate.
- merely moving the pointer back over an unfocused surface does not reactivate it (#4121).
- layer-shell follows keyboard interactivity:
exclusivecan activate,on-demandactivates after click focus, andnonecannot activate. - cursor hints remain surface-local and deferred, apply at most once, and survive current geometry/output changes without overriding a newer compositor warp.
The exact head passes 27 pointer-constraint tests and all 222 Niri tests. It also includes regressions for lock and confine, layer-shell focus changes, updated regions/input regions, workspace moves, popups/subsurfaces, overview animation, hidden cursor, multiple constraints, and active/inactive destruction.
There is a Smithay prerequisite: the branch pins fe0320bc, one commit on top of Niri's current Smithay 7609ab82, which already contains merged #2107. This is deliberately not the older #2107 head 46a875 that would have regressed Niri's later Smithay fixes. The extra commit separates temporary deactivation from final removal and provides the concrete lifecycle identity needed by Niri; it still needs a separate upstream Smithay review before the Niri PR can drop the fork.
@bczhc, if possible, please test the exact head with your original demo and mouse-toggle layer variant, especially Locked/Confined, Alt+Tab/Mod+HJKL leave-and-return, and the exclusive/on-demand/none layer matrix.
Sorry but the keyboard focus change is like 20 lines and not a +2653 line presumably vibe coded PR. I'm not eager to review that
Sorry but the keyboard focus change is like 20 lines and not a +2653 line presumably vibe coded PR. I'm not eager to review that
Yeah, my Codex setup pushes quite hard on checking edge cases, so this grew far beyond the roughly 20-line focus-policy change. For context, 1,882 of the 2,634 added lines are the test client and regression tests.
I can cut it down to a focused PR containing only the required behavior. The current version additionally covers:
- both locked and confined pointers;
- persistent and one-shot lifecycles;
- deferred cursor hints and applying them only once;
- root surfaces, popups, subsurfaces and layer-shell focus;
- constraint and surface input-region changes;
- moved windows, workspaces and outputs;
- overview animation and hidden cursors;
- multiple constraints, destruction and compositor warps.
I’m not particularly attached to keeping all of that in this PR.
Honestly, the only reason I’m doing this is to make my own life easier on Linux with niri, because Windows is garbage. If the fix helps other users too, even better. I also don’t want to maintain a permanent fork, keep tens of gigabytes of Rust build cache around, and repeatedly rebuild and rebase it whenever upstream changes if this can be fixed properly in the original repository.
That’s why I’d like to take it to completion upstream. I’m happy to reduce or split the current work based on your feedback if the fix itself is useful.
Yeah, my Codex setup pushes quite hard on checking edge cases, so this grew far beyond the roughly 20-line focus-policy change. For context, 1,882 of the 2,634 added lines are the test client and regression tests.
I can cut it down to a focused PR containing only the required behavior. The current version additionally covers:
- both locked and confined pointers;
- persistent and one-shot lifecycles;
- deferred cursor hints and applying them only once;
- root surfaces, popups, subsurfaces and layer-shell focus;
- constraint and surface input-region changes;
- moved windows, workspaces and outputs;
- overview animation and hidden cursors;
- multiple constraints, destruction and compositor warps.
I’m not particularly attached to keeping all of that in this PR.
Honestly, the only reason I’m doing this is to make my own life easier on Linux with niri, because Windows is garbage. If the fix helps other users too, even better. I also don’t want to maintain a permanent fork, keep tens of gigabytes of Rust build cache around, and repeatedly rebuild and rebase it whenever upstream changes if this can be fixed properly in the original repository.
That’s why I’d like to take it to completion upstream. I’m happy to reduce or split the current work based on your feedback if the fix itself is useful.
You can open the overview or the screenshot UI or switch workspaces or do anything else that causes the pointer to leave the surface though