Merge pull request #750 from PekingSpades/master

Add error-returning mouse click helpers (ClickE)
This commit is contained in:
Evans 2026-01-06 15:52:59 -08:00 committed by GitHub
commit dafd29e5ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 161 additions and 11 deletions

View file

@ -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;
#elif defined(IS_WINDOWS)
// mouse_event(MMMouseToMEventF(down, button), 0, 0, 0, 0);
INPUT mouseInput;
@ -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);
microsleep(5.0);
toggleMouse(false, button);
void toggleMouse(bool down, MMMouseButton button) {
toggleMouseErr(down, button);
}
/* Special function for sending double clicks, needed for MacOS. */
void doubleClick(MMMouseButton button){
int clickMouseErr(MMMouseButton button){
int err = toggleMouseErr(true, button);
if (err != 0) {
return err;
}
microsleep(5.0);
return toggleMouseErr(false, button);
}
void clickMouse(MMMouseButton button){
clickMouseErr(button);
}
int doubleClickErr(MMMouseButton button){
#if defined(IS_MACOSX)
/* Double click for Mac. */
const CGPoint currentPos = CGPointFromMMPointInt32(location());
@ -202,6 +224,11 @@ void doubleClick(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);
CGEventPost(kCGHIDEventTap, event);
@ -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)

View file

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