Skip to content

Commit

Permalink
Bugfix/120/macos doubleclick fix (#122)
Browse files Browse the repository at this point in the history
* (#120) Only set kCGMouseEventClickState = 2 on second click to fire a single doubleclick event after two clicks

* (#120) Extend toggleMouse such that it manually handles double clicks by tracking intervals between different click events

* (#120) Added explanatory comment on custom doubleclick implementation
  • Loading branch information
s1hofmann authored May 13, 2022
1 parent b21b07c commit b864bc5
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 139 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ set(SOURCE_FILES "src/main.cc" "src/deadbeef_rand.c" "src/MMBitmap.c")
if (UNIX AND NOT APPLE)
set(SOURCE_FILES "${SOURCE_FILES}" "src/linux/keycode.c" "src/linux/keypress.c" "src/linux/mouse.c" "src/linux/screen.c" "src/linux/screengrab.c" "src/linux/xdisplay.c" "src/linux/highlightwindow.c" "src/linux/window_manager.cc")
elseif (UNIX AND APPLE)
set(SOURCE_FILES "${SOURCE_FILES}" "src/macos/keycode.c" "src/macos/keypress.c" "src/macos/mouse.c" "src/macos/screen.c" "src/macos/screengrab.c" "src/macos/highlightwindow.m" "src/macos/window_manager.mm")
set(SOURCE_FILES "${SOURCE_FILES}" "src/macos/keycode.c" "src/macos/keypress.c" "src/macos/mouse.c" "src/macos/mouse_utils.mm" "src/macos/screen.c" "src/macos/screengrab.c" "src/macos/highlightwindow.m" "src/macos/window_manager.mm")
elseif (WIN32)
set(SOURCE_FILES "${SOURCE_FILES}" "src/win32/keycode.c" "src/win32/keypress.c" "src/win32/mouse.c" "src/win32/screen.c" "src/win32/screengrab.c" "src/win32/highlightwindow.c" "src/win32/window_manager.cc")
endif()
Expand Down
271 changes: 133 additions & 138 deletions src/macos/mouse.c
Original file line number Diff line number Diff line change
@@ -1,188 +1,183 @@
#include "../mouse.h"
#include "../deadbeef_rand.h"
#include "../microsleep.h"
#include "mouse_utils.h"

#include <math.h> /* For floor() */
#include <ApplicationServices/ApplicationServices.h>
#include <math.h> /* For floor() */

#if !defined(M_SQRT2)
#define M_SQRT2 1.4142135623730950488016887 /* Fix for MSVC. */
#endif
static int32_t DEFAULT_DOUBLE_CLICK_INTERVAL_MS = 200;

#define MMMouseToCGEventType(down, button) \
(down ? MMMouseDownToCGEventType(button) : MMMouseUpToCGEventType(button))
#define MMMouseToCGEventType(down, button) \
(down ? MMMouseDownToCGEventType(button) : MMMouseUpToCGEventType(button))

#define MMMouseDownToCGEventType(button) \
((button) == (LEFT_BUTTON) ? kCGEventLeftMouseDown \
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDown \
: kCGEventOtherMouseDown))
#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 MMMouseUpToCGEventType(button) \
((button) == LEFT_BUTTON \
? kCGEventLeftMouseUp \
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseUp \
: kCGEventOtherMouseUp))

#define MMMouseDragToCGEventType(button) \
((button) == LEFT_BUTTON ? kCGEventLeftMouseDragged \
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDragged \
: kCGEventOtherMouseDragged))
#define MMMouseDragToCGEventType(button) \
((button) == LEFT_BUTTON \
? kCGEventLeftMouseDragged \
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDragged \
: kCGEventOtherMouseDragged))

/**
* 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/octalmage/robotjs/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);
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/octalmage/robotjs/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);
}

/**
* Move the mouse to a specific point.
* @param point The coordinates to move the mouse to (x, y).
*/
void moveMouse(MMPoint point)
{
CGPoint position = CGPointMake(point.x, point.y);
// Create an HID hardware event source
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

CGEventRef evt = NULL;
if (CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonLeft))
{
// Create a left button drag
evt = CGEventCreateMouseEvent(src, kCGEventLeftMouseDragged, position, kCGMouseButtonLeft);
}
else
{
if (CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight))
{
// Create a right button drag
evt = CGEventCreateMouseEvent(src, kCGEventRightMouseDragged, position, kCGMouseButtonLeft);
}
else
{
// Create a mouse move event
evt = CGEventCreateMouseEvent(src, kCGEventMouseMoved, position, kCGMouseButtonLeft);
}
}

// Post mouse event and release
CGEventPost(kCGHIDEventTap, evt);
if (evt != NULL)
{
CFRelease(evt);
void moveMouse(MMPoint point) {
CGPoint position = CGPointMake(point.x, point.y);
// Create an HID hardware event source
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);

CGEventRef evt = NULL;
if (CGEventSourceButtonState(kCGEventSourceStateHIDSystemState,
kCGMouseButtonLeft)) {
// Create a left button drag
evt = CGEventCreateMouseEvent(src, kCGEventLeftMouseDragged, position,
kCGMouseButtonLeft);
} else {
if (CGEventSourceButtonState(kCGEventSourceStateHIDSystemState,
kCGMouseButtonRight)) {
// Create a right button drag
evt = CGEventCreateMouseEvent(src, kCGEventRightMouseDragged, position,
kCGMouseButtonLeft);
} else {
// Create a mouse move event
evt = CGEventCreateMouseEvent(src, kCGEventMouseMoved, position,
kCGMouseButtonLeft);
}
CFRelease(src);
}

// Post mouse event and release
CGEventPost(kCGHIDEventTap, evt);
if (evt != NULL) {
CFRelease(evt);
}
CFRelease(src);
}

void dragMouse(MMPoint point, const MMMouseButton button)
{
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
const CGEventType dragType = MMMouseDragToCGEventType(button);
CGEventRef drag = CGEventCreateMouseEvent(src, dragType, CGPointFromMMPoint(point), (CGMouseButton)button);
calculateDeltas(&drag, point);
void dragMouse(MMPoint point, const MMMouseButton button) {
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
const CGEventType dragType = MMMouseDragToCGEventType(button);
CGEventRef drag = CGEventCreateMouseEvent(
src, dragType, CGPointFromMMPoint(point), (CGMouseButton)button);
calculateDeltas(&drag, point);

CGEventPost(kCGHIDEventTap, drag);
CFRelease(drag);
CFRelease(src);
CGEventPost(kCGHIDEventTap, drag);
CFRelease(drag);
CFRelease(src);
}

MMPoint getMousePos()
{
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreate(src);
CGPoint point = CGEventGetLocation(event);
CFRelease(event);
CFRelease(src);
MMPoint getMousePos() {
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreate(src);
CGPoint point = CGEventGetLocation(event);
CFRelease(event);
CFRelease(src);

return MMPointFromCGPoint(point);
return MMPointFromCGPoint(point);
}

/**
* Press down a button, or release it.
* @param down True for down, false for up.
* @param button The button to press down or release.
*
* This function ships a manual implementation to handle double clicks by tracking the time interval between mouse events.
* Reason for this is the fact that https://developer.apple.com/documentation/coregraphics/1408790-cgeventsourcesecondssincelasteve?language=objc
* has a bit of latency and will stop working correctly if the time between two consecutive clicks is not long enough.
*
* This implementation captures the current timestamp for up/down events on each of left/middle/right mouse buttons.
* If the interval between two clicks is lower than https://developer.apple.com/documentation/appkit/nsevent/1528384-doubleclickinterval?language=objc
* and both clicks happen at the same position, we alter the mouse event to trigger a double click by setting kCGMouseEventClickState = 2 on the event
*/
void toggleMouse(bool down, MMMouseButton button)
{
const CGPoint currentPos = CGPointFromMMPoint(getMousePos());
const CGEventType mouseType = MMMouseToCGEventType(down, button);
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateMouseEvent(src, mouseType, currentPos, (CGMouseButton)button);
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
CFRelease(src);
void toggleMouse(bool down, MMMouseButton button) {
static ClickTimer clickTimer = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};

MMPoint currentMMPoint= getMousePos();

clock_t intervalSinceLastClick = timeSinceLastClick(&clickTimer, down, button, clock());

const CGPoint currentPos = CGPointFromMMPoint(currentMMPoint);
const CGEventType mouseType = MMMouseToCGEventType(down, button);
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateMouseEvent(src, mouseType, currentPos,
(CGMouseButton)button);
double maxInterval = GetDoubleClickTime();
if (intervalSinceLastClick > 0 && intervalSinceLastClick <= maxInterval &&
areSamePoint(currentMMPoint, clickTimer.clickLocation)) {
CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2);
}
CGEventPost(kCGHIDEventTap, event);
CFRelease(event);
CFRelease(src);
recordClickTime(&clickTimer, down, button, currentMMPoint);
}

void clickMouse(MMMouseButton button)
{
toggleMouse(true, button);
toggleMouse(false, button);
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) {
/* Double click for Mac. */
const CGPoint currentPos = CGPointFromMMPoint(getMousePos());
const CGEventType mouseTypeDown = MMMouseToCGEventType(true, button);
const CGEventType mouseTypeUp = MMMouseToCGEventType(false, button);

CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
CGEventRef event = CGEventCreateMouseEvent(src, mouseTypeDown, currentPos,
button);

// First down
CGEventPost(kCGHIDEventTap, event);

// First up
CGEventSetType(event, mouseTypeUp);
CGEventPost(kCGHIDEventTap, event);

/* Set event to double click. */
CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2);

// Second down
CGEventSetType(event, mouseTypeDown);
CGEventPost(kCGHIDEventTap, event);

// Second up
CGEventSetType(event, mouseTypeUp);
CGEventPost(kCGHIDEventTap, event);

CFRelease(event);
CFRelease(src);
double maxDoubleClickTime = GetDoubleClickTime();
clickMouse(button);
if (maxDoubleClickTime > DEFAULT_DOUBLE_CLICK_INTERVAL_MS) {
microsleep(DEFAULT_DOUBLE_CLICK_INTERVAL_MS);
} else {
microsleep(DEADBEEF_RANDRANGE(1, maxDoubleClickTime));
}
clickMouse(button);
}

void scrollMouse(int x, int y)
{
/*
* Direction should only be considered based on the scrollDirection.
* This should not interfere.
* Set up the OS specific solution
*/
void scrollMouse(int x, int y) {
/*
* Direction should only be considered based on the scrollDirection.
* This should not interfere.
* Set up the OS specific solution
*/

CGEventRef event;
CGEventRef event;

event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 2, y, x);
CGEventPost(kCGHIDEventTap, event);
event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 2, y, x);
CGEventPost(kCGHIDEventTap, event);

CFRelease(event);
CFRelease(event);
}
Loading

0 comments on commit b864bc5

Please sign in to comment.