src/video/cocoa/SDL_cocoamouse.m
author Ryan C. Gordon <icculus@icculus.org>
Wed, 25 Jun 2014 17:06:12 -0400
changeset 8953 dc80dc0bd22e
parent 8862 523abf9a4fb4
parent 8952 4bb098814ec4
child 8986 1c81316ac642
permissions -rw-r--r--
Merged Ryan's SDL-gui-backend branch.

Adds three APIs, and implements them on X11, Cocoa, and Windows:

- SDL_CaptureMouse()
- SDL_GetGlobalMouseState()
- SDL_SetWindowHitTest()
     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 void
   245 Cocoa_WarpMouseGlobal(int x, int y)
   246 {
   247     SDL_Mouse *mouse = SDL_GetMouse();
   248     CGPoint point = CGPointMake((float)x, (float)y);
   249 
   250     Cocoa_HandleMouseWarp(point.x, point.y);
   251 
   252     /* According to the docs, this was deprecated in 10.6, but it's still
   253      * around. The substitute requires a CGEventSource, but I'm not entirely
   254      * sure how we'd procure the right one for this event.
   255      */
   256     CGSetLocalEventsSuppressionInterval(0.0);
   257     CGWarpMouseCursorPosition(point);
   258     CGSetLocalEventsSuppressionInterval(0.25);
   259 
   260     if (!mouse->relative_mode && mouse->focus) {
   261         /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our
   262          * other implementations' APIs.
   263          */
   264         SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 0, x - mouse->focus->x, y - mouse->focus->y);
   265     }
   266 }
   267 
   268 static int
   269 Cocoa_SetRelativeMouseMode(SDL_bool enabled)
   270 {
   271     /* We will re-apply the relative mode when the window gets focus, if it
   272      * doesn't have focus right now.
   273      */
   274     SDL_Window *window = SDL_GetMouseFocus();
   275     if (!window) {
   276       return 0;
   277     }
   278 
   279     /* We will re-apply the relative mode when the window finishes being moved,
   280      * if it is being moved right now.
   281      */
   282     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   283     if ([data->listener isMoving]) {
   284         return 0;
   285     }
   286 
   287     CGError result;
   288     if (enabled) {
   289         DLog("Turning on.");
   290         result = CGAssociateMouseAndMouseCursorPosition(NO);
   291     } else {
   292         DLog("Turning off.");
   293         result = CGAssociateMouseAndMouseCursorPosition(YES);
   294     }
   295     if (result != kCGErrorSuccess) {
   296         return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
   297     }
   298 
   299     /* The hide/unhide calls are redundant most of the time, but they fix
   300      * https://bugzilla.libsdl.org/show_bug.cgi?id=2550
   301      */
   302     if (enabled) {
   303         [NSCursor hide];
   304     } else {
   305         [NSCursor unhide];
   306     }
   307     return 0;
   308 }
   309 
   310 static int
   311 Cocoa_CaptureMouse(SDL_Window *window)
   312 {
   313     /* our Cocoa event code already tracks the mouse outside the window,
   314         so all we have to do here is say "okay" and do what we always do. */
   315     return 0;
   316 }
   317 
   318 static Uint32
   319 Cocoa_GetGlobalMouseState(int *x, int *y)
   320 {
   321     const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons];
   322     const NSPoint cocoaLocation = [NSEvent mouseLocation];
   323     Uint32 retval = 0;
   324 
   325     for (NSScreen *screen in [NSScreen screens]) {
   326         NSRect frame = [screen frame];
   327         if (NSPointInRect(cocoaLocation, frame)) {
   328             *x = (int) cocoaLocation.x;
   329             *y = (int) ((frame.origin.y + frame.size.height) - cocoaLocation.y);
   330             break;
   331         }
   332     }
   333 
   334     retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0;
   335     retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0;
   336     retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0;
   337     retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0;
   338     retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0;
   339 
   340     return retval;
   341 }
   342 
   343 void
   344 Cocoa_InitMouse(_THIS)
   345 {
   346     SDL_Mouse *mouse = SDL_GetMouse();
   347 
   348     mouse->driverdata = SDL_calloc(1, sizeof(SDL_MouseData));
   349 
   350     mouse->CreateCursor = Cocoa_CreateCursor;
   351     mouse->CreateSystemCursor = Cocoa_CreateSystemCursor;
   352     mouse->ShowCursor = Cocoa_ShowCursor;
   353     mouse->FreeCursor = Cocoa_FreeCursor;
   354     mouse->WarpMouse = Cocoa_WarpMouse;
   355     mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal;
   356     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
   357     mouse->CaptureMouse = Cocoa_CaptureMouse;
   358     mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState;
   359 
   360     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
   361 
   362     Cocoa_InitMouseEventTap(mouse->driverdata);
   363 
   364     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   365     const NSPoint location =  [NSEvent mouseLocation];
   366     driverdata->lastMoveX = location.x;
   367     driverdata->lastMoveY = location.y;
   368 }
   369 
   370 void
   371 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
   372 {
   373     switch ([event type])
   374     {
   375         case NSMouseMoved:
   376         case NSLeftMouseDragged:
   377         case NSRightMouseDragged:
   378         case NSOtherMouseDragged:
   379             break;
   380 
   381         default:
   382             /* Ignore any other events. */
   383             return;
   384     }
   385 
   386     SDL_Mouse *mouse = SDL_GetMouse();
   387     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   388     if (!driverdata) {
   389         return;  /* can happen when returning from fullscreen Space on shutdown */
   390     }
   391 
   392     const SDL_bool seenWarp = driverdata->seenWarp;
   393     driverdata->seenWarp = NO;
   394 
   395     const NSPoint location =  [NSEvent mouseLocation];
   396     const CGFloat lastMoveX = driverdata->lastMoveX;
   397     const CGFloat lastMoveY = driverdata->lastMoveY;
   398     driverdata->lastMoveX = location.x;
   399     driverdata->lastMoveY = location.y;
   400     DLog("Last seen mouse: (%g, %g)", location.x, location.y);
   401 
   402     /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
   403     if (!mouse->relative_mode) {
   404         return;
   405     }
   406 
   407     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   408     if ([event window]) {
   409         NSRect windowRect = [[[event window] contentView] frame];
   410         if (!NSPointInRect([event locationInWindow], windowRect)) {
   411             return;
   412         }
   413     }
   414 
   415     float deltaX = [event deltaX];
   416     float deltaY = [event deltaY];
   417 
   418     if (seenWarp)
   419     {
   420         deltaX += (lastMoveX - driverdata->lastWarpX);
   421         deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY);
   422 
   423         DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY);
   424     }
   425 
   426     SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 1, (int)deltaX, (int)deltaY);
   427 }
   428 
   429 void
   430 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
   431 {
   432     SDL_Mouse *mouse = SDL_GetMouse();
   433 
   434     float x = -[event deltaX];
   435     float y = [event deltaY];
   436 
   437     if (x > 0) {
   438         x += 0.9f;
   439     } else if (x < 0) {
   440         x -= 0.9f;
   441     }
   442     if (y > 0) {
   443         y += 0.9f;
   444     } else if (y < 0) {
   445         y -= 0.9f;
   446     }
   447     SDL_SendMouseWheel(window, mouse->mouseID, (int)x, (int)y);
   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 
   473         SDL_free(mouse->driverdata);
   474     }
   475 }
   476 
   477 #endif /* SDL_VIDEO_DRIVER_COCOA */
   478 
   479 /* vi: set ts=4 sw=4 expandtab: */