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