Skip to content

Commit b864bc5

Browse files
authored
Bugfix/120/macos doubleclick fix (#122)
* (#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
1 parent b21b07c commit b864bc5

File tree

4 files changed

+224
-139
lines changed

4 files changed

+224
-139
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ set(SOURCE_FILES "src/main.cc" "src/deadbeef_rand.c" "src/MMBitmap.c")
88
if (UNIX AND NOT APPLE)
99
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")
1010
elseif (UNIX AND APPLE)
11-
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")
11+
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")
1212
elseif (WIN32)
1313
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")
1414
endif()

src/macos/mouse.c

Lines changed: 133 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -1,188 +1,183 @@
11
#include "../mouse.h"
2+
#include "../deadbeef_rand.h"
23
#include "../microsleep.h"
4+
#include "mouse_utils.h"
35

4-
#include <math.h> /* For floor() */
56
#include <ApplicationServices/ApplicationServices.h>
7+
#include <math.h> /* For floor() */
68

7-
#if !defined(M_SQRT2)
8-
#define M_SQRT2 1.4142135623730950488016887 /* Fix for MSVC. */
9-
#endif
9+
static int32_t DEFAULT_DOUBLE_CLICK_INTERVAL_MS = 200;
1010

11-
#define MMMouseToCGEventType(down, button) \
12-
(down ? MMMouseDownToCGEventType(button) : MMMouseUpToCGEventType(button))
11+
#define MMMouseToCGEventType(down, button) \
12+
(down ? MMMouseDownToCGEventType(button) : MMMouseUpToCGEventType(button))
1313

14-
#define MMMouseDownToCGEventType(button) \
15-
((button) == (LEFT_BUTTON) ? kCGEventLeftMouseDown \
16-
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDown \
17-
: kCGEventOtherMouseDown))
14+
#define MMMouseDownToCGEventType(button) \
15+
((button) == (LEFT_BUTTON) \
16+
? kCGEventLeftMouseDown \
17+
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDown \
18+
: kCGEventOtherMouseDown))
1819

19-
#define MMMouseUpToCGEventType(button) \
20-
((button) == LEFT_BUTTON ? kCGEventLeftMouseUp \
21-
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseUp \
22-
: kCGEventOtherMouseUp))
20+
#define MMMouseUpToCGEventType(button) \
21+
((button) == LEFT_BUTTON \
22+
? kCGEventLeftMouseUp \
23+
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseUp \
24+
: kCGEventOtherMouseUp))
2325

24-
#define MMMouseDragToCGEventType(button) \
25-
((button) == LEFT_BUTTON ? kCGEventLeftMouseDragged \
26-
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDragged \
27-
: kCGEventOtherMouseDragged))
26+
#define MMMouseDragToCGEventType(button) \
27+
((button) == LEFT_BUTTON \
28+
? kCGEventLeftMouseDragged \
29+
: ((button) == RIGHT_BUTTON ? kCGEventRightMouseDragged \
30+
: kCGEventOtherMouseDragged))
2831

2932
/**
3033
* Calculate the delta for a mouse move and add them to the event.
3134
* @param event The mouse move event (by ref).
3235
* @param point The new mouse x and y.
3336
*/
34-
void calculateDeltas(CGEventRef *event, MMPoint point)
35-
{
36-
/**
37-
* The next few lines are a workaround for games not detecting mouse moves.
38-
* See this issue for more information:
39-
* https://github.com/octalmage/robotjs/issues/159
40-
*/
41-
CGEventRef get = CGEventCreate(NULL);
42-
CGPoint mouse = CGEventGetLocation(get);
43-
44-
// Calculate the deltas.
45-
int64_t deltaX = point.x - mouse.x;
46-
int64_t deltaY = point.y - mouse.y;
47-
48-
CGEventSetIntegerValueField(*event, kCGMouseEventDeltaX, deltaX);
49-
CGEventSetIntegerValueField(*event, kCGMouseEventDeltaY, deltaY);
50-
51-
CFRelease(get);
37+
void calculateDeltas(CGEventRef *event, MMPoint point) {
38+
/**
39+
* The next few lines are a workaround for games not detecting mouse moves.
40+
* See this issue for more information:
41+
* https://github.com/octalmage/robotjs/issues/159
42+
*/
43+
CGEventRef get = CGEventCreate(NULL);
44+
CGPoint mouse = CGEventGetLocation(get);
45+
46+
// Calculate the deltas.
47+
int64_t deltaX = point.x - mouse.x;
48+
int64_t deltaY = point.y - mouse.y;
49+
50+
CGEventSetIntegerValueField(*event, kCGMouseEventDeltaX, deltaX);
51+
CGEventSetIntegerValueField(*event, kCGMouseEventDeltaY, deltaY);
52+
53+
CFRelease(get);
5254
}
5355

5456
/**
5557
* Move the mouse to a specific point.
5658
* @param point The coordinates to move the mouse to (x, y).
5759
*/
58-
void moveMouse(MMPoint point)
59-
{
60-
CGPoint position = CGPointMake(point.x, point.y);
61-
// Create an HID hardware event source
62-
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
63-
64-
CGEventRef evt = NULL;
65-
if (CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonLeft))
66-
{
67-
// Create a left button drag
68-
evt = CGEventCreateMouseEvent(src, kCGEventLeftMouseDragged, position, kCGMouseButtonLeft);
69-
}
70-
else
71-
{
72-
if (CGEventSourceButtonState(kCGEventSourceStateHIDSystemState, kCGMouseButtonRight))
73-
{
74-
// Create a right button drag
75-
evt = CGEventCreateMouseEvent(src, kCGEventRightMouseDragged, position, kCGMouseButtonLeft);
76-
}
77-
else
78-
{
79-
// Create a mouse move event
80-
evt = CGEventCreateMouseEvent(src, kCGEventMouseMoved, position, kCGMouseButtonLeft);
81-
}
82-
}
83-
84-
// Post mouse event and release
85-
CGEventPost(kCGHIDEventTap, evt);
86-
if (evt != NULL)
87-
{
88-
CFRelease(evt);
60+
void moveMouse(MMPoint point) {
61+
CGPoint position = CGPointMake(point.x, point.y);
62+
// Create an HID hardware event source
63+
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
64+
65+
CGEventRef evt = NULL;
66+
if (CGEventSourceButtonState(kCGEventSourceStateHIDSystemState,
67+
kCGMouseButtonLeft)) {
68+
// Create a left button drag
69+
evt = CGEventCreateMouseEvent(src, kCGEventLeftMouseDragged, position,
70+
kCGMouseButtonLeft);
71+
} else {
72+
if (CGEventSourceButtonState(kCGEventSourceStateHIDSystemState,
73+
kCGMouseButtonRight)) {
74+
// Create a right button drag
75+
evt = CGEventCreateMouseEvent(src, kCGEventRightMouseDragged, position,
76+
kCGMouseButtonLeft);
77+
} else {
78+
// Create a mouse move event
79+
evt = CGEventCreateMouseEvent(src, kCGEventMouseMoved, position,
80+
kCGMouseButtonLeft);
8981
}
90-
CFRelease(src);
82+
}
83+
84+
// Post mouse event and release
85+
CGEventPost(kCGHIDEventTap, evt);
86+
if (evt != NULL) {
87+
CFRelease(evt);
88+
}
89+
CFRelease(src);
9190
}
9291

93-
void dragMouse(MMPoint point, const MMMouseButton button)
94-
{
95-
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
96-
const CGEventType dragType = MMMouseDragToCGEventType(button);
97-
CGEventRef drag = CGEventCreateMouseEvent(src, dragType, CGPointFromMMPoint(point), (CGMouseButton)button);
98-
calculateDeltas(&drag, point);
92+
void dragMouse(MMPoint point, const MMMouseButton button) {
93+
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
94+
const CGEventType dragType = MMMouseDragToCGEventType(button);
95+
CGEventRef drag = CGEventCreateMouseEvent(
96+
src, dragType, CGPointFromMMPoint(point), (CGMouseButton)button);
97+
calculateDeltas(&drag, point);
9998

100-
CGEventPost(kCGHIDEventTap, drag);
101-
CFRelease(drag);
102-
CFRelease(src);
99+
CGEventPost(kCGHIDEventTap, drag);
100+
CFRelease(drag);
101+
CFRelease(src);
103102
}
104103

105-
MMPoint getMousePos()
106-
{
107-
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
108-
CGEventRef event = CGEventCreate(src);
109-
CGPoint point = CGEventGetLocation(event);
110-
CFRelease(event);
111-
CFRelease(src);
104+
MMPoint getMousePos() {
105+
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
106+
CGEventRef event = CGEventCreate(src);
107+
CGPoint point = CGEventGetLocation(event);
108+
CFRelease(event);
109+
CFRelease(src);
112110

113-
return MMPointFromCGPoint(point);
111+
return MMPointFromCGPoint(point);
114112
}
115113

116114
/**
117115
* Press down a button, or release it.
118116
* @param down True for down, false for up.
119117
* @param button The button to press down or release.
118+
*
119+
* This function ships a manual implementation to handle double clicks by tracking the time interval between mouse events.
120+
* Reason for this is the fact that https://developer.apple.com/documentation/coregraphics/1408790-cgeventsourcesecondssincelasteve?language=objc
121+
* has a bit of latency and will stop working correctly if the time between two consecutive clicks is not long enough.
122+
*
123+
* This implementation captures the current timestamp for up/down events on each of left/middle/right mouse buttons.
124+
* If the interval between two clicks is lower than https://developer.apple.com/documentation/appkit/nsevent/1528384-doubleclickinterval?language=objc
125+
* 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
120126
*/
121-
void toggleMouse(bool down, MMMouseButton button)
122-
{
123-
const CGPoint currentPos = CGPointFromMMPoint(getMousePos());
124-
const CGEventType mouseType = MMMouseToCGEventType(down, button);
125-
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
126-
CGEventRef event = CGEventCreateMouseEvent(src, mouseType, currentPos, (CGMouseButton)button);
127-
CGEventPost(kCGHIDEventTap, event);
128-
CFRelease(event);
129-
CFRelease(src);
127+
void toggleMouse(bool down, MMMouseButton button) {
128+
static ClickTimer clickTimer = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
129+
130+
MMPoint currentMMPoint= getMousePos();
131+
132+
clock_t intervalSinceLastClick = timeSinceLastClick(&clickTimer, down, button, clock());
133+
134+
const CGPoint currentPos = CGPointFromMMPoint(currentMMPoint);
135+
const CGEventType mouseType = MMMouseToCGEventType(down, button);
136+
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
137+
CGEventRef event = CGEventCreateMouseEvent(src, mouseType, currentPos,
138+
(CGMouseButton)button);
139+
double maxInterval = GetDoubleClickTime();
140+
if (intervalSinceLastClick > 0 && intervalSinceLastClick <= maxInterval &&
141+
areSamePoint(currentMMPoint, clickTimer.clickLocation)) {
142+
CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2);
143+
}
144+
CGEventPost(kCGHIDEventTap, event);
145+
CFRelease(event);
146+
CFRelease(src);
147+
recordClickTime(&clickTimer, down, button, currentMMPoint);
130148
}
131149

132-
void clickMouse(MMMouseButton button)
133-
{
134-
toggleMouse(true, button);
135-
toggleMouse(false, button);
150+
void clickMouse(MMMouseButton button) {
151+
toggleMouse(true, button);
152+
toggleMouse(false, button);
136153
}
137154

138155
/**
139156
* Special function for sending double clicks, needed for Mac OS X.
140157
* @param button Button to click.
141158
*/
142159
void doubleClick(MMMouseButton button) {
143-
/* Double click for Mac. */
144-
const CGPoint currentPos = CGPointFromMMPoint(getMousePos());
145-
const CGEventType mouseTypeDown = MMMouseToCGEventType(true, button);
146-
const CGEventType mouseTypeUp = MMMouseToCGEventType(false, button);
147-
148-
CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
149-
CGEventRef event = CGEventCreateMouseEvent(src, mouseTypeDown, currentPos,
150-
button);
151-
152-
// First down
153-
CGEventPost(kCGHIDEventTap, event);
154-
155-
// First up
156-
CGEventSetType(event, mouseTypeUp);
157-
CGEventPost(kCGHIDEventTap, event);
158-
159-
/* Set event to double click. */
160-
CGEventSetIntegerValueField(event, kCGMouseEventClickState, 2);
161-
162-
// Second down
163-
CGEventSetType(event, mouseTypeDown);
164-
CGEventPost(kCGHIDEventTap, event);
165-
166-
// Second up
167-
CGEventSetType(event, mouseTypeUp);
168-
CGEventPost(kCGHIDEventTap, event);
169-
170-
CFRelease(event);
171-
CFRelease(src);
160+
double maxDoubleClickTime = GetDoubleClickTime();
161+
clickMouse(button);
162+
if (maxDoubleClickTime > DEFAULT_DOUBLE_CLICK_INTERVAL_MS) {
163+
microsleep(DEFAULT_DOUBLE_CLICK_INTERVAL_MS);
164+
} else {
165+
microsleep(DEADBEEF_RANDRANGE(1, maxDoubleClickTime));
166+
}
167+
clickMouse(button);
172168
}
173169

174-
void scrollMouse(int x, int y)
175-
{
176-
/*
177-
* Direction should only be considered based on the scrollDirection.
178-
* This should not interfere.
179-
* Set up the OS specific solution
180-
*/
170+
void scrollMouse(int x, int y) {
171+
/*
172+
* Direction should only be considered based on the scrollDirection.
173+
* This should not interfere.
174+
* Set up the OS specific solution
175+
*/
181176

182-
CGEventRef event;
177+
CGEventRef event;
183178

184-
event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 2, y, x);
185-
CGEventPost(kCGHIDEventTap, event);
179+
event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 2, y, x);
180+
CGEventPost(kCGHIDEventTap, event);
186181

187-
CFRelease(event);
182+
CFRelease(event);
188183
}

0 commit comments

Comments
 (0)