Skip to content

Conversation

@PekingSpades
Copy link

@PekingSpades PekingSpades commented Dec 23, 2025

Description

This PR adds error-returning variants of the mouse click functions, allowing users to detect and handle mouse operation failures.

Motivation

We use robotgo in production and occasionally experience silent click failures. Currently, we rely on retry mechanisms as a workaround, but without direct error feedback from the API, we cannot reliably determine whether a click operation succeeded.

Changes

C layer (mouse/mouse_c.h):

  • Added toggleMouseErr(bool down, MMMouseButton button) - returns error code instead of void
  • Added clickMouseErr(MMMouseButton button) - returns error code for single click
  • Added doubleClickErr(MMMouseButton button) - returns error code for double click
  • Original functions (toggleMouse, clickMouse, doubleClick) preserved as wrappers for backward compatibility

Go layer (robotgo.go):

  • Added ClickE(args ...interface{}) error - click with error handling
  • Added MouseButtonString(btn C.MMMouseButton) string - converts button code to readable name
  • Added formatClickError() - formats platform-specific error messages

Platform-specific error handling

Platform Error Source
macOS CGEvent creation failure returns kCGErrorCannotComplete
Windows SendInput return value and GetLastError()
Linux (X11) XTestFakeButtonEvent return status

Why this is needed

The existing Click() function does not provide any feedback when a mouse operation fails. In production environments, system configurations are complex and failures are difficult to reproduce. To demonstrate that these system APIs can indeed fail, we have gathered the following documented cases:

macOS (CGEventCreateMouseEvent):

  • Returns NULL when accessibility permissions are not granted
  • CGEventTapCreate fails on macOS Mojave+ without proper permissions (GitHub Issue)

Windows (SendInput):

  • Returns 0 when input is blocked by another thread (Microsoft Docs)
  • Silently fails when blocked by UIPI (User Interface Privilege Isolation) - neither return value nor GetLastError() indicates the cause (MSDN Archive)
  • Fails when calling thread is not on active desktop or lacks DESKTOP_JOURNALPLAYBACK access (Microsoft Blog)
  • Go runtime may report incorrect return value on secure desktop (Go Issue #31685)

Linux (XTestFakeButtonEvent):

With ClickE(), users can now detect these failures and implement proper error handling or retry logic in their automation scripts.

Testing

  • Tested on Windows
  • Tested on macOS
  • Tested on Linux (X11)

Test code:

package main

import (
	"fmt"
	"github.com/go-vgo/robotgo"
)

func main() {
	// Test ClickE with default left button
	if err := robotgo.ClickE(); err != nil {
		fmt.Printf("Click failed: %v\n", err)
	} else {
		fmt.Println("Left click succeeded")
	}

	// Test ClickE with right button
	if err := robotgo.ClickE("right"); err != nil {
		fmt.Printf("Right click failed: %v\n", err)
	} else {
		fmt.Println("Right click succeeded")
	}

	// Test ClickE with double click
	if err := robotgo.ClickE("left", true); err != nil {
		fmt.Printf("Double click failed: %v\n", err)
	} else {
		fmt.Println("Double click succeeded")
	}
}

Backward compatibility

This PR maintains full backward compatibility. All existing functions continue to work as before. The new ClickE() function is an additional option for users who need error handling.


Signed-off-by: PekingSpades [email protected]

Summary by CodeRabbit

  • New Features
    • Added ClickE() function for mouse clicks with detailed error reporting
    • Mouse button operations now return platform-specific error codes instead of silently failing
    • Enhanced error messages to help identify click operation failures across all platforms
    • Added helper function for mouse button identification

✏️ Tip: You can customize this high-level summary in your review settings.

@CLAassistant
Copy link

CLAassistant commented Dec 23, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link

coderabbitai bot commented Dec 23, 2025

Walkthrough

This pull request introduces error-aware variants for mouse actions across Mac, X11, and Windows platforms, replacing fire-and-forget semantics with explicit error returns. Additionally, a new ClickE function is added to the Go package for click operations with detailed error reporting, alongside platform-specific error handling and coordinate scaling transformations.

Changes

Cohort / File(s) Summary
Error-aware mouse action variants
mouse/mouse_c.h
Introduced toggleMouseErr(), clickMouseErr(), and doubleClickErr() functions that return platform-specific error codes (CGError on Mac, Status on X11, Windows error codes). Updated existing toggleMouse(), clickMouse(), and doubleClick() to delegate to their error-returning counterparts. Implemented error handling for all three platforms with proper propagation.
Go error reporting and helpers
robotgo.go
Added new ClickE() function with optional button string and double-click flag parameters; introduced MouseButtonString() helper to convert mouse button constants to readable names; added formatClickError() to map platform-specific error codes to human-readable messages. Imported fmt and syscall for error handling. Modified MoveScale() behavior to apply scaling via ScaleF(displayId...) for coordinate transformation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add error-returning mouse click helpers (ClickE)' accurately summarizes the main change: introducing new error-returning variants of mouse click functions.
Description check ✅ Passed The PR description comprehensively addresses all template requirements: explains motivation, lists changes, documents platform-specific error handling, provides test code, and confirms backward compatibility.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@breece1991
Copy link

breece1991 commented Dec 23, 2025 via email

Copy link

@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

🧹 Nitpick comments (1)
robotgo.go (1)

724-724: Consider moving defer after argument validation.

The defer MilliSleep(MouseSleep) at line 724 will execute even when returning early due to invalid arguments (lines 711, 719). If no C call is made, sleeping is unnecessary overhead.

🔎 Suggested fix

Move the defer after argument validation, or remove it and add explicit MilliSleep before return nil:

 	if len(args) > 1 {
 		dbl, ok := args[1].(bool)
 		if !ok {
 			return errors.New("second argument must be a bool indicating double click")
 		}
 		double = dbl
 	}

-	defer MilliSleep(MouseSleep)
-
 	if !double {
 		if code := C.toggleMouseErr(true, button); code != 0 {
 			return formatClickError(int(code), button, "down", false)
 		}

 		// match clickMouse timing
 		C.microsleep(C.double(5.0))

 		if code := C.toggleMouseErr(false, button); code != 0 {
 			return formatClickError(int(code), button, "up", false)
 		}
 	} else {
 		if code := C.doubleClickErr(button); code != 0 {
 			return formatClickError(int(code), button, "double", true)
 		}
 	}

+	MilliSleep(MouseSleep)
 	return nil
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 381e4bd and 1040e75.

📒 Files selected for processing (2)
  • mouse/mouse_c.h
  • robotgo.go
🧰 Additional context used
🧬 Code graph analysis (2)
robotgo.go (1)
keycode.go (4)
  • WheelDown (28-28)
  • WheelUp (29-29)
  • WheelLeft (30-30)
  • WheelRight (31-31)
mouse/mouse_c.h (2)
base/xdisplay_c.h (1)
  • XGetMainDisplay (18-48)
robotgo_win.go (1)
  • SendInput (39-41)
🔇 Additional comments (6)
robotgo.go (3)

55-58: LGTM!

The new imports (fmt for error formatting and syscall for Windows error code translation) are correctly added to support the new error-returning functionality.


510-526: LGTM!

The function correctly maps button constants to readable names with a sensible fallback for unknown values. This pairs well with the error formatting logic.


746-783: LGTM with minor observation.

The platform-specific error formatting is well-structured. Note that 0: "kCGErrorSuccess" (line 757) is unreachable since this function is only called when code != 0, but it's harmless documentation of the full enum.

mouse/mouse_c.h (3)

198-200: LGTM!

The wrapper correctly maintains backward compatibility by delegating to the new error-returning function.


202-215: LGTM!

The error propagation logic is correct - errors from either the down or up toggle are properly surfaced.


256-259: LGTM!

The wrapper correctly maintains backward compatibility.

Comment on lines 176 to +181
#elif defined(USE_X11)
Display *display = XGetMainDisplay();
XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime);
Status status = XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime);
XSync(display, false);

return status ? 0 : 1;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing NULL check for X11 display.

XGetMainDisplay() can return NULL if it fails to open a display (as shown in base/xdisplay_c.h). Calling XTestFakeButtonEvent with a NULL display will cause undefined behavior or crash.

🔎 Suggested fix
 	#elif defined(USE_X11)
 		Display *display = XGetMainDisplay();
+		if (display == NULL) {
+			return 1;
+		}
 		Status status = XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime);
 		XSync(display, false);

 		return status ? 0 : 1;
📝 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
#elif defined(USE_X11)
Display *display = XGetMainDisplay();
XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime);
Status status = XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime);
XSync(display, false);
return status ? 0 : 1;
#elif defined(USE_X11)
Display *display = XGetMainDisplay();
if (display == NULL) {
return 1;
}
Status status = XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime);
XSync(display, false);
return status ? 0 : 1;
🤖 Prompt for AI Agents
In mouse/mouse_c.h around lines 176-181, XGetMainDisplay() may return NULL and
the code calls XTestFakeButtonEvent/XSync unconditionally; add a NULL check
immediately after obtaining display and handle the failure (e.g., log the error
if logging is available and return the same non-zero error value used elsewhere)
so you do not call XTestFakeButtonEvent or XSync with a NULL display.

Comment on lines 224 to 225
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateMouseEvent(source, mouseTypeDown, currentPos, kCGMouseButtonLeft);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hardcoded kCGMouseButtonLeft inconsistent with toggleMouseErr.

Line 225 uses kCGMouseButtonLeft as the button parameter, but toggleMouseErr (line 164) correctly uses (CGMouseButton)button. This inconsistency may cause issues when double-clicking with center or other mouse buttons.

🔎 Suggested fix
-		CGEventRef event = CGEventCreateMouseEvent(source, mouseTypeDown, currentPos, kCGMouseButtonLeft);
+		CGEventRef event = CGEventCreateMouseEvent(source, mouseTypeDown, currentPos, (CGMouseButton)button);
📝 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
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateMouseEvent(source, mouseTypeDown, currentPos, kCGMouseButtonLeft);
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateMouseEvent(source, mouseTypeDown, currentPos, (CGMouseButton)button);
🤖 Prompt for AI Agents
In mouse/mouse_c.h around lines 224 to 225, the CGEventCreateMouseEvent call
hardcodes kCGMouseButtonLeft for the button argument while toggleMouseErr used
(CGMouseButton)button; change the hardcoded kCGMouseButtonLeft to use
(CGMouseButton)button so the created mouse event respects the provided button
value (and apply the same change for any corresponding mouse-up event nearby),
ensuring the 'button' variable is in scope and consistent with the rest of the
function.

@vcaesar vcaesar added this to the v1.10.0 milestone Dec 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants