Atlas - SDL_cocoamouse.m

Home / ext / SDL / src / video / cocoa Lines: 2 | Size: 32156 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 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#ifdef SDL_VIDEO_DRIVER_COCOA 24 25#include "SDL_cocoamouse.h" 26#include "SDL_cocoavideo.h" 27 28#include "../../events/SDL_mouse_c.h" 29 30#import <GameController/GameController.h> 31 32#if 0 33#define DEBUG_COCOAMOUSE 34#endif 35 36#ifdef DEBUG_COCOAMOUSE 37#define DLog(fmt, ...) printf("%s: " fmt "\n", SDL_FUNCTION, ##__VA_ARGS__) 38#else 39#define DLog(...) \ 40 do { \ 41 } while (0) 42#endif 43 44@implementation NSCursor (InvisibleCursor) 45+ (NSCursor *)invisibleCursor 46{ 47 static NSCursor *invisibleCursor = NULL; 48 if (!invisibleCursor) { 49 const int size = 32; 50 NSImage *cursorImage = [[NSImage alloc] initWithSize:NSMakeSize(size, size)]; 51 NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL 52 pixelsWide:size 53 pixelsHigh:size 54 bitsPerSample:8 55 samplesPerPixel:4 56 hasAlpha:YES 57 isPlanar:NO 58 colorSpaceName:NSDeviceRGBColorSpace 59 bytesPerRow:(size * 4) 60 bitsPerPixel:32]; 61 [cursorImage addRepresentation:imgrep]; 62 63 invisibleCursor = [[NSCursor alloc] initWithImage:cursorImage 64 hotSpot:NSZeroPoint]; 65 } 66 67 return invisibleCursor; 68} 69@end 70 71static SDL_Cursor *Cocoa_CreateAnimatedCursor(SDL_CursorFrameInfo *frames, int frame_count, int hot_x, int hot_y) 72{ 73 @autoreleasepool { 74 NSImage *nsimage; 75 NSCursor *nscursor = NULL; 76 SDL_Cursor *cursor = NULL; 77 78 cursor = SDL_calloc(1, sizeof(*cursor)); 79 if (cursor) { 80 SDL_CursorData *cdata = SDL_calloc(1, sizeof(*cdata) + (sizeof(*cdata->frames) * frame_count)); 81 if (!cdata) { 82 SDL_free(cursor); 83 return NULL; 84 } 85 86 cursor->internal = cdata; 87 88 for (int i = 0; i < frame_count; ++i) { 89 nsimage = Cocoa_CreateImage(frames[i].surface); 90 if (nsimage) { 91 nscursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(hot_x, hot_y)]; 92 } 93 94 if (nscursor) { 95 ++cdata->num_cursors; 96 cdata->frames[i].cursor = (void *)CFBridgingRetain(nscursor); 97 cdata->frames[i].duration = frames[i].duration; 98 } else { 99 for (int j = 0; j < i; ++j) { 100 CFBridgingRelease(cdata->frames[i].cursor); 101 } 102 103 SDL_free(cdata); 104 SDL_free(cursor); 105 cursor = NULL; 106 break; 107 } 108 } 109 110 return cursor; 111 } 112 } 113 114 return NULL; 115} 116 117static SDL_Cursor *Cocoa_CreateCursor(SDL_Surface *surface, int hot_x, int hot_y) 118{ 119 SDL_CursorFrameInfo frame = { 120 surface, 0 121 }; 122 123 return Cocoa_CreateAnimatedCursor(&frame, 1, hot_x, hot_y); 124} 125 126/* there are .pdf files of some of the cursors we need, installed by default on macOS, but not available through NSCursor. 127 If we can load them ourselves, use them, otherwise fallback to something standard but not super-great. 128 Since these are under /System, they should be available even to sandboxed apps. */ 129static NSCursor *LoadHiddenSystemCursor(NSString *cursorName, SEL fallback) 130{ 131 NSString *cursorPath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:cursorName]; 132 NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]]; 133 // we can't do animation atm. :/ 134 const int frames = (int)[[info valueForKey:@"frames"] integerValue]; 135 NSCursor *cursor; 136 NSImage *image = [[NSImage alloc] initWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]]; 137 if ((image == nil) || (image.isValid == NO)) { 138 return [NSCursor performSelector:fallback]; 139 } 140 141 if (frames > 1) { 142#ifdef MAC_OS_VERSION_12_0 // same value as deprecated symbol. 143 const NSCompositingOperation operation = NSCompositingOperationCopy; 144#else 145 const NSCompositingOperation operation = NSCompositeCopy; 146#endif 147 const NSSize cropped_size = NSMakeSize(image.size.width, (int)(image.size.height / frames)); 148 NSImage *cropped = [[NSImage alloc] initWithSize:cropped_size]; 149 if (cropped == nil) { 150 return [NSCursor performSelector:fallback]; 151 } 152 153 [cropped lockFocus]; 154 { 155 const NSRect cropped_rect = NSMakeRect(0, 0, cropped_size.width, cropped_size.height); 156 [image drawInRect:cropped_rect fromRect:cropped_rect operation:operation fraction:1]; 157 } 158 [cropped unlockFocus]; 159 image = cropped; 160 } 161 162 cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue], [[info valueForKey:@"hoty"] doubleValue])]; 163 return cursor; 164} 165 166static SDL_Cursor *Cocoa_CreateSystemCursor(SDL_SystemCursor id) 167{ 168 @autoreleasepool { 169 NSCursor *nscursor = NULL; 170 SDL_Cursor *cursor = NULL; 171 172 switch (id) { 173 case SDL_SYSTEM_CURSOR_DEFAULT: 174 nscursor = [NSCursor arrowCursor]; 175 break; 176 case SDL_SYSTEM_CURSOR_TEXT: 177 nscursor = [NSCursor IBeamCursor]; 178 break; 179 case SDL_SYSTEM_CURSOR_CROSSHAIR: 180 nscursor = [NSCursor crosshairCursor]; 181 break; 182 case SDL_SYSTEM_CURSOR_WAIT: // !!! FIXME: this is more like WAITARROW 183 nscursor = LoadHiddenSystemCursor(@"busybutclickable", @selector(arrowCursor)); 184 break; 185 case SDL_SYSTEM_CURSOR_PROGRESS: // !!! FIXME: this is meant to be animated 186 nscursor = LoadHiddenSystemCursor(@"busybutclickable", @selector(arrowCursor)); 187 break; 188 case SDL_SYSTEM_CURSOR_NWSE_RESIZE: 189 nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); 190 break; 191 case SDL_SYSTEM_CURSOR_NESW_RESIZE: 192 nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); 193 break; 194 case SDL_SYSTEM_CURSOR_EW_RESIZE: 195 nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); 196 break; 197 case SDL_SYSTEM_CURSOR_NS_RESIZE: 198 nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); 199 break; 200 case SDL_SYSTEM_CURSOR_MOVE: 201 nscursor = LoadHiddenSystemCursor(@"move", @selector(closedHandCursor)); 202 break; 203 case SDL_SYSTEM_CURSOR_NOT_ALLOWED: 204 nscursor = [NSCursor operationNotAllowedCursor]; 205 break; 206 case SDL_SYSTEM_CURSOR_POINTER: 207 nscursor = [NSCursor pointingHandCursor]; 208 break; 209 case SDL_SYSTEM_CURSOR_NW_RESIZE: 210 nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); 211 break; 212 case SDL_SYSTEM_CURSOR_N_RESIZE: 213 nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); 214 break; 215 case SDL_SYSTEM_CURSOR_NE_RESIZE: 216 nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); 217 break; 218 case SDL_SYSTEM_CURSOR_E_RESIZE: 219 nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); 220 break; 221 case SDL_SYSTEM_CURSOR_SE_RESIZE: 222 nscursor = LoadHiddenSystemCursor(@"resizenorthwestsoutheast", @selector(closedHandCursor)); 223 break; 224 case SDL_SYSTEM_CURSOR_S_RESIZE: 225 nscursor = LoadHiddenSystemCursor(@"resizenorthsouth", @selector(resizeUpDownCursor)); 226 break; 227 case SDL_SYSTEM_CURSOR_SW_RESIZE: 228 nscursor = LoadHiddenSystemCursor(@"resizenortheastsouthwest", @selector(closedHandCursor)); 229 break; 230 case SDL_SYSTEM_CURSOR_W_RESIZE: 231 nscursor = LoadHiddenSystemCursor(@"resizeeastwest", @selector(resizeLeftRightCursor)); 232 break; 233 default: 234 SDL_assert(!"Unknown system cursor"); 235 return NULL; 236 } 237 238 if (nscursor) { 239 cursor = SDL_calloc(1, sizeof(*cursor)); 240 if (cursor) { 241 SDL_CursorData *cdata = SDL_calloc(1, sizeof(*cdata) + sizeof(*cdata->frames)); 242 // We'll free it later, so retain it here 243 cursor->internal = cdata; 244 cdata->frames[0].cursor = (void *)CFBridgingRetain(nscursor); 245 cdata->num_cursors = 1; 246 } 247 } 248 249 return cursor; 250 } 251} 252 253static SDL_Cursor *Cocoa_CreateDefaultCursor(void) 254{ 255 SDL_SystemCursor id = SDL_GetDefaultSystemCursor(); 256 return Cocoa_CreateSystemCursor(id); 257} 258 259// GCMouse support for raw (unaccelerated) mouse input on macOS 11.0+ 260static id cocoa_mouse_connect_observer = nil; 261static id cocoa_mouse_disconnect_observer = nil; 262// Atomic for thread-safe access during high-frequency mouse input 263static SDL_AtomicInt cocoa_gcmouse_relative_mode; 264static bool cocoa_has_gcmouse = false; 265static SDL_MouseWheelDirection cocoa_mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL; 266 267static void Cocoa_UpdateGCMouseScrollDirection(void) 268{ 269 Boolean keyExistsAndHasValidFormat = NO; 270 Boolean naturalScrollDirection = CFPreferencesGetAppBooleanValue( 271 CFSTR("com.apple.swipescrolldirection"), 272 kCFPreferencesAnyApplication, 273 &keyExistsAndHasValidFormat); 274 if (!keyExistsAndHasValidFormat) { 275 // Couldn't read the preference, assume natural scrolling direction 276 naturalScrollDirection = YES; 277 } 278 if (naturalScrollDirection) { 279 cocoa_mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED; 280 } else { 281 cocoa_mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL; 282 } 283} 284 285static bool Cocoa_SetGCMouseRelativeMode(bool enabled) 286{ 287 SDL_SetAtomicInt(&cocoa_gcmouse_relative_mode, enabled ? 1 : 0); 288 return true; 289} 290 291static void Cocoa_OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, 292 BOOL pressed) 293{ 294 Uint64 timestamp = SDL_GetTicksNS(); 295 SDL_SendMouseButton(timestamp, SDL_GetMouseFocus(), mouseID, button, 296 pressed); 297} 298 299static void Cocoa_OnGCMouseConnected(GCMouse *mouse) 300 API_AVAILABLE(macos(11.0)) 301{ 302 SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse; 303 304 SDL_AddMouse(mouseID, NULL); 305 cocoa_has_gcmouse = true; 306 307 // Sync with SDL's current relative mode state (may have been set before 308 // GCMouse connected) 309 SDL_Mouse *sdl_mouse = SDL_GetMouse(); 310 if (sdl_mouse && sdl_mouse->relative_mode) { 311 SDL_SetAtomicInt(&cocoa_gcmouse_relative_mode, 1); 312 } 313 314 mouse.mouseInput.leftButton.pressedChangedHandler = 315 ^(GCControllerButtonInput *button, float value, BOOL pressed) { 316 Cocoa_OnGCMouseButtonChanged(mouseID, SDL_BUTTON_LEFT, pressed); 317 }; 318 mouse.mouseInput.middleButton.pressedChangedHandler = 319 ^(GCControllerButtonInput *button, float value, BOOL pressed) { 320 Cocoa_OnGCMouseButtonChanged(mouseID, SDL_BUTTON_MIDDLE, pressed); 321 }; 322 mouse.mouseInput.rightButton.pressedChangedHandler = 323 ^(GCControllerButtonInput *button, float value, BOOL pressed) { 324 Cocoa_OnGCMouseButtonChanged(mouseID, SDL_BUTTON_RIGHT, pressed); 325 }; 326 327 int auxiliary_button = SDL_BUTTON_X1; 328 for (GCControllerButtonInput *btn in mouse.mouseInput.auxiliaryButtons) { 329 const int current_button = auxiliary_button; 330 btn.pressedChangedHandler = 331 ^(GCControllerButtonInput *button, float value, BOOL pressed) { 332 Cocoa_OnGCMouseButtonChanged(mouseID, current_button, pressed); 333 }; 334 ++auxiliary_button; 335 } 336 337 mouse.mouseInput.mouseMovedHandler = 338 ^(GCMouseInput *mouseInput, float deltaX, float deltaY) { 339 if (Cocoa_GCMouseRelativeMode()) { 340 // Skip raw input if user wants system-scaled (accelerated) deltas 341 SDL_Mouse *m = SDL_GetMouse(); 342 if (m && m->enable_relative_system_scale) { 343 return; 344 } 345 Uint64 timestamp = SDL_GetTicksNS(); 346 SDL_SendMouseMotion(timestamp, SDL_GetMouseFocus(), mouseID, 347 true, deltaX, -deltaY); 348 } 349 }; 350 351 mouse.mouseInput.scroll.valueChangedHandler = 352 ^(GCControllerDirectionPad *dpad, float xValue, float yValue) { 353 Uint64 timestamp = SDL_GetTicksNS(); 354 // Raw scroll values: vertical in first axis, horizontal in second. 355 // Vertical values are inverted compared to SDL conventions. 356 float vertical = -xValue; 357 float horizontal = yValue; 358 359 if (cocoa_mouse_scroll_direction == SDL_MOUSEWHEEL_FLIPPED) { 360 vertical = -vertical; 361 horizontal = -horizontal; 362 } 363 SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), mouseID, 364 horizontal, vertical, 365 cocoa_mouse_scroll_direction); 366 }; 367 Cocoa_UpdateGCMouseScrollDirection(); 368 369 // Use high-priority queue for low-latency input 370 dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.mouse", 371 DISPATCH_QUEUE_SERIAL); 372 dispatch_set_target_queue(queue, 373 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); 374 mouse.handlerQueue = queue; 375} 376 377static void Cocoa_OnGCMouseDisconnected(GCMouse *mouse) 378 API_AVAILABLE(macos(11.0)) 379{ 380 SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse; 381 382 mouse.mouseInput.mouseMovedHandler = nil; 383 mouse.mouseInput.leftButton.pressedChangedHandler = nil; 384 mouse.mouseInput.middleButton.pressedChangedHandler = nil; 385 mouse.mouseInput.rightButton.pressedChangedHandler = nil; 386 mouse.mouseInput.scroll.valueChangedHandler = nil; 387 388 for (GCControllerButtonInput *button in mouse.mouseInput.auxiliaryButtons) { 389 button.pressedChangedHandler = nil; 390 } 391 392 SDL_RemoveMouse(mouseID); 393 394 // Check if any GCMouse devices remain 395 if (@available(macOS 11.0, *)) { 396 cocoa_has_gcmouse = ([GCMouse mice].count > 0); 397 } 398} 399 400void Cocoa_InitGCMouse(void) 401{ 402 @autoreleasepool { 403 if (@available(macOS 11.0, *)) { 404 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 405 406 cocoa_mouse_connect_observer = [center 407 addObserverForName:GCMouseDidConnectNotification 408 object:nil 409 queue:nil 410 usingBlock:^(NSNotification *note) { 411 GCMouse *mouse = note.object; 412 Cocoa_OnGCMouseConnected(mouse); 413 }]; 414 415 cocoa_mouse_disconnect_observer = [center 416 addObserverForName:GCMouseDidDisconnectNotification 417 object:nil 418 queue:nil 419 usingBlock:^(NSNotification *note) { 420 GCMouse *mouse = note.object; 421 Cocoa_OnGCMouseDisconnected(mouse); 422 }]; 423 424 // Enumerate already-connected mice 425 for (GCMouse *mouse in [GCMouse mice]) { 426 Cocoa_OnGCMouseConnected(mouse); 427 } 428 } 429 } 430} 431 432bool Cocoa_GCMouseRelativeMode(void) 433{ 434 return SDL_GetAtomicInt(&cocoa_gcmouse_relative_mode) != 0; 435} 436 437bool Cocoa_HasGCMouse(void) 438{ 439 return cocoa_has_gcmouse; 440} 441 442void Cocoa_QuitGCMouse(void) 443{ 444 @autoreleasepool { 445 if (@available(macOS 11.0, *)) { 446 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 447 448 if (cocoa_mouse_connect_observer) { 449 [center removeObserver:cocoa_mouse_connect_observer 450 name:GCMouseDidConnectNotification 451 object:nil]; 452 cocoa_mouse_connect_observer = nil; 453 } 454 455 if (cocoa_mouse_disconnect_observer) { 456 [center removeObserver:cocoa_mouse_disconnect_observer 457 name:GCMouseDidDisconnectNotification 458 object:nil]; 459 cocoa_mouse_disconnect_observer = nil; 460 } 461 462 for (GCMouse *mouse in [GCMouse mice]) { 463 Cocoa_OnGCMouseDisconnected(mouse); 464 } 465 466 cocoa_has_gcmouse = false; 467 SDL_SetAtomicInt(&cocoa_gcmouse_relative_mode, 0); 468 } 469 } 470} 471 472static void Cocoa_FreeCursor(SDL_Cursor *cursor) 473{ 474 @autoreleasepool { 475 SDL_CursorData *cdata = cursor->internal; 476 if (cdata->frameTimer) { 477 [cdata->frameTimer invalidate]; 478 } 479 for (int i = 0; i < cdata->num_cursors; ++i) { 480 CFBridgingRelease(cdata->frames[i].cursor); 481 } 482 SDL_free(cdata); 483 SDL_free(cursor); 484 } 485} 486 487static bool Cocoa_ShowCursor(SDL_Cursor *cursor) 488{ 489 @autoreleasepool { 490 SDL_VideoDevice *device = SDL_GetVideoDevice(); 491 SDL_Window *window = (device ? device->windows : NULL); 492 493 if (cursor != NULL) { 494 SDL_CursorData *cdata = cursor->internal; 495 cdata->current_frame = 0; 496 if (cdata->frameTimer) { 497 [cdata->frameTimer invalidate]; 498 cdata->frameTimer = nil; 499 } 500 } 501 502 for (; window != NULL; window = window->next) { 503 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 504 if (data) { 505 [data.nswindow performSelectorOnMainThread:@selector(invalidateCursorRectsForView:) 506 withObject:[data.nswindow contentView] 507 waitUntilDone:NO]; 508 } 509 } 510 return true; 511 } 512} 513 514static SDL_Window *SDL_FindWindowAtPoint(const float x, const float y) 515{ 516 const SDL_FPoint pt = { x, y }; 517 SDL_Window *i; 518 for (i = SDL_GetVideoDevice()->windows; i; i = i->next) { 519 const SDL_FRect r = { (float)i->x, (float)i->y, (float)i->w, (float)i->h }; 520 if (SDL_PointInRectFloat(&pt, &r)) { 521 return i; 522 } 523 } 524 525 return NULL; 526} 527 528static bool Cocoa_WarpMouseGlobal(float x, float y) 529{ 530 CGPoint point; 531 SDL_Mouse *mouse = SDL_GetMouse(); 532 if (mouse->focus) { 533 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)mouse->focus->internal; 534 if ([data.listener isMovingOrFocusClickPending]) { 535 DLog("Postponing warp, window being moved or focused."); 536 [data.listener setPendingMoveX:x Y:y]; 537 return true; 538 } 539 } 540 point = CGPointMake(x, y); 541 542 Cocoa_HandleMouseWarp(point.x, point.y); 543 544 CGWarpMouseCursorPosition(point); 545 546 /* CGWarpMouse causes a short delay by default, which is preventable by 547 * Calling this directly after. CGSetLocalEventsSuppressionInterval can also 548 * prevent it, but it's deprecated as macOS 10.6. 549 */ 550 if (!mouse->relative_mode) { 551 CGAssociateMouseAndMouseCursorPosition(YES); 552 } 553 554 /* CGWarpMouseCursorPosition doesn't generate a window event, unlike our 555 * other implementations' APIs. Send what's appropriate. 556 */ 557 if (!mouse->relative_mode) { 558 SDL_Window *win = SDL_FindWindowAtPoint(x, y); 559 SDL_SetMouseFocus(win); 560 if (win) { 561 SDL_assert(win == mouse->focus); 562 SDL_SendMouseMotion(0, win, SDL_GLOBAL_MOUSE_ID, false, x - win->x, y - win->y); 563 } 564 } 565 566 return true; 567} 568 569static bool Cocoa_WarpMouse(SDL_Window *window, float x, float y) 570{ 571 return Cocoa_WarpMouseGlobal(window->x + x, window->y + y); 572} 573 574static bool Cocoa_SetRelativeMouseMode(bool enabled) 575{ 576 CGError result; 577 578 // Update GCMouse relative mode state if available 579 if (Cocoa_HasGCMouse()) { 580 Cocoa_SetGCMouseRelativeMode(enabled); 581 } 582 583 if (enabled) { 584 SDL_Window *window = SDL_GetKeyboardFocus(); 585 if (window) { 586 /* We will re-apply the relative mode when the window finishes 587 * being moved, if it is being moved right now. 588 */ 589 SDL_CocoaWindowData *data = 590 (__bridge SDL_CocoaWindowData *)window->internal; 591 if ([data.listener isMovingOrFocusClickPending]) { 592 return true; 593 } 594 595 // Make sure the mouse isn't at the corner of the window, as this 596 // can confuse things if macOS thinks a window resize is happening 597 // on the first click. 598 const CGPoint point = CGPointMake( 599 (float)(window->x + (window->w / 2)), 600 (float)(window->y + (window->h / 2))); 601 Cocoa_HandleMouseWarp(point.x, point.y); 602 CGWarpMouseCursorPosition(point); 603 } 604 DLog("Turning on."); 605 result = CGAssociateMouseAndMouseCursorPosition(NO); 606 } else { 607 DLog("Turning off."); 608 result = CGAssociateMouseAndMouseCursorPosition(YES); 609 } 610 if (result != kCGErrorSuccess) { 611 return SDL_SetError("CGAssociateMouseAndMouseCursorPosition() failed"); 612 } 613 614 /* The hide/unhide calls are redundant most of the time, but they fix 615 * https://bugzilla.libsdl.org/show_bug.cgi?id=2550 616 */ 617 if (enabled) { 618 [NSCursor hide]; 619 } else { 620 [NSCursor unhide]; 621 } 622 return true; 623} 624 625static bool Cocoa_CaptureMouse(SDL_Window *window) 626{ 627 /* our Cocoa event code already tracks the mouse outside the window, 628 so all we have to do here is say "okay" and do what we always do. */ 629 return true; 630} 631 632static SDL_MouseButtonFlags Cocoa_GetGlobalMouseState(float *x, float *y) 633{ 634 const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons]; 635 const NSPoint cocoaLocation = [NSEvent mouseLocation]; 636 SDL_MouseButtonFlags result = 0; 637 SDL_VideoDevice *device = SDL_GetVideoDevice(); 638 SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)device->internal; 639 640 *x = cocoaLocation.x; 641 *y = (videodata.mainDisplayHeight - cocoaLocation.y); 642 643 result |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0; 644 result |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0; 645 result |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0; 646 result |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0; 647 result |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0; 648 649 return result; 650} 651 652bool Cocoa_InitMouse(SDL_VideoDevice *_this) 653{ 654 NSPoint location; 655 SDL_Mouse *mouse = SDL_GetMouse(); 656 SDL_MouseData *data = (SDL_MouseData *)SDL_calloc(1, sizeof(SDL_MouseData)); 657 if (data == NULL) { 658 return false; 659 } 660 661 mouse->internal = data; 662 mouse->CreateCursor = Cocoa_CreateCursor; 663 mouse->CreateAnimatedCursor = Cocoa_CreateAnimatedCursor; 664 mouse->CreateSystemCursor = Cocoa_CreateSystemCursor; 665 mouse->ShowCursor = Cocoa_ShowCursor; 666 mouse->FreeCursor = Cocoa_FreeCursor; 667 mouse->WarpMouse = Cocoa_WarpMouse; 668 mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal; 669 mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode; 670 mouse->CaptureMouse = Cocoa_CaptureMouse; 671 mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState; 672 673 SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor()); 674 675 location = [NSEvent mouseLocation]; 676 data->lastMoveX = location.x; 677 data->lastMoveY = location.y; 678 return true; 679} 680 681static void Cocoa_HandleTitleButtonEvent(SDL_VideoDevice *_this, NSEvent *event) 682{ 683 SDL_Window *window; 684 NSWindow *nswindow = [event window]; 685 686 /* You might land in this function before SDL_Init if showing a message box. 687 Don't dereference a NULL pointer if that happens. */ 688 if (_this == NULL) { 689 return; 690 } 691 692 for (window = _this->windows; window; window = window->next) { 693 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 694 if (data && data.nswindow == nswindow) { 695 switch ([event type]) { 696 case NSEventTypeLeftMouseDown: 697 case NSEventTypeRightMouseDown: 698 case NSEventTypeOtherMouseDown: 699 [data.listener setFocusClickPending:[event buttonNumber]]; 700 break; 701 case NSEventTypeLeftMouseUp: 702 case NSEventTypeRightMouseUp: 703 case NSEventTypeOtherMouseUp: 704 [data.listener clearFocusClickPending:[event buttonNumber]]; 705 break; 706 default: 707 break; 708 } 709 break; 710 } 711 } 712} 713 714static NSWindow *Cocoa_MouseFocus; 715 716NSWindow *Cocoa_GetMouseFocus() 717{ 718 return Cocoa_MouseFocus; 719} 720 721static void Cocoa_ReconcileButtonState(NSEvent *event) 722{ 723 // Send mouse up events for any buttons that are no longer pressed 724 Uint32 buttons = SDL_GetMouseState(NULL, NULL); 725 if (buttons && ![NSEvent pressedMouseButtons]) { 726 Uint8 button = SDL_BUTTON_LEFT; 727 while (buttons) { 728 if (buttons & 0x01) { 729 SDL_SendMouseButton(Cocoa_GetEventTimestamp([event timestamp]), SDL_GetMouseFocus(), SDL_GLOBAL_MOUSE_ID, button, false); 730 } 731 ++button; 732 buttons >>= 1; 733 } 734 } 735} 736 737void Cocoa_HandleMouseEvent(SDL_VideoDevice *_this, NSEvent *event) 738{ 739 SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID; 740 SDL_Mouse *mouse; 741 SDL_MouseData *data; 742 NSPoint location; 743 CGFloat lastMoveX, lastMoveY; 744 float deltaX, deltaY; 745 bool seenWarp; 746 747 // All events except NSEventTypeMouseExited can only happen if the window 748 // has mouse focus, so we'll always set the focus even if we happen to miss 749 // NSEventTypeMouseEntered, which apparently happens if the window is 750 // created under the mouse on macOS 12.7. But, only set the focus if 751 // the event actually has a non-NULL window, otherwise what would happen 752 // is that after an NSEventTypeMouseEntered there would sometimes be 753 // NSEventTypeMouseMoved without a window causing us to suppress subsequent 754 // mouse move events. 755 NSEventType event_type = [event type]; 756 if (event_type == NSEventTypeMouseExited) { 757 Cocoa_MouseFocus = NULL; 758 } else { 759 if ([event window] != NULL) { 760 Cocoa_MouseFocus = [event window]; 761 Cocoa_ReconcileButtonState(event); 762 } 763 } 764 765 switch (event_type) { 766 case NSEventTypeMouseEntered: 767 case NSEventTypeMouseExited: 768 // Focus is handled above 769 return; 770 771 case NSEventTypeMouseMoved: 772 case NSEventTypeLeftMouseDragged: 773 case NSEventTypeRightMouseDragged: 774 case NSEventTypeOtherMouseDragged: 775 break; 776 777 case NSEventTypeLeftMouseDown: 778 case NSEventTypeLeftMouseUp: 779 case NSEventTypeRightMouseDown: 780 case NSEventTypeRightMouseUp: 781 case NSEventTypeOtherMouseDown: 782 case NSEventTypeOtherMouseUp: 783 if ([event window]) { 784 NSRect windowRect = [[[event window] contentView] frame]; 785 if (!NSMouseInRect([event locationInWindow], windowRect, NO)) { 786 Cocoa_HandleTitleButtonEvent(_this, event); 787 return; 788 } 789 } 790 return; 791 792 default: 793 // Ignore any other events. 794 return; 795 } 796 797 mouse = SDL_GetMouse(); 798 data = (SDL_MouseData *)mouse->internal; 799 if (!data) { 800 return; // can happen when returning from fullscreen Space on shutdown 801 } 802 803 seenWarp = data->seenWarp; 804 data->seenWarp = NO; 805 806 location = [NSEvent mouseLocation]; 807 lastMoveX = data->lastMoveX; 808 lastMoveY = data->lastMoveY; 809 data->lastMoveX = location.x; 810 data->lastMoveY = location.y; 811 DLog("Last seen mouse: (%g, %g)", location.x, location.y); 812 813 // Non-relative movement is handled in -[SDL3Cocoa_WindowListener mouseMoved:] 814 if (!mouse->relative_mode) { 815 return; 816 } 817 818 // When GCMouse is active in relative mode, it handles motion events 819 // directly with raw (unaccelerated) deltas. Skip NSEvent-based motion 820 // unless the user wants system-scaled (accelerated) input. 821 if (Cocoa_HasGCMouse() && Cocoa_GCMouseRelativeMode()) { 822 if (!mouse->enable_relative_system_scale) { 823 // GCMouse is providing raw input, skip NSEvent deltas 824 return; 825 } 826 // SYSTEM_SCALE is enabled: use NSEvent accelerated deltas instead 827 } 828 829 // Ignore events that aren't inside the client area (i.e. title bar.) 830 if ([event window]) { 831 NSRect windowRect = [[[event window] contentView] frame]; 832 if (!NSMouseInRect([event locationInWindow], windowRect, NO)) { 833 return; 834 } 835 } 836 837 deltaX = [event deltaX]; 838 deltaY = [event deltaY]; 839 840 if (seenWarp) { 841 SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->internal; 842 deltaX += (lastMoveX - data->lastWarpX); 843 deltaY += ((videodata.mainDisplayHeight - lastMoveY) - data->lastWarpY); 844 845 DLog("Motion was (%g, %g), offset to (%g, %g)", [event deltaX], 846 [event deltaY], deltaX, deltaY); 847 } 848 849 SDL_SendMouseMotion(Cocoa_GetEventTimestamp([event timestamp]), 850 mouse->focus, mouseID, true, deltaX, deltaY); 851} 852 853void Cocoa_HandleMouseWheel(SDL_Window *window, NSEvent *event) 854{ 855 // GCMouse handles scroll events directly, skip NSEvent path to avoid duplicates 856 if (Cocoa_HasGCMouse()) { 857 return; 858 } 859 860 SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID; 861 SDL_MouseWheelDirection direction; 862 CGFloat x, y; 863 864 x = -[event scrollingDeltaX]; 865 y = [event scrollingDeltaY]; 866 direction = SDL_MOUSEWHEEL_NORMAL; 867 868 if ([event isDirectionInvertedFromDevice] == YES) { 869 direction = SDL_MOUSEWHEEL_FLIPPED; 870 } 871 872 if ([event hasPreciseScrollingDeltas]) { 873 x *= 0.1; 874 y *= 0.1; 875 } 876 877 SDL_SendMouseWheel(Cocoa_GetEventTimestamp([event timestamp]), window, mouseID, x, y, direction); 878} 879 880void Cocoa_HandleMouseWarp(CGFloat x, CGFloat y) 881{ 882 /* This makes Cocoa_HandleMouseEvent ignore the delta caused by the warp, 883 * since it gets included in the next movement event. 884 */ 885 SDL_MouseData *data = (SDL_MouseData *)SDL_GetMouse()->internal; 886 data->lastWarpX = x; 887 data->lastWarpY = y; 888 data->seenWarp = true; 889 890 DLog("(%g, %g)", x, y); 891} 892 893void Cocoa_QuitMouse(SDL_VideoDevice *_this) 894{ 895} 896 897#endif // SDL_VIDEO_DRIVER_COCOA 898
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.