robotgo/mouse/mouse_c.h
2018-02-25 15:43:29 +08:00

428 lines
13 KiB
C

#include "mouse.h"
// #include "../screen/screen.h"
// #include "../screen/screen_c.h"
#include "../base/deadbeef_rand_c.h"
// #include "../deadbeef_rand.h"
#include "../base/microsleep.h"
#include <math.h> /* For floor() */
#if defined(IS_MACOSX)
// #include </System/Library/Frameworks/ApplicationServices.framework/Headers/ApplicationServices.h>
#include <ApplicationServices/ApplicationServices.h>
// #include </System/Library/Frameworks/ApplicationServices.framework/Versions/A/Headers/ApplicationServices.h>
#elif defined(USE_X11)
#include <X11/Xlib.h>
#include <X11/extensions/XTest.h>
#include <stdlib.h>
// #include "../base/xdisplay_c.h"
#endif
#if !defined(M_SQRT2)
#define M_SQRT2 1.4142135623730950488016887 /* Fix for MSVC. */
#endif
/* Some convenience macros for converting our enums to the system API types. */
#if defined(IS_MACOSX)
#define MMMouseToCGEventType(down, button) \
(down ? MMMouseDownToCGEventType(button) : MMMouseUpToCGEventType(button))
#define MMMouseDownToCGEventType(button) \
((button) == (LEFT_BUTTON) ? kCGEventLeftMouseDown \
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDown \
: kCGEventOtherMouseDown))
#define MMMouseUpToCGEventType(button) \
((button) == LEFT_BUTTON ? kCGEventLeftMouseUp \
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseUp \
: kCGEventOtherMouseUp))
#define MMMouseDragToCGEventType(button) \
((button) == LEFT_BUTTON ? kCGEventLeftMouseDragged \
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDragged \
: kCGEventOtherMouseDragged))
#elif defined(IS_WINDOWS)
#define MMMouseToMEventF(down, button) \
(down ? MMMouseDownToMEventF(button) : MMMouseUpToMEventF(button))
#define MMMouseUpToMEventF(button) \
((button) == LEFT_BUTTON ? MOUSEEVENTF_LEFTUP \
: ((button) == RIGHT_BUTTON ? MOUSEEVENTF_RIGHTUP \
: MOUSEEVENTF_MIDDLEUP))
#define MMMouseDownToMEventF(button) \
((button) == LEFT_BUTTON ? MOUSEEVENTF_LEFTDOWN \
: ((button) == RIGHT_BUTTON ? MOUSEEVENTF_RIGHTDOWN \
: MOUSEEVENTF_MIDDLEDOWN))
#endif
#if defined(IS_MACOSX)
/**
* Calculate the delta for a mouse move and add them to the event.
* @param event The mouse move event (by ref).
* @param point The new mouse x and y.
*/
void calculateDeltas(CGEventRef *event, MMPoint point){
/**
* The next few lines are a workaround for games not detecting mouse moves.
* See this issue for more information:
* https://github.com/go-vgo/robotgo/issues/159
*/
CGEventRef get = CGEventCreate(NULL);
CGPoint mouse = CGEventGetLocation(get);
// Calculate the deltas.
int64_t deltaX = point.x - mouse.x;
int64_t deltaY = point.y - mouse.y;
CGEventSetIntegerValueField(*event, kCGMouseEventDeltaX, deltaX);
CGEventSetIntegerValueField(*event, kCGMouseEventDeltaY, deltaY);
CFRelease(get);
}
#endif
/**
* Move the mouse to a specific point.
* @param point The coordinates to move the mouse to (x, y).
*/
void moveMouse(MMPoint point){
#if defined(IS_MACOSX)
CGEventRef move = CGEventCreateMouseEvent(NULL, kCGEventMouseMoved,
CGPointFromMMPoint(point),
kCGMouseButtonLeft);
calculateDeltas(&move, point);
CGEventPost(kCGSessionEventTap, move);
CFRelease(move);
#elif defined(USE_X11)
Display *display = XGetMainDisplay();
XWarpPointer(display, None, DefaultRootWindow(display),
0, 0, 0, 0, point.x, point.y);
XFlush(display);
#elif defined(IS_WINDOWS)
//Mouse motion is now done using SendInput with MOUSEINPUT. We use Absolute mouse positioning
#define MOUSE_COORD_TO_ABS(coord, width_or_height) (((65536 * coord) / width_or_height) + (coord < 0 ? -1 : 1))
point.x = MOUSE_COORD_TO_ABS(point.x, GetSystemMetrics(SM_CXSCREEN));
point.y = MOUSE_COORD_TO_ABS(point.y, GetSystemMetrics(SM_CYSCREEN));
INPUT mouseInput;
mouseInput.type = INPUT_MOUSE;
mouseInput.mi.dx = point.x;
mouseInput.mi.dy = point.y;
mouseInput.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
mouseInput.mi.time = 0; //System will provide the timestamp
mouseInput.mi.dwExtraInfo = 0;
mouseInput.mi.mouseData = 0;
SendInput(1, &mouseInput, sizeof(mouseInput));
#endif
}
void dragMouse(MMPoint point, const MMMouseButton button){
#if defined(IS_MACOSX)
const CGEventType dragType = MMMouseDragToCGEventType(button);
CGEventRef drag = CGEventCreateMouseEvent(NULL, dragType,
CGPointFromMMPoint(point),
(CGMouseButton)button);
calculateDeltas(&drag, point);
CGEventPost(kCGSessionEventTap, drag);
CFRelease(drag);
#else
moveMouse(point);
#endif
}
MMPoint getMousePos(){
#if defined(IS_MACOSX)
CGEventRef event = CGEventCreate(NULL);
CGPoint point = CGEventGetLocation(event);
CFRelease(event);
return MMPointFromCGPoint(point);
#elif defined(USE_X11)
int x, y; /* This is all we care about. Seriously. */
Window garb1, garb2; /* Why you can't specify NULL as a parameter */
int garb_x, garb_y; /* is beyond me. */
unsigned int more_garbage;
Display *display = XGetMainDisplay();
XQueryPointer(display, XDefaultRootWindow(display), &garb1, &garb2,
&x, &y, &garb_x, &garb_y, &more_garbage);
return MMPointMake(x, y);
#elif defined(IS_WINDOWS)
POINT point;
GetCursorPos(&point);
return MMPointFromPOINT(point);
#endif
}
/**
* Press down a button, or release it.
* @param down True for down, false for up.
* @param button The button to press down or release.
*/
void toggleMouse(bool down, MMMouseButton button){
#if defined(IS_MACOSX)
const CGPoint currentPos = CGPointFromMMPoint(getMousePos());
const CGEventType mouseType = MMMouseToCGEventType(down, button);
CGEventRef event = CGEventCreateMouseEvent(NULL,
mouseType,
currentPos,
(CGMouseButton)button);
CGEventPost(kCGSessionEventTap, event);
CFRelease(event);
#elif defined(USE_X11)
Display *display = XGetMainDisplay();
XTestFakeButtonEvent(display, button, down ? True : False, CurrentTime);
XFlush(display);
#elif defined(IS_WINDOWS)
mouse_event(MMMouseToMEventF(down, button), 0, 0, 0, 0);
#endif
}
void clickMouse(MMMouseButton button){
toggleMouse(true, button);
toggleMouse(false, button);
}
/**
* Special function for sending double clicks, needed for Mac OS X.
* @param button Button to click.
*/
void doubleClick(MMMouseButton button){
#if defined(IS_MACOSX)
/* Double click for Mac. */
const CGPoint currentPos = CGPointFromMMPoint(getMousePos());
const CGEventType mouseTypeDown = MMMouseToCGEventType(true, button);
const CGEventType mouseTypeUP = MMMouseToCGEventType(false, button);
CGEventRef event = CGEventCreateMouseEvent(NULL, mouseTypeDown, currentPos, kCGMouseButtonLeft);
/* Set event to double click. */
CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2);
CGEventPost(kCGHIDEventTap, event);
CGEventSetType(event, mouseTypeUP);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
#else
/* Double click for everything else. */
clickMouse(button);
microsleep(200);
clickMouse(button);
#endif
}
/**
* Function used to scroll the screen in the required direction.
* This uses the magnitude to scroll the required amount in the direction.
* TODO Requires further fine tuning based on the requirements.
*/
void scrollMouse(int scrollMagnitude, MMMouseWheelDirection scrollDirection){
#if defined(IS_WINDOWS)
// Fix for #97 https://github.com/go-vgo/robotgo/issues/97,
// C89 needs variables declared on top of functions (mouseScrollInput)
INPUT mouseScrollInput;
#endif
/* Direction should only be considered based on the scrollDirection. This
* Should not interfere. */
int cleanScrollMagnitude = abs(scrollMagnitude);
if (!(scrollDirection == DIRECTION_UP || scrollDirection == DIRECTION_DOWN)){
return;
}
/* Set up the OS specific solution */
#if defined(__APPLE__)
CGWheelCount wheel = 1;
CGEventRef event;
/* Make scroll magnitude negative if we're scrolling down. */
cleanScrollMagnitude = cleanScrollMagnitude * scrollDirection;
event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, wheel, cleanScrollMagnitude, 0);
CGEventPost(kCGHIDEventTap, event);
#elif defined(USE_X11)
int x;
int dir = 4; /* Button 4 is up, 5 is down. */
Display *display = XGetMainDisplay();
if (scrollDirection == DIRECTION_DOWN)
{
dir = 5;
}
for (x = 0; x < cleanScrollMagnitude; x++)
{
XTestFakeButtonEvent(display, dir, 1, CurrentTime);
XTestFakeButtonEvent(display, dir, 0, CurrentTime);
}
XFlush(display);
#elif defined(IS_WINDOWS)
mouseScrollInput.type = INPUT_MOUSE;
mouseScrollInput.mi.dx = 0;
mouseScrollInput.mi.dy = 0;
mouseScrollInput.mi.dwFlags = MOUSEEVENTF_WHEEL;
mouseScrollInput.mi.time = 0;
mouseScrollInput.mi.dwExtraInfo = 0;
mouseScrollInput.mi.mouseData = WHEEL_DELTA * scrollDirection * cleanScrollMagnitude;
SendInput(1, &mouseScrollInput, sizeof(mouseScrollInput));
#endif
}
void scrollMouseXY(int x, int y){
#if defined(IS_WINDOWS)
// Fix for #97,
// C89 needs variables declared on top of functions (mouseScrollInput)
INPUT mouseScrollInputH;
INPUT mouseScrollInputV;
#endif
/* Direction should only be considered based on the scrollDirection. This
* Should not interfere. */
/* Set up the OS specific solution */
#if defined(__APPLE__)
CGEventRef event;
event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 2, y, x);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
#elif defined(USE_X11)
int ydir = 4; /* Button 4 is up, 5 is down. */
int xdir = 6;
Display *display = XGetMainDisplay();
if (y < 0){
ydir = 5;
}
if (x < 0){
xdir = 7;
}
int xi;
int yi;
for (xi = 0; xi < abs(x); xi++) {
XTestFakeButtonEvent(display, xdir, 1, CurrentTime);
XTestFakeButtonEvent(display, xdir, 0, CurrentTime);
}
for (yi = 0; yi < abs(y); yi++) {
XTestFakeButtonEvent(display, ydir, 1, CurrentTime);
XTestFakeButtonEvent(display, ydir, 0, CurrentTime);
}
XFlush(display);
#elif defined(IS_WINDOWS)
mouseScrollInputH.type = INPUT_MOUSE;
mouseScrollInputH.mi.dx = 0;
mouseScrollInputH.mi.dy = 0;
mouseScrollInputH.mi.dwFlags = MOUSEEVENTF_WHEEL;
mouseScrollInputH.mi.time = 0;
mouseScrollInputH.mi.dwExtraInfo = 0;
mouseScrollInputH.mi.mouseData = WHEEL_DELTA * x;
mouseScrollInputV.type = INPUT_MOUSE;
mouseScrollInputV.mi.dx = 0;
mouseScrollInputV.mi.dy = 0;
mouseScrollInputV.mi.dwFlags = MOUSEEVENTF_WHEEL;
mouseScrollInputV.mi.time = 0;
mouseScrollInputV.mi.dwExtraInfo = 0;
mouseScrollInputV.mi.mouseData = WHEEL_DELTA * y;
SendInput(1, &mouseScrollInputH, sizeof(mouseScrollInputH));
SendInput(1, &mouseScrollInputV, sizeof(mouseScrollInputV));
#endif
}
/*
* A crude, fast hypot() approximation to get around the fact that hypot() is
* not a standard ANSI C function.
*
* It is not particularly accurate but that does not matter for our use case.
*
* Taken from this StackOverflow answer:
* http://stackoverflow.com/questions/3506404/fast-hypotenuse-algorithm-for-embedded-processor#3507882
*
*/
static double crude_hypot(double x, double y){
double big = fabs(x); /* max(|x|, |y|) */
double small = fabs(y); /* min(|x|, |y|) */
if (big > small) {
double temp = big;
big = small;
small = temp;
}
return ((M_SQRT2 - 1.0) * small) + big;
}
bool smoothlyMoveMouse(MMPoint endPoint, double lowSpeed, double highSpeed){
MMPoint pos = getMousePos();
MMSize screenSize = getMainDisplaySize();
double velo_x = 0.0, velo_y = 0.0;
double distance;
while ((distance =
crude_hypot((double)pos.x - endPoint.x, (double)pos.y - endPoint.y)
) > 1.0) {
double gravity = DEADBEEF_UNIFORM(5.0, 500.0);
// double gravity = DEADBEEF_UNIFORM(lowSpeed, highSpeed);
double veloDistance;
velo_x += (gravity * ((double)endPoint.x - pos.x)) / distance;
velo_y += (gravity * ((double)endPoint.y - pos.y)) / distance;
/* Normalize velocity to get a unit vector of length 1. */
veloDistance = crude_hypot(velo_x, velo_y);
velo_x /= veloDistance;
velo_y /= veloDistance;
pos.x += floor(velo_x + 0.5);
pos.y += floor(velo_y + 0.5);
/* Make sure we are in the screen boundaries!
* (Strange things will happen if we are not.) */
if (pos.x >= screenSize.width || pos.y >= screenSize.height) {
return false;
}
moveMouse(pos);
/* Wait 1 - 3 milliseconds. */
microsleep(DEADBEEF_UNIFORM(lowSpeed, highSpeed));
// microsleep(DEADBEEF_UNIFORM(1.0, 3.0));
}
return true;
}