zenilib  0.5.3.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
SDL_cocoamousetap.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2013 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_config.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;
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_MouseData *driverdata = (SDL_MouseData*)refcon;
64  SDL_Mouse *mouse = SDL_GetMouse();
65  SDL_Window *window = SDL_GetKeyboardFocus();
66  NSRect windowRect;
67  CGPoint eventLocation;
68 
69  switch (type)
70  {
71  case kCGEventTapDisabledByTimeout:
72  case kCGEventTapDisabledByUserInput:
73  {
74  CGEventTapEnable(((SDL_MouseEventTapData*)(driverdata->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  eventLocation = CGEventGetUnflippedLocation(event);
96  windowRect = [((SDL_WindowData *) window->driverdata)->nswindow frame];
97 
98  if (!NSPointInRect(NSPointFromCGPoint(eventLocation), windowRect)) {
99 
100  /* This is in CGs global screenspace coordinate system, which has a
101  * flipped Y.
102  */
103  CGPoint newLocation = CGEventGetLocation(event);
104 
105  if (eventLocation.x < NSMinX(windowRect)) {
106  newLocation.x = NSMinX(windowRect);
107  } else if (eventLocation.x >= NSMaxX(windowRect)) {
108  newLocation.x = NSMaxX(windowRect) - 1.0;
109  }
110 
111  if (eventLocation.y < NSMinY(windowRect)) {
112  newLocation.y -= (NSMinY(windowRect) - eventLocation.y + 1);
113  } else if (eventLocation.y >= NSMaxY(windowRect)) {
114  newLocation.y += (eventLocation.y - NSMaxY(windowRect) + 1);
115  }
116 
117  CGSetLocalEventsSuppressionInterval(0);
118  CGWarpMouseCursorPosition(newLocation);
119  CGSetLocalEventsSuppressionInterval(0.25);
120 
121  if ((CGEventMaskBit(type) & movementEventsMask) == 0) {
122  /* For click events, we just constrain the event to the window, so
123  * no other app receives the click event. We can't due the same to
124  * movement events, since they mean that our warp cursor above
125  * behaves strangely.
126  */
127  CGEventSetLocation(event, newLocation);
128  }
129  }
130 
131  return event;
132 }
133 
134 static void
135 SemaphorePostCallback(CFRunLoopTimerRef timer, void *info)
136 {
137  SDL_SemPost((SDL_sem*)info);
138 }
139 
140 static int
141 Cocoa_MouseTapThread(void *data)
142 {
143  SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)data;
144 
145  /* Create a tap. */
146  CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
147  kCGEventTapOptionDefault, allGrabbedEventsMask,
148  &Cocoa_MouseTapCallback, tapdata);
149  if (eventTap) {
150  /* Try to create a runloop source we can schedule. */
151  CFRunLoopSourceRef runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
152  if (runloopSource) {
153  tapdata->tap = eventTap;
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->thread = SDL_CreateThread(&Cocoa_MouseTapThread, "Event Tap Loop", tapdata);
206  if (!tapdata->thread) {
207  SDL_DestroySemaphore(tapdata->runloopStartedSemaphore);
208  }
209  }
210 
211  if (!tapdata->thread) {
212  SDL_free(driverdata->tapdata);
213  driverdata->tapdata = NULL;
214  }
215 }
216 
217 void
219 {
220  SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
221  int status;
222 
223  /* Ensure that the runloop has been started first.
224  * TODO: Move this to InitMouseEventTap, check for error conditions that can
225  * happen in Cocoa_MouseTapThread, and fall back to the non-EventTap way of
226  * grabbing the mouse if it fails to Init.
227  */
228  status = SDL_SemWaitTimeout(tapdata->runloopStartedSemaphore, 5000);
229  if (status > -1) {
230  /* Then stop it, which will cause Cocoa_MouseTapThread to return. */
231  CFRunLoopStop(tapdata->runloop);
232  /* And then wait for Cocoa_MouseTapThread to finish cleaning up. It
233  * releases some of the pointers in tapdata. */
234  SDL_WaitThread(tapdata->thread, &status);
235  }
236 
237  SDL_free(driverdata->tapdata);
238  driverdata->tapdata = NULL;
239 }
240 
241 #else /* SDL_MAC_NO_SANDBOX */
242 
243 void
245 {
246 }
247 
248 void
250 {
251 }
252 
253 #endif /* !SDL_MAC_NO_SANDBOX */
254 
255 #endif /* SDL_VIDEO_DRIVER_COCOA */
256 
257 /* vi: set ts=4 sw=4 expandtab: */
DECLSPEC SDL_sem *SDLCALL SDL_CreateSemaphore(Uint32 initial_value)
Definition: SDL_syssem.c:85
SDL_Mouse * SDL_GetMouse(void)
Definition: SDL_mouse.c:62
GLint GLenum GLsizei GLsizei GLsizei GLint GLenum GLenum type
Definition: gl2ext.h:845
DECLSPEC void *SDLCALL SDL_calloc(size_t nmemb, size_t size)
struct SDL_semaphore SDL_sem
Definition: SDL_mutex.h:107
#define NULL
Definition: ftobjs.h:61
DECLSPEC int SDLCALL SDL_SemWaitTimeout(SDL_sem *sem, Uint32 ms)
Definition: SDL_syssem.c:150
DECLSPEC void SDLCALL SDL_free(void *mem)
DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread *thread, int *status)
Definition: SDL_thread.c:399
void Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: gl2ext.h:848
DECLSPEC Uint32 SDLCALL SDL_SemValue(SDL_sem *sem)
Definition: SDL_syssem.c:186
SDL_bool relative_mode
Definition: SDL_mouse_c.h:68
DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_sem *sem)
Definition: SDL_syssem.c:111
DECLSPEC SDL_Thread *SDLCALL SDL_CreateThread(SDL_ThreadFunction fn, const char *name, void *data)
static SDL_Thread * thread
EGLSurface EGLint EGLint y
Definition: eglext.h:293
DECLSPEC SDL_Window *SDLCALL SDL_GetKeyboardFocus(void)
Get the window which currently has keyboard focus.
Definition: SDL_keyboard.c:603
TParseContext * context
Uint32 flags
Definition: SDL_sysvideo.h:81
DECLSPEC int SDLCALL SDL_SemPost(SDL_sem *sem)
Definition: SDL_syssem.c:200
void Cocoa_InitMouseEventTap(SDL_MouseData *driverdata)
cl_event event
Definition: glew.h:3556