From 4804438bc7575b2f5720307c4bf6b6981ad207ca Mon Sep 17 00:00:00 2001 From: "J?rgen P. Tjern?" Date: Wed, 7 Aug 2013 16:29:15 -0700 Subject: [PATCH] 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. --- Xcode/SDL/SDL.xcodeproj/project.pbxproj | 19 ++ src/video/cocoa/SDL_cocoamouse.h | 1 + src/video/cocoa/SDL_cocoamouse.m | 9 + src/video/cocoa/SDL_cocoamousetap.h | 33 ++++ src/video/cocoa/SDL_cocoamousetap.m | 252 ++++++++++++++++++++++++ src/video/cocoa/SDL_cocoawindow.m | 6 + 6 files changed, 320 insertions(+) create mode 100644 src/video/cocoa/SDL_cocoamousetap.h create mode 100644 src/video/cocoa/SDL_cocoamousetap.m diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index 2fccf0f7c..fd91642c7 100755 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -516,6 +516,12 @@ AADA5B8716CCAB3000107CF7 /* SDL_bits.h in Headers */ = {isa = PBXBuildFile; fileRef = AADA5B8616CCAB3000107CF7 /* SDL_bits.h */; }; AADA5B8816CCAB3000107CF7 /* SDL_bits.h in Headers */ = {isa = PBXBuildFile; fileRef = AADA5B8616CCAB3000107CF7 /* SDL_bits.h */; }; BBFC088D164C6647003E6A99 /* SDL_gamecontroller.c in Sources */ = {isa = PBXBuildFile; fileRef = BBFC088A164C6514003E6A99 /* SDL_gamecontroller.c */; }; + D55A1B81179F262300625D7C /* SDL_cocoamousetap.h in Headers */ = {isa = PBXBuildFile; fileRef = D55A1B7F179F262300625D7C /* SDL_cocoamousetap.h */; }; + D55A1B82179F262300625D7C /* SDL_cocoamousetap.m in Sources */ = {isa = PBXBuildFile; fileRef = D55A1B80179F262300625D7C /* SDL_cocoamousetap.m */; }; + D55A1B83179F263500625D7C /* SDL_cocoamousetap.m in Sources */ = {isa = PBXBuildFile; fileRef = D55A1B80179F262300625D7C /* SDL_cocoamousetap.m */; }; + D55A1B84179F263600625D7C /* SDL_cocoamousetap.m in Sources */ = {isa = PBXBuildFile; fileRef = D55A1B80179F262300625D7C /* SDL_cocoamousetap.m */; }; + D55A1B85179F278E00625D7C /* SDL_cocoamousetap.h in Headers */ = {isa = PBXBuildFile; fileRef = D55A1B7F179F262300625D7C /* SDL_cocoamousetap.h */; }; + D55A1B86179F278F00625D7C /* SDL_cocoamousetap.h in Headers */ = {isa = PBXBuildFile; fileRef = D55A1B7F179F262300625D7C /* SDL_cocoamousetap.h */; }; DB313F7417554B71006C0E22 /* SDL_diskaudio.h in Headers */ = {isa = PBXBuildFile; fileRef = 04BDFD8912E6671700899322 /* SDL_diskaudio.h */; }; DB313F7517554B71006C0E22 /* SDL_dummyaudio.h in Headers */ = {isa = PBXBuildFile; fileRef = 04BDFD9512E6671700899322 /* SDL_dummyaudio.h */; }; DB313F7617554B71006C0E22 /* SDL_coreaudio.h in Headers */ = {isa = PBXBuildFile; fileRef = 04BDFDA112E6671700899322 /* SDL_coreaudio.h */; }; @@ -1044,6 +1050,8 @@ BECDF66C0761BA81005FE872 /* SDL2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SDL2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BECDF6B30761BA81005FE872 /* libSDL2.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSDL2.a; sourceTree = BUILT_PRODUCTS_DIR; }; BECDF6BE0761BA81005FE872 /* Standard DMG */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Standard DMG"; sourceTree = BUILT_PRODUCTS_DIR; }; + D55A1B7F179F262300625D7C /* SDL_cocoamousetap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_cocoamousetap.h; sourceTree = ""; }; + D55A1B80179F262300625D7C /* SDL_cocoamousetap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDL_cocoamousetap.m; sourceTree = ""; }; DB31407717554B71006C0E22 /* libSDL2.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libSDL2.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; F59C70FF00D5CB5801000001 /* ReadMe.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = ReadMe.txt; sourceTree = ""; }; F59C710000D5CB5801000001 /* Welcome.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = Welcome.txt; sourceTree = ""; }; @@ -1534,6 +1542,8 @@ 04BDFEC912E6671800899322 /* SDL_cocoamodes.m */, 04BDFECA12E6671800899322 /* SDL_cocoamouse.h */, 04BDFECB12E6671800899322 /* SDL_cocoamouse.m */, + D55A1B7F179F262300625D7C /* SDL_cocoamousetap.h */, + D55A1B80179F262300625D7C /* SDL_cocoamousetap.m */, 04BDFECC12E6671800899322 /* SDL_cocoaopengl.h */, 04BDFECD12E6671800899322 /* SDL_cocoaopengl.m */, 04BDFECE12E6671800899322 /* SDL_cocoashape.h */, @@ -1611,8 +1621,11 @@ BEC562FE0761C0E800A33029 /* Linked Frameworks */, ); comments = "To build Universal Binaries, we have experimented with a variety of different options.\nThe complication is that we must retain compatibility with at least 10.2. \nThe Universal Binary defaults only work for > 10.3.9\n\nSo far, we have found:\ngcc 4.0.0 with Xcode 2.1 always links against libgcc_s. gcc 4.0.1 from Xcode 2.2 fixes this problem.\n\nBut gcc 4.0 will not work with < 10.3.9 because we continue to get an undefined symbol to _fprintf$LDBL128.\nSo we must use gcc 3.3 on PPC to accomplish 10.2 support. (But 4.0 is required for i386.)\n\nSetting the deployment target to 10.4 will disable prebinding, so for PPC, we set it less than 10.4 to preserve prebinding for legacy support.\n\nSetting the PPC SDKROOT to /Developers/SDKs/MacOSX10.2.8.sdk will link to 63.0.0 libSystem.B.dylib. Leaving it at current or 10.4u links to 88.1.2. However, as long as we are using gcc 3.3, it doesn't seem to matter as testing has demonstrated both will run. We have decided not to invoke the 10.2.8 SDK because it is not a default installed component with Xcode which will probably cause most people problems. However, rather than deleting the SDKROOT_ppc entry entirely, we have mapped it to 10.4u in case we decide we need to change this setting.\n\nTo use Altivec or SSE, we needed architecture specific flags:\nOTHER_CFLAGS_ppc\nOTHER_CFLAGS_i386\nOTHER_CFLAGS=$(OTHER_CFLAGS_($CURRENT_ARCH))\n\nThe general OTHER_CFLAGS needed to be manually mapped to architecture specific options because Xcode didn't do this automatically for us.\n\n\n"; + indentWidth = 4; name = SDLFramework; sourceTree = ""; + tabWidth = 4; + usesTabs = 0; }; 089C1665FE841158C02AAC07 /* Resources */ = { isa = PBXGroup; @@ -1842,6 +1855,7 @@ AA628AD3159367F2005138DD /* SDL_x11xinput2.h in Headers */, AABCC38D164063D200AB8930 /* SDL_cocoamessagebox.h in Headers */, AADA5B8716CCAB3000107CF7 /* SDL_bits.h in Headers */, + D55A1B81179F262300625D7C /* SDL_cocoamousetap.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1985,6 +1999,7 @@ AABCC38E164063D200AB8930 /* SDL_cocoamessagebox.h in Headers */, A77E6EB5167AB0A90010E40B /* SDL_gamecontroller.h in Headers */, AADA5B8816CCAB3000107CF7 /* SDL_bits.h in Headers */, + D55A1B85179F278E00625D7C /* SDL_cocoamousetap.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2128,6 +2143,7 @@ DB313FFA17554B71006C0E22 /* SDL_cocoamessagebox.h in Headers */, DB313FFB17554B71006C0E22 /* SDL_gamecontroller.h in Headers */, DB313FFC17554B71006C0E22 /* SDL_bits.h in Headers */, + D55A1B86179F278F00625D7C /* SDL_cocoamousetap.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2410,6 +2426,7 @@ AABCC38F164063D200AB8930 /* SDL_cocoamessagebox.m in Sources */, AA0AD09D16648D1700CE5896 /* SDL_gamecontroller.c in Sources */, AA0F8491178D5ECC00823F9D /* SDL_systls.c in Sources */, + D55A1B82179F262300625D7C /* SDL_cocoamousetap.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2526,6 +2543,7 @@ AA9E4094163BE51E007A2AD0 /* SDL_x11messagebox.c in Sources */, AABCC390164063D200AB8930 /* SDL_cocoamessagebox.m in Sources */, AA0F8492178D5ECC00823F9D /* SDL_systls.c in Sources */, + D55A1B84179F263600625D7C /* SDL_cocoamousetap.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2642,6 +2660,7 @@ DB31406917554B71006C0E22 /* SDL_x11messagebox.c in Sources */, DB31406A17554B71006C0E22 /* SDL_cocoamessagebox.m in Sources */, AA0F8493178D5ECC00823F9D /* SDL_systls.c in Sources */, + D55A1B83179F263500625D7C /* SDL_cocoamousetap.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/video/cocoa/SDL_cocoamouse.h b/src/video/cocoa/SDL_cocoamouse.h index bff8ef6dd..eb6f5891f 100644 --- a/src/video/cocoa/SDL_cocoamouse.h +++ b/src/video/cocoa/SDL_cocoamouse.h @@ -33,6 +33,7 @@ extern void Cocoa_QuitMouse(_THIS); typedef struct { int deltaXOffset; int deltaYOffset; + void *tapdata; } SDL_MouseData; @interface NSCursor (InvisibleCursor) diff --git a/src/video/cocoa/SDL_cocoamouse.m b/src/video/cocoa/SDL_cocoamouse.m index 12e755cf9..faba9618e 100644 --- a/src/video/cocoa/SDL_cocoamouse.m +++ b/src/video/cocoa/SDL_cocoamouse.m @@ -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 @@ + (NSCursor *)invisibleCursor cursor = SDL_calloc(1, sizeof(*cursor)); if (cursor) { cursor->driverdata = nscursor; + } else { + [nscursor release]; } } @@ -266,6 +269,8 @@ + (NSCursor *)invisibleCursor mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode; SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor()); + + Cocoa_InitMouseEventTap(mouse->driverdata); } void @@ -313,6 +318,10 @@ + (NSCursor *)invisibleCursor { SDL_Mouse *mouse = SDL_GetMouse(); if (mouse) { + if (mouse->driverdata) { + Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata)); + } + SDL_free(mouse->driverdata); } } diff --git a/src/video/cocoa/SDL_cocoamousetap.h b/src/video/cocoa/SDL_cocoamousetap.h new file mode 100644 index 000000000..a13d97bb5 --- /dev/null +++ b/src/video/cocoa/SDL_cocoamousetap.h @@ -0,0 +1,33 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + 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: */ diff --git a/src/video/cocoa/SDL_cocoamousetap.m b/src/video/cocoa/SDL_cocoamousetap.m new file mode 100644 index 000000000..01769b321 --- /dev/null +++ b/src/video/cocoa/SDL_cocoamousetap.m @@ -0,0 +1,252 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2013 Sam Lantinga + + 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: */ diff --git a/src/video/cocoa/SDL_cocoawindow.m b/src/video/cocoa/SDL_cocoawindow.m index 28a58e4e6..0c37f4de0 100644 --- a/src/video/cocoa/SDL_cocoawindow.m +++ b/src/video/cocoa/SDL_cocoawindow.m @@ -414,6 +414,11 @@ - (void)mouseMoved:(NSEvent *)theEvent 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 @@ - (void)mouseMoved:(NSEvent *)theEvent CGSetLocalEventsSuppressionInterval(0.0); CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint); CGSetLocalEventsSuppressionInterval(0.25); +#endif } } SDL_SendMouseMotion(window, 0, 0, x, y);