cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

SDL_cocoamousetap.m (9004B)


      1/*
      2  Simple DirectMedia Layer
      3  Copyright (C) 1997-2014 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
     40typedef struct {
     41    CFMachPortRef tap;
     42    CFRunLoopRef runloop;
     43    CFRunLoopSourceRef runloopSource;
     44    SDL_Thread *thread;
     45    SDL_sem *runloopStartedSemaphore;
     46} SDL_MouseEventTapData;
     47
     48static const CGEventMask movementEventsMask =
     49      CGEventMaskBit(kCGEventLeftMouseDragged)
     50    | CGEventMaskBit(kCGEventRightMouseDragged)
     51    | CGEventMaskBit(kCGEventMouseMoved);
     52
     53static 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
     60static CGEventRef
     61Cocoa_MouseTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
     62{
     63    SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)refcon;
     64    SDL_Mouse *mouse = SDL_GetMouse();
     65    SDL_Window *window = SDL_GetKeyboardFocus();
     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
    135static void
    136SemaphorePostCallback(CFRunLoopTimerRef timer, void *info)
    137{
    138    SDL_SemPost((SDL_sem*)info);
    139}
    140
    141static int
    142Cocoa_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
    197void
    198Cocoa_InitMouseEventTap(SDL_MouseData* driverdata)
    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
    218void
    219Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
    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
    244void
    245Cocoa_InitMouseEventTap(SDL_MouseData *unused)
    246{
    247}
    248
    249void
    250Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
    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: */