window-rule: add block-pointer-constraints to suppress pointer-lock activation#4045
window-rule: add block-pointer-constraints to suppress pointer-lock activation#4045joshsymonds wants to merge 5 commits into
Conversation
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>
|
Thanks, however in this case it sounds like the app is buggy and should fix its behavior. |
|
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. |
|
Closing. Today's Discord thread made clear that contributions from anyone using AI assistance aren't welcome here, independent of whether the code is correct. That's a community choice you're entitled to make, but it means there's no point sending further PRs. Keeping this patch on my fork. |
This ain't even true. The author of the project isn't anti-AI in any sense of the word. I'm pretty sure this is a matter of correctness, not just code quality. |
|
@faetalize I was harassed in the community Discord for this PR while @YaLTeR was present and participating, with no pushback from anyone involved. My PR descriptions may have been too long or too AI-flavored, and I will own that -- but I do not think that justifies language like "speedrunning being their most hated person." I was also told, verbatim: "we dont want purely ai-generated code. end of story." The rejection wasn't about correctness. It was about the tooling I used to write the PR, and the way I got mocked for using it in this project's Discord. I realize niri has no Code of Conduct, but I'm still disappointed that an open source project of this size allows harassment of contributors in its community spaces. |
I saw those comments and they do not represent the opinion of the main developer. The community is mixed and reactionary, and most people in small niches feel very strongly about AI. It is what it is. Even the main dev himself was once a target of an Anti-AI campaign, where he was demanded to take a stronger stance on AI. I get the impression that the main dev does not want to be involved in these discussions (which makes perfect sense) explaining his abstention from getting involved in the conversation. People are going to subtweet and screenshot things and whatnot, that's just the name of the game in these communities. But what happens in a Discord channel does not represent at all the Developer's opinions or wishes. When you're building for an Open Source project, minimal interaction with the community is advised especially when AI usage is involved. Highly passionate young adults and teenagers (that think they got the world figured out) will make their opinion known on everything. The main takeaway from this is that you ought to have discussed these changes (or the motivations behind the PR) before implementing (or having AI implement) it. Making 10/10 code doesn't necessarily imply it would get merged; it needs to align with the dev's vision, and be a feature that makes sense, implemented in a way that's easy to maintain, etc. From what I noticed, the dev seems to not even agree with the principle behind this PR (ie, it's Zoom's fault, not niri's) and these details matter and need to be established with the lead dev before writing a single line of code. |
|
Actually, I think I'm overstepping with the response above. I shouldn't speak for the developer or presume to know what their opinion/stance is. Also, I apologize if my comment might come off as dismissive of your experience. I still maintain that discussion about the patch should have happened before implementation. |
|
@faetalize I appreciate your apology! To be clear about what I was reacting to: I was opening what I thought were useful PRs, and was shocked to discover I was being mocked in the project Discord with the maintainer present and participating. That's an unusual experience in my time contributing to OSS, to put it lightly. To the substance. The "you should've discussed this" framing is orthogonal to what happened. I was never told that by anyone here or in the Discord. @YaLTeR's stated reason for declining was "the app is buggy and should fix its behavior." And yes, agreed, Zoom should fix this. Until that time, which I'm sure is imminent, I've fixed it upstream. The Discord pile-on was about AI provenance. The process angle was raised only by you, and only after I was already harassed away from this community. Pre-discussing the change wouldn't have shifted YaLTeR's view that this is Zoom's problem, and it wouldn't have stopped anyone from calling working code "slop." The advice has nothing to do with what actually happened here. Even setting that aside, this PR is utterly trivial: a single opt-in boolean on WindowRule with last-Some-wins resolution and a gate inside an existing callback. It changes zero behavior for anyone who doesn't write the rule. I had more substantive changes merged into Rails core years ago with no prior discussion. "Discuss before substantial PRs" doesn't apply to small opt-in additive features. On the harassment piece: "that's just the name of the game" only works as a defense if the people with authority push back. The actual owner of the community was right there, participating in the discussion himself. When that happens, silence functions as endorsement -- it says explicitly that this is allowed here and accepted as a community norm, and that is how I experienced this. Framing the harassers as "passionate young adults and teenagers (that think they got the world figured out)" doesn't change the fact that it happened and was a-okay with everyone involved. Harassing people is bad and should be against your rules. Unless you or the niri project are seriously arguing that people treating each other like dirt is an acceptable part of how this community operates? |
|
Thanks, I like PRs that are self-documenting! Could you also attach the screenshots of someone saying I was "speedrunning to be their most-hated person" while the maintainer responded 4 minutes later -- but not to that comment? I think it would be useful for other contributors to see as well. |
|
Oh peep -- okay, sorry about that one! In the heat of the moment when I saw people were mocking me, I read that and assumed it was about me too: it was not and I'm sorry to have dragged it in. That said, my larger point about being harassed for my contributions stands: my PR was posted on the niri community Discord because people didn't like it and was then subsequently crapped on, including by the maintainer himself. Like, I was contributing in good faith and I guess I had an expectation that I would be engaged with in good faith in return? As I said earlier, I've been involved in other OSS projects and this is the first time I've seen behavior like this. If you didn't like the PR or wanted the description different that's fine -- like, talk to me about it? Or post on the PR? Or subtweet me on your Discord I guess, it's your community. Just not what I'd consider contributor-friendly behavior, but I guess if contributors aren't using community-approved tooling contributions aren't welcome anyway. Separately, while I do not think you specifically were harassing me, you definitely did say that the project doesn't want AI code, which contradicts what @faetalize said, sooo I dunno, guess y'all should square that too. |
|
It can be true that you should have talked it through with the maintainers and that we don't tend to like accepting slop into the Niri codebase. For future reference, we are fine with AI assistance, but do not have AI entirely author a PR on your behalf and expect us to merge it. If the messages that you specifically cited as harassment were not harassment, then I'd be very interested to see which messages you thought were. I'm sorry if you felt that way about our comments, but we receive quite a few slop PRs and none of us have the care to go through and individually deal with any one of them to much extent. Given that and how little I care about vibecoding, I will no longer be replying here either. |
|
Who was expecting you to merge anything? I said in my first reply that if you didn't want to merge it, that's fine -- I can maintain a fork. I was just trying to add a feature I found useful, thinking other people who use Zoom on niri would benefit too: nothing more. My expectation was simply that I wouldn't be treated like crap in your community: a bar that you and the other community members here failed to meet and continue to fail to meet. When I authored my PR I did not expect phisch's "I didn't even find the patience to read the text of that PR" and "just blindly letting the AI do everything is lazy and puts the work on the maintainer," and YaLTeR's "even the ai slop aside." Certainly that kind of conduct about a PR is unbecoming -- which I guess is why it was said behind my back in your Discord, instead of on my actual PR. And I think it's incredibly disingenuous of you to imply this is somehow my fault for not having come to the maintainers first or because of the quality of the code. Is that a prerequisite for PRs to be afforded the respect of not being posted to your Discord and mocked there? I assume that's somewhere in your If the response is "what you experienced reflects our policy of how we engage with AI-coauthored PRs" -- having such a policy is your prerogative. Communicating it on the PR, via "thanks, we don't accept AI contributions, closing" would have been how you should actually enforce it. And I would suggest you act more professionally in the future: perhaps you should add something about this to your |
…anup Three confirmed review improvements: - Doc-comments on `WindowRule::block_focus_cursor_warp` and `ResolvedWindowRules::block_focus_cursor_warp` were inaccurate: they named `Niri::maybe_warp_cursor_to_focus` as the function that short-circuits, but the gate actually lives in `Niri::move_cursor_to_focused_tile`. Updated both, and noted that the single chokepoint covers both `maybe_warp_cursor_to_focus` and `maybe_warp_cursor_to_focus_centered` (and any future warp wrapper). - Added a fourth gate test, `gate_does_not_fire_when_rule_does_not_match`. Covers the rule-resolution loop's "skip non-matching rule" branch for this field — a regression that moved the merge outside the matcher guard would leak `Some(true)` across to unrelated windows. The pre-existing "no rule at all" test couldn't catch this since its config had no rules, leaving the loop body unreachable. - Simplified the `map_titled_window` test helper. All three (now four) call sites destructured into `_id, _surface` and never used them; helper now returns `Fixture` directly. Drops the unused `client::ClientId` and `wayland_client::WlSurface` imports. Skipped (documented): wiki docs entry in `docs/wiki/Configuration:-Window-Rules.md`. Same rationale as the previous epic's S-I1 finding: fork-only patch (upstream PR niri-wm#4045 for the sibling `block-pointer-constraints` was closed unmerged, see INTEGRATION.md), the wiki file is upstream's, adding to it creates rebase friction with no consumer.
|
I appreciate the points you made in Discord after I left. The maintenance point is fair -- you carry these patches forever, not me. And I get why a stream of low-effort AI PRs is exhausting; I deal with it at work constantly. But the claim that "ai slop" is a neutral term you'd use even in praise is unserious. The conversation about my PR was clearly negative, and you participated in it. To be clear, I didn't pursue any of this. I closed the PR with a measured note saying exactly why: the Discord had made clear AI-assisted contributions weren't wanted, and aaxper confirmed as much themselves. I was met with hostility, left immediately, and closed all my PRs. Then @faetalize quoted me back in; I engaged in good faith; only then did the screenshots and "presumably leaving" show up. The message even shifts depending on the room: publicly, "we're fine with AI assistance"; in the Discord, "end of story." I read that as an invitation to leave, and gladly took it. On "presumably leaving:" by your own account you took me to have gone, then composed replies to someone you'd assumed had left. How did you intend for me to read your response? What's truly galling is that my commit is 54 lines in total, and around 40 of those are documentation and comments. The functional part is roughly ten lines in Where I come from, people who act badly own it and try to do better. They don't tell the person they treated badly that he deserved it and it wasn't that bad anyway. That's conduct that hurts people who show up in good faith. It hurt me, and I left because of how you and this community behaved. My fork works and fixes the problem; whether it's upstreamed has been irrelevant to me since my first reply, where I said exactly that. I won't be sending more. The only people worse off for any of this are others running Zoom on niri. I hope this thread is instructive for others looking to contribute here. I know I will be using it as an exemplar of how this project and its maintainers behave in public and private. |




Summary
Adds a
block-pointer-constraintswindow-rule (Option<bool>) thatsuppresses activation of
zwp_pointer_constraints_v1constraints formatching windows. Both
LockedandConfinedvariants are suppressedunconditionally — 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_toolbaroverlay (a smallfloating 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
niri-config/src/window_rule.rs): newblock_pointer_constraints: Option<bool>field onWindowRuleadjacent to
block_out_from, with the standard#[knuffel(child, unwrap(argument))]annotation.src/window/mod.rs): same field onResolvedWindowRules, last-Some-wins resolution incompute().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_constraintcallback after the constraint-presentearly-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_surfacehelper (popups + subsurfaces arehandled correctly).
niri-config/src/lib.rsextends the existingwindow-rule fixture to cover the new property round-tripping.
#### block-pointer-constraintssection indocs/wiki/Configuration:-Window-Rules.mdbetweenblock-out-fromand
opacity, with the motivating Zoom example.Design notes
binds and the client's request still arrives at smithay; niri just
never calls
constraint.activate(). Well-behaved clients gaterelative-motion mode and similar features on the constraint being
active, so they degrade gracefully to normal absolute-motion input.
Confinedconstraint with asufficiently large region produces the same user-visible "cursor
stuck" pattern as
Locked; suppressing both keeps the rule'ssemantics predictable. A future enhancement could split this into an
enum if a real use case demands variant-specific control.
break games, drawing tablets, and Blender-style middle-drag
interactions; per-window keeps the rule narrowly scoped.
Test plan
cargo build --workspacecleancargo test -p niri-configpasses (parser test exercises the new field, wiki-parses test confirms the KDL example parses)cargo clippy --workspace --all-targets -- -D warningscleancargo fmt --check --allcleanannotate_toolbar) — verification in progress on next session