src/video/cocoa/SDL_cocoamouse.m
author Sam Lantinga <slouken@libsdl.org>
Sat, 02 Jan 2016 10:10:34 -0800
changeset 9998 f67cf37e9cd4
parent 9807 57b448735f48
child 10122 f0645cd8fd39
permissions -rw-r--r--
Updated copyright to 2016
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 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 
    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     /* According to the docs, this was deprecated in 10.6, but it's still
   230      * around. The substitute requires a CGEventSource, but I'm not entirely
   231      * sure how we'd procure the right one for this event.
   232      */
   233     CGSetLocalEventsSuppressionInterval(0.0);
   234     CGWarpMouseCursorPosition(point);
   235     CGSetLocalEventsSuppressionInterval(0.25);
   236 
   237     /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our
   238      * other implementations' APIs. Send what's appropriate.
   239      */
   240     if (!mouse->relative_mode) {
   241         SDL_Window *win = SDL_FindWindowAtPoint(x, y);
   242         SDL_SetMouseFocus(win);
   243         if (win) {
   244             SDL_assert(win == mouse->focus);
   245             SDL_SendMouseMotion(win, mouse->mouseID, 0, x - win->x, y - win->y);
   246         }
   247     }
   248 
   249     return 0;
   250 }
   251 
   252 static void
   253 Cocoa_WarpMouse(SDL_Window * window, int x, int y)
   254 {
   255     Cocoa_WarpMouseGlobal(x + window->x, y + window->y);
   256 }
   257 
   258 static int
   259 Cocoa_SetRelativeMouseMode(SDL_bool enabled)
   260 {
   261     /* We will re-apply the relative mode when the window gets focus, if it
   262      * doesn't have focus right now.
   263      */
   264     SDL_Window *window = SDL_GetMouseFocus();
   265     if (!window) {
   266       return 0;
   267     }
   268 
   269     /* We will re-apply the relative mode when the window finishes being moved,
   270      * if it is being moved right now.
   271      */
   272     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   273     if ([data->listener isMoving]) {
   274         return 0;
   275     }
   276 
   277     CGError result;
   278     if (enabled) {
   279         DLog("Turning on.");
   280         result = CGAssociateMouseAndMouseCursorPosition(NO);
   281     } else {
   282         DLog("Turning off.");
   283         result = CGAssociateMouseAndMouseCursorPosition(YES);
   284     }
   285     if (result != kCGErrorSuccess) {
   286         return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
   287     }
   288 
   289     /* The hide/unhide calls are redundant most of the time, but they fix
   290      * https://bugzilla.libsdl.org/show_bug.cgi?id=2550
   291      */
   292     if (enabled) {
   293         [NSCursor hide];
   294     } else {
   295         [NSCursor unhide];
   296     }
   297     return 0;
   298 }
   299 
   300 static int
   301 Cocoa_CaptureMouse(SDL_Window *window)
   302 {
   303     /* our Cocoa event code already tracks the mouse outside the window,
   304         so all we have to do here is say "okay" and do what we always do. */
   305     return 0;
   306 }
   307 
   308 static Uint32
   309 Cocoa_GetGlobalMouseState(int *x, int *y)
   310 {
   311     const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons];
   312     const NSPoint cocoaLocation = [NSEvent mouseLocation];
   313     Uint32 retval = 0;
   314 
   315     for (NSScreen *screen in [NSScreen screens]) {
   316         NSRect frame = [screen frame];
   317         if (NSPointInRect(cocoaLocation, frame)) {
   318             *x = (int) cocoaLocation.x;
   319             *y = (int) ((frame.origin.y + frame.size.height) - cocoaLocation.y);
   320             break;
   321         }
   322     }
   323 
   324     retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0;
   325     retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0;
   326     retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0;
   327     retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0;
   328     retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0;
   329 
   330     return retval;
   331 }
   332 
   333 void
   334 Cocoa_InitMouse(_THIS)
   335 {
   336     SDL_Mouse *mouse = SDL_GetMouse();
   337 
   338     mouse->driverdata = SDL_calloc(1, sizeof(SDL_MouseData));
   339 
   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(mouse->driverdata);
   353 
   354     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   355     const NSPoint location =  [NSEvent mouseLocation];
   356     driverdata->lastMoveX = location.x;
   357     driverdata->lastMoveY = location.y;
   358 }
   359 
   360 void
   361 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
   362 {
   363     switch ([event type]) {
   364         case NSMouseMoved:
   365         case NSLeftMouseDragged:
   366         case NSRightMouseDragged:
   367         case NSOtherMouseDragged:
   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     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 (!NSPointInRect([event locationInWindow], windowRect)) {
   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, mouse->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 
   422     float x = -[event deltaX];
   423     float y = [event deltaY];
   424     SDL_MouseWheelDirection direction = SDL_MOUSEWHEEL_NORMAL;
   425 
   426     if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)]) {
   427         if ([event isDirectionInvertedFromDevice] == YES) {
   428             direction = SDL_MOUSEWHEEL_FLIPPED;
   429         }
   430     }
   431 
   432     if (x > 0) {
   433         x += 0.9f;
   434     } else if (x < 0) {
   435         x -= 0.9f;
   436     }
   437     if (y > 0) {
   438         y += 0.9f;
   439     } else if (y < 0) {
   440         y -= 0.9f;
   441     }
   442     SDL_SendMouseWheel(window, mouse->mouseID, (int)x, (int)y, direction);
   443 }
   444 
   445 void
   446 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y)
   447 {
   448     /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp,
   449      * since it gets included in the next movement event.
   450      */
   451     SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata;
   452     driverdata->lastWarpX = x;
   453     driverdata->lastWarpY = y;
   454     driverdata->seenWarp = SDL_TRUE;
   455 
   456     DLog("(%g, %g)", x, y);
   457 }
   458 
   459 void
   460 Cocoa_QuitMouse(_THIS)
   461 {
   462     SDL_Mouse *mouse = SDL_GetMouse();
   463     if (mouse) {
   464         if (mouse->driverdata) {
   465             Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
   466         }
   467 
   468         SDL_free(mouse->driverdata);
   469     }
   470 }
   471 
   472 #endif /* SDL_VIDEO_DRIVER_COCOA */
   473 
   474 /* vi: set ts=4 sw=4 expandtab: */