src/video/cocoa/SDL_cocoamouse.m
author Edward Rudd <urkle@outoforder.cc>
Sun, 23 Nov 2014 21:09:54 -0500
changeset 9257 6f41196c2d6b
parent 9087 eef2e43f60d1
child 9619 b94b6d0bff0f
permissions -rw-r--r--
add in support for passing down the "natural" (or flipped) scrolling direction in the MouseWheelEvent event
     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 { @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     Cocoa_WarpMouseGlobal(x + window->x, y + window->y);
   234 }
   235 
   236 static int
   237 Cocoa_SetRelativeMouseMode(SDL_bool enabled)
   238 {
   239     /* We will re-apply the relative mode when the window gets focus, if it
   240      * doesn't have focus right now.
   241      */
   242     SDL_Window *window = SDL_GetMouseFocus();
   243     if (!window) {
   244       return 0;
   245     }
   246 
   247     /* We will re-apply the relative mode when the window finishes being moved,
   248      * if it is being moved right now.
   249      */
   250     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   251     if ([data->listener isMoving]) {
   252         return 0;
   253     }
   254 
   255     CGError result;
   256     if (enabled) {
   257         DLog("Turning on.");
   258         result = CGAssociateMouseAndMouseCursorPosition(NO);
   259     } else {
   260         DLog("Turning off.");
   261         result = CGAssociateMouseAndMouseCursorPosition(YES);
   262     }
   263     if (result != kCGErrorSuccess) {
   264         return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed");
   265     }
   266 
   267     /* The hide/unhide calls are redundant most of the time, but they fix
   268      * https://bugzilla.libsdl.org/show_bug.cgi?id=2550
   269      */
   270     if (enabled) {
   271         [NSCursor hide];
   272     } else {
   273         [NSCursor unhide];
   274     }
   275     return 0;
   276 }
   277 
   278 static int
   279 Cocoa_CaptureMouse(SDL_Window *window)
   280 {
   281     /* our Cocoa event code already tracks the mouse outside the window,
   282         so all we have to do here is say "okay" and do what we always do. */
   283     return 0;
   284 }
   285 
   286 static Uint32
   287 Cocoa_GetGlobalMouseState(int *x, int *y)
   288 {
   289     const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons];
   290     const NSPoint cocoaLocation = [NSEvent mouseLocation];
   291     Uint32 retval = 0;
   292 
   293     for (NSScreen *screen in [NSScreen screens]) {
   294         NSRect frame = [screen frame];
   295         if (NSPointInRect(cocoaLocation, frame)) {
   296             *x = (int) cocoaLocation.x;
   297             *y = (int) ((frame.origin.y + frame.size.height) - cocoaLocation.y);
   298             break;
   299         }
   300     }
   301 
   302     retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0;
   303     retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0;
   304     retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0;
   305     retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0;
   306     retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0;
   307 
   308     return retval;
   309 }
   310 
   311 void
   312 Cocoa_InitMouse(_THIS)
   313 {
   314     SDL_Mouse *mouse = SDL_GetMouse();
   315 
   316     mouse->driverdata = SDL_calloc(1, sizeof(SDL_MouseData));
   317 
   318     mouse->CreateCursor = Cocoa_CreateCursor;
   319     mouse->CreateSystemCursor = Cocoa_CreateSystemCursor;
   320     mouse->ShowCursor = Cocoa_ShowCursor;
   321     mouse->FreeCursor = Cocoa_FreeCursor;
   322     mouse->WarpMouse = Cocoa_WarpMouse;
   323     mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal;
   324     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
   325     mouse->CaptureMouse = Cocoa_CaptureMouse;
   326     mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState;
   327 
   328     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
   329 
   330     Cocoa_InitMouseEventTap(mouse->driverdata);
   331 
   332     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   333     const NSPoint location =  [NSEvent mouseLocation];
   334     driverdata->lastMoveX = location.x;
   335     driverdata->lastMoveY = location.y;
   336 }
   337 
   338 void
   339 Cocoa_HandleMouseEvent(_THIS, NSEvent *event)
   340 {
   341     switch ([event type]) {
   342         case NSMouseMoved:
   343         case NSLeftMouseDragged:
   344         case NSRightMouseDragged:
   345         case NSOtherMouseDragged:
   346             break;
   347 
   348         default:
   349             /* Ignore any other events. */
   350             return;
   351     }
   352 
   353     SDL_Mouse *mouse = SDL_GetMouse();
   354     SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata;
   355     if (!driverdata) {
   356         return;  /* can happen when returning from fullscreen Space on shutdown */
   357     }
   358 
   359     const SDL_bool seenWarp = driverdata->seenWarp;
   360     driverdata->seenWarp = NO;
   361 
   362     const NSPoint location =  [NSEvent mouseLocation];
   363     const CGFloat lastMoveX = driverdata->lastMoveX;
   364     const CGFloat lastMoveY = driverdata->lastMoveY;
   365     driverdata->lastMoveX = location.x;
   366     driverdata->lastMoveY = location.y;
   367     DLog("Last seen mouse: (%g, %g)", location.x, location.y);
   368 
   369     /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */
   370     if (!mouse->relative_mode) {
   371         return;
   372     }
   373 
   374     /* Ignore events that aren't inside the client area (i.e. title bar.) */
   375     if ([event window]) {
   376         NSRect windowRect = [[[event window] contentView] frame];
   377         if (!NSPointInRect([event locationInWindow], windowRect)) {
   378             return;
   379         }
   380     }
   381 
   382     float deltaX = [event deltaX];
   383     float deltaY = [event deltaY];
   384 
   385     if (seenWarp) {
   386         deltaX += (lastMoveX - driverdata->lastWarpX);
   387         deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY);
   388 
   389         DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY);
   390     }
   391 
   392     SDL_SendMouseMotion(mouse->focus, mouse->mouseID, 1, (int)deltaX, (int)deltaY);
   393 }
   394 
   395 void
   396 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event)
   397 {
   398     SDL_Mouse *mouse = SDL_GetMouse();
   399 
   400     float x = -[event deltaX];
   401     float y = [event deltaY];
   402     SDL_MouseWheelDirection direction = SDL_MOUSEWHEEL_NORMAL;
   403 
   404     if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)]) {
   405         if ([event isDirectionInvertedFromDevice] == YES) {
   406             direction = SDL_MOUSEWHEEL_FLIPPED;
   407         }
   408     }
   409 
   410     if (x > 0) {
   411         x += 0.9f;
   412     } else if (x < 0) {
   413         x -= 0.9f;
   414     }
   415     if (y > 0) {
   416         y += 0.9f;
   417     } else if (y < 0) {
   418         y -= 0.9f;
   419     }
   420     SDL_SendMouseWheel(window, mouse->mouseID, (int)x, (int)y, direction);
   421 }
   422 
   423 void
   424 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y)
   425 {
   426     /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp,
   427      * since it gets included in the next movement event.
   428      */
   429     SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata;
   430     driverdata->lastWarpX = x;
   431     driverdata->lastWarpY = y;
   432     driverdata->seenWarp = SDL_TRUE;
   433 
   434     DLog("(%g, %g)", x, y);
   435 }
   436 
   437 void
   438 Cocoa_QuitMouse(_THIS)
   439 {
   440     SDL_Mouse *mouse = SDL_GetMouse();
   441     if (mouse) {
   442         if (mouse->driverdata) {
   443             Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata));
   444         }
   445 
   446         SDL_free(mouse->driverdata);
   447     }
   448 }
   449 
   450 #endif /* SDL_VIDEO_DRIVER_COCOA */
   451 
   452 /* vi: set ts=4 sw=4 expandtab: */