From a768e8311118f11d8788a470ad157b26b93a8310 Mon Sep 17 00:00:00 2001 From: PekingSpades <180665176+PekingSpades@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:40:18 +0800 Subject: [PATCH 1/2] Update: add error-returning mouse click helpers --- mouse/mouse_c.h | 65 ++++++++++++++++++++-------- robotgo.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 17 deletions(-) diff --git a/mouse/mouse_c.h b/mouse/mouse_c.h index 002787d3..42d43302 100644 --- a/mouse/mouse_c.h +++ b/mouse/mouse_c.h @@ -156,20 +156,24 @@ 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); - CGEventPost(kCGHIDEventTap, event); + CGError err = CGEventPost(kCGHIDEventTap, event); CFRelease(event); CFRelease(source); + + return err == kCGErrorSuccess ? 0 : (int)err; #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(IS_WINDOWS) // mouse_event(MMMouseToMEventF(down, button), 0, 0, 0, 0); INPUT mouseInput; @@ -177,22 +181,35 @@ void toggleMouse(bool down, MMMouseButton button) { mouseInput.type = INPUT_MOUSE; mouseInput.mi.dx = 0; mouseInput.mi.dy = 0; - mouseInput.mi.dwFlags = MMMouseToMEventF(down, button); - mouseInput.mi.time = 0; - mouseInput.mi.dwExtraInfo = 0; - mouseInput.mi.mouseData = 0; - SendInput(1, &mouseInput, sizeof(mouseInput)); + mouseInput.mi.dwFlags = MMMouseToMEventF(down, button); + mouseInput.mi.time = 0; + mouseInput.mi.dwExtraInfo = 0; + mouseInput.mi.mouseData = 0; + 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()); @@ -204,21 +221,35 @@ void doubleClick(MMMouseButton button){ /* Set event to double click. */ CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2); - CGEventPost(kCGHIDEventTap, event); + CGError err = CGEventPost(kCGHIDEventTap, event); CGEventSetType(event, mouseTypeUP); - CGEventPost(kCGHIDEventTap, event); + if (err == kCGErrorSuccess) { + err = CGEventPost(kCGHIDEventTap, event); + } CFRelease(event); CFRelease(source); + + return err == kCGErrorSuccess ? 0 : (int)err; #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) diff --git a/robotgo.go b/robotgo.go index a7dc8d30..a8757903 100644 --- a/robotgo.go +++ b/robotgo.go @@ -52,8 +52,10 @@ import "C" import ( "errors" + "fmt" "image" "runtime" + "syscall" "time" "unsafe" @@ -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" { @@ -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) From 1040e75eecd2dfe7102bac484a12e52ac2c260b5 Mon Sep 17 00:00:00 2001 From: PekingSpades <180665176+PekingSpades@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:48:41 +0800 Subject: [PATCH 2/2] Update: fix mac build for click error helpers --- mouse/mouse_c.h | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/mouse/mouse_c.h b/mouse/mouse_c.h index 42d43302..fc8bdb4f 100644 --- a/mouse/mouse_c.h +++ b/mouse/mouse_c.h @@ -163,11 +163,16 @@ int toggleMouseErr(bool down, MMMouseButton button) { CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef event = CGEventCreateMouseEvent(source, mouseType, currentPos, (CGMouseButton)button); - CGError err = CGEventPost(kCGHIDEventTap, event); + if (event == NULL) { + CFRelease(source); + return (int)kCGErrorCannotComplete; + } + + CGEventPost(kCGHIDEventTap, event); CFRelease(event); CFRelease(source); - return err == kCGErrorSuccess ? 0 : (int)err; + return 0; #elif defined(USE_X11) Display *display = XGetMainDisplay(); Status status = XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime); @@ -181,12 +186,12 @@ int toggleMouseErr(bool down, MMMouseButton button) { mouseInput.type = INPUT_MOUSE; mouseInput.mi.dx = 0; mouseInput.mi.dy = 0; - mouseInput.mi.dwFlags = MMMouseToMEventF(down, button); - mouseInput.mi.time = 0; - mouseInput.mi.dwExtraInfo = 0; - mouseInput.mi.mouseData = 0; - UINT sent = SendInput(1, &mouseInput, sizeof(mouseInput)); - return sent == 1 ? 0 : (int)GetLastError(); + mouseInput.mi.dwFlags = MMMouseToMEventF(down, button); + mouseInput.mi.time = 0; + mouseInput.mi.dwExtraInfo = 0; + mouseInput.mi.mouseData = 0; + UINT sent = SendInput(1, &mouseInput, sizeof(mouseInput)); + return sent == 1 ? 0 : (int)GetLastError(); #endif } @@ -219,19 +224,22 @@ int doubleClickErr(MMMouseButton button){ CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); CGEventRef event = CGEventCreateMouseEvent(source, mouseTypeDown, currentPos, kCGMouseButtonLeft); + if (event == NULL) { + CFRelease(source); + return (int)kCGErrorCannotComplete; + } + /* Set event to double click. */ CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2); - CGError err = CGEventPost(kCGHIDEventTap, event); + CGEventPost(kCGHIDEventTap, event); CGEventSetType(event, mouseTypeUP); - if (err == kCGErrorSuccess) { - err = CGEventPost(kCGHIDEventTap, event); - } + CGEventPost(kCGHIDEventTap, event); CFRelease(event); CFRelease(source); - return err == kCGErrorSuccess ? 0 : (int)err; + return 0; #else /* Double click for everything else. */ int err = clickMouseErr(button);