SDL_cocoamouse.m (13976B)
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_events.h" 26 #include "SDL_cocoamouse.h" 27 #include "SDL_cocoamousetap.h" 28 #include "SDL_cocoavideo.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 int 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 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 254 static void 255 Cocoa_WarpMouse(SDL_Window * window, int x, int y) 256 { 257 Cocoa_WarpMouseGlobal(x + window->x, y + window->y); 258 } 259 260 static int 261 Cocoa_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 302 static int 303 Cocoa_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 310 static Uint32 311 Cocoa_GetGlobalMouseState(int *x, int *y) 312 { 313 const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons]; 314 const NSPoint cocoaLocation = [NSEvent mouseLocation]; 315 Uint32 retval = 0; 316 317 *x = (int) cocoaLocation.x; 318 *y = (int) (CGDisplayPixelsHigh(kCGDirectMainDisplay) - cocoaLocation.y); 319 320 retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0; 321 retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0; 322 retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0; 323 retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0; 324 retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0; 325 326 return retval; 327 } 328 329 int 330 Cocoa_InitMouse(_THIS) 331 { 332 SDL_Mouse *mouse = SDL_GetMouse(); 333 SDL_MouseData *driverdata = (SDL_MouseData*) SDL_calloc(1, sizeof(SDL_MouseData)); 334 if (driverdata == NULL) { 335 return SDL_OutOfMemory(); 336 } 337 338 mouse->driverdata = driverdata; 339 mouse->CreateCursor = Cocoa_CreateCursor; 340 mouse->CreateSystemCursor = Cocoa_CreateSystemCursor; 341 mouse->ShowCursor = Cocoa_ShowCursor; 342 mouse->FreeCursor = Cocoa_FreeCursor; 343 mouse->WarpMouse = Cocoa_WarpMouse; 344 mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal; 345 mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode; 346 mouse->CaptureMouse = Cocoa_CaptureMouse; 347 mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState; 348 349 SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor()); 350 351 Cocoa_InitMouseEventTap(driverdata); 352 353 const NSPoint location = [NSEvent mouseLocation]; 354 driverdata->lastMoveX = location.x; 355 driverdata->lastMoveY = location.y; 356 return 0; 357 } 358 359 void 360 Cocoa_HandleMouseEvent(_THIS, NSEvent *event) 361 { 362 switch ([event type]) { 363 case NSEventTypeMouseMoved: 364 case NSEventTypeLeftMouseDragged: 365 case NSEventTypeRightMouseDragged: 366 case NSEventTypeOtherMouseDragged: 367 break; 368 369 default: 370 /* Ignore any other events. */ 371 return; 372 } 373 374 SDL_Mouse *mouse = SDL_GetMouse(); 375 SDL_MouseData *driverdata = (SDL_MouseData*)mouse->driverdata; 376 if (!driverdata) { 377 return; /* can happen when returning from fullscreen Space on shutdown */ 378 } 379 380 SDL_MouseID mouseID = mouse ? mouse->mouseID : 0; 381 const SDL_bool seenWarp = driverdata->seenWarp; 382 driverdata->seenWarp = NO; 383 384 const NSPoint location = [NSEvent mouseLocation]; 385 const CGFloat lastMoveX = driverdata->lastMoveX; 386 const CGFloat lastMoveY = driverdata->lastMoveY; 387 driverdata->lastMoveX = location.x; 388 driverdata->lastMoveY = location.y; 389 DLog("Last seen mouse: (%g, %g)", location.x, location.y); 390 391 /* Non-relative movement is handled in -[Cocoa_WindowListener mouseMoved:] */ 392 if (!mouse->relative_mode) { 393 return; 394 } 395 396 /* Ignore events that aren't inside the client area (i.e. title bar.) */ 397 if ([event window]) { 398 NSRect windowRect = [[[event window] contentView] frame]; 399 if (!NSMouseInRect([event locationInWindow], windowRect, NO)) { 400 return; 401 } 402 } 403 404 float deltaX = [event deltaX]; 405 float deltaY = [event deltaY]; 406 407 if (seenWarp) { 408 deltaX += (lastMoveX - driverdata->lastWarpX); 409 deltaY += ((CGDisplayPixelsHigh(kCGDirectMainDisplay) - lastMoveY) - driverdata->lastWarpY); 410 411 DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], [event deltaY], deltaX, deltaY); 412 } 413 414 SDL_SendMouseMotion(mouse->focus, mouseID, 1, (int)deltaX, (int)deltaY); 415 } 416 417 void 418 Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event) 419 { 420 SDL_Mouse *mouse = SDL_GetMouse(); 421 if (!mouse) { 422 return; 423 } 424 425 SDL_MouseID mouseID = mouse->mouseID; 426 CGFloat x = -[event deltaX]; 427 CGFloat y = [event deltaY]; 428 SDL_MouseWheelDirection direction = SDL_MOUSEWHEEL_NORMAL; 429 430 if ([event respondsToSelector:@selector(isDirectionInvertedFromDevice)]) { 431 if ([event isDirectionInvertedFromDevice] == YES) { 432 direction = SDL_MOUSEWHEEL_FLIPPED; 433 } 434 } 435 436 if (x > 0) { 437 x = SDL_ceil(x); 438 } else if (x < 0) { 439 x = SDL_floor(x); 440 } 441 if (y > 0) { 442 y = SDL_ceil(y); 443 } else if (y < 0) { 444 y = SDL_floor(y); 445 } 446 447 SDL_SendMouseWheel(window, mouseID, x, y, direction); 448 } 449 450 void 451 Cocoa_HandleMouseWarp(CGFloat x, CGFloat y) 452 { 453 /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp, 454 * since it gets included in the next movement event. 455 */ 456 SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata; 457 driverdata->lastWarpX = x; 458 driverdata->lastWarpY = y; 459 driverdata->seenWarp = SDL_TRUE; 460 461 DLog("(%g, %g)", x, y); 462 } 463 464 void 465 Cocoa_QuitMouse(_THIS) 466 { 467 SDL_Mouse *mouse = SDL_GetMouse(); 468 if (mouse) { 469 if (mouse->driverdata) { 470 Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata)); 471 472 SDL_free(mouse->driverdata); 473 mouse->driverdata = NULL; 474 } 475 } 476 } 477 478 #endif /* SDL_VIDEO_DRIVER_COCOA */ 479 480 /* vi: set ts=4 sw=4 expandtab: */