Repository navigation
🐞 Avoid cursor stutter when suppressing Mission Control - #1133
jvanderen1 wants to merge 8 commits into
Conversation
Rewrite top-edge drag events in an active tap instead of warping the cursor each frame, which caused the stutter reported in mrkai77#609. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@mrkai77 I was wondering if you had q chance to see this PR? I have noticed the "Supress Misson Control" option in Loop was quite laggy on the top edge. This resolves that issue. |
Rewrite top-edge drags before window lookup finishes, and resolve the display with CoreGraphics so the tap thread does not touch AppKit. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Thanks for digging into this!
The thing stopping me from merging is the always-on active event tap (and I apologize for taking this long to respond). Right now every leftMouseDragged event on the system goes through our callback before macOS delivers it. That's true for all drags, for as long as Loop is running, even for users who have snapping or suppressMissionControlOnTopDrag turned off. A listen-only tap failing just means Loop misses events. If an active tap stalls or gets disabled by a timeout, drag input breaks for the whole system, and it's a failure point I'd rather avoid adding for this feature.
I think Rectangle handles this ideally, and it's worth a look. It rewrites the event location the same way this PR does, but it only keeps the active tap while the setting is on (startEventMonitor / reloadFromDefaults). Rectangle only blocks fast drags into Mission Control, and it may make sense to adopt this feature in Loop too.
Could we narrow the tap's lifetime along those lines? Some ideas from me:
- Only while a window is being dragged. Keep the passive monitor as the default, create the active tap once we've resolved a window and seen it move, and remove it on mouse-up. Non-window drags would then never reach the active tap, so they'd stop getting rewritten.
- At minimum, only while the setting is on. Use the passive monitor when
suppressMissionControlOnTopDragor snapping is off, and swap monitors when the setting changes, like Rectangle does.
If the lifetime is tied to a window drag, the generation/pause state probably goes away too. That would also remove a race I found while reviewing: the lookup Task reads generation only when it starts running, which can be after mouse-up has already bumped it, so a leftover pause can carry into the next drag.
One smaller thing: topEdgeY also matches the border between stacked displays, where Mission Control can't trigger. Rectangle's dragPrevY check avoids that by only rewriting when the cursor was already at the edge on the previous event.
An always-on active tap can stall every drag on the system. Snap detection stays listen-only, and the active tap is installed only after a resolved window moves. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The active tap is no longer always on. Snap detection stays on the listen-only monitor. The active tap is created only after a window has been resolved and has moved, and it is removed on mouse-up, so a non-window drag never reaches it. The generation/pause state is gone with that. Rewriting also waits until the previous event was already on the same top edge, so crossing the seam between stacked displays once is left alone. |
Co-authored-by: Cursor <cursoragent@cursor.com>
Description
With Suppress Mission Control enabled, dragging a window to the top of the screen stutters. #609 reported this and was closed by turning the toggle off. The stutter is still in current
developwhen the toggle stays on. Tracked again in #1159.Why: Loop keeps Mission Control closed by warping the cursor 1px down on every drag event (
CGWarpMouseCursorPosition). That fights the upward drag, so the cursor thrashes.Fix: Rewrite the drag event’s location instead of warping the cursor. Snap detection stays on a listen-only tap. The active tap is created only after a window has been resolved and has moved, and it is removed on mouse-up. Drags that are not a window never hit it. A held top-edge drag is rewritten only once the previous event was already on that edge, so a single crossing between stacked displays is left alone.
Fixes #1159
How has this been tested?
Local Debug build on macOS 27.0 (also checked earlier on macOS Tahoe):
The checked drag tests are from the earlier build. This update changes when the tap exists: it starts only after the window moves, and it is gone on mouse-up.
Screencast
output.mp4
Checklist:
Please describe to which degree, if any, an LLM was used in creating this pull request.
Cursor (Grok) helped investigate the stutter and draft the change in
WindowDragManager, including the later event-tap hardening. I reviewed the code, confirmed it builds, and manually tested top-edge dragging before opening this PR.