1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 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#include "SDL_cocoavideo.h" 30 31#include "../../events/SDL_mouse_c.h" 32 33/* #define DEBUG_COCOAMOUSE */ 34 35#ifdef DEBUG_COCOAMOUSE 36#define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__) 37#else 38#define DLog(...) do { } while (0) 39#endif 40 41@implementation NSCursor (InvisibleCursor) 42+ (NSCursor *)invisibleCursor 43{ 44 static NSCursor *invisibleCursor = NULL; 45 if (!invisibleCursor) { 46 /* RAW 16x16 transparent GIF */ 47 static unsigned char cursorBytes[] = { 48 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, 49 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 50 0x01, 0x00, 0x00, 0x01, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x10, 51 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x8C, 0x8F, 0xA9, 0xCB, 0xED, 52 0x0F, 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B 53 }; 54 55 NSData *cursorData = [NSData dataWithBytesNoCopy:&cursorBytes[0] 56 length:sizeof(cursorBytes) 57 freeWhenDone:NO]; 58 NSImage *cursorImage = [[[NSImage alloc] initWithData:cursorData] autorelease]; 59 invisibleCursor = [[NSCursor alloc] initWithImage:cursorImage 60 hotSpot:NSZeroPoint]; 61 } 62 63 return invisibleCursor; 64} 65@end 66 67 68static SDL_Cursor * 69Cocoa_CreateDefaultCursor() 70{ @autoreleasepool 71{ 72 NSCursor *nscursor; 73 SDL_Cursor *cursor = NULL; 74 75 nscursor = [NSCursor arrowCursor]; 76 77 if (nscursor) { 78 cursor = SDL_calloc(1, sizeof(*cursor)); 79 if (cursor) { 80 cursor->driverdata = nscursor; 81 [nscursor retain]; 82 } 83 } 84 85 return cursor; 86}} 87 88static SDL_Cursor * 89Cocoa_CreateCursor(SDL_Surface * surface, int hot_x, int hot_y) 90{ @autoreleasepool 91{ 92 NSImage *nsimage; 93 NSCursor *nscursor = NULL; 94 SDL_Cursor *cursor = NULL; 95 96 nsimage = Cocoa_CreateImage(surface); 97 if (nsimage) { 98 nscursor = [[NSCursor alloc] initWithImage: nsimage hotSpot: NSMakePoint(hot_x, hot_y)]; 99 } 100 101 if (nscursor) { 102 cursor = SDL_calloc(1, sizeof(*cursor)); 103 if (cursor) { 104 cursor->driverdata = nscursor; 105 } else { 106 [nscursor release]; 107 } 108 } 109 110 return cursor; 111}} 112 113static SDL_Cursor * 114Cocoa_CreateSystemCursor(SDL_SystemCursor id) 115{ @autoreleasepool 116{ 117 NSCursor *nscursor = NULL; 118 SDL_Cursor *cursor = NULL; 119 120 switch(id) { 121 case SDL_SYSTEM_CURSOR_ARROW: 122 nscursor = [NSCursor arrowCursor]; 123 break; 124 case SDL_SYSTEM_CURSOR_IBEAM: 125 nscursor = [NSCursor IBeamCursor]; 126 break; 127 case SDL_SYSTEM_CURSOR_WAIT: 128 nscursor = [NSCursor arrowCursor]; 129 break; 130 case SDL_SYSTEM_CURSOR_CROSSHAIR: 131 nscursor = [NSCursor crosshairCursor]; 132 break; 133 case SDL_SYSTEM_CURSOR_WAITARROW: 134 nscursor = [NSCursor arrowCursor]; 135 break; 136 case SDL_SYSTEM_CURSOR_SIZENWSE: 137 case SDL_SYSTEM_CURSOR_SIZENESW: 138 nscursor = [NSCursor closedHandCursor]; 139 break; 140 case SDL_SYSTEM_CURSOR_SIZEWE: 141 nscursor = [NSCursor resizeLeftRightCursor]; 142 break; 143 case SDL_SYSTEM_CURSOR_SIZENS: 144 nscursor = [NSCursor resizeUpDownCursor]; 145 break; 146 case SDL_SYSTEM_CURSOR_SIZEALL: 147 nscursor = [NSCursor closedHandCursor]; 148 break; 149 case SDL_SYSTEM_CURSOR_NO: 150 nscursor = [NSCursor operationNotAllowedCursor]; 151 break; 152 case SDL_SYSTEM_CURSOR_HAND: 153 nscursor = [NSCursor pointingHandCursor]; 154 break; 155 default: 156 SDL_assert(!"Unknown system cursor"); 157 return NULL; 158 } 159 160 if (nscursor) { 161 cursor = SDL_calloc(1, sizeof(*cursor)); 162 if (cursor) { 163 /* We'll free it later, so retain it here */ 164 [nscursor retain]; 165 cursor->driverdata = nscursor; 166 } 167 } 168 169 return cursor; 170}} 171 172static void 173Cocoa_FreeCursor(SDL_Cursor * cursor) 174{ @autoreleasepool 175{ 176 NSCursor *nscursor = (NSCursor *)cursor->driverdata; 177 178 [nscursor release]; 179 SDL_free(cursor); 180}} 181 182static int 183Cocoa_ShowCursor(SDL_Cursor * cursor) 184{ @autoreleasepool 185{ 186 SDL_VideoDevice *device = SDL_GetVideoDevice(); 187 SDL_Window *window = (device ? device->windows : NULL); 188 for (; window != NULL; window = window->next) { 189 SDL_WindowData *driverdata = (SDL_WindowData *)window->driverdata; 190 if (driverdata) { 191 [driverdata->nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:) 192 withObject:[driverdata->nswindow contentView] 193 waitUntilDone:NO]; 194 } 195 } 196 return 0; 197}} 198 199static SDL_Window * 200SDL_FindWindowAtPoint(const int x, const int y) 201{ 202 const SDL_Point pt = { x, y }; 203 SDL_Window *i; 204 for (i = SDL_GetVideoDevice()->windows; i; i = i->next) { 205 const SDL_Rect r = { i->x, i->y, i->w, i->h }; 206 if (SDL_PointInRect(&pt, &r)) { 207 return i; 208 } 209 } 210 211 return NULL; 212} 213 214static int 215Cocoa_WarpMouseGlobal(int x, int y) 216{ 217 SDL_Mouse *mouse = SDL_GetMouse(); 218 if (mouse->focus) { 219 SDL_WindowData *data = (SDL_WindowData *) mouse->focus->driverdata; 220 if ([data->listener isMoving]) { 221 DLog("Postponing warp, window being moved."); 222 [data->listener setPendingMoveX:x Y:y]; 223 return 0; 224 } 225 } 226 const CGPoint point = CGPointMake((float)x, (float)y); 227 228 Cocoa_HandleMouseWarp(point.x, point.y); 229 230 CGWarpMouseCursorPosition(point); 231 232 /* CGWarpMouse causes a short delay by default, which is preventable by 233 * Calling this directly after. CGSetLocalEventsSuppressionInterval can also 234 * prevent it, but it's deprecated as of OS X 10.6. 235 */ 236 if (!mouse->relative_mode) { 237 CGAssociateMouseAndMouseCursorPosition(YES); 238 } 239 240 /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our 241 * other implementations' APIs. Send what's appropriate. 242 */ 243 if (!mouse->relative_mode) { 244 SDL_Window *win = SDL_FindWindowAtPoint(x, y); 245 SDL_SetMouseFocus(win); 246 if (win) { 247 SDL_assert(win == mouse->focus); 248 SDL_SendMouseMotion(win, mouse->mouseID, 0, x - win->x, y - win->y); 249 } 250 } 251 252 return 0; 253} 254 255static void 256Cocoa_WarpMouse(SDL_Window * window, int x, int y) 257{ 258 Cocoa_WarpMouseGlobal(x + window->x, y + window->y); 259} 260 261static int 262Cocoa_SetRelativeMouseMode(SDL_bool enabled) 263{ 264 /* We will re-apply the relative mode when the window gets focus, if it 265 * doesn't have focus right now. 266 */ 267 SDL_Window *window = SDL_GetMouseFocus(); 268 if (!window) { 269 return 0; 270 } 271 272 /* We will re-apply the relative mode when the window finishes being moved, 273 * if it is being moved right now. 274 */ 275 SDL_WindowData *data = (SDL_WindowData *) window->driverdata; 276 if ([data->listener isMoving]) { 277 return 0; 278 } 279 280 CGError result; 281 if (enabled) { 282 DLog("Turning on."); 283 result = CGAssociateMouseAndMouseCursorPosition(NO); 284 } else { 285 DLog("Turning off."); 286 result = CGAssociateMouseAndMouseCursorPosition(YES); 287 } 288 if (result != kCGErrorSuccess) { 289 return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed"); 290 } 291 292 /* The hide/unhide calls are redundant most of the time, but they fix 293 * https://bugzilla.libsdl.org/show_bug.cgi?id=2550 294 */ 295 if (enabled) { 296 [NSCursor hide]; 297 } else { 298 [NSCursor unhide]; 299 } 300 return 0; 301} 302 303static int 304Cocoa_CaptureMouse(SDL_Window *window) 305{ 306 /* our Cocoa event code already tracks the mouse outside the window, 307 so all we have to do here is say "okay" and do what we always do. */ 308 return 0; 309} 310 311static Uint32 312Cocoa_GetGlobalMouseState(int *x, int *y) 313{ 314 const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons]; 315 const NSPoint cocoaLocation = [NSEvent mouseLocation]; 316 Uint32 retval = 0; 317 318 *x = (int) cocoaLocation.x; 319 *y = (int) (CGDisplayPixelsHigh(kCGDirectMainDisplay) - cocoaLocation.y); 320 321 retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0; 322 retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0; 323 retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0; 324 retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0; 325 retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0; 326 327 return retval; 328} 329 330int 331Cocoa_InitMouse(_THIS) 332{ 333 SDL_Mouse *mouse = SDL_GetMouse(); 334 SDL_MouseData *driverdata = (SDL_MouseData*) SDL_calloc(1, sizeof(SDL_MouseData)); 335 if (driverdata == NULL) { 336 return SDL_OutOfMemory(); 337 } 338 339 mouse->driverdata = driverdata; 340 mouse->CreateCursor = Cocoa_CreateCursor; 341 mouse->CreateSystemCursor = Cocoa_CreateSystemCursor; 342 mouse->ShowCursor = Cocoa_ShowCursor; 343 mouse->FreeCursor = Cocoa_FreeCursor; 344 mouse->WarpMouse = Cocoa_WarpMouse; 345 mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal; 346 mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode; 347 mouse->CaptureMouse = Cocoa_CaptureMouse; 348 mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState; 349 350 SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor()); 351 352 Cocoa_InitMouseEventTap(driverdata); 353 354 const NSPoint location = [NSEvent mouseLocation]; 355 driverdata->lastMoveX = location.x; 356 driverdata->lastMoveY = location.y; 357 return 0; 358} 359 360void 361Cocoa_HandleMouseEvent(_THIS, NSEvent *event) 362{ 363 switch ([event type]) { 364 case NSEventTypeMouseMoved: 365 case NSEventTypeLeftMouseDragged: 366 case NSEventTypeRightMouseDragged: 367 case NSEventTypeOtherMouseDragged: 368 break; 369 370 default: 371 /* Ignore any other events. */ 372 return; 373 } 374 375 SDL_Mouse *mouse = SDL_GetMouse(); 376 SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata; 377 if (!driverdata) { 378 return; /* can happen when returning from fullscreen Space on shutdown */ 379 } 380 381 SDL_MouseID mouseID = mouse ? mouse->mouseID : 0; 382 const SDL_bool seenWarp = driverdata->seenWarp; 383 driverdata->seenWarp = NO; 384 385 const NSPoint location = [NSEvent mouseLocation]; 386 const CGFloat lastMoveX = driverdata->lastMoveX; 387 const CGFloat lastMoveY = driverdata->lastMoveY; 388 driverdata->lastMoveX = location.x; 389 driverdata->lastMoveY = location.y; 390 DLog("Last seen mouse: (%g, %g)", location.x, location.y); 391 392 /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */ 393 if (!mouse->relative_mode) { 394 return; 395 } 396 397 /* Ignore events that aren't inside the client area (i.e. title bar.) */ 398 if ([event window]) { 399 NSRect windowRect = [[[event window] contentView] frame]; 400 if (!NSMouseInRect([event locationInWindow], windowRect, NO)) { 401 return; 402 } 403 } 404 405 float deltaX = [event deltaX]; 406 float deltaY = [event deltaY]; 407 408 if (seenWarp) { 409 deltaX += (lastMoveX - driverdata->lastWarpX); 410 deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY); 411 412 DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY); 413 } 414 415 SDL_SendMouseMotion(mouse->focus, mouseID, 1, (int)deltaX, (int)deltaY); 416} 417 418void 419Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event) 420{ 421 SDL_Mouse *mouse = SDL_GetMouse(); 422 if (!mouse) { 423 return; 424 } 425 426 SDL_MouseID mouseID = mouse->mouseID; 427 CGFloat x = -[event deltaX]; 428 CGFloat y = [event deltaY]; 429 SDL_MouseWheelDirection direction = SDL_MOUSEWHEEL_NORMAL; 430 431 if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)]) { 432 if ([event isDirectionInvertedFromDevice] == YES) { 433 direction = SDL_MOUSEWHEEL_FLIPPED; 434 } 435 } 436 437 if (x > 0) { 438 x = SDL_ceil(x); 439 } else if (x < 0) { 440 x = SDL_floor(x); 441 } 442 if (y > 0) { 443 y = SDL_ceil(y); 444 } else if (y < 0) { 445 y = SDL_floor(y); 446 } 447 448 SDL_SendMouseWheel(window, mouseID, x, y, direction); 449} 450 451void 452Cocoa_HandleMouseWarp(CGFloat x, CGFloat y) 453{ 454 /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp, 455 * since it gets included in the next movement event. 456 */ 457 SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata; 458 driverdata->lastWarpX = x; 459 driverdata->lastWarpY = y; 460 driverdata->seenWarp = SDL_TRUE; 461 462 DLog("(%g, %g)", x, y); 463} 464 465void 466Cocoa_QuitMouse(_THIS) 467{ 468 SDL_Mouse *mouse = SDL_GetMouse(); 469 if (mouse) { 470 if (mouse->driverdata) { 471 Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata)); 472 473 SDL_free(mouse->driverdata); 474 mouse->driverdata = NULL; 475 } 476 } 477} 478 479#endif /* SDL_VIDEO_DRIVER_COCOA */ 480 481/* vi: set ts=4 sw=4 expandtab: */ 482