src/video/cocoa/SDL_cocoamouse.m
author Jørgen P. Tjernø <jorgen@valvesoftware.com>
Wed, 26 Feb 2014 11:35:02 -0800
changeset 8261 841b66e4397a
parent 8260 028ed8da2524
child 8262 acc0dcf38b3d
permissions -rw-r--r--
Mac: Redo cursor warp handling.

This fixes bugs related to getting unnaturally large xrel/yrel for
SDL_MOUSEMOTION after warps and enabling / disabling relative mode.

Bug: https://bugzilla.libsdl.org/show_bug.cgi?id=1836
     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     CGError result;
   248 
   249     if (enabled) {
   250         DLog("Turning on.");
   251         result = CGAssociateMouseAndMouseCursorPosition(NO);
   252     } else {
   253         DLog("Turning off.");
   254         result = CGAssociateMouseAndMouseCursorPosition(YES);
   255     }
   256     if (result != kCGErrorSuccess) {
   257         return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
   258     }
   259     return 0;
   260 }
   261 
   262 void
   263 Cocoa_InitMouse(_THIS)
   264 {
   265     SDL_Mouse *mouse = SDL_GetMouse();
   266 
   267     mouse->driverdata = SDL_calloc(1, sizeof(SDL_MouseData));
   268 
   269     mouse->CreateCursor = Cocoa_CreateCursor;
   270     mouse->CreateSystemCursor = Cocoa_CreateSystemCursor;
   271     mouse->ShowCursor = Cocoa_ShowCursor;
   272     mouse->FreeCursor = Cocoa_FreeCursor;
   273     mouse->WarpMouse = Cocoa_WarpMouse;
   274     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
   275 
   276     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
   277 
   278     Cocoa_InitMouseEventTap(mouse->driverdata);
   279 
   280     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   281     const NSPoint location =  [NSEvent mouseLocation];
   282     driverdata->lastMoveX = location.x;
   283     driverdata->lastMoveY = location.y;
   284 }
   285 
   286 void
   287 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
   288 {
   289     switch ([event type])
   290     {
   291         case NSMouseMoved:
   292         case NSLeftMouseDragged:
   293         case NSRightMouseDragged:
   294         case NSOtherMouseDragged:
   295             break;
   296 
   297         default:
   298             /* Ignore any other events. */
   299             return;
   300     }
   301 
   302     SDL_Mouse *mouse = SDL_GetMouse();
   303 
   304     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   305     const SDL_bool seenWarp = driverdata->seenWarp;
   306     driverdata->seenWarp = NO;
   307 
   308     const NSPoint location =  [NSEvent mouseLocation];
   309     const CGFloat lastMoveX = driverdata->lastMoveX;
   310     const CGFloat lastMoveY = driverdata->lastMoveY;
   311     driverdata->lastMoveX = location.x;
   312     driverdata->lastMoveY = location.y;
   313     DLog("Last seen mouse: (%g, %g)", location.x, location.y);
   314 
   315     /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
   316     if (!mouse->relative_mode) {
   317         return;
   318     }
   319 
   320     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   321     if ([event window]) {
   322         NSRect windowRect = [[[event window] contentView] frame];
   323         if (!NSPointInRect([event locationInWindow], windowRect)) {
   324             return;
   325         }
   326     }
   327 
   328     float deltaX = [event deltaX];
   329     float deltaY = [event deltaY];
   330 
   331     if (seenWarp)
   332     {
   333         deltaX += (lastMoveX - driverdata->lastWarpX);
   334         deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY);
   335 
   336         DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY);
   337     }
   338 
   339     SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 1, (int)deltaX, (int)deltaY);
   340 }
   341 
   342 void
   343 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
   344 {
   345     SDL_Mouse *mouse = SDL_GetMouse();
   346 
   347     float x = -[event deltaX];
   348     float y = [event deltaY];
   349 
   350     if (x > 0) {
   351         x += 0.9f;
   352     } else if (x < 0) {
   353         x -= 0.9f;
   354     }
   355     if (y > 0) {
   356         y += 0.9f;
   357     } else if (y < 0) {
   358         y -= 0.9f;
   359     }
   360     SDL_SendMouseWheel(window, mouse->mouseID, (int)x, (int)y);
   361 }
   362 
   363 void
   364 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y)
   365 {
   366     /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp,
   367      * since it gets included in the next movement event.
   368      */
   369     SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata;
   370     driverdata->lastWarpX = x;
   371     driverdata->lastWarpY = y;
   372     driverdata->seenWarp = SDL_TRUE;
   373 
   374     DLog("(%g, %g)", x, y);
   375 }
   376 
   377 void
   378 Cocoa_QuitMouse(_THIS)
   379 {
   380     SDL_Mouse *mouse = SDL_GetMouse();
   381     if (mouse) {
   382         if (mouse->driverdata) {
   383             Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
   384         }
   385 
   386         SDL_free(mouse->driverdata);
   387     }
   388 }
   389 
   390 #endif /* SDL_VIDEO_DRIVER_COCOA */
   391 
   392 /* vi: set ts=4 sw=4 expandtab: */