Mac: Better mouse-grab if you define SDL_MAC_NO_SANDBOX.
This uses a better mouse grab if you define SDL_MAC_NO_SANDBOX. This mouse grab uses CGEventTapCreate, which you cannot access if you have sandboxing enabled.
This commit is contained in:
parent
b03f072d95
commit
8d8238f8ae
6 changed files with 320 additions and 0 deletions
|
@ -33,6 +33,7 @@ extern void Cocoa_QuitMouse(_THIS);
|
|||
typedef struct {
|
||||
int deltaXOffset;
|
||||
int deltaYOffset;
|
||||
void *tapdata;
|
||||
} SDL_MouseData;
|
||||
|
||||
@interface NSCursor (InvisibleCursor)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "SDL_assert.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_cocoamouse.h"
|
||||
#include "SDL_cocoamousetap.h"
|
||||
|
||||
#include "../../events/SDL_mouse_c.h"
|
||||
|
||||
|
@ -94,6 +95,8 @@ Cocoa_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
|
|||
cursor = SDL_calloc(1, sizeof(*cursor));
|
||||
if (cursor) {
|
||||
cursor->driverdata = nscursor;
|
||||
} else {
|
||||
[nscursor release];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,6 +269,8 @@ Cocoa_InitMouse(_THIS)
|
|||
mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
|
||||
|
||||
SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
|
||||
|
||||
Cocoa_InitMouseEventTap(mouse->driverdata);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -313,6 +318,10 @@ Cocoa_QuitMouse(_THIS)
|
|||
{
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
if (mouse) {
|
||||
if (mouse->driverdata) {
|
||||
Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
|
||||
}
|
||||
|
||||
SDL_free(mouse->driverdata);
|
||||
}
|
||||
}
|
||||
|
|
33
src/video/cocoa/SDL_cocoamousetap.h
Normal file
33
src/video/cocoa/SDL_cocoamousetap.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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_config.h"
|
||||
|
||||
#ifndef _SDL_cocoamousetap_h
|
||||
#define _SDL_cocoamousetap_h
|
||||
|
||||
#include "SDL_cocoamouse.h"
|
||||
|
||||
extern void Cocoa_InitMouseEventTap(SDL_MouseData *driverdata);
|
||||
extern void Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata);
|
||||
|
||||
#endif /* _SDL_cocoamousetap_h */
|
||||
|
||||
/* vi: set ts=4 sw=4 expandtab: */
|
252
src/video/cocoa/SDL_cocoamousetap.m
Normal file
252
src/video/cocoa/SDL_cocoamousetap.m
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2013 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
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_config.h"
|
||||
|
||||
#if SDL_VIDEO_DRIVER_COCOA
|
||||
|
||||
#define SDL_MAC_NO_SANDBOX 1
|
||||
|
||||
#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_thread.h"
|
||||
#include "SDL_cocoavideo.h"
|
||||
|
||||
#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_MouseData *driverdata = (SDL_MouseData*)refcon;
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
SDL_Window *window = SDL_GetKeyboardFocus();
|
||||
NSRect windowRect;
|
||||
CGPoint eventLocation;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case kCGEventTapDisabledByTimeout:
|
||||
case kCGEventTapDisabledByUserInput:
|
||||
{
|
||||
CGEventTapEnable(((SDL_MouseEventTapData*)(driverdata->tapdata))->tap, true);
|
||||
return NULL;
|
||||
}
|
||||
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. */
|
||||
eventLocation = CGEventGetUnflippedLocation(event);
|
||||
windowRect = [((SDL_WindowData *) window->driverdata)->nswindow frame];
|
||||
|
||||
if (!NSPointInRect(NSPointFromCGPoint(eventLocation), windowRect)) {
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (eventLocation.y < NSMinY(windowRect)) {
|
||||
newLocation.y -= (NSMinY(windowRect) - eventLocation.y + 1);
|
||||
} else if (eventLocation.y >= NSMaxY(windowRect)) {
|
||||
newLocation.y += (eventLocation.y - NSMaxY(windowRect) + 1);
|
||||
}
|
||||
|
||||
CGSetLocalEventsSuppressionInterval(0);
|
||||
CGWarpMouseCursorPosition(newLocation);
|
||||
CGSetLocalEventsSuppressionInterval(0.25);
|
||||
|
||||
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 int
|
||||
Cocoa_MouseTapThread(void *data)
|
||||
{
|
||||
SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)data;
|
||||
|
||||
/* Create a tap. */
|
||||
CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
|
||||
kCGEventTapOptionDefault, allGrabbedEventsMask,
|
||||
&Cocoa_MouseTapCallback, tapdata);
|
||||
if (eventTap) {
|
||||
/* Try to create a runloop source we can schedule. */
|
||||
CFRunLoopSourceRef runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
|
||||
if (runloopSource) {
|
||||
tapdata->tap = eventTap;
|
||||
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);
|
||||
CFRunLoopPerformBlock(tapdata->runloop, kCFRunLoopCommonModes, ^{
|
||||
/* We signal this *after* the run loop has started, indicating it's safe to CFRunLoopStop it. */
|
||||
SDL_SemPost(tapdata->runloopStartedSemaphore);
|
||||
});
|
||||
|
||||
/* 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) {
|
||||
tapdata->thread = SDL_CreateThread(&Cocoa_MouseTapThread, "Event Tap Loop", tapdata);
|
||||
if (!tapdata->thread) {
|
||||
SDL_DestroySemaphore(tapdata->runloopStartedSemaphore);
|
||||
}
|
||||
}
|
||||
|
||||
if (!tapdata->thread) {
|
||||
SDL_free(driverdata->tapdata);
|
||||
driverdata->tapdata = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
|
||||
{
|
||||
SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
|
||||
int status;
|
||||
|
||||
/* 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)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* !SDL_MAC_NO_SANDBOX */
|
||||
|
||||
#endif /* SDL_VIDEO_DRIVER_COCOA */
|
||||
|
||||
/* vi: set ts=4 sw=4 expandtab: */
|
|
@ -414,6 +414,11 @@ static __inline__ void ConvertNSRect(NSRect *r)
|
|||
y = window->h - 1;
|
||||
}
|
||||
|
||||
#if !SDL_MAC_NO_SANDBOX
|
||||
/* When SDL_MAC_NO_SANDBOX is set, this is handled by
|
||||
* SDL_cocoamousetap.m.
|
||||
*/
|
||||
|
||||
cgpoint.x = window->x + x;
|
||||
cgpoint.y = window->y + y;
|
||||
|
||||
|
@ -424,6 +429,7 @@ static __inline__ void ConvertNSRect(NSRect *r)
|
|||
CGSetLocalEventsSuppressionInterval(0.0);
|
||||
CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint);
|
||||
CGSetLocalEventsSuppressionInterval(0.25);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
SDL_SendMouseMotion(window, 0, 0, x, y);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue