Skip to content

fix(macos): use CGWarpMouseCursorPosition for mouse control in VMs#1163

Open
robotlearning123 wants to merge 1 commit intotrycua:mainfrom
robotlearning123:fix/macos-vm-mouse-cgwarp
Open

fix(macos): use CGWarpMouseCursorPosition for mouse control in VMs#1163
robotlearning123 wants to merge 1 commit intotrycua:mainfrom
robotlearning123:fix/macos-vm-mouse-cgwarp

Conversation

@robotlearning123
Copy link

@robotlearning123 robotlearning123 commented Mar 12, 2026

Summary

  • Replace pynput's MouseController.position setter with a CGWarpMouseCursorPosition-based descriptor that works reliably in macOS VMs under Apple's Virtualization.framework
  • Fixes silent mouse positioning failure affecting all mouse operations (left_click, right_click, double_click, move_cursor, drag_to, drag_path)
  • Zero changes to existing method signatures or behavior — drop-in replacement at module level

Problem

pynput internally uses CGEventPost with kCGEventMouseMoved to set cursor position. This silently fails in macOS VMs running under Virtualization.framework (Tart, Lume, any VZVirtualMachine-based hypervisor). The handler returns {"success": true} but clicks land at the wrong coordinates.

Method Works in VM?
CGWarpMouseCursorPosition Yes
CGDisplayMoveCursorToPoint Yes
CGEventPost(kCGEventMouseMoved) No
pynput mouse.position = (x, y) No

Fix

A Python descriptor (_CGWarpPosition) replaces pynput's default position property on MouseController. It uses CGWarpMouseCursorPosition for setting and CGEventGetLocation for getting. Applied once at module level — all existing mouse methods benefit automatically.

CGWarpMouseCursorPosition and CGAssociateMouseAndMouseCursorPosition are already available in scope via the existing from Quartz.CoreGraphics import * on line 48.

Test Results (in macOS VM)

  • 10/10 click accuracy: 0.0px error on all coordinates
  • Click latency: ~0.003s/click
  • 21/21 capability tests passed (all mouse + keyboard + screenshot operations)
  • Works identically on bare metal and in VMs

Test Plan

  • Verify mouse clicks land at correct coordinates in a macOS VM (Tart/Lume)
  • Verify mouse clicks still work correctly on bare-metal macOS
  • Verify drag operations work in VM environment
  • Run existing test suite

Fixes #1162

Summary by CodeRabbit

  • Bug Fixes

    • Improved macOS cursor positioning reliability by enhancing how the mouse position is tracked and controlled.
  • Refactor

    • Optimized mouse position handling on macOS for better performance and stability.

…in VMs

pynput's MouseController.position setter uses CGEventPost with
kCGEventMouseMoved internally, which silently fails in macOS VMs
running under Apple's Virtualization.framework (Tart, Lume). The
cursor doesn't move but no error is raised, causing clicks to land
at wrong coordinates.

Replace with a descriptor that uses CGWarpMouseCursorPosition, which
works reliably in both VM and bare-metal environments. All existing
mouse methods (left_click, right_click, double_click, move_cursor,
drag_to, drag_path) benefit automatically with zero code changes.

Fixes trycua#1162
@vercel
Copy link
Contributor

vercel bot commented Mar 12, 2026

@robotlearning123 is attempting to deploy a commit to the Cua Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

A new descriptor class _CGWarpPosition is introduced to replace pynput's default mouse position handling. The descriptor uses CoreGraphics APIs (CGWarpMouseCursorPosition and CGEventGetLocation) for getting and setting cursor coordinates, replacing the problematic position setter on the MouseController instance.

Changes

Cohort / File(s) Summary
Mouse Position Descriptor
libs/python/computer-server/computer_server/handlers/macos.py
Added _CGWarpPosition descriptor class with __get__ and __set__ methods to handle cursor position via CoreGraphics APIs; assigned to MouseController.position as a module-level patch.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A cursor that danced in the void,
Now glides through the VM undeployed,
CoreGraphics warp, no more silent fall,
The mouse finds its target, precise through it all!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: using CGWarpMouseCursorPosition for macOS mouse control in VMs, which is exactly what the PR implements.
Linked Issues check ✅ Passed The PR fully addresses issue #1162: replaces pynput's position setter with CGWarpMouseCursorPosition descriptor, resolves silent cursor movement failures in VMs, and maintains compatibility with all mouse operations.
Out of Scope Changes check ✅ Passed All changes are in scope: the _CGWarpPosition descriptor and its assignment to MouseController.position directly address the VM cursor positioning issue described in #1162.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@libs/python/computer-server/computer_server/handlers/macos.py`:
- Around line 100-106: Replace the cursor warp in _CGWarpPosition.__set__
(currently calling CGWarpMouseCursorPosition and
CGAssociateMouseAndMouseCursorPosition) with synthesized drag motion events:
detect when a mouse button is held (used by MouseController.position during
drag_to/drag), create a CGEvent via CGEventCreateMouseEvent with the appropriate
event type (e.g., kCGEventLeftMouseDragged or the button-specific dragged event)
for each position update, and post it with CGEventPost so target apps receive
kCGEvent*Dragged events; if you still call
CGWarpMouseCursorPosition/CGAssociateMouseAndMouseCursorPosition, check and
handle their return codes and only fall back to warping when event synthesis
fails.
- Around line 102-103: The code currently calls CGWarpMouseCursorPosition and
CGAssociateMouseAndMouseCursorPosition without checking their CGError return
values; update the handlers (move_cursor, left_click, mouse_down) to capture
each function's return, verify it equals kCGErrorSuccess (or zero), and if not
log/return a failure payload (e.g., {"success": False, "error": "<CGError code
or message>"}); ensure you short-circuit subsequent calls when a warp/associate
fails and include enough context (function name and error code) in the
logged/returned error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc806c4f-1332-48a6-a17c-d831431cedcf

📥 Commits

Reviewing files that changed from the base of the PR and between f90eadc and 98f5eee.

📒 Files selected for processing (1)
  • libs/python/computer-server/computer_server/handlers/macos.py

Comment on lines +100 to +106
def __set__(self, obj, value):
x, y = value
CGWarpMouseCursorPosition((float(x), float(y)))
CGAssociateMouseAndMouseCursorPosition(True)


MouseController.position = _CGWarpPosition()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Does Quartz CGWarpMouseCursorPosition generate mouse moved or mouse dragged events on macOS, and what API is recommended to synthesize drag motion while a mouse button is held?

💡 Result:

No. CGWarpMouseCursorPosition moves the cursor but does not generate a mouse event, so it won’t produce either “mouse moved” or “mouse dragged” events (neither NSMouseMoved nor NSLeftMouseDragged/NSRightMouseDragged, etc.). [1]

To synthesize drag motion while a button is held, Apple’s recommended approach is to post Quartz CGEvents: create mouse events with CGEventCreateMouseEvent and post them with CGEventPost, using a dragged event type such as kCGEventLeftMouseDragged (described as “a mouse drag event with the left button down”). [2][3]


🏁 Script executed:

cat -n libs/python/computer-server/computer_server/handlers/macos.py | sed -n '80,110p'

Repository: trycua/cua

Length of output: 1434


🏁 Script executed:

cat -n libs/python/computer-server/computer_server/handlers/macos.py | sed -n '1150,1230p'

Repository: trycua/cua

Length of output: 3603


🏁 Script executed:

rg "CGWarpMouseCursorPosition|CGAssociateMouseAndMouseCursorPosition" libs/python/computer-server/computer_server/handlers/macos.py -A 2 -B 2

Repository: trycua/cua

Length of output: 676


Synthesize drag motion events instead of warping cursor during drag operations.

drag_to() and drag() press a button, then repeatedly set self.mouse.position while holding it down (lines 1182, 1220). The replacement CGWarpMouseCursorPosition warps the cursor without generating any mouse events—target applications won't receive kCGEventLeftMouseDragged or equivalent events, so drag operations will fail silently. Use CGEventCreateMouseEvent with kCGEventLeftMouseDragged (or appropriate button type) and post it with CGEventPost to synthesize drag motion while a button is held.

Additionally, CGWarpMouseCursorPosition and CGAssociateMouseAndMouseCursorPosition (lines 102–103) return error codes that are never checked, reintroducing the silent-failure behavior the PR aimed to fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/python/computer-server/computer_server/handlers/macos.py` around lines
100 - 106, Replace the cursor warp in _CGWarpPosition.__set__ (currently calling
CGWarpMouseCursorPosition and CGAssociateMouseAndMouseCursorPosition) with
synthesized drag motion events: detect when a mouse button is held (used by
MouseController.position during drag_to/drag), create a CGEvent via
CGEventCreateMouseEvent with the appropriate event type (e.g.,
kCGEventLeftMouseDragged or the button-specific dragged event) for each position
update, and post it with CGEventPost so target apps receive kCGEvent*Dragged
events; if you still call
CGWarpMouseCursorPosition/CGAssociateMouseAndMouseCursorPosition, check and
handle their return codes and only fall back to warping when event synthesis
fails.

Comment on lines +102 to +103
CGWarpMouseCursorPosition((float(x), float(y)))
CGAssociateMouseAndMouseCursorPosition(True)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What do Quartz CGWarpMouseCursorPositionandCGAssociateMouseAndMouseCursorPositionreturn on macOS, and iskCGErrorSuccess the expected success value?

💡 Result:

On macOS, both Quartz functions return a CGError status code:

  • CGWarpMouseCursorPosition(CGPoint) → returns a “result code” (CGError). On success it returns kCGErrorSuccess. [1]
  • CGAssociateMouseAndMouseCursorPosition(boolean_t connected) → returns a “result code” (CGError). On success it returns kCGErrorSuccess. [2]

Yes—kCGErrorSuccess is the expected success value, and it is defined as 0 in the Quartz Display Services result codes. [3]

Sources
[1] Quartz Display Services Reference — CGWarpMouseCursorPosition return value section (leopard-adc.pepas.com)
[2] Quartz Display Services Reference — CGAssociateMouseAndMouseCursorPosition return value section (leopard-adc.pepas.com)
[3] Quartz Display Services Reference — Result codes table (kCGErrorSuccess = 0) (leopard-adc.pepas.com)

Citations:


🏁 Script executed:

head -n 110 libs/python/computer-server/computer_server/handlers/macos.py | tail -n 20

Repository: trycua/cua

Length of output: 616


🏁 Script executed:

rg -n "def (move_cursor|left_click|mouse_down)" libs/python/computer-server/computer_server/handlers/macos.py

Repository: trycua/cua

Length of output: 261


🏁 Script executed:

sed -n '1137,1150p' libs/python/computer-server/computer_server/handlers/macos.py

Repository: trycua/cua

Length of output: 497


🏁 Script executed:

sed -n '1069,1085p' libs/python/computer-server/computer_server/handlers/macos.py

Repository: trycua/cua

Length of output: 757


🏁 Script executed:

sed -n '1019,1040p' libs/python/computer-server/computer_server/handlers/macos.py

Repository: trycua/cua

Length of output: 920


Check the CoreGraphics return codes instead of assuming success.

Both CGWarpMouseCursorPosition and CGAssociateMouseAndMouseCursorPosition return CGError; they do not raise on failure. Currently, a rejected warp lets move_cursor, left_click, and mouse_down return {"success": True}, silently masking failures to the API caller.

Possible fix
     def __set__(self, obj, value):
         x, y = value
-        CGWarpMouseCursorPosition((float(x), float(y)))
-        CGAssociateMouseAndMouseCursorPosition(True)
+        err = CGWarpMouseCursorPosition((float(x), float(y)))
+        if err != kCGErrorSuccess:
+            raise RuntimeError(f"CGWarpMouseCursorPosition failed: {err}")
+
+        err = CGAssociateMouseAndMouseCursorPosition(True)
+        if err != kCGErrorSuccess:
+            raise RuntimeError(f"CGAssociateMouseAndMouseCursorPosition failed: {err}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CGWarpMouseCursorPosition((float(x), float(y)))
CGAssociateMouseAndMouseCursorPosition(True)
err = CGWarpMouseCursorPosition((float(x), float(y)))
if err != kCGErrorSuccess:
raise RuntimeError(f"CGWarpMouseCursorPosition failed: {err}")
err = CGAssociateMouseAndMouseCursorPosition(True)
if err != kCGErrorSuccess:
raise RuntimeError(f"CGAssociateMouseAndMouseCursorPosition failed: {err}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/python/computer-server/computer_server/handlers/macos.py` around lines
102 - 103, The code currently calls CGWarpMouseCursorPosition and
CGAssociateMouseAndMouseCursorPosition without checking their CGError return
values; update the handlers (move_cursor, left_click, mouse_down) to capture
each function's return, verify it equals kCGErrorSuccess (or zero), and if not
log/return a failure payload (e.g., {"success": False, "error": "<CGError code
or message>"}); ensure you short-circuit subsequent calls when a warp/associate
fails and include enough context (function name and error code) in the
logged/returned error.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(macos): mouse position fails silently in macOS VMs (Virtualization.framework)

2 participants