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