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