Skip to content

Fix middle-drag smoothness and add compatibility for some apps#104

Merged
NullPointerDepressiveDisorder merged 9 commits intomainfrom
bug/smoothing
Feb 13, 2026
Merged

Fix middle-drag smoothness and add compatibility for some apps#104
NullPointerDepressiveDisorder merged 9 commits intomainfrom
bug/smoothing

Conversation

@NullPointerDepressiveDisorder
Copy link
Owner

@NullPointerDepressiveDisorder NullPointerDepressiveDisorder commented Feb 13, 2026

Resolves a smoothness regression introduced after v1.4.1 and adds proper support for apps like Fusion 360 that read absolute position from drag events.

Root cause of smoothness regression: The touch callback was allocating a Swift Array via map on every frame at 100Hz+, creating per-frame GC pressure that caused visible jitter. Replaced with a single memcpy into a raw buffer, same safety (data copied before async dispatch, freed after), near-zero overhead.

CAD app support via disassociate-and-warp: macOS CGEvent drag events carry both an absolute position field and delta fields independently, standard apps read deltas, but 3D/CAD apps (Fusion 360, Unity) read position. Setting both on the same event causes the window server to fight with delta-driven cursor movement. The fix uses CGAssociateMouseAndMouseCursorPosition(false) to freeze the cursor during drags, posts events with both correct position and deltas, then warps the cursor explicitly each frame via CGWarpMouseCursorPosition. All drag exit paths (end, cancel, force-release, watchdog) call reassociateCursor() to restore normal behavior.


This pull request introduces several improvements to the mouse drag and multitouch event handling logic, focusing on more robust cursor management during drag operations and optimizing touch data copying to reduce performance overhead. The most important changes are grouped below:

Cursor Association and Drag Handling Enhancements

  • Added a lastDragPosition property to accumulate drag position, ensuring consistent event position reporting and cursor warping during disassociated drags.
  • Implemented explicit cursor association/disassociation logic via the new reassociateCursor() method, which is now called at the end of drag operations and during drag cancellation to restore normal cursor behavior. [1] [2] [3] [4] [5] [6]
  • Updated drag position advancement in updateDrag() to use the accumulated lastDragPosition instead of the current mouse location, providing smoother and more predictable cursor movement.
  • Enhanced event posting to set both integer and double delta fields for compatibility with various applications, and ensured the visible cursor is warped to the accumulated position during disassociated drags.

Multitouch Data Copy Optimization

  • Replaced Swift array allocation and mapping with a raw memory copy for touch data, significantly reducing per-frame garbage collection pressure and improving performance during high-frequency multitouch events.

Note

Medium Risk
Touches core input event injection and cursor association/warping paths; mistakes could cause stuck cursor behavior or incorrect drag/click events across apps and multi-display setups.

Overview
Improves middle-drag behavior by disassociating the cursor during drags, tracking an accumulated lastDragPosition, and warping the visible cursor each frame so apps that rely on absolute event positions (e.g. CAD/3D tools) get consistent movement.

Adds clamping to global display bounds, rounds Quartz drag deltas to integer fields explicitly, and hardens drag teardown/recovery paths (endDrag/cancelDrag/watchdog/force-up) to re-associate the cursor atomically and avoid races.

Reduces high-frequency multitouch overhead by replacing per-frame Swift array creation with a raw memcpy buffer that’s freed after async processing, and adds targeted tests for drag position accumulation, bounds clamping, and delta rounding.

Written by Cursor Bugbot for commit 19452b6. This will update automatically on new commits. Configure here.

…et position; optimize touch data copying in MultitouchManager to reduce GC pressure.
…enerator

- Introduced `lastDragPosition` to track the accumulated drag position for improved cursor handling in applications requiring absolute positioning.
- Added `reassociateCursor` method to restore normal cursor behavior after drag operations.
- Updated drag handling to use accumulated position for mouse events, enhancing compatibility with various applications.
- Ensured cursor is properly reassociated after drag ends or is canceled to prevent freezing issues.
Copilot AI review requested due to automatic review settings February 13, 2026 01:59
@NullPointerDepressiveDisorder NullPointerDepressiveDisorder added bug Something isn't working enhancement New feature or request labels Feb 13, 2026
@NullPointerDepressiveDisorder NullPointerDepressiveDisorder changed the title Fix middle-drag smoothness and add CAD/3D app compatibility Fix middle-drag smoothness and add compatibility for some apps Feb 13, 2026
@sentry
Copy link
Contributor

sentry bot commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 90.72848% with 14 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
MiddleDrag/Core/MouseEventGenerator.swift 92.59% 10 Missing ⚠️
MiddleDrag/Managers/MultitouchManager.swift 75.00% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses middle-drag jitter introduced after v1.4.1 by reducing per-frame allocations in the multitouch callback, and improves compatibility with CAD/3D apps by posting drag events with consistent absolute positions while explicitly managing cursor association/warping during drags.

Changes:

  • Replace per-frame Swift Array + map touch copying with a raw buffer memcpy and deferred deallocation on the gesture queue.
  • Add accumulated drag position tracking and cursor disassociate/warp logic so drag events provide both correct absolute positions and deltas.
  • Populate both integer and double delta fields on synthetic drag events for broader app compatibility.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
MiddleDrag/Managers/MultitouchManager.swift Optimizes touch frame copying to reduce high-frequency allocation overhead before async gesture processing.
MiddleDrag/Core/MouseEventGenerator.swift Implements cursor disassociate/warp drag strategy with accumulated positions and expanded delta field setting.
Comments suppressed due to low confidence (3)

MiddleDrag/Core/MouseEventGenerator.swift:248

  • endDrag() re-associates the cursor before setting isMiddleMouseDown = false. Since updateDrag() can run concurrently (it guards on isMiddleMouseDown), there’s a small race where a drag update can still post/warp after the cursor has been re-associated, potentially causing visible cursor jumps. Set isMiddleMouseDown = false (and/or gate CGWarpMouseCursorPosition behind a dedicated “disassociated” flag) before calling reassociateCursor().
        // Stop watchdog since drag is ending normally
        stopWatchdog()
        
        // Re-associate cursor so it's no longer frozen
        reassociateCursor()

        // CRITICAL: Set isMiddleMouseDown = false SYNCHRONOUSLY to match startDrag
        // This prevents race conditions with rapid start/end cycles and ensures
        // updateDrag() stops processing immediately
        isMiddleMouseDown = false

MiddleDrag/Core/MouseEventGenerator.swift:345

  • cancelDrag() re-associates the cursor before clearing isMiddleMouseDown. Because updateDrag() can be running on another queue, this ordering can allow one last drag update to post/warp after reassociation, leading to cursor jitter/jumps. Clear isMiddleMouseDown first (and/or maintain an explicit disassociation state flag) before calling reassociateCursor().
    func cancelDrag() {
        guard isMiddleMouseDown else { return }
        
        // Stop watchdog since drag is being cancelled
        stopWatchdog()
        
        // Re-associate cursor so it's no longer frozen
        reassociateCursor()

        // CRITICAL: Set isMiddleMouseDown = false SYNCHRONOUSLY to match startDrag
        // This prevents race conditions with rapid cancel/start cycles and ensures
        // updateDrag() stops processing immediately
        isMiddleMouseDown = false

MiddleDrag/Core/MouseEventGenerator.swift:375

  • forceMiddleMouseUp() re-associates the cursor before atomically clearing the drag state. If updateDrag() is still in-flight, it can still post/warp after reassociation. Consider clearing isMiddleMouseDown / bumping generation first, then re-associate (or gate warp/post on a dedicated “cursorDisassociated” flag tied to the generation).
        Log.warning("Force sending MIDDLE_UP (unconditional)", category: .gesture)
        
        // Stop watchdog if running
        stopWatchdog()
        
        // Re-associate cursor so it's no longer frozen
        reassociateCursor()
        
        // Atomically reset state and capture generation
        let currentGeneration: UInt64 = stateLock.withLock {
            _isMiddleMouseDown = false
            return _dragGeneration
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…optimize touch data copying in MultitouchManager to reduce GC pressure
…d clamping to display bounds; enhance test coverage for drag position accumulation and clamping behavior
…ventGenerator; ensure atomic cursor reassociation during drag release to prevent race conditions
…order of double and integer values for improved compatibility with various applications
…d state updates during drag cancellation, preventing race conditions and improving drag state management.
…d drag state updates during normal drag termination, preventing race conditions and enhancing overall drag state management.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

…ring atomic cancellation of existing drags, preventing race conditions during new drag initiation.
@NullPointerDepressiveDisorder NullPointerDepressiveDisorder merged commit f37100d into main Feb 13, 2026
7 checks passed
@NullPointerDepressiveDisorder NullPointerDepressiveDisorder deleted the bug/smoothing branch February 13, 2026 06:21
middledrag-releaser bot pushed a commit that referenced this pull request Feb 13, 2026
* Refactor mouse event handling to use current position instead of target position; optimize touch data copying in MultitouchManager to reduce GC pressure.

* Add accumulated drag position and cursor reassociation in MouseEventGenerator

- Introduced `lastDragPosition` to track the accumulated drag position for improved cursor handling in applications requiring absolute positioning.
- Added `reassociateCursor` method to restore normal cursor behavior after drag operations.
- Updated drag handling to use accumulated position for mouse events, enhancing compatibility with various applications.
- Ensured cursor is properly reassociated after drag ends or is canceled to prevent freezing issues.

* Enhance cursor re-association error handling in MouseEventGenerator; optimize touch data copying in MultitouchManager to reduce GC pressure

* Refactor MouseEventGenerator to improve drag position handling and add clamping to display bounds; enhance test coverage for drag position accumulation and clamping behavior

* Enhance display bounds caching and reconfiguration handling in MouseEventGenerator; ensure atomic cursor reassociation during drag release to prevent race conditions

* Refactor delta field setting in MouseEventGenerator to ensure proper order of double and integer values for improved compatibility with various applications

* Refactor MouseEventGenerator to ensure atomic cursor reassociation and state updates during drag cancellation, preventing race conditions and improving drag state management.

* Refactor MouseEventGenerator to ensure atomic cursor reassociation and drag state updates during normal drag termination, preventing race conditions and enhancing overall drag state management.

* Refactor MouseEventGenerator to enhance drag state management by ensuring atomic cancellation of existing drags, preventing race conditions during new drag initiation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments