sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_cocoamouse.m (13976B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 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_events.h"
     26 #include "SDL_cocoamouse.h"
     27 #include "SDL_cocoamousetap.h"
     28 #include "SDL_cocoavideo.h"
     29 
     30 #include "../../events/SDL_mouse_c.h"
     31 
     32 /* #define DEBUG_COCOAMOUSE */
     33 
     34 #ifdef DEBUG_COCOAMOUSE
     35 #define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__)
     36 #else
     37 #define DLog(...) do { } while (0)
     38 #endif
     39 
     40 @implementation NSCursor (InvisibleCursor)
     41 + (NSCursor *)invisibleCursor
     42 {
     43     static NSCursor *invisibleCursor = NULL;
     44     if (!invisibleCursor) {
     45         /* RAW 16x16 transparent GIF */
     46         static unsigned char cursorBytes[] = {
     47             0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80,
     48             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04,
     49             0x01, 0x00, 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10,
     50             0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED,
     51             0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B
     52         };
     53 
     54         NSData *cursorData = [NSData dataWithBytesNoCopy:&cursorBytes[0]
     55                                                   length:sizeof(cursorBytes)
     56                                             freeWhenDone:NO];
     57         NSImage *cursorImage = [[[NSImage alloc] initWithData:cursorData] autorelease];
     58         invisibleCursor = [[NSCursor alloc] initWithImage:cursorImage
     59                                                   hotSpot:NSZeroPoint];
     60     }
     61 
     62     return invisibleCursor;
     63 }
     64 @end
     65 
     66 
     67 static SDL_Cursor *
     68 Cocoa_CreateDefaultCursor()
     69 { @autoreleasepool
     70 {
     71     NSCursor *nscursor;
     72     SDL_Cursor *cursor = NULL;
     73 
     74     nscursor = [NSCursor arrowCursor];
     75 
     76     if (nscursor) {
     77         cursor = SDL_calloc(1, sizeof(*cursor));
     78         if (cursor) {
     79             cursor->driverdata = nscursor;
     80             [nscursor retain];
     81         }
     82     }
     83 
     84     return cursor;
     85 }}
     86 
     87 static SDL_Cursor *
     88 Cocoa_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
     89 { @autoreleasepool
     90 {
     91     NSImage *nsimage;
     92     NSCursor *nscursor = NULL;
     93     SDL_Cursor *cursor = NULL;
     94 
     95     nsimage = Cocoa_CreateImage(surface);
     96     if (nsimage) {
     97         nscursor = [[NSCursor alloc] initWithImage: nsimage hotSpot: NSMakePoint(hot_x, hot_y)];
     98     }
     99 
    100     if (nscursor) {
    101         cursor = SDL_calloc(1, sizeof(*cursor));
    102         if (cursor) {
    103             cursor->driverdata = nscursor;
    104         } else {
    105             [nscursor release];
    106         }
    107     }
    108 
    109     return cursor;
    110 }}
    111 
    112 static SDL_Cursor *
    113 Cocoa_CreateSystemCursor(SDL_SystemCursor id)
    114 { @autoreleasepool
    115 {
    116     NSCursor *nscursor = NULL;
    117     SDL_Cursor *cursor = NULL;
    118 
    119     switch(id) {
    120     case SDL_SYSTEM_CURSOR_ARROW:
    121         nscursor = [NSCursor arrowCursor];
    122         break;
    123     case SDL_SYSTEM_CURSOR_IBEAM:
    124         nscursor = [NSCursor IBeamCursor];
    125         break;
    126     case SDL_SYSTEM_CURSOR_WAIT:
    127         nscursor = [NSCursor arrowCursor];
    128         break;
    129     case SDL_SYSTEM_CURSOR_CROSSHAIR:
    130         nscursor = [NSCursor crosshairCursor];
    131         break;
    132     case SDL_SYSTEM_CURSOR_WAITARROW:
    133         nscursor = [NSCursor arrowCursor];
    134         break;
    135     case SDL_SYSTEM_CURSOR_SIZENWSE:
    136     case SDL_SYSTEM_CURSOR_SIZENESW:
    137         nscursor = [NSCursor closedHandCursor];
    138         break;
    139     case SDL_SYSTEM_CURSOR_SIZEWE:
    140         nscursor = [NSCursor resizeLeftRightCursor];
    141         break;
    142     case SDL_SYSTEM_CURSOR_SIZENS:
    143         nscursor = [NSCursor resizeUpDownCursor];
    144         break;
    145     case SDL_SYSTEM_CURSOR_SIZEALL:
    146         nscursor = [NSCursor closedHandCursor];
    147         break;
    148     case SDL_SYSTEM_CURSOR_NO:
    149         nscursor = [NSCursor operationNotAllowedCursor];
    150         break;
    151     case SDL_SYSTEM_CURSOR_HAND:
    152         nscursor = [NSCursor pointingHandCursor];
    153         break;
    154     default:
    155         SDL_assert(!"Unknown system cursor");
    156         return NULL;
    157     }
    158 
    159     if (nscursor) {
    160         cursor = SDL_calloc(1, sizeof(*cursor));
    161         if (cursor) {
    162             /* We'll free it later, so retain it here */
    163             [nscursor retain];
    164             cursor->driverdata = nscursor;
    165         }
    166     }
    167 
    168     return cursor;
    169 }}
    170 
    171 static void
    172 Cocoa_FreeCursor(SDL_Cursor * cursor)
    173 { @autoreleasepool
    174 {
    175     NSCursor *nscursor = (NSCursor *)cursor->driverdata;
    176 
    177     [nscursor release];
    178     SDL_free(cursor);
    179 }}
    180 
    181 static int
    182 Cocoa_ShowCursor(SDL_Cursor * cursor)
    183 { @autoreleasepool
    184 {
    185     SDL_VideoDevice *device = SDL_GetVideoDevice();
    186     SDL_Window *window = (device ? device->windows : NULL);
    187     for (; window != NULL; window = window->next) {
    188         SDL_WindowData *driverdata = (SDL_WindowData *)window->driverdata;
    189         if (driverdata) {
    190             [driverdata->nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:)
    191                                                    withObject:[driverdata->nswindow contentView]
    192                                                 waitUntilDone:NO];
    193         }
    194     }
    195     return 0;
    196 }}
    197 
    198 static SDL_Window *
    199 SDL_FindWindowAtPoint(const int x, const int y)
    200 {
    201     const SDL_Point pt = { x, y };
    202     SDL_Window *i;
    203     for (i = SDL_GetVideoDevice()->windows; i; i = i->next) {
    204         const SDL_Rect r = { i->x, i->y, i->w, i->h };
    205         if (SDL_PointInRect(&pt, &r)) {
    206             return i;
    207         }
    208     }
    209 
    210     return NULL;
    211 }
    212 
    213 static int
    214 Cocoa_WarpMouseGlobal(int x, int y)
    215 {
    216     SDL_Mouse *mouse = SDL_GetMouse();
    217     if (mouse->focus) {
    218         SDL_WindowData *data = (SDL_WindowData *) mouse->focus->driverdata;
    219         if ([data->listener isMoving]) {
    220             DLog("Postponing warp, window being moved.");
    221             [data->listener setPendingMoveX:x Y:y];
    222             return 0;
    223         }
    224     }
    225     const CGPoint point = CGPointMake((float)x, (float)y);
    226 
    227     Cocoa_HandleMouseWarp(point.x, point.y);
    228 
    229     CGWarpMouseCursorPosition(point);
    230 
    231     /* CGWarpMouse causes a short delay by default, which is preventable by
    232      * Calling this directly after. CGSetLocalEventsSuppressionInterval can also
    233      * prevent it, but it's deprecated as of OS X 10.6.
    234      */
    235     if (!mouse->relative_mode) {
    236         CGAssociateMouseAndMouseCursorPosition(YES);
    237     }
    238 
    239     /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our
    240      * other implementations' APIs. Send what's appropriate.
    241      */
    242     if (!mouse->relative_mode) {
    243         SDL_Window *win = SDL_FindWindowAtPoint(x, y);
    244         SDL_SetMouseFocus(win);
    245         if (win) {
    246             SDL_assert(win == mouse->focus);
    247             SDL_SendMouseMotion(win, mouse->mouseID, 0, x - win->x, y - win->y);
    248         }
    249     }
    250 
    251     return 0;
    252 }
    253 
    254 static void
    255 Cocoa_WarpMouse(SDL_Window * window, int x, int y)
    256 {
    257     Cocoa_WarpMouseGlobal(x + window->x, y + window->y);
    258 }
    259 
    260 static int
    261 Cocoa_SetRelativeMouseMode(SDL_bool enabled)
    262 {
    263     /* We will re-apply the relative mode when the window gets focus, if it
    264      * doesn't have focus right now.
    265      */
    266     SDL_Window *window = SDL_GetMouseFocus();
    267     if (!window) {
    268       return 0;
    269     }
    270 
    271     /* We will re-apply the relative mode when the window finishes being moved,
    272      * if it is being moved right now.
    273      */
    274     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
    275     if ([data->listener isMoving]) {
    276         return 0;
    277     }
    278 
    279     CGError result;
    280     if (enabled) {
    281         DLog("Turning on.");
    282         result = CGAssociateMouseAndMouseCursorPosition(NO);
    283     } else {
    284         DLog("Turning off.");
    285         result = CGAssociateMouseAndMouseCursorPosition(YES);
    286     }
    287     if (result != kCGErrorSuccess) {
    288         return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
    289     }
    290 
    291     /* The hide/unhide calls are redundant most of the time, but they fix
    292      * https://bugzilla.libsdl.org/show_bug.cgi?id=2550
    293      */
    294     if (enabled) {
    295         [NSCursor hide];
    296     } else {
    297         [NSCursor unhide];
    298     }
    299     return 0;
    300 }
    301 
    302 static int
    303 Cocoa_CaptureMouse(SDL_Window *window)
    304 {
    305     /* our Cocoa event code already tracks the mouse outside the window,
    306         so all we have to do here is say "okay" and do what we always do. */
    307     return 0;
    308 }
    309 
    310 static Uint32
    311 Cocoa_GetGlobalMouseState(int *x, int *y)
    312 {
    313     const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons];
    314     const NSPoint cocoaLocation = [NSEvent mouseLocation];
    315     Uint32 retval = 0;
    316 
    317     *x = (int) cocoaLocation.x;
    318     *y = (int) (CGDisplayPixelsHigh(kCGDirectMainDisplay) - cocoaLocation.y);
    319 
    320     retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0;
    321     retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0;
    322     retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0;
    323     retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0;
    324     retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0;
    325 
    326     return retval;
    327 }
    328 
    329 int
    330 Cocoa_InitMouse(_THIS)
    331 {
    332     SDL_Mouse *mouse = SDL_GetMouse();
    333     SDL_MouseData *driverdata = (SDL_MouseData*) SDL_calloc(1, sizeof(SDL_MouseData));
    334     if (driverdata == NULL) {
    335         return SDL_OutOfMemory();
    336     }
    337 
    338     mouse->driverdata = driverdata;
    339     mouse->CreateCursor = Cocoa_CreateCursor;
    340     mouse->CreateSystemCursor = Cocoa_CreateSystemCursor;
    341     mouse->ShowCursor = Cocoa_ShowCursor;
    342     mouse->FreeCursor = Cocoa_FreeCursor;
    343     mouse->WarpMouse = Cocoa_WarpMouse;
    344     mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal;
    345     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
    346     mouse->CaptureMouse = Cocoa_CaptureMouse;
    347     mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState;
    348 
    349     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
    350 
    351     Cocoa_InitMouseEventTap(driverdata);
    352 
    353     const NSPoint location =  [NSEvent mouseLocation];
    354     driverdata->lastMoveX = location.x;
    355     driverdata->lastMoveY = location.y;
    356     return 0;
    357 }
    358 
    359 void
    360 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
    361 {
    362     switch ([event type]) {
    363         case NSEventTypeMouseMoved:
    364         case NSEventTypeLeftMouseDragged:
    365         case NSEventTypeRightMouseDragged:
    366         case NSEventTypeOtherMouseDragged:
    367             break;
    368 
    369         default:
    370             /* Ignore any other events. */
    371             return;
    372     }
    373 
    374     SDL_Mouse *mouse = SDL_GetMouse();
    375     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
    376     if (!driverdata) {
    377         return;  /* can happen when returning from fullscreen Space on shutdown */
    378     }
    379 
    380     SDL_MouseID mouseID = mouse ? mouse->mouseID : 0;
    381     const SDL_bool seenWarp = driverdata->seenWarp;
    382     driverdata->seenWarp = NO;
    383 
    384     const NSPoint location =  [NSEvent mouseLocation];
    385     const CGFloat lastMoveX = driverdata->lastMoveX;
    386     const CGFloat lastMoveY = driverdata->lastMoveY;
    387     driverdata->lastMoveX = location.x;
    388     driverdata->lastMoveY = location.y;
    389     DLog("Last seen mouse: (%g, %g)", location.x, location.y);
    390 
    391     /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
    392     if (!mouse->relative_mode) {
    393         return;
    394     }
    395 
    396     /* Ignore events that aren't inside the client area (i.e. title bar.) */
    397     if ([event window]) {
    398         NSRect windowRect = [[[event window] contentView] frame];
    399         if (!NSMouseInRect([event locationInWindow], windowRect, NO)) {
    400             return;
    401         }
    402     }
    403 
    404     float deltaX = [event deltaX];
    405     float deltaY = [event deltaY];
    406 
    407     if (seenWarp) {
    408         deltaX += (lastMoveX - driverdata->lastWarpX);
    409         deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY);
    410 
    411         DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY);
    412     }
    413 
    414     SDL_SendMouseMotion(mouse->focus, mouseID, 1, (int)deltaX, (int)deltaY);
    415 }
    416 
    417 void
    418 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
    419 {
    420     SDL_Mouse *mouse = SDL_GetMouse();
    421     if (!mouse) {
    422         return;
    423     }
    424 
    425     SDL_MouseID mouseID = mouse->mouseID;
    426     CGFloat x = -[event deltaX];
    427     CGFloat y = [event deltaY];
    428     SDL_MouseWheelDirection direction = SDL_MOUSEWHEEL_NORMAL;
    429 
    430     if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)]) {
    431         if ([event isDirectionInvertedFromDevice] == YES) {
    432             direction = SDL_MOUSEWHEEL_FLIPPED;
    433         }
    434     }
    435 
    436     if (x > 0) {
    437         x = SDL_ceil(x);
    438     } else if (x < 0) {
    439         x = SDL_floor(x);
    440     }
    441     if (y > 0) {
    442         y = SDL_ceil(y);
    443     } else if (y < 0) {
    444         y = SDL_floor(y);
    445     }
    446 
    447     SDL_SendMouseWheel(window, mouseID, x, y, direction);
    448 }
    449 
    450 void
    451 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y)
    452 {
    453     /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp,
    454      * since it gets included in the next movement event.
    455      */
    456     SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata;
    457     driverdata->lastWarpX = x;
    458     driverdata->lastWarpY = y;
    459     driverdata->seenWarp = SDL_TRUE;
    460 
    461     DLog("(%g, %g)", x, y);
    462 }
    463 
    464 void
    465 Cocoa_QuitMouse(_THIS)
    466 {
    467     SDL_Mouse *mouse = SDL_GetMouse();
    468     if (mouse) {
    469         if (mouse->driverdata) {
    470             Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
    471 
    472             SDL_free(mouse->driverdata);
    473             mouse->driverdata = NULL;
    474         }
    475     }
    476 }
    477 
    478 #endif /* SDL_VIDEO_DRIVER_COCOA */
    479 
    480 /* vi: set ts=4 sw=4 expandtab: */