-
-
Notifications
You must be signed in to change notification settings - Fork 816
fix(macos): use CGWarpMouseCursorPosition for mouse control in VMs #1163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,6 +80,32 @@ | |
| logger.debug(f"Failed to trigger screenshot permissions prompt: {e}") | ||
|
|
||
|
|
||
| # Fix: pynput's MouseController.position setter uses CGEventPost with | ||
| # kCGEventMouseMoved internally, which silently fails in macOS VMs running | ||
| # under Apple's Virtualization.framework (Tart, Lume, or any VZVirtualMachine- | ||
| # based hypervisor). The cursor doesn't move but no error is raised. | ||
| # | ||
| # CGWarpMouseCursorPosition works reliably in both VM and bare-metal environments. | ||
| # This descriptor replaces pynput's default position property with a CGWarp-based | ||
| # implementation. See: https://github.com/trycua/cua/issues/1162 | ||
| class _CGWarpPosition: | ||
| """Descriptor that uses CGWarpMouseCursorPosition instead of pynput's default.""" | ||
|
|
||
| def __get__(self, obj, objtype=None): | ||
| if obj is None: | ||
| return self | ||
| pos = CGEventGetLocation(CGEventCreate(None)) | ||
| return (int(pos.x), int(pos.y)) | ||
|
|
||
| def __set__(self, obj, value): | ||
| x, y = value | ||
| CGWarpMouseCursorPosition((float(x), float(y))) | ||
| CGAssociateMouseAndMouseCursorPosition(True) | ||
|
|
||
|
|
||
| MouseController.position = _CGWarpPosition() | ||
|
Comment on lines
+100
to
+106
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No. To synthesize drag motion while a button is held, Apple’s recommended approach is to post Quartz CGEvents: create mouse events with 🏁 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 2Repository: trycua/cua Length of output: 676 Synthesize drag motion events instead of warping cursor during drag operations.
Additionally, 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| # Constants for accessibility API | ||
| kAXErrorSuccess = 0 | ||
| kAXRoleAttribute = "AXRole" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
What do QuartzCGWarpMouseCursorPositionandCGAssociateMouseAndMouseCursorPositionreturn on macOS, and iskCGErrorSuccessthe expected success value?💡 Result:
On macOS, both Quartz functions return a
CGErrorstatus code:CGWarpMouseCursorPosition(CGPoint)→ returns a “result code” (CGError). On success it returnskCGErrorSuccess. [1]CGAssociateMouseAndMouseCursorPosition(boolean_t connected)→ returns a “result code” (CGError). On success it returnskCGErrorSuccess. [2]Yes—
kCGErrorSuccessis the expected success value, and it is defined as0in the Quartz Display Services result codes. [3]Sources
[1] Quartz Display Services Reference —
CGWarpMouseCursorPositionreturn value section (leopard-adc.pepas.com)[2] Quartz Display Services Reference —
CGAssociateMouseAndMouseCursorPositionreturn 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 20Repository: 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.pyRepository: trycua/cua
Length of output: 261
🏁 Script executed:
sed -n '1137,1150p' libs/python/computer-server/computer_server/handlers/macos.pyRepository: trycua/cua
Length of output: 497
🏁 Script executed:
sed -n '1069,1085p' libs/python/computer-server/computer_server/handlers/macos.pyRepository: trycua/cua
Length of output: 757
🏁 Script executed:
sed -n '1019,1040p' libs/python/computer-server/computer_server/handlers/macos.pyRepository: trycua/cua
Length of output: 920
Check the CoreGraphics return codes instead of assuming success.
Both
CGWarpMouseCursorPositionandCGAssociateMouseAndMouseCursorPositionreturnCGError; they do not raise on failure. Currently, a rejected warp letsmove_cursor,left_click, andmouse_downreturn{"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
🤖 Prompt for AI Agents