src/video/cocoa/SDL_cocoamouse.m
author Ryan C. Gordon <icculus@icculus.org>
Sun, 31 May 2015 13:58:36 -0400
changeset 9691 c0ca56ba6749
parent 9619 b94b6d0bff0f
child 9692 1982dc994254
permissions -rw-r--r--
Cocoa: send a MOUSEMOTION event when warping cursor from outside the window.

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