diff options
Diffstat (limited to 'source/3rd-party/SDL2/src/video/cocoa/SDL_cocoamousetap.m')
| -rw-r--r-- | source/3rd-party/SDL2/src/video/cocoa/SDL_cocoamousetap.m | 286 | 
1 files changed, 286 insertions, 0 deletions
diff --git a/source/3rd-party/SDL2/src/video/cocoa/SDL_cocoamousetap.m b/source/3rd-party/SDL2/src/video/cocoa/SDL_cocoamousetap.m new file mode 100644 index 0000000..aa4f152 --- /dev/null +++ b/source/3rd-party/SDL2/src/video/cocoa/SDL_cocoamousetap.m @@ -0,0 +1,286 @@ +/* +  Simple DirectMedia Layer +  Copyright (C) 1997-2018 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_internal.h" + +#if SDL_VIDEO_DRIVER_COCOA + +#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_cocoavideo.h" +#include "../../thread/SDL_systhread.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_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)refcon; +    SDL_Mouse *mouse = SDL_GetMouse(); +    SDL_Window *window = SDL_GetKeyboardFocus(); +    NSWindow *nswindow; +    NSRect windowRect; +    CGPoint eventLocation; + +    switch (type) { +        case kCGEventTapDisabledByTimeout: +            { +                CGEventTapEnable(tapdata->tap, true); +                return NULL; +            } +        case kCGEventTapDisabledByUserInput: +            { +                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. */ +    nswindow = ((SDL_WindowData *) window->driverdata)->nswindow; +    eventLocation = CGEventGetUnflippedLocation(event); +    windowRect = [nswindow contentRectForFrameRect:[nswindow frame]]; + +    if (!NSMouseInRect(NSPointFromCGPoint(eventLocation), windowRect, NO)) { + +        /* 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)); +        } + +        CGWarpMouseCursorPosition(newLocation); +        CGAssociateMouseAndMouseCursorPosition(YES); + +        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 void +SemaphorePostCallback(CFRunLoopTimerRef timer, void *info) +{ +    SDL_SemPost((SDL_sem*)info); +} + +static int +Cocoa_MouseTapThread(void *data) +{ +    SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)data; + +    /* Tap was created on main thread but we own it now. */ +    CFMachPortRef eventTap = tapdata->tap; +    if (eventTap) { +        /* Try to create a runloop source we can schedule. */ +        CFRunLoopSourceRef runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0); +        if  (runloopSource) { +            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); +    CFRunLoopTimerContext context = {.info = tapdata->runloopStartedSemaphore}; +    /* We signal the runloop started semaphore *after* the run loop has started, indicating it's safe to CFRunLoopStop it. */ +    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, &SemaphorePostCallback, &context); +    CFRunLoopAddTimer(tapdata->runloop, timer, kCFRunLoopCommonModes); +    CFRelease(timer); + +    /* 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->tap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, +                                        kCGEventTapOptionDefault, allGrabbedEventsMask, +                                        &Cocoa_MouseTapCallback, tapdata); +        if (tapdata->tap) { +            /* Tap starts disabled, until app requests mouse grab */ +            CGEventTapEnable(tapdata->tap, false); +            tapdata->thread = SDL_CreateThreadInternal(&Cocoa_MouseTapThread, "Event Tap Loop", 512 * 1024, tapdata); +            if (tapdata->thread) { +                /* Success - early out. Ownership transferred to thread. */ +                return; +            } +            CFRelease(tapdata->tap); +        } +        SDL_DestroySemaphore(tapdata->runloopStartedSemaphore); +    } +    SDL_free(driverdata->tapdata); +    driverdata->tapdata = NULL; +} + +void +Cocoa_EnableMouseEventTap(SDL_MouseData *driverdata, SDL_bool enabled) +{ +    SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)driverdata->tapdata; +    if (tapdata && tapdata->tap) +    { +        CGEventTapEnable(tapdata->tap, !!enabled); +    } +} + +void +Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata) +{ +    SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)driverdata->tapdata; +    int status; + +    if (tapdata == NULL) { +        /* event tap was already cleaned up (possibly due to CGEventTapCreate +         * returning null.) +         */ +        return; +    } + +    /* 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_EnableMouseEventTap(SDL_MouseData *driverdata, SDL_bool enabled) +{ +} + +void +Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata) +{ +} + +#endif /* !SDL_MAC_NO_SANDBOX */ + +#endif /* SDL_VIDEO_DRIVER_COCOA */ + +/* vi: set ts=4 sw=4 expandtab: */  | 
