Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions mouse/mouse_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,20 +156,29 @@ MMPointInt32 location() {
}

/* Press down a button, or release it. */
void toggleMouse(bool down, MMMouseButton button) {
int toggleMouseErr(bool down, MMMouseButton button) {
#if defined(IS_MACOSX)
const CGPoint currentPos = CGPointFromMMPointInt32(location());
const CGEventType mouseType = MMMouseToCGEventType(down, button);
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateMouseEvent(source, mouseType, currentPos, (CGMouseButton)button);

if (event == NULL) {
CFRelease(source);
return (int)kCGErrorCannotComplete;
}

CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
CFRelease(source);

return 0;
#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;
Comment on lines 176 to +181
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.

#elif defined(IS_WINDOWS)
// mouse_event(MMMouseToMEventF(down, button), 0, 0, 0, 0);
INPUT mouseInput;
Expand All @@ -181,18 +190,31 @@ void toggleMouse(bool down, MMMouseButton button) {
mouseInput.mi.time = 0;
mouseInput.mi.dwExtraInfo = 0;
mouseInput.mi.mouseData = 0;
SendInput(1, &mouseInput, sizeof(mouseInput));
UINT sent = SendInput(1, &mouseInput, sizeof(mouseInput));
return sent == 1 ? 0 : (int)GetLastError();
#endif
}

void clickMouse(MMMouseButton button){
toggleMouse(true, button);
void toggleMouse(bool down, MMMouseButton button) {
toggleMouseErr(down, button);
}

int clickMouseErr(MMMouseButton button){
int err = toggleMouseErr(true, button);
if (err != 0) {
return err;
}

microsleep(5.0);
toggleMouse(false, button);

return toggleMouseErr(false, button);
}

/* Special function for sending double clicks, needed for MacOS. */
void doubleClick(MMMouseButton button){
void clickMouse(MMMouseButton button){
clickMouseErr(button);
}

int doubleClickErr(MMMouseButton button){
#if defined(IS_MACOSX)
/* Double click for Mac. */
const CGPoint currentPos = CGPointFromMMPointInt32(location());
Expand All @@ -202,6 +224,11 @@ void doubleClick(MMMouseButton button){
CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateMouseEvent(source, mouseTypeDown, currentPos, kCGMouseButtonLeft);
Comment on lines 224 to 225
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.


if (event == NULL) {
CFRelease(source);
return (int)kCGErrorCannotComplete;
}

/* Set event to double click. */
CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2);
CGEventPost(kCGHIDEventTap, event);
Expand All @@ -211,14 +238,26 @@ void doubleClick(MMMouseButton button){

CFRelease(event);
CFRelease(source);

return 0;
#else
/* Double click for everything else. */
clickMouse(button);
int err = clickMouseErr(button);
if (err != 0) {
return err;
}

microsleep(200);
clickMouse(button);

return clickMouseErr(button);
#endif
}

/* Special function for sending double clicks, needed for MacOS. */
void doubleClick(MMMouseButton button){
doubleClickErr(button);
}

/* Function used to scroll the screen in the required direction. */
void scrollMouseXY(int x, int y) {
#if defined(IS_WINDOWS)
Expand Down
111 changes: 111 additions & 0 deletions robotgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ import "C"

import (
"errors"
"fmt"
"image"
"runtime"
"syscall"
"time"
"unsafe"

Expand Down Expand Up @@ -505,6 +507,24 @@ func CheckMouse(btn string) C.MMMouseButton {
return C.LEFT_BUTTON
}

// MouseButtonString converts a C.MMMouseButton to a readable name.
func MouseButtonString(btn C.MMMouseButton) string {
m1 := map[C.MMMouseButton]string{
C.LEFT_BUTTON: "left",
C.CENTER_BUTTON: "center",
C.RIGHT_BUTTON: "right",
C.WheelDown: "wheelDown",
C.WheelUp: "wheelUp",
C.WheelLeft: "wheelLeft",
C.WheelRight: "wheelRight",
}
if v, ok := m1[btn]; ok {
return v
}

return fmt.Sprintf("button%d", btn)
}

// MoveScale calculate the os scale factor x, y
func MoveScale(x, y int, displayId ...int) (int, int) {
if Scale || runtime.GOOS == "windows" {
Expand Down Expand Up @@ -671,6 +691,97 @@ func Click(args ...interface{}) {
MilliSleep(MouseSleep)
}

// ClickE click the mouse button and return error
//
// robotgo.ClickE(button string, double bool)
//
// Examples:
//
// err := robotgo.ClickE() // default is left button
// err := robotgo.ClickE("right")
func ClickE(args ...interface{}) error {
var (
button C.MMMouseButton = C.LEFT_BUTTON
double bool
)

if len(args) > 0 {
btn, ok := args[0].(string)
if !ok {
return errors.New("first argument must be a button string")
}
button = CheckMouse(btn)
}

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)
}
}

return nil
}

func formatClickError(code int, button C.MMMouseButton, stage string, double bool) error {
btnName := MouseButtonString(button)
detail := ""

switch runtime.GOOS {
case "windows":
if code != 0 {
detail = syscall.Errno(code).Error()
}
case "darwin":
cgErrors := map[int]string{
0: "kCGErrorSuccess",
1000: "kCGErrorFailure",
1001: "kCGErrorIllegalArgument",
1002: "kCGErrorInvalidConnection",
1003: "kCGErrorInvalidContext",
1004: "kCGErrorCannotComplete",
1005: "kCGErrorNotImplemented",
1006: "kCGErrorRangeCheck",
1007: "kCGErrorTypeCheck",
1008: "kCGErrorNoCurrentPoint",
1010: "kCGErrorInvalidOperation",
}
if v, ok := cgErrors[code]; ok {
detail = v
}
default:
if code == 1 {
detail = "XTestFakeButtonEvent returned false"
}
}

if detail != "" {
return fmt.Errorf("click %s failed (%s, double=%v): %s (code=%d)", stage, btnName, double, detail, code)
}

return fmt.Errorf("click %s failed (%s, double=%v), code=%d", stage, btnName, double, code)
}

// MoveClick move and click the mouse
//
// robotgo.MoveClick(x, y int, button string, double bool)
Expand Down