SDL_cocoamouse.m (13087B)
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 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 void 199Cocoa_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 230static void 231Cocoa_WarpMouse(SDL_Window * window, int x, int y) 232{ 233 Cocoa_WarpMouseGlobal(x + window->x, y + window->y); 234} 235 236static int 237Cocoa_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 278static int 279Cocoa_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 286static Uint32 287Cocoa_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 311void 312Cocoa_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 338void 339Cocoa_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 395void 396Cocoa_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 403 if (x > 0) { 404 x += 0.9f; 405 } else if (x < 0) { 406 x -= 0.9f; 407 } 408 if (y > 0) { 409 y += 0.9f; 410 } else if (y < 0) { 411 y -= 0.9f; 412 } 413 SDL_SendMouseWheel(window, mouse->mouseID, (int)x, (int)y); 414} 415 416void 417Cocoa_HandleMouseWarp(CGFloat x, CGFloat y) 418{ 419 /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp, 420 * since it gets included in the next movement event. 421 */ 422 SDL_MouseData *driverdata = (SDL_MouseData*)SDL_GetMouse()->driverdata; 423 driverdata->lastWarpX = x; 424 driverdata->lastWarpY = y; 425 driverdata->seenWarp = SDL_TRUE; 426 427 DLog("(%g, %g)", x, y); 428} 429 430void 431Cocoa_QuitMouse(_THIS) 432{ 433 SDL_Mouse *mouse = SDL_GetMouse(); 434 if (mouse) { 435 if (mouse->driverdata) { 436 Cocoa_QuitMouseEventTap(((SDL_MouseData*)mouse->driverdata)); 437 } 438 439 SDL_free(mouse->driverdata); 440 } 441} 442 443#endif /* SDL_VIDEO_DRIVER_COCOA */ 444 445/* vi: set ts=4 sw=4 expandtab: */