src/video/cocoa/SDL_cocoamouse.m
author Ryan C. Gordon <icculus@icculus.org>
Sun, 31 May 2015 21:43:36 -0400
changeset 9692 1982dc994254
parent 9691 c0ca56ba6749
child 9807 57b448735f48
permissions -rw-r--r--
Cocoa: deal with mouse focus when warping the cursor from outside a window.

Otherwise, you might not get appropriate mouse enter/leave events.

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