src/video/cocoa/SDL_cocoamouse.m
author Ryan C. Gordon <icculus@icculus.org>
Wed, 25 Jun 2014 16:16:55 -0400
changeset 8952 4bb098814ec4
parent 8951 692ba71b9c04
child 8953 dc80dc0bd22e
permissions -rw-r--r--
Changed SDL_GetAbsoluteMouseState() to SDL_GetGlobalMouseState().

This matches naming conventions in the main repository, between
SDL_GetRelativeMouseState() and SDL_WarpMouseGlobal().
     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_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 {
    70     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    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     [pool release];
    85 
    86     return cursor;
    87 }
    88 
    89 static SDL_Cursor *
    90 Cocoa_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y)
    91 {
    92     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    93     NSImage *nsimage;
    94     NSCursor *nscursor = NULL;
    95     SDL_Cursor *cursor = NULL;
    96 
    97     nsimage = Cocoa_CreateImage(surface);
    98     if (nsimage) {
    99         nscursor = [[NSCursor alloc] initWithImage: nsimage hotSpot: NSMakePoint(hot_x, hot_y)];
   100     }
   101 
   102     if (nscursor) {
   103         cursor = SDL_calloc(1, sizeof(*cursor));
   104         if (cursor) {
   105             cursor->driverdata = nscursor;
   106         } else {
   107             [nscursor release];
   108         }
   109     }
   110 
   111     [pool release];
   112 
   113     return cursor;
   114 }
   115 
   116 static SDL_Cursor *
   117 Cocoa_CreateSystemCursor(SDL_SystemCursor id)
   118 {
   119     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   120     NSCursor *nscursor = NULL;
   121     SDL_Cursor *cursor = NULL;
   122 
   123     switch(id)
   124     {
   125     case SDL_SYSTEM_CURSOR_ARROW:
   126         nscursor = [NSCursor arrowCursor];
   127         break;
   128     case SDL_SYSTEM_CURSOR_IBEAM:
   129         nscursor = [NSCursor IBeamCursor];
   130         break;
   131     case SDL_SYSTEM_CURSOR_WAIT:
   132         nscursor = [NSCursor arrowCursor];
   133         break;
   134     case SDL_SYSTEM_CURSOR_CROSSHAIR:
   135         nscursor = [NSCursor crosshairCursor];
   136         break;
   137     case SDL_SYSTEM_CURSOR_WAITARROW:
   138         nscursor = [NSCursor arrowCursor];
   139         break;
   140     case SDL_SYSTEM_CURSOR_SIZENWSE:
   141     case SDL_SYSTEM_CURSOR_SIZENESW:
   142         nscursor = [NSCursor closedHandCursor];
   143         break;
   144     case SDL_SYSTEM_CURSOR_SIZEWE:
   145         nscursor = [NSCursor resizeLeftRightCursor];
   146         break;
   147     case SDL_SYSTEM_CURSOR_SIZENS:
   148         nscursor = [NSCursor resizeUpDownCursor];
   149         break;
   150     case SDL_SYSTEM_CURSOR_SIZEALL:
   151         nscursor = [NSCursor closedHandCursor];
   152         break;
   153     case SDL_SYSTEM_CURSOR_NO:
   154         nscursor = [NSCursor operationNotAllowedCursor];
   155         break;
   156     case SDL_SYSTEM_CURSOR_HAND:
   157         nscursor = [NSCursor pointingHandCursor];
   158         break;
   159     default:
   160         SDL_assert(!"Unknown system cursor");
   161         return NULL;
   162     }
   163 
   164     if (nscursor) {
   165         cursor = SDL_calloc(1, sizeof(*cursor));
   166         if (cursor) {
   167             /* We'll free it later, so retain it here */
   168             [nscursor retain];
   169             cursor->driverdata = nscursor;
   170         }
   171     }
   172 
   173     [pool release];
   174 
   175     return cursor;
   176 }
   177 
   178 static void
   179 Cocoa_FreeCursor(SDL_Cursor * cursor)
   180 {
   181     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   182     NSCursor *nscursor = (NSCursor *)cursor->driverdata;
   183 
   184     [nscursor release];
   185     SDL_free(cursor);
   186 
   187     [pool release];
   188 }
   189 
   190 static int
   191 Cocoa_ShowCursor(SDL_Cursor * cursor)
   192 {
   193     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
   194 
   195     SDL_VideoDevice *device = SDL_GetVideoDevice();
   196     SDL_Window *window = (device ? device->windows : NULL);
   197     for (; window != NULL; window = window->next) {
   198         SDL_WindowData *driverdata = (SDL_WindowData *)window->driverdata;
   199         if (driverdata) {
   200             [driverdata->nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:)
   201                                                    withObject:[driverdata->nswindow contentView]
   202                                                 waitUntilDone:NO];
   203         }
   204     }
   205 
   206     [pool release];
   207 
   208     return 0;
   209 }
   210 
   211 static void
   212 Cocoa_WarpMouse(SDL_Window * window, int x, int y)
   213 {
   214     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   215     if ([data->listener isMoving])
   216     {
   217         DLog("Postponing warp, window being moved.");
   218         [data->listener setPendingMoveX:x
   219                                       Y:y];
   220         return;
   221     }
   222 
   223     SDL_Mouse *mouse = SDL_GetMouse();
   224     CGPoint point = CGPointMake(x + (float)window->x, y + (float)window->y);
   225 
   226     Cocoa_HandleMouseWarp(point.x, point.y);
   227 
   228     /* According to the docs, this was deprecated in 10.6, but it's still
   229      * around. The substitute requires a CGEventSource, but I'm not entirely
   230      * sure how we'd procure the right one for this event.
   231      */
   232     CGSetLocalEventsSuppressionInterval(0.0);
   233     CGWarpMouseCursorPosition(point);
   234     CGSetLocalEventsSuppressionInterval(0.25);
   235 
   236     if (!mouse->relative_mode) {
   237         /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our
   238          * other implementations' APIs.
   239          */
   240         SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 0, x, y);
   241     }
   242 }
   243 
   244 static int
   245 Cocoa_SetRelativeMouseMode(SDL_bool enabled)
   246 {
   247     /* We will re-apply the relative mode when the window gets focus, if it
   248      * doesn't have focus right now.
   249      */
   250     SDL_Window *window = SDL_GetMouseFocus();
   251     if (!window) {
   252       return 0;
   253     }
   254 
   255     /* We will re-apply the relative mode when the window finishes being moved,
   256      * if it is being moved right now.
   257      */
   258     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   259     if ([data->listener isMoving]) {
   260         return 0;
   261     }
   262 
   263     CGError result;
   264     if (enabled) {
   265         DLog("Turning on.");
   266         result = CGAssociateMouseAndMouseCursorPosition(NO);
   267     } else {
   268         DLog("Turning off.");
   269         result = CGAssociateMouseAndMouseCursorPosition(YES);
   270     }
   271     if (result != kCGErrorSuccess) {
   272         return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
   273     }
   274     return 0;
   275 }
   276 
   277 static int
   278 Cocoa_CaptureMouse(SDL_Window *window)
   279 {
   280     /* our Cocoa event code already tracks the mouse outside the window,
   281         so all we have to do here is say "okay" and do what we always do. */
   282     return 0;
   283 }
   284 
   285 static Uint32
   286 Cocoa_GetGlobalMouseState(int *x, int *y)
   287 {
   288     const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons];
   289     const NSPoint cocoaLocation = [NSEvent mouseLocation];
   290     Uint32 retval = 0;
   291 
   292     for (NSScreen *screen in [NSScreen screens]) {
   293         NSRect frame = [screen frame];
   294         if (NSPointInRect(cocoaLocation, frame)) {
   295             *x = (int) cocoaLocation.x;
   296             *y = (int) ((frame.origin.y + frame.size.height) - cocoaLocation.y);
   297             break;
   298         }
   299     }
   300 
   301     retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0;
   302     retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0;
   303     retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0;
   304     retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0;
   305     retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0;
   306 
   307     return retval;
   308 }
   309 
   310 void
   311 Cocoa_InitMouse(_THIS)
   312 {
   313     SDL_Mouse *mouse = SDL_GetMouse();
   314 
   315     mouse->driverdata = SDL_calloc(1, sizeof(SDL_MouseData));
   316 
   317     mouse->CreateCursor = Cocoa_CreateCursor;
   318     mouse->CreateSystemCursor = Cocoa_CreateSystemCursor;
   319     mouse->ShowCursor = Cocoa_ShowCursor;
   320     mouse->FreeCursor = Cocoa_FreeCursor;
   321     mouse->WarpMouse = Cocoa_WarpMouse;
   322     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
   323     mouse->CaptureMouse = Cocoa_CaptureMouse;
   324     mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState;
   325 
   326     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
   327 
   328     Cocoa_InitMouseEventTap(mouse->driverdata);
   329 
   330     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   331     const NSPoint location =  [NSEvent mouseLocation];
   332     driverdata->lastMoveX = location.x;
   333     driverdata->lastMoveY = location.y;
   334 }
   335 
   336 void
   337 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
   338 {
   339     switch ([event type])
   340     {
   341         case NSMouseMoved:
   342         case NSLeftMouseDragged:
   343         case NSRightMouseDragged:
   344         case NSOtherMouseDragged:
   345             break;
   346 
   347         default:
   348             /* Ignore any other events. */
   349             return;
   350     }
   351 
   352     SDL_Mouse *mouse = SDL_GetMouse();
   353 
   354     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   355     const SDL_bool seenWarp = driverdata->seenWarp;
   356     driverdata->seenWarp = NO;
   357 
   358     const NSPoint location =  [NSEvent mouseLocation];
   359     const CGFloat lastMoveX = driverdata->lastMoveX;
   360     const CGFloat lastMoveY = driverdata->lastMoveY;
   361     driverdata->lastMoveX = location.x;
   362     driverdata->lastMoveY = location.y;
   363     DLog("Last seen mouse: (%g, %g)", location.x, location.y);
   364 
   365     /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
   366     if (!mouse->relative_mode) {
   367         return;
   368     }
   369 
   370     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   371     if ([event window]) {
   372         NSRect windowRect = [[[event window] contentView] frame];
   373         if (!NSPointInRect([event locationInWindow], windowRect)) {
   374             return;
   375         }
   376     }
   377 
   378     float deltaX = [event deltaX];
   379     float deltaY = [event deltaY];
   380 
   381     if (seenWarp)
   382     {
   383         deltaX += (lastMoveX - driverdata->lastWarpX);
   384         deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY);
   385 
   386         DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY);
   387     }
   388 
   389     SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 1, (int)deltaX, (int)deltaY);
   390 }
   391 
   392 void
   393 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
   394 {
   395     SDL_Mouse *mouse = SDL_GetMouse();
   396 
   397     float x = -[event deltaX];
   398     float y = [event deltaY];
   399 
   400     if (x > 0) {
   401         x += 0.9f;
   402     } else if (x < 0) {
   403         x -= 0.9f;
   404     }
   405     if (y > 0) {
   406         y += 0.9f;
   407     } else if (y < 0) {
   408         y -= 0.9f;
   409     }
   410     SDL_SendMouseWheel(window, mouse->mouseID, (int)x, (int)y);
   411 }
   412 
   413 void
   414 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y)
   415 {
   416     /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp,
   417      * since it gets included in the next movement event.
   418      */
   419     SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata;
   420     driverdata->lastWarpX = x;
   421     driverdata->lastWarpY = y;
   422     driverdata->seenWarp = SDL_TRUE;
   423 
   424     DLog("(%g, %g)", x, y);
   425 }
   426 
   427 void
   428 Cocoa_QuitMouse(_THIS)
   429 {
   430     SDL_Mouse *mouse = SDL_GetMouse();
   431     if (mouse) {
   432         if (mouse->driverdata) {
   433             Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
   434         }
   435 
   436         SDL_free(mouse->driverdata);
   437     }
   438 }
   439 
   440 #endif /* SDL_VIDEO_DRIVER_COCOA */
   441 
   442 /* vi: set ts=4 sw=4 expandtab: */