SDL  2.0
SDL_cocoamousetap.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2016 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_thread.h"
36 #include "SDL_cocoavideo.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  case kCGEventTapDisabledByUserInput:
73  {
74  CGEventTapEnable(tapdata->tap, true);
75  return NULL;
76  }
77  default:
78  break;
79  }
80 
81 
82  if (!window || !mouse) {
83  return event;
84  }
85 
86  if (mouse->relative_mode) {
87  return event;
88  }
89 
90  if (!(window->flags & SDL_WINDOW_INPUT_GRABBED)) {
91  return event;
92  }
93 
94  /* This is the same coordinate system as Cocoa uses. */
95  nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
96  eventLocation = CGEventGetUnflippedLocation(event);
97  windowRect = [nswindow contentRectForFrameRect:[nswindow frame]];
98 
99  if (!NSPointInRect(NSPointFromCGPoint(eventLocation), windowRect)) {
100 
101  /* This is in CGs global screenspace coordinate system, which has a
102  * flipped Y.
103  */
104  CGPoint newLocation = CGEventGetLocation(event);
105 
106  if (eventLocation.x < NSMinX(windowRect)) {
107  newLocation.x = NSMinX(windowRect);
108  } else if (eventLocation.x >= NSMaxX(windowRect)) {
109  newLocation.x = NSMaxX(windowRect) - 1.0;
110  }
111 
112  if (eventLocation.y < NSMinY(windowRect)) {
113  newLocation.y -= (NSMinY(windowRect) - eventLocation.y + 1);
114  } else if (eventLocation.y >= NSMaxY(windowRect)) {
115  newLocation.y += (eventLocation.y - NSMaxY(windowRect) + 1);
116  }
117 
118  CGSetLocalEventsSuppressionInterval(0);
119  CGWarpMouseCursorPosition(newLocation);
120  CGSetLocalEventsSuppressionInterval(0.25);
121 
122  if ((CGEventMaskBit(type) & movementEventsMask) == 0) {
123  /* For click events, we just constrain the event to the window, so
124  * no other app receives the click event. We can't due the same to
125  * movement events, since they mean that our warp cursor above
126  * behaves strangely.
127  */
128  CGEventSetLocation(event, newLocation);
129  }
130  }
131 
132  return event;
133 }
134 
135 static void
136 SemaphorePostCallback(CFRunLoopTimerRef timer, void *info)
137 {
138  SDL_SemPost((SDL_sem*)info);
139 }
140 
141 static int
142 Cocoa_MouseTapThread(void *data)
143 {
144  SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)data;
145 
146  /* Create a tap. */
147  CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
148  kCGEventTapOptionDefault, allGrabbedEventsMask,
149  &Cocoa_MouseTapCallback, tapdata);
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->tap = eventTap;
155  tapdata->runloopSource = runloopSource;
156  } else {
157  CFRelease(eventTap);
158  SDL_SemPost(tapdata->runloopStartedSemaphore);
159  /* TODO: Both here and in the return below, set some state in
160  * tapdata to indicate that initialization failed, which we should
161  * check in InitMouseEventTap, after we move the semaphore check
162  * from Quit to Init.
163  */
164  return 1;
165  }
166  } else {
167  SDL_SemPost(tapdata->runloopStartedSemaphore);
168  return 1;
169  }
170 
171  tapdata->runloop = CFRunLoopGetCurrent();
172  CFRunLoopAddSource(tapdata->runloop, tapdata->runloopSource, kCFRunLoopCommonModes);
173  CFRunLoopTimerContext context = {.info = tapdata->runloopStartedSemaphore};
174  /* We signal the runloop started semaphore *after* the run loop has started, indicating it's safe to CFRunLoopStop it. */
175  CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, &SemaphorePostCallback, &context);
176  CFRunLoopAddTimer(tapdata->runloop, timer, kCFRunLoopCommonModes);
177  CFRelease(timer);
178 
179  /* Run the event loop to handle events in the event tap. */
180  CFRunLoopRun();
181  /* Make sure this is signaled so that SDL_QuitMouseEventTap knows it can safely SDL_WaitThread for us. */
182  if (SDL_SemValue(tapdata->runloopStartedSemaphore) < 1) {
183  SDL_SemPost(tapdata->runloopStartedSemaphore);
184  }
185  CFRunLoopRemoveSource(tapdata->runloop, tapdata->runloopSource, kCFRunLoopCommonModes);
186 
187  /* Clean up. */
188  CGEventTapEnable(tapdata->tap, false);
189  CFRelease(tapdata->runloopSource);
190  CFRelease(tapdata->tap);
191  tapdata->runloopSource = NULL;
192  tapdata->tap = NULL;
193 
194  return 0;
195 }
196 
197 void
199 {
200  SDL_MouseEventTapData *tapdata;
201  driverdata->tapdata = SDL_calloc(1, sizeof(SDL_MouseEventTapData));
202  tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
203 
204  tapdata->runloopStartedSemaphore = SDL_CreateSemaphore(0);
205  if (tapdata->runloopStartedSemaphore) {
206  tapdata->thread = SDL_CreateThread(&Cocoa_MouseTapThread, "Event Tap Loop", tapdata);
207  if (!tapdata->thread) {
208  SDL_DestroySemaphore(tapdata->runloopStartedSemaphore);
209  }
210  }
211 
212  if (!tapdata->thread) {
213  SDL_free(driverdata->tapdata);
214  driverdata->tapdata = NULL;
215  }
216 }
217 
218 void
220 {
221  SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
222  int status;
223 
224  /* Ensure that the runloop has been started first.
225  * TODO: Move this to InitMouseEventTap, check for error conditions that can
226  * happen in Cocoa_MouseTapThread, and fall back to the non-EventTap way of
227  * grabbing the mouse if it fails to Init.
228  */
229  status = SDL_SemWaitTimeout(tapdata->runloopStartedSemaphore, 5000);
230  if (status > -1) {
231  /* Then stop it, which will cause Cocoa_MouseTapThread to return. */
232  CFRunLoopStop(tapdata->runloop);
233  /* And then wait for Cocoa_MouseTapThread to finish cleaning up. It
234  * releases some of the pointers in tapdata. */
235  SDL_WaitThread(tapdata->thread, &status);
236  }
237 
238  SDL_free(driverdata->tapdata);
239  driverdata->tapdata = NULL;
240 }
241 
242 #else /* SDL_MAC_NO_SANDBOX */
243 
244 void
246 {
247 }
248 
249 void
251 {
252 }
253 
254 #endif /* !SDL_MAC_NO_SANDBOX */
255 
256 #endif /* SDL_VIDEO_DRIVER_COCOA */
257 
258 /* vi: set ts=4 sw=4 expandtab: */
SDL_Mouse * SDL_GetMouse(void)
Definition: SDL_mouse.c:66
#define SDL_CreateSemaphore
SDL_Window * window
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1967
#define SDL_GetKeyboardFocus
#define SDL_SemPost
void * SDL_calloc(size_t nmemb, size_t size)
void Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
#define SDL_SemWaitTimeout
struct _cl_event * event
void SDL_free(void *mem)
SDL_bool relative_mode
Definition: SDL_mouse_c.h:84
#define SDL_CreateThread
#define NULL
Definition: begin_code.h:143
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1567
The type used to identify a window.
Definition: SDL_sysvideo.h:71
#define SDL_DestroySemaphore
void * driverdata
Definition: SDL_sysvideo.h:106
GLuint GLuint GLsizei GLenum type
Definition: SDL_opengl.h:1564
#define SDL_SemValue
Uint32 flags
Definition: SDL_sysvideo.h:81
void Cocoa_InitMouseEventTap(SDL_MouseData *driverdata)
#define SDL_WaitThread