Skip to content

window-rule: add block-pointer-constraints to suppress pointer-lock activation#4045

Open
joshsymonds wants to merge 5 commits into
niri-wm:mainfrom
joshsymonds:josh/block-pointer-constraints
Open

window-rule: add block-pointer-constraints to suppress pointer-lock activation#4045
joshsymonds wants to merge 5 commits into
niri-wm:mainfrom
joshsymonds:josh/block-pointer-constraints

Conversation

@joshsymonds
Copy link
Copy Markdown

Summary

Adds a block-pointer-constraints window-rule (Option<bool>) that
suppresses activation of zwp_pointer_constraints_v1 constraints for
matching windows. Both Locked and Confined variants are suppressed
unconditionally — the rule is opt-in per window, so apps that
legitimately need pointer-constraints (games, drawing tablets, 3D
modelers) are unaffected unless explicitly matched.

Motivation

Some clients request a pointer-lock as part of a tooltip / annotation /
overlay UI surface that users don't actually want to engage when the
cursor merely crosses it — the constraint locks the cursor in place
until pointer focus moves away (which from a user's perspective looks
like "the cursor is stuck"). A per-window opt-out preserves the
protocol for its intended uses while letting users disable it for
specific surfaces.

Concrete motivating case: Zoom's annotate_toolbar overlay (a small
floating toolbar that appears during screen-sharing-with-annotation)
requests a pointer-lock on hover. Users only want to interact with the
toolbar deliberately, but on niri the lock activates whenever the
cursor crosses the overlay's region.

Implementation

  • Schema (niri-config/src/window_rule.rs): new
    block_pointer_constraints: Option<bool> field on WindowRule
    adjacent to block_out_from, with the standard
    #[knuffel(child, unwrap(argument))] annotation.
  • Resolution (src/window/mod.rs): same field on
    ResolvedWindowRules, last-Some-wins resolution in compute().
  • Gate (src/niri.rs): Niri::maybe_activate_pointer_constraint()
    short-circuits when the constrained surface's root toplevel matches a
    rule with this flag set. The check is placed inside the
    with_pointer_constraint callback after the constraint-present
    early-return, so the no-constraint fast path that runs on every
    pointer motion is unchanged. Root-surface resolution uses the existing
    Niri::find_root_shell_surface helper (popups + subsurfaces are
    handled correctly).
  • Test: parser test in niri-config/src/lib.rs extends the existing
    window-rule fixture to cover the new property round-tripping.
  • Wiki: new #### block-pointer-constraints section in
    docs/wiki/Configuration:-Window-Rules.md between block-out-from
    and opacity, with the motivating Zoom example.

Design notes

  • Activation-time gate, not bind-time refusal. The protocol still
    binds and the client's request still arrives at smithay; niri just
    never calls constraint.activate(). Well-behaved clients gate
    relative-motion mode and similar features on the constraint being
    active, so they degrade gracefully to normal absolute-motion input.
  • Both variants, unconditionally. A Confined constraint with a
    sufficiently large region produces the same user-visible "cursor
    stuck" pattern as Locked; suppressing both keeps the rule's
    semantics predictable. A future enhancement could split this into an
    enum if a real use case demands variant-specific control.
  • Per-window, opt-in. Global pointer-constraint suppression would
    break games, drawing tablets, and Blender-style middle-drag
    interactions; per-window keeps the rule narrowly scoped.

Test plan

  • cargo build --workspace clean
  • cargo test -p niri-config passes (parser test exercises the new field, wiki-parses test confirms the KDL example parses)
  • cargo clippy --workspace --all-targets -- -D warnings clean
  • cargo fmt --check --all clean
  • Deployed live on Linux/Wayland; config parses, rule resolves on the motivating window
  • End-to-end cursor-suppression on the motivating app (Zoom annotate_toolbar) — verification in progress on next session

joshsymonds and others added 5 commits May 12, 2026 12:38
New per-window-rule bool, default unset. Mirrors the block-out-from
pattern: knuffel #[child, unwrap(argument)] accepting
`block-pointer-constraints true` in KDL. No runtime behavior yet;
that lands in a follow-up commit once ResolvedWindowRules + the
activation gate at maybe_activate_pointer_constraint are in place.

Parser test extends the existing window-rule fixture in lib.rs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds block_pointer_constraints to ResolvedWindowRules with last-Some-
wins resolution in compute(), and short-circuits
Niri::maybe_activate_pointer_constraint when the constrained surface's
root toplevel belongs to a window matched by a rule with this flag
set. The walk-to-root before the layout lookup mirrors the existing
pattern in PointerConstraintsHandler::cursor_position_hint — pointer
constraints can be requested on subsurfaces too, but window rules
resolve on the toplevel.

Both Locked and Confined variants are suppressed unconditionally: the
rule is opt-in per window, so apps that legitimately need either
variant (games, drawing tablets, 3D modelers) are unaffected unless
they match a user-authored rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the wiki section users will hit when they look for "why is my
cursor sticking on an overlay." Example KDL is intentionally generic
(app-id="some-app") rather than naming a specific vendor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four small fixups surfaced by the four-reviewer audit:

- src/niri.rs: replace inline `get_parent`-walk with the existing
  `Niri::find_root_shell_surface` helper. The helper consults the
  cached root_surface map and walks popups to their parent toplevel
  via find_popup_root_surface — the raw walk lacked both. Drops the
  newly-added `get_parent` import, restoring the pre-patch import
  surface in niri.rs.
- src/window/mod.rs: trim the 7-line doc comment on
  ResolvedWindowRules::block_pointer_constraints to a one-line
  summary matching adjacent fields. Long-form prose lives in the
  wiki.
- docs/wiki/Configuration:-Window-Rules.md: change `Since: next` to
  `Since: next release` to match upstream's release-bump workflow
  (commit 8fd9fb7's find/replace targets the longer form). Replace
  the generic app-id="some-app" example with a concrete Zoom
  annotate_toolbar example that matches the originally-stated
  use case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewer feedback: the gate ran unconditionally before
with_pointer_constraint, paying for find_root_shell_surface +
find_window_and_output on every pointer motion even when no
constraint exists for the focused surface (which is the common
case — ~100% of motions over non-constraint surfaces).

Relocate the check inside the with_pointer_constraint callback,
after the constraint-present early-return. Restores the pre-patch
fast path: the rule lookup only runs when a constraint actually
exists to be activated.

Semantics are preserved — the block still short-circuits before
constraint.activate(); the only observable difference is where the
short-circuit lives relative to the protocol bind/activate boundary.
The constraint stays bound but inactive either way.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Sempyos Sempyos added area:input Keyboard, mouse, touchpad, tablet, gestures, pointer area:config Config parsing, default config, new settings pr kind:feature New features and functionality labels May 13, 2026
@YaLTeR
Copy link
Copy Markdown
Member

YaLTeR commented May 13, 2026

Thanks, however in this case it sounds like the app is buggy and should fix its behavior.

@joshsymonds
Copy link
Copy Markdown
Author

Maybe, but I think my chances of getting Zoom to do this are pretty low! No worries if you don't want to accept this though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:config Config parsing, default config, new settings area:input Keyboard, mouse, touchpad, tablet, gestures, pointer pr kind:feature New features and functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants