Skip to content

Latest commit

 

History

History
286 lines (240 loc) · 9.61 KB

SDL_cocoamousetap.m

File metadata and controls

286 lines (240 loc) · 9.61 KB
 
1
2
/*
Simple DirectMedia Layer
Jan 3, 2018
Jan 3, 2018
3
Copyright (C) 1997-2018 Sam Lantinga <slouken@libsdl.org>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "../../SDL_internal.h"
#if SDL_VIDEO_DRIVER_COCOA
#include "SDL_cocoamousetap.h"
/* Event taps are forbidden in the Mac App Store, so we can only enable this
* code if your app doesn't need to ship through the app store.
* This code makes it so that a grabbed cursor cannot "leak" a mouse click
* past the edge of the window if moving the cursor too fast.
*/
#if SDL_MAC_NO_SANDBOX
#include "SDL_keyboard.h"
#include "SDL_cocoavideo.h"
Apr 12, 2016
Apr 12, 2016
36
#include "../../thread/SDL_systhread.h"
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include "../../events/SDL_mouse_c.h"
typedef struct {
CFMachPortRef tap;
CFRunLoopRef runloop;
CFRunLoopSourceRef runloopSource;
SDL_Thread *thread;
SDL_sem *runloopStartedSemaphore;
} SDL_MouseEventTapData;
static const CGEventMask movementEventsMask =
CGEventMaskBit(kCGEventLeftMouseDragged)
| CGEventMaskBit(kCGEventRightMouseDragged)
| CGEventMaskBit(kCGEventMouseMoved);
static const CGEventMask allGrabbedEventsMask =
CGEventMaskBit(kCGEventLeftMouseDown) | CGEventMaskBit(kCGEventLeftMouseUp)
| CGEventMaskBit(kCGEventRightMouseDown) | CGEventMaskBit(kCGEventRightMouseUp)
| CGEventMaskBit(kCGEventOtherMouseDown) | CGEventMaskBit(kCGEventOtherMouseUp)
| CGEventMaskBit(kCGEventLeftMouseDragged) | CGEventMaskBit(kCGEventRightMouseDragged)
| CGEventMaskBit(kCGEventMouseMoved);
static CGEventRef
Cocoa_MouseTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
{
SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)refcon;
SDL_Mouse *mouse = SDL_GetMouse();
SDL_Window *window = SDL_GetKeyboardFocus();
NSWindow *nswindow;
NSRect windowRect;
CGPoint eventLocation;
switch (type) {
case kCGEventTapDisabledByTimeout:
{
CGEventTapEnable(tapdata->tap, true);
return NULL;
}
Nov 26, 2016
Nov 26, 2016
76
case kCGEventTapDisabledByUserInput:
Nov 26, 2016
Nov 26, 2016
77
78
79
{
return NULL;
}
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
default:
break;
}
if (!window || !mouse) {
return event;
}
if (mouse->relative_mode) {
return event;
}
if (!(window->flags & SDL_WINDOW_INPUT_GRABBED)) {
return event;
}
/* This is the same coordinate system as Cocoa uses. */
nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
eventLocation = CGEventGetUnflippedLocation(event);
windowRect = [nswindow contentRectForFrameRect:[nswindow frame]];
May 2, 2016
May 2, 2016
102
if (!NSMouseInRect(NSPointFromCGPoint(eventLocation), windowRect, NO)) {
103
104
105
106
107
108
109
110
111
112
113
114
/* This is in CGs global screenspace coordinate system, which has a
* flipped Y.
*/
CGPoint newLocation = CGEventGetLocation(event);
if (eventLocation.x < NSMinX(windowRect)) {
newLocation.x = NSMinX(windowRect);
} else if (eventLocation.x >= NSMaxX(windowRect)) {
newLocation.x = NSMaxX(windowRect) - 1.0;
}
May 2, 2016
May 2, 2016
115
if (eventLocation.y <= NSMinY(windowRect)) {
116
newLocation.y -= (NSMinY(windowRect) - eventLocation.y + 1);
May 2, 2016
May 2, 2016
117
118
} else if (eventLocation.y > NSMaxY(windowRect)) {
newLocation.y += (eventLocation.y - NSMaxY(windowRect));
119
120
121
}
CGWarpMouseCursorPosition(newLocation);
Mar 20, 2016
Mar 20, 2016
122
CGAssociateMouseAndMouseCursorPosition(YES);
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
if ((CGEventMaskBit(type) & movementEventsMask) == 0) {
/* For click events, we just constrain the event to the window, so
* no other app receives the click event. We can't due the same to
* movement events, since they mean that our warp cursor above
* behaves strangely.
*/
CGEventSetLocation(event, newLocation);
}
}
return event;
}
static void
SemaphorePostCallback(CFRunLoopTimerRef timer, void *info)
{
SDL_SemPost((SDL_sem*)info);
}
static int
Cocoa_MouseTapThread(void *data)
{
SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)data;
Nov 26, 2016
Nov 26, 2016
148
149
/* Tap was created on main thread but we own it now. */
CFMachPortRef eventTap = tapdata->tap;
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
if (eventTap) {
/* Try to create a runloop source we can schedule. */
CFRunLoopSourceRef runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
if (runloopSource) {
tapdata->runloopSource = runloopSource;
} else {
CFRelease(eventTap);
SDL_SemPost(tapdata->runloopStartedSemaphore);
/* TODO: Both here and in the return below, set some state in
* tapdata to indicate that initialization failed, which we should
* check in InitMouseEventTap, after we move the semaphore check
* from Quit to Init.
*/
return 1;
}
} else {
SDL_SemPost(tapdata->runloopStartedSemaphore);
return 1;
}
tapdata->runloop = CFRunLoopGetCurrent();
CFRunLoopAddSource(tapdata->runloop, tapdata->runloopSource, kCFRunLoopCommonModes);
CFRunLoopTimerContext context = {.info = tapdata->runloopStartedSemaphore};
/* We signal the runloop started semaphore *after* the run loop has started, indicating it's safe to CFRunLoopStop it. */
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, &SemaphorePostCallback, &context);
CFRunLoopAddTimer(tapdata->runloop, timer, kCFRunLoopCommonModes);
CFRelease(timer);
/* Run the event loop to handle events in the event tap. */
CFRunLoopRun();
/* Make sure this is signaled so that SDL_QuitMouseEventTap knows it can safely SDL_WaitThread for us. */
if (SDL_SemValue(tapdata->runloopStartedSemaphore) < 1) {
SDL_SemPost(tapdata->runloopStartedSemaphore);
}
CFRunLoopRemoveSource(tapdata->runloop, tapdata->runloopSource, kCFRunLoopCommonModes);
/* Clean up. */
CGEventTapEnable(tapdata->tap, false);
CFRelease(tapdata->runloopSource);
CFRelease(tapdata->tap);
tapdata->runloopSource = NULL;
tapdata->tap = NULL;
return 0;
}
void
Cocoa_InitMouseEventTap(SDL_MouseData* driverdata)
{
SDL_MouseEventTapData *tapdata;
driverdata->tapdata = SDL_calloc(1, sizeof(SDL_MouseEventTapData));
tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
tapdata->runloopStartedSemaphore = SDL_CreateSemaphore(0);
if (tapdata->runloopStartedSemaphore) {
Nov 26, 2016
Nov 26, 2016
205
206
207
208
tapdata->tap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
kCGEventTapOptionDefault, allGrabbedEventsMask,
&Cocoa_MouseTapCallback, tapdata);
if (tapdata->tap) {
Nov 26, 2016
Nov 26, 2016
209
210
/* Tap starts disabled, until app requests mouse grab */
CGEventTapEnable(tapdata->tap, false);
Nov 26, 2016
Nov 26, 2016
211
212
213
tapdata->thread = SDL_CreateThreadInternal(&Cocoa_MouseTapThread, "Event Tap Loop", 512 * 1024, tapdata);
if (tapdata->thread) {
/* Success - early out. Ownership transferred to thread. */
Sep 24, 2018
Sep 24, 2018
214
return;
Nov 26, 2016
Nov 26, 2016
215
216
}
CFRelease(tapdata->tap);
Nov 26, 2016
Nov 26, 2016
218
SDL_DestroySemaphore(tapdata->runloopStartedSemaphore);
Nov 26, 2016
Nov 26, 2016
220
221
222
SDL_free(driverdata->tapdata);
driverdata->tapdata = NULL;
}
Nov 26, 2016
Nov 26, 2016
224
225
226
227
228
229
void
Cocoa_EnableMouseEventTap(SDL_MouseData *driverdata, SDL_bool enabled)
{
SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
if (tapdata && tapdata->tap)
{
Nov 26, 2016
Nov 26, 2016
230
CGEventTapEnable(tapdata->tap, !!enabled);
231
232
233
234
235
236
237
238
239
}
}
void
Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
{
SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
int status;
Sep 30, 2018
Sep 30, 2018
240
241
242
243
244
245
246
if (tapdata == NULL) {
/* event tap was already cleaned up (possibly due to CGEventTapCreate
* returning null.)
*/
return;
}
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
/* Ensure that the runloop has been started first.
* TODO: Move this to InitMouseEventTap, check for error conditions that can
* happen in Cocoa_MouseTapThread, and fall back to the non-EventTap way of
* grabbing the mouse if it fails to Init.
*/
status = SDL_SemWaitTimeout(tapdata->runloopStartedSemaphore, 5000);
if (status > -1) {
/* Then stop it, which will cause Cocoa_MouseTapThread to return. */
CFRunLoopStop(tapdata->runloop);
/* And then wait for Cocoa_MouseTapThread to finish cleaning up. It
* releases some of the pointers in tapdata. */
SDL_WaitThread(tapdata->thread, &status);
}
SDL_free(driverdata->tapdata);
driverdata->tapdata = NULL;
}
#else /* SDL_MAC_NO_SANDBOX */
void
Cocoa_InitMouseEventTap(SDL_MouseData *unused)
{
}
Nov 26, 2016
Nov 26, 2016
272
273
274
275
276
void
Cocoa_EnableMouseEventTap(SDL_MouseData *driverdata, SDL_bool enabled)
{
}
277
278
279
280
281
282
283
284
285
286
void
Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
{
}
#endif /* !SDL_MAC_NO_SANDBOX */
#endif /* SDL_VIDEO_DRIVER_COCOA */
/* vi: set ts=4 sw=4 expandtab: */