src/video/cocoa/SDL_cocoamouse.m
author Sam Lantinga <slouken@libsdl.org>
Wed, 04 Jun 2014 10:56:17 -0700
changeset 8816 7e2f5d693149
parent 8815 c6d0a457f3b2
child 8862 523abf9a4fb4
permissions -rw-r--r--
Fixed Mac OS X build
     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 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->WarpMouseGlobal = Cocoa_WarpMouseGlobal;
   323     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
   324 
   325     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
   326 
   327     Cocoa_InitMouseEventTap(mouse->driverdata);
   328 
   329     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   330     const NSPoint location =  [NSEvent mouseLocation];
   331     driverdata->lastMoveX = location.x;
   332     driverdata->lastMoveY = location.y;
   333 }
   334 
   335 void
   336 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
   337 {
   338     switch ([event type])
   339     {
   340         case NSMouseMoved:
   341         case NSLeftMouseDragged:
   342         case NSRightMouseDragged:
   343         case NSOtherMouseDragged:
   344             break;
   345 
   346         default:
   347             /* Ignore any other events. */
   348             return;
   349     }
   350 
   351     SDL_Mouse *mouse = SDL_GetMouse();
   352 
   353     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   354     const SDL_bool seenWarp = driverdata->seenWarp;
   355     driverdata->seenWarp = NO;
   356 
   357     const NSPoint location =  [NSEvent mouseLocation];
   358     const CGFloat lastMoveX = driverdata->lastMoveX;
   359     const CGFloat lastMoveY = driverdata->lastMoveY;
   360     driverdata->lastMoveX = location.x;
   361     driverdata->lastMoveY = location.y;
   362     DLog("Last seen mouse: (%g, %g)", location.x, location.y);
   363 
   364     /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
   365     if (!mouse->relative_mode) {
   366         return;
   367     }
   368 
   369     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   370     if ([event window]) {
   371         NSRect windowRect = [[[event window] contentView] frame];
   372         if (!NSPointInRect([event locationInWindow], windowRect)) {
   373             return;
   374         }
   375     }
   376 
   377     float deltaX = [event deltaX];
   378     float deltaY = [event deltaY];
   379 
   380     if (seenWarp)
   381     {
   382         deltaX += (lastMoveX - driverdata->lastWarpX);
   383         deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY);
   384 
   385         DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY);
   386     }
   387 
   388     SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 1, (int)deltaX, (int)deltaY);
   389 }
   390 
   391 void
   392 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
   393 {
   394     SDL_Mouse *mouse = SDL_GetMouse();
   395 
   396     float x = -[event deltaX];
   397     float y = [event deltaY];
   398 
   399     if (x > 0) {
   400         x += 0.9f;
   401     } else if (x < 0) {
   402         x -= 0.9f;
   403     }
   404     if (y > 0) {
   405         y += 0.9f;
   406     } else if (y < 0) {
   407         y -= 0.9f;
   408     }
   409     SDL_SendMouseWheel(window, mouse->mouseID, (int)x, (int)y);
   410 }
   411 
   412 void
   413 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y)
   414 {
   415     /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp,
   416      * since it gets included in the next movement event.
   417      */
   418     SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata;
   419     driverdata->lastWarpX = x;
   420     driverdata->lastWarpY = y;
   421     driverdata->seenWarp = SDL_TRUE;
   422 
   423     DLog("(%g, %g)", x, y);
   424 }
   425 
   426 void
   427 Cocoa_QuitMouse(_THIS)
   428 {
   429     SDL_Mouse *mouse = SDL_GetMouse();
   430     if (mouse) {
   431         if (mouse->driverdata) {
   432             Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
   433         }
   434 
   435         SDL_free(mouse->driverdata);
   436     }
   437 }
   438 
   439 #endif /* SDL_VIDEO_DRIVER_COCOA */
   440 
   441 /* vi: set ts=4 sw=4 expandtab: */