src/video/cocoa/SDL_cocoamousetap.m
author Ryan C. Gordon <icculus@icculus.org>
Tue, 12 Apr 2016 16:45:10 -0400
changeset 10146 471eb08040ce
parent 10122 f0645cd8fd39
child 10147 ddbdc9c1b92f
permissions -rw-r--r--
threads: Move SDL's own thread creation to a new internal API.

This allows us to set an explicit stack size (overriding the system default
and the global hint an app might have set), and remove all the macro salsa
for dealing with _beginthreadex and such, as internal threads always set those
to NULL anyhow.

I've taken some guesses on reasonable (and tiny!) stack sizes for our
internal threads, but some of these might turn out to be too small in
practice and need an increase. Most of them are simple functions, though.
jorgen@7593
     1
/*
jorgen@7593
     2
  Simple DirectMedia Layer
slouken@9998
     3
  Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
jorgen@7593
     4
jorgen@7593
     5
  This software is provided 'as-is', without any express or implied
jorgen@7593
     6
  warranty.  In no event will the authors be held liable for any damages
jorgen@7593
     7
  arising from the use of this software.
jorgen@7593
     8
jorgen@7593
     9
  Permission is granted to anyone to use this software for any purpose,
jorgen@7593
    10
  including commercial applications, and to alter it and redistribute it
jorgen@7593
    11
  freely, subject to the following restrictions:
jorgen@7593
    12
jorgen@7593
    13
  1. The origin of this software must not be misrepresented; you must not
jorgen@7593
    14
     claim that you wrote the original software. If you use this software
jorgen@7593
    15
     in a product, an acknowledgment in the product documentation would be
jorgen@7593
    16
     appreciated but is not required.
jorgen@7593
    17
  2. Altered source versions must be plainly marked as such, and must not be
jorgen@7593
    18
     misrepresented as being the original software.
jorgen@7593
    19
  3. This notice may not be removed or altered from any source distribution.
jorgen@7593
    20
*/
icculus@8093
    21
#include "../../SDL_internal.h"
jorgen@7593
    22
jorgen@7593
    23
#if SDL_VIDEO_DRIVER_COCOA
jorgen@7593
    24
jorgen@7593
    25
#include "SDL_cocoamousetap.h"
jorgen@7593
    26
jorgen@7593
    27
/* Event taps are forbidden in the Mac App Store, so we can only enable this
jorgen@7593
    28
 * code if your app doesn't need to ship through the app store.
jorgen@7593
    29
 * This code makes it so that a grabbed cursor cannot "leak" a mouse click
jorgen@7593
    30
 * past the edge of the window if moving the cursor too fast.
jorgen@7593
    31
 */
jorgen@7593
    32
#if SDL_MAC_NO_SANDBOX
jorgen@7593
    33
jorgen@7593
    34
#include "SDL_keyboard.h"
jorgen@7593
    35
#include "SDL_cocoavideo.h"
icculus@10146
    36
#include "../thread/SDL_systhread.h"
jorgen@7593
    37
jorgen@7593
    38
#include "../../events/SDL_mouse_c.h"
jorgen@7593
    39
jorgen@7593
    40
typedef struct {
jorgen@7593
    41
    CFMachPortRef tap;
jorgen@7593
    42
    CFRunLoopRef runloop;
jorgen@7593
    43
    CFRunLoopSourceRef runloopSource;
jorgen@7593
    44
    SDL_Thread *thread;
jorgen@7593
    45
    SDL_sem *runloopStartedSemaphore;
jorgen@7593
    46
} SDL_MouseEventTapData;
jorgen@7593
    47
jorgen@7593
    48
static const CGEventMask movementEventsMask =
jorgen@7593
    49
      CGEventMaskBit(kCGEventLeftMouseDragged)
jorgen@7593
    50
    | CGEventMaskBit(kCGEventRightMouseDragged)
jorgen@7593
    51
    | CGEventMaskBit(kCGEventMouseMoved);
jorgen@7593
    52
jorgen@7593
    53
static const CGEventMask allGrabbedEventsMask =
jorgen@7593
    54
      CGEventMaskBit(kCGEventLeftMouseDown)    | CGEventMaskBit(kCGEventLeftMouseUp)
jorgen@7593
    55
    | CGEventMaskBit(kCGEventRightMouseDown)   | CGEventMaskBit(kCGEventRightMouseUp)
jorgen@7593
    56
    | CGEventMaskBit(kCGEventOtherMouseDown)   | CGEventMaskBit(kCGEventOtherMouseUp)
jorgen@7593
    57
    | CGEventMaskBit(kCGEventLeftMouseDragged) | CGEventMaskBit(kCGEventRightMouseDragged)
jorgen@7593
    58
    | CGEventMaskBit(kCGEventMouseMoved);
jorgen@7593
    59
jorgen@7593
    60
static CGEventRef
jorgen@7593
    61
Cocoa_MouseTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)
jorgen@7593
    62
{
slouken@7917
    63
    SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)refcon;
jorgen@7593
    64
    SDL_Mouse *mouse = SDL_GetMouse();
jorgen@7593
    65
    SDL_Window *window = SDL_GetKeyboardFocus();
slouken@8054
    66
    NSWindow *nswindow;
jorgen@7593
    67
    NSRect windowRect;
jorgen@7593
    68
    CGPoint eventLocation;
jorgen@7593
    69
slouken@8986
    70
    switch (type) {
jorgen@7593
    71
        case kCGEventTapDisabledByTimeout:
jorgen@7593
    72
        case kCGEventTapDisabledByUserInput:
jorgen@7593
    73
            {
slouken@7917
    74
                CGEventTapEnable(tapdata->tap, true);
jorgen@7593
    75
                return NULL;
jorgen@7593
    76
            }
jorgen@7593
    77
        default:
jorgen@7593
    78
            break;
jorgen@7593
    79
    }
jorgen@7593
    80
jorgen@7593
    81
jorgen@7593
    82
    if (!window || !mouse) {
jorgen@7593
    83
        return event;
jorgen@7593
    84
    }
jorgen@7593
    85
jorgen@7593
    86
    if (mouse->relative_mode) {
jorgen@7593
    87
        return event;
jorgen@7593
    88
    }
jorgen@7593
    89
jorgen@7593
    90
    if (!(window->flags & SDL_WINDOW_INPUT_GRABBED)) {
jorgen@7593
    91
        return event;
jorgen@7593
    92
    }
jorgen@7593
    93
jorgen@7593
    94
    /* This is the same coordinate system as Cocoa uses. */
slouken@8054
    95
    nswindow = ((SDL_WindowData *) window->driverdata)->nswindow;
jorgen@7593
    96
    eventLocation = CGEventGetUnflippedLocation(event);
slouken@8005
    97
    windowRect = [nswindow contentRectForFrameRect:[nswindow frame]];
jorgen@7593
    98
jorgen@7593
    99
    if (!NSPointInRect(NSPointFromCGPoint(eventLocation), windowRect)) {
jorgen@7593
   100
jorgen@7593
   101
        /* This is in CGs global screenspace coordinate system, which has a
jorgen@7593
   102
         * flipped Y.
jorgen@7593
   103
         */
jorgen@7593
   104
        CGPoint newLocation = CGEventGetLocation(event);
jorgen@7593
   105
jorgen@7593
   106
        if (eventLocation.x < NSMinX(windowRect)) {
jorgen@7593
   107
            newLocation.x = NSMinX(windowRect);
jorgen@7593
   108
        } else if (eventLocation.x >= NSMaxX(windowRect)) {
jorgen@7593
   109
            newLocation.x = NSMaxX(windowRect) - 1.0;
jorgen@7593
   110
        }
jorgen@7593
   111
jorgen@7593
   112
        if (eventLocation.y < NSMinY(windowRect)) {
jorgen@7593
   113
            newLocation.y -= (NSMinY(windowRect) - eventLocation.y + 1);
jorgen@7593
   114
        } else if (eventLocation.y >= NSMaxY(windowRect)) {
jorgen@7593
   115
            newLocation.y += (eventLocation.y - NSMaxY(windowRect) + 1);
jorgen@7593
   116
        }
jorgen@7593
   117
jorgen@7593
   118
        CGWarpMouseCursorPosition(newLocation);
slime73@10122
   119
        CGAssociateMouseAndMouseCursorPosition(YES);
jorgen@7593
   120
jorgen@7593
   121
        if ((CGEventMaskBit(type) & movementEventsMask) == 0) {
jorgen@7593
   122
            /* For click events, we just constrain the event to the window, so
jorgen@7593
   123
             * no other app receives the click event. We can't due the same to
jorgen@7593
   124
             * movement events, since they mean that our warp cursor above
jorgen@7593
   125
             * behaves strangely.
jorgen@7593
   126
             */
jorgen@7593
   127
            CGEventSetLocation(event, newLocation);
jorgen@7593
   128
        }
jorgen@7593
   129
    }
jorgen@7593
   130
jorgen@7593
   131
    return event;
jorgen@7593
   132
}
jorgen@7593
   133
jorgen@7610
   134
static void
jorgen@7610
   135
SemaphorePostCallback(CFRunLoopTimerRef timer, void *info)
jorgen@7610
   136
{
jorgen@7610
   137
    SDL_SemPost((SDL_sem*)info);
jorgen@7610
   138
}
jorgen@7610
   139
jorgen@7593
   140
static int
jorgen@7593
   141
Cocoa_MouseTapThread(void *data)
jorgen@7593
   142
{
jorgen@7593
   143
    SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)data;
jorgen@7593
   144
jorgen@7593
   145
    /* Create a tap. */
jorgen@7593
   146
    CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,
jorgen@7593
   147
                                              kCGEventTapOptionDefault, allGrabbedEventsMask,
jorgen@7593
   148
                                              &Cocoa_MouseTapCallback, tapdata);
jorgen@7593
   149
    if (eventTap) {
jorgen@7593
   150
        /* Try to create a runloop source we can schedule. */
jorgen@7593
   151
        CFRunLoopSourceRef runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
jorgen@7593
   152
        if  (runloopSource) {
jorgen@7593
   153
            tapdata->tap = eventTap;
jorgen@7593
   154
            tapdata->runloopSource = runloopSource;
jorgen@7593
   155
        } else {
jorgen@7593
   156
            CFRelease(eventTap);
jorgen@7593
   157
            SDL_SemPost(tapdata->runloopStartedSemaphore);
jorgen@7593
   158
            /* TODO: Both here and in the return below, set some state in
jorgen@7593
   159
             * tapdata to indicate that initialization failed, which we should
jorgen@7593
   160
             * check in InitMouseEventTap, after we move the semaphore check
jorgen@7593
   161
             * from Quit to Init.
jorgen@7593
   162
             */
jorgen@7593
   163
            return 1;
jorgen@7593
   164
        }
jorgen@7593
   165
    } else {
jorgen@7593
   166
        SDL_SemPost(tapdata->runloopStartedSemaphore);
jorgen@7593
   167
        return 1;
jorgen@7593
   168
    }
jorgen@7593
   169
jorgen@7593
   170
    tapdata->runloop = CFRunLoopGetCurrent();
jorgen@7593
   171
    CFRunLoopAddSource(tapdata->runloop, tapdata->runloopSource, kCFRunLoopCommonModes);
jorgen@7610
   172
    CFRunLoopTimerContext context = {.info = tapdata->runloopStartedSemaphore};
jorgen@7610
   173
    /* We signal the runloop started semaphore *after* the run loop has started, indicating it's safe to CFRunLoopStop it. */
jorgen@7610
   174
    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, &SemaphorePostCallback, &context);
jorgen@7610
   175
    CFRunLoopAddTimer(tapdata->runloop, timer, kCFRunLoopCommonModes);
jorgen@7610
   176
    CFRelease(timer);
jorgen@7593
   177
jorgen@7593
   178
    /* Run the event loop to handle events in the event tap. */
jorgen@7593
   179
    CFRunLoopRun();
jorgen@7593
   180
    /* Make sure this is signaled so that SDL_QuitMouseEventTap knows it can safely SDL_WaitThread for us. */
jorgen@7593
   181
    if (SDL_SemValue(tapdata->runloopStartedSemaphore) < 1) {
jorgen@7593
   182
        SDL_SemPost(tapdata->runloopStartedSemaphore);
jorgen@7593
   183
    }
jorgen@7593
   184
    CFRunLoopRemoveSource(tapdata->runloop, tapdata->runloopSource, kCFRunLoopCommonModes);
jorgen@7593
   185
jorgen@7593
   186
    /* Clean up. */
jorgen@7593
   187
    CGEventTapEnable(tapdata->tap, false);
jorgen@7593
   188
    CFRelease(tapdata->runloopSource);
jorgen@7593
   189
    CFRelease(tapdata->tap);
jorgen@7593
   190
    tapdata->runloopSource = NULL;
jorgen@7593
   191
    tapdata->tap = NULL;
jorgen@7593
   192
jorgen@7593
   193
    return 0;
jorgen@7593
   194
}
jorgen@7593
   195
jorgen@7593
   196
void
jorgen@7593
   197
Cocoa_InitMouseEventTap(SDL_MouseData* driverdata)
jorgen@7593
   198
{
jorgen@7593
   199
    SDL_MouseEventTapData *tapdata;
jorgen@7593
   200
    driverdata->tapdata = SDL_calloc(1, sizeof(SDL_MouseEventTapData));
jorgen@7593
   201
    tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
jorgen@7593
   202
jorgen@7593
   203
    tapdata->runloopStartedSemaphore = SDL_CreateSemaphore(0);
jorgen@7593
   204
    if (tapdata->runloopStartedSemaphore) {
icculus@10146
   205
        tapdata->thread = SDL_CreateThreadInternal(&Cocoa_MouseTapThread, "Event Tap Loop", 64 * 1024, tapdata);
jorgen@7593
   206
        if (!tapdata->thread) {
jorgen@7593
   207
            SDL_DestroySemaphore(tapdata->runloopStartedSemaphore);
jorgen@7593
   208
        }
jorgen@7593
   209
    }
jorgen@7593
   210
jorgen@7593
   211
    if (!tapdata->thread) {
jorgen@7593
   212
        SDL_free(driverdata->tapdata);
jorgen@7593
   213
        driverdata->tapdata = NULL;
jorgen@7593
   214
    }
jorgen@7593
   215
}
jorgen@7593
   216
jorgen@7593
   217
void
jorgen@7593
   218
Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
jorgen@7593
   219
{
jorgen@7593
   220
    SDL_MouseEventTapData *tapdata = (SDL_MouseEventTapData*)driverdata->tapdata;
jorgen@7593
   221
    int status;
jorgen@7593
   222
jorgen@7593
   223
    /* Ensure that the runloop has been started first.
jorgen@7593
   224
     * TODO: Move this to InitMouseEventTap, check for error conditions that can
jorgen@7593
   225
     * happen in Cocoa_MouseTapThread, and fall back to the non-EventTap way of
jorgen@7593
   226
     * grabbing the mouse if it fails to Init.
jorgen@7593
   227
     */
jorgen@7593
   228
    status = SDL_SemWaitTimeout(tapdata->runloopStartedSemaphore, 5000);
jorgen@7593
   229
    if (status > -1) {
jorgen@7593
   230
        /* Then stop it, which will cause Cocoa_MouseTapThread to return. */
jorgen@7593
   231
        CFRunLoopStop(tapdata->runloop);
jorgen@7593
   232
        /* And then wait for Cocoa_MouseTapThread to finish cleaning up. It
jorgen@7593
   233
         * releases some of the pointers in tapdata. */
jorgen@7593
   234
        SDL_WaitThread(tapdata->thread, &status);
jorgen@7593
   235
    }
jorgen@7593
   236
jorgen@7593
   237
    SDL_free(driverdata->tapdata);
jorgen@7593
   238
    driverdata->tapdata = NULL;
jorgen@7593
   239
}
jorgen@7593
   240
jorgen@7593
   241
#else /* SDL_MAC_NO_SANDBOX */
jorgen@7593
   242
jorgen@7593
   243
void
jorgen@7593
   244
Cocoa_InitMouseEventTap(SDL_MouseData *unused)
jorgen@7593
   245
{
jorgen@7593
   246
}
jorgen@7593
   247
jorgen@7593
   248
void
jorgen@7593
   249
Cocoa_QuitMouseEventTap(SDL_MouseData *driverdata)
jorgen@7593
   250
{
jorgen@7593
   251
}
jorgen@7593
   252
jorgen@7593
   253
#endif /* !SDL_MAC_NO_SANDBOX */
jorgen@7593
   254
jorgen@7593
   255
#endif /* SDL_VIDEO_DRIVER_COCOA */
jorgen@7593
   256
jorgen@7593
   257
/* vi: set ts=4 sw=4 expandtab: */