Atlas - SDL_cocoawindow.m
Home / ext / SDL / src / video / cocoa Lines: 12 | Size: 126214 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 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 <float.h> // For FLT_MAX 26 27#include "../../events/SDL_dropevents_c.h" 28#include "../../events/SDL_keyboard_c.h" 29#include "../../events/SDL_mouse_c.h" 30#include "../../events/SDL_touch_c.h" 31#include "../../events/SDL_windowevents_c.h" 32#include "../SDL_sysvideo.h" 33 34#include "SDL_cocoamouse.h" 35#include "SDL_cocoaopengl.h" 36#include "SDL_cocoaopengles.h" 37#include "SDL_cocoavideo.h" 38 39#if 0 40#define DEBUG_COCOAWINDOW 41#endif 42 43#ifdef DEBUG_COCOAWINDOW 44#define DLog(fmt, ...) printf("%s: " fmt "\n", __func__, ##__VA_ARGS__) 45#else 46#define DLog(...) \ 47 do { \ 48 } while (0) 49#endif 50 51#ifndef MAC_OS_X_VERSION_10_12 52#define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask 53#endif 54#ifndef NSAppKitVersionNumber10_13_2 55#define NSAppKitVersionNumber10_13_2 1561.2 56#endif 57#ifndef NSAppKitVersionNumber10_14 58#define NSAppKitVersionNumber10_14 1671 59#endif 60 61@implementation SDL_CocoaWindowData 62 63@end 64 65@interface NSScreen (SDL) 66#if MAC_OS_X_VERSION_MAX_ALLOWED < 120000 // Added in the 12.0 SDK 67@property(readonly) NSEdgeInsets safeAreaInsets; 68#endif 69@end 70 71@interface NSWindow (SDL) 72// This is available as of 10.13.2, but isn't in public headers 73@property(nonatomic) NSRect mouseConfinementRect; 74@end 75 76@interface SDL3Window : NSWindow <NSDraggingDestination> 77// These are needed for borderless/fullscreen windows 78- (BOOL)canBecomeKeyWindow; 79- (BOOL)canBecomeMainWindow; 80- (void)sendEvent:(NSEvent *)event; 81- (void)doCommandBySelector:(SEL)aSelector; 82 83// Handle drag-and-drop of files onto the SDL window. 84- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender; 85- (void)draggingExited:(id<NSDraggingInfo>)sender; 86- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender; 87- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender; 88- (BOOL)wantsPeriodicDraggingUpdates; 89- (BOOL)validateMenuItem:(NSMenuItem *)menuItem; 90 91- (SDL_Window *)findSDLWindow; 92@end 93 94@implementation SDL3Window 95 96- (BOOL)validateMenuItem:(NSMenuItem *)menuItem 97{ 98 /* Only allow using the macOS native fullscreen toggle menubar item if the 99 * window is resizable and not in a SDL fullscreen mode. 100 */ 101 if ([menuItem action] == @selector(toggleFullScreen:)) { 102 SDL_Window *window = [self findSDLWindow]; 103 if (!window) { 104 return NO; 105 } 106 107 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 108 if ((window->flags & SDL_WINDOW_FULLSCREEN) && ![data.listener isInFullscreenSpace]) { 109 return NO; 110 } else if (!(window->flags & SDL_WINDOW_RESIZABLE)) { 111 return NO; 112 } 113 } 114 return [super validateMenuItem:menuItem]; 115} 116 117- (BOOL)canBecomeKeyWindow 118{ 119 SDL_Window *window = [self findSDLWindow]; 120 if (window && !(window->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_NOT_FOCUSABLE))) { 121 return YES; 122 } else { 123 return NO; 124 } 125} 126 127- (BOOL)canBecomeMainWindow 128{ 129 SDL_Window *window = [self findSDLWindow]; 130 if (window && !(window->flags & (SDL_WINDOW_TOOLTIP | SDL_WINDOW_NOT_FOCUSABLE)) && !SDL_WINDOW_IS_POPUP(window)) { 131 return YES; 132 } else { 133 return NO; 134 } 135} 136 137- (void)sendEvent:(NSEvent *)event 138{ 139 id delegate; 140 [super sendEvent:event]; 141 142 if ([event type] != NSEventTypeLeftMouseUp) { 143 return; 144 } 145 146 delegate = [self delegate]; 147 if (![delegate isKindOfClass:[SDL3Cocoa_WindowListener class]]) { 148 return; 149 } 150 151 if ([delegate isMoving]) { 152 [delegate windowDidFinishMoving]; 153 } 154} 155 156/* We'll respond to selectors by doing nothing so we don't beep. 157 * The escape key gets converted to a "cancel" selector, etc. 158 */ 159- (void)doCommandBySelector:(SEL)aSelector 160{ 161 // NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector)); 162} 163 164- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender 165{ 166 if (([sender draggingSourceOperationMask] & NSDragOperationGeneric) == NSDragOperationGeneric) { 167 return NSDragOperationGeneric; 168 } else if (([sender draggingSourceOperationMask] & NSDragOperationCopy) == NSDragOperationCopy) { 169 return NSDragOperationCopy; 170 } 171 172 return NSDragOperationNone; // no idea what to do with this, reject it. 173} 174 175- (void)draggingExited:(id<NSDraggingInfo>)sender 176{ 177 SDL_Window *sdlwindow = [self findSDLWindow]; 178 SDL_SendDropComplete(sdlwindow); 179} 180 181- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender 182{ 183 if (([sender draggingSourceOperationMask] & NSDragOperationGeneric) == NSDragOperationGeneric) { 184 SDL_Window *sdlwindow = [self findSDLWindow]; 185 NSPoint point = [sender draggingLocation]; 186 float x, y; 187 x = point.x; 188 y = (sdlwindow->h - point.y); 189 SDL_SendDropPosition(sdlwindow, x, y); 190 return NSDragOperationGeneric; 191 } else if (([sender draggingSourceOperationMask] & NSDragOperationCopy) == NSDragOperationCopy) { 192 SDL_Window *sdlwindow = [self findSDLWindow]; 193 NSPoint point = [sender draggingLocation]; 194 float x, y; 195 x = point.x; 196 y = (sdlwindow->h - point.y); 197 SDL_SendDropPosition(sdlwindow, x, y); 198 return NSDragOperationCopy; 199 } 200 201 return NSDragOperationNone; // no idea what to do with this, reject it. 202} 203 204- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender 205{ 206 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, 207 ". [SDL] In performDragOperation, draggingSourceOperationMask %lx, " 208 "expected Generic %lx, others Copy %lx, Link %lx, Private %lx, Move %lx, Delete %lx\n", 209 (unsigned long)[sender draggingSourceOperationMask], 210 (unsigned long)NSDragOperationGeneric, 211 (unsigned long)NSDragOperationCopy, 212 (unsigned long)NSDragOperationLink, 213 (unsigned long)NSDragOperationPrivate, 214 (unsigned long)NSDragOperationMove, 215 (unsigned long)NSDragOperationDelete); 216 if ([sender draggingPasteboard]) { 217 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, 218 ". [SDL] In performDragOperation, valid draggingPasteboard, " 219 "name [%s] '%s', changeCount %ld\n", 220 [[[[sender draggingPasteboard] name] className] UTF8String], 221 [[[[sender draggingPasteboard] name] description] UTF8String], 222 (long)[[sender draggingPasteboard] changeCount]); 223 } 224 @autoreleasepool { 225 NSPasteboard *pasteboard = [sender draggingPasteboard]; 226 NSString *desiredType = [pasteboard availableTypeFromArray:@[ NSFilenamesPboardType, NSPasteboardTypeString ]]; 227 SDL_Window *sdlwindow = [self findSDLWindow]; 228 NSData *pboardData; 229 id pboardPlist; 230 NSString *pboardString; 231 NSPoint point; 232 float x, y; 233 234 for (NSString *supportedType in [pasteboard types]) { 235 NSString *typeString = [pasteboard stringForType:supportedType]; 236 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, 237 ". [SDL] In performDragOperation, Pasteboard type '%s', stringForType (%lu) '%s'\n", 238 [[supportedType description] UTF8String], 239 (unsigned long)[[typeString description] length], 240 [[typeString description] UTF8String]); 241 } 242 243 if (desiredType == nil) { 244 return NO; // can't accept anything that's being dropped here. 245 } 246 pboardData = [pasteboard dataForType:desiredType]; 247 if (pboardData == nil) { 248 return NO; 249 } 250 SDL_assert([desiredType isEqualToString:NSFilenamesPboardType] || 251 [desiredType isEqualToString:NSPasteboardTypeString]); 252 253 pboardString = [pasteboard stringForType:desiredType]; 254 pboardPlist = [pasteboard propertyListForType:desiredType]; 255 256 // Use SendDropPosition to update the mouse location 257 point = [sender draggingLocation]; 258 x = point.x; 259 y = (sdlwindow->h - point.y); 260 if (x >= 0.0f && x < (float)sdlwindow->w && y >= 0.0f && y < (float)sdlwindow->h) { 261 SDL_SendDropPosition(sdlwindow, x, y); 262 } 263 // Use SendDropPosition to update the mouse location 264 265 if ([desiredType isEqualToString:NSFilenamesPboardType]) { 266 for (NSString *path in (NSArray *)pboardPlist) { 267 NSURL *fileURL = [NSURL fileURLWithPath:path]; 268 NSNumber *isAlias = nil; 269 270 [fileURL getResourceValue:&isAlias forKey:NSURLIsAliasFileKey error:nil]; 271 272 // If the URL is an alias, resolve it. 273 if ([isAlias boolValue]) { 274 NSURLBookmarkResolutionOptions opts = NSURLBookmarkResolutionWithoutMounting | 275 NSURLBookmarkResolutionWithoutUI; 276 NSData *bookmark = [NSURL bookmarkDataWithContentsOfURL:fileURL error:nil]; 277 if (bookmark != nil) { 278 NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:bookmark 279 options:opts 280 relativeToURL:nil 281 bookmarkDataIsStale:nil 282 error:nil]; 283 if (resolvedURL != nil) { 284 fileURL = resolvedURL; 285 } 286 } 287 } 288 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, 289 ". [SDL] In performDragOperation, desiredType '%s', " 290 "Submitting DropFile as (%lu) '%s'\n", 291 [[desiredType description] UTF8String], 292 (unsigned long)[[[fileURL path] description] length], 293 [[[fileURL path] description] UTF8String]); 294 if (!SDL_SendDropFile(sdlwindow, NULL, [[[fileURL path] description] UTF8String])) { 295 return NO; 296 } 297 } 298 } else if ([desiredType isEqualToString:NSPasteboardTypeString]) { 299 char *buffer = SDL_strdup([[pboardString description] UTF8String]); 300 char *saveptr = NULL; 301 char *token = SDL_strtok_r(buffer, "\r\n", &saveptr); 302 while (token) { 303 SDL_LogTrace(SDL_LOG_CATEGORY_INPUT, 304 ". [SDL] In performDragOperation, desiredType '%s', " 305 "Submitting DropText as (%lu) '%s'\n", 306 [[desiredType description] UTF8String], 307 SDL_strlen(token), token); 308 if (!SDL_SendDropText(sdlwindow, token)) { 309 SDL_free(buffer); 310 return NO; 311 } 312 token = SDL_strtok_r(NULL, "\r\n", &saveptr); 313 } 314 SDL_free(buffer); 315 } 316 317 SDL_SendDropComplete(sdlwindow); 318 return YES; 319 } 320} 321 322- (BOOL)wantsPeriodicDraggingUpdates 323{ 324 return NO; 325} 326 327- (SDL_Window *)findSDLWindow 328{ 329 SDL_Window *sdlwindow = NULL; 330 SDL_VideoDevice *_this = SDL_GetVideoDevice(); 331 332 // !!! FIXME: is there a better way to do this? 333 if (_this) { 334 for (sdlwindow = _this->windows; sdlwindow; sdlwindow = sdlwindow->next) { 335 NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)sdlwindow->internal).nswindow; 336 if (nswindow == self) { 337 break; 338 } 339 } 340 } 341 342 return sdlwindow; 343} 344 345@end 346 347bool b_inModeTransition; 348 349static CGFloat SqDistanceToRect(const NSPoint *point, const NSRect *rect) 350{ 351 NSPoint edge = *point; 352 CGFloat left = NSMinX(*rect), right = NSMaxX(*rect); 353 CGFloat bottom = NSMinX(*rect), top = NSMaxY(*rect); 354 NSPoint delta; 355 356 if (point->x < left) { 357 edge.x = left; 358 } else if (point->x > right) { 359 edge.x = right; 360 } 361 362 if (point->y < bottom) { 363 edge.y = bottom; 364 } else if (point->y > top) { 365 edge.y = top; 366 } 367 368 delta = NSMakePoint(edge.x - point->x, edge.y - point->y); 369 return delta.x * delta.x + delta.y * delta.y; 370} 371 372static NSScreen *ScreenForPoint(const NSPoint *point) 373{ 374 NSScreen *screen; 375 376 // Do a quick check first to see if the point lies on a specific screen 377 for (NSScreen *candidate in [NSScreen screens]) { 378 if (NSPointInRect(*point, [candidate frame])) { 379 screen = candidate; 380 break; 381 } 382 } 383 384 // Find the screen the point is closest to 385 if (!screen) { 386 CGFloat closest = MAXFLOAT; 387 for (NSScreen *candidate in [NSScreen screens]) { 388 NSRect screenRect = [candidate frame]; 389 390 CGFloat sqdist = SqDistanceToRect(point, &screenRect); 391 if (sqdist < closest) { 392 screen = candidate; 393 closest = sqdist; 394 } 395 } 396 } 397 398 return screen; 399} 400 401bool Cocoa_IsWindowInFullscreenSpace(SDL_Window *window) 402{ 403 @autoreleasepool { 404 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 405 406 if ([data.listener isInFullscreenSpace]) { 407 return true; 408 } else { 409 return false; 410 } 411 } 412} 413 414bool Cocoa_IsWindowInFullscreenSpaceTransition(SDL_Window *window) 415{ 416 @autoreleasepool { 417 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 418 419 if ([data.listener isInFullscreenSpaceTransition]) { 420 return true; 421 } else { 422 return false; 423 } 424 } 425} 426 427bool Cocoa_IsWindowZoomed(SDL_Window *window) 428{ 429 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 430 NSWindow *nswindow = data.nswindow; 431 bool zoomed = false; 432 433 // isZoomed always returns true if the window is not resizable or the window is fullscreen 434 if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed] && 435 !(window->flags & SDL_WINDOW_FULLSCREEN) && !Cocoa_IsWindowInFullscreenSpace(window)) { 436 // If we are at our desired floating area, then we're not zoomed 437 bool floating = (window->x == window->floating.x && 438 window->y == window->floating.y && 439 window->w == window->floating.w && 440 window->h == window->floating.h); 441 if (!floating) { 442 zoomed = true; 443 } 444 } 445 return zoomed; 446} 447 448bool Cocoa_IsShowingModalDialog(SDL_Window *window) 449{ 450 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 451 return data.has_modal_dialog; 452} 453 454void Cocoa_SetWindowHasModalDialog(SDL_Window *window, bool has_modal) 455{ 456 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 457 data.has_modal_dialog = has_modal; 458} 459 460typedef enum CocoaMenuVisibility 461{ 462 COCOA_MENU_VISIBILITY_AUTO = 0, 463 COCOA_MENU_VISIBILITY_NEVER, 464 COCOA_MENU_VISIBILITY_ALWAYS 465} CocoaMenuVisibility; 466 467static CocoaMenuVisibility menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO; 468 469static void Cocoa_ToggleFullscreenSpaceMenuVisibility(SDL_Window *window) 470{ 471 if (window && Cocoa_IsWindowInFullscreenSpace(window)) { 472 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 473 474 // 'Auto' sets the menu to visible if fullscreen wasn't explicitly entered via SDL_SetWindowFullscreen(). 475 if ((menu_visibility_hint == COCOA_MENU_VISIBILITY_AUTO && !data.fullscreen_space_requested) || 476 menu_visibility_hint == COCOA_MENU_VISIBILITY_ALWAYS) { 477 [NSMenu setMenuBarVisible:YES]; 478 } else { 479 [NSMenu setMenuBarVisible:NO]; 480 } 481 } 482} 483 484void Cocoa_MenuVisibilityCallback(void *userdata, const char *name, const char *oldValue, const char *newValue) 485{ 486 if (newValue) { 487 if (*newValue == '0' || SDL_strcasecmp(newValue, "false") == 0) { 488 menu_visibility_hint = COCOA_MENU_VISIBILITY_NEVER; 489 } else if (*newValue == '1' || SDL_strcasecmp(newValue, "true") == 0) { 490 menu_visibility_hint = COCOA_MENU_VISIBILITY_ALWAYS; 491 } else { 492 menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO; 493 } 494 } else { 495 menu_visibility_hint = COCOA_MENU_VISIBILITY_AUTO; 496 } 497 498 // Update the current menu visibility. 499 Cocoa_ToggleFullscreenSpaceMenuVisibility(SDL_GetKeyboardFocus()); 500} 501 502static NSScreen *ScreenForRect(const NSRect *rect) 503{ 504 NSPoint center = NSMakePoint(NSMidX(*rect), NSMidY(*rect)); 505 return ScreenForPoint(¢er); 506} 507 508static void ConvertNSRect(NSRect *r) 509{ 510 r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - r->origin.y - r->size.height; 511} 512 513static void ScheduleContextUpdates(SDL_CocoaWindowData *data) 514{ 515// We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. 516#ifdef SDL_VIDEO_OPENGL 517 518#ifdef __clang__ 519#pragma clang diagnostic push 520#pragma clang diagnostic ignored "-Wdeprecated-declarations" 521#endif 522 523 NSOpenGLContext *currentContext; 524 NSMutableArray *contexts; 525 if (!data || !data.nscontexts) { 526 return; 527 } 528 529 currentContext = [NSOpenGLContext currentContext]; 530 contexts = data.nscontexts; 531 @synchronized(contexts) { 532 for (SDL3OpenGLContext *context in contexts) { 533 if (context == currentContext) { 534 [context update]; 535 } else { 536 [context scheduleUpdate]; 537 } 538 } 539 } 540 541#ifdef __clang__ 542#pragma clang diagnostic pop 543#endif 544 545#endif // SDL_VIDEO_OPENGL 546} 547 548// !!! FIXME: this should use a hint callback. 549static bool GetHintCtrlClickEmulateRightClick(void) 550{ 551 return SDL_GetHintBoolean(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, false); 552} 553 554static NSUInteger GetWindowWindowedStyle(SDL_Window *window) 555{ 556 /* IF YOU CHANGE ANY FLAGS IN HERE, PLEASE READ 557 the NSWindowStyleMaskBorderless comments in SetupWindowData()! */ 558 559 /* always allow miniaturization, otherwise you can't programmatically 560 minimize the window, whether there's a title bar or not */ 561 NSUInteger style = NSWindowStyleMaskMiniaturizable; 562 563 if (!SDL_WINDOW_IS_POPUP(window)) { 564 if (window->flags & SDL_WINDOW_BORDERLESS) { 565 style |= NSWindowStyleMaskBorderless; 566 } else { 567 style |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable); 568 } 569 if (window->flags & SDL_WINDOW_RESIZABLE) { 570 style |= NSWindowStyleMaskResizable; 571 } 572 } else { 573 style |= NSWindowStyleMaskBorderless; 574 } 575 return style; 576} 577 578static NSUInteger GetWindowStyle(SDL_Window *window) 579{ 580 NSUInteger style = 0; 581 582 if (window->flags & SDL_WINDOW_FULLSCREEN) { 583 style = NSWindowStyleMaskBorderless; 584 } else { 585 style = GetWindowWindowedStyle(window); 586 } 587 return style; 588} 589 590static bool SetWindowStyle(SDL_Window *window, NSUInteger style) 591{ 592 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 593 NSWindow *nswindow = data.nswindow; 594 595 // The view responder chain gets messed with during setStyleMask 596 if ([data.sdlContentView nextResponder] == data.listener) { 597 [data.sdlContentView setNextResponder:nil]; 598 } 599 600 [nswindow setStyleMask:style]; 601 602 // The view responder chain gets messed with during setStyleMask 603 if ([data.sdlContentView nextResponder] != data.listener) { 604 [data.sdlContentView setNextResponder:data.listener]; 605 } 606 607 return true; 608} 609 610static bool ShouldAdjustCoordinatesForGrab(SDL_Window *window) 611{ 612 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 613 614 if (!data || [data.listener isMovingOrFocusClickPending]) { 615 return false; 616 } 617 618 if (!(window->flags & SDL_WINDOW_INPUT_FOCUS)) { 619 return false; 620 } 621 622 if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) || (window->mouse_rect.w > 0 && window->mouse_rect.h > 0)) { 623 return true; 624 } 625 return false; 626} 627 628static bool AdjustCoordinatesForGrab(SDL_Window *window, float x, float y, CGPoint *adjusted) 629{ 630 if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) { 631 SDL_Rect window_rect; 632 SDL_Rect mouse_rect; 633 634 window_rect.x = 0; 635 window_rect.y = 0; 636 window_rect.w = window->w; 637 window_rect.h = window->h; 638 639 if (SDL_GetRectIntersection(&window->mouse_rect, &window_rect, &mouse_rect)) { 640 float left = (float)window->x + mouse_rect.x; 641 float right = left + mouse_rect.w - 1; 642 float top = (float)window->y + mouse_rect.y; 643 float bottom = top + mouse_rect.h - 1; 644 if (x < left || x > right || y < top || y > bottom) { 645 adjusted->x = SDL_clamp(x, left, right); 646 adjusted->y = SDL_clamp(y, top, bottom); 647 return true; 648 } 649 return false; 650 } 651 } 652 653 if (window->flags & SDL_WINDOW_MOUSE_GRABBED) { 654 float left = (float)window->x; 655 float right = left + window->w - 1; 656 float top = (float)window->y; 657 float bottom = top + window->h - 1; 658 if (x < left || x > right || y < top || y > bottom) { 659 adjusted->x = SDL_clamp(x, left, right); 660 adjusted->y = SDL_clamp(y, top, bottom); 661 return true; 662 } 663 } 664 return false; 665} 666 667static void Cocoa_UpdateClipCursor(SDL_Window *window) 668{ 669 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 670 671 if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_13_2) { 672 NSWindow *nswindow = data.nswindow; 673 SDL_Rect mouse_rect; 674 675 SDL_zero(mouse_rect); 676 677 if (ShouldAdjustCoordinatesForGrab(window)) { 678 SDL_Rect window_rect; 679 680 window_rect.x = 0; 681 window_rect.y = 0; 682 window_rect.w = window->w; 683 window_rect.h = window->h; 684 685 if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) { 686 SDL_GetRectIntersection(&window->mouse_rect, &window_rect, &mouse_rect); 687 } 688 689 if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0 && 690 SDL_RectEmpty(&mouse_rect)) { 691 SDL_memcpy(&mouse_rect, &window_rect, sizeof(mouse_rect)); 692 } 693 } 694 695 if (SDL_RectEmpty(&mouse_rect)) { 696 nswindow.mouseConfinementRect = NSZeroRect; 697 } else { 698 NSRect rect; 699 rect.origin.x = mouse_rect.x; 700 rect.origin.y = [nswindow contentLayoutRect].size.height - mouse_rect.y - mouse_rect.h; 701 rect.size.width = mouse_rect.w; 702 rect.size.height = mouse_rect.h; 703 nswindow.mouseConfinementRect = rect; 704 } 705 } else { 706 // Move the cursor to the nearest point in the window 707 if (ShouldAdjustCoordinatesForGrab(window)) { 708 float x, y; 709 CGPoint cgpoint; 710 711 SDL_GetGlobalMouseState(&x, &y); 712 if (AdjustCoordinatesForGrab(window, x, y, &cgpoint)) { 713 Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y); 714 CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint); 715 } 716 } 717 } 718} 719 720static SDL_Window *GetParentToplevelWindow(SDL_Window *window) 721{ 722 SDL_Window *toplevel = window; 723 724 // Find the topmost parent 725 while (SDL_WINDOW_IS_POPUP(toplevel)) { 726 toplevel = toplevel->parent; 727 } 728 729 return toplevel; 730} 731 732static void Cocoa_SetKeyboardFocus(SDL_Window *window, bool set_active_focus) 733{ 734 SDL_Window *toplevel = GetParentToplevelWindow(window); 735 toplevel->keyboard_focus = window; 736 737 if (set_active_focus && !window->is_hiding && !window->is_destroying) { 738 SDL_SetKeyboardFocus(window); 739 } 740} 741 742static void Cocoa_SendExposedEventIfVisible(SDL_Window *window) 743{ 744 NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow; 745 if ([nswindow occlusionState] & NSWindowOcclusionStateVisible) { 746 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); 747 } 748} 749 750static void Cocoa_WaitForMiniaturizable(SDL_Window *window) 751{ 752 NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow; 753 NSButton *button = [nswindow standardWindowButton:NSWindowMiniaturizeButton]; 754 if (button) { 755 int iterations = 0; 756 while (![button isEnabled] && (iterations < 100)) { 757 SDL_Delay(10); 758 SDL_PumpEvents(); 759 iterations++; 760 } 761 } 762} 763 764static void Cocoa_IncrementCursorFrame(void) 765{ 766 SDL_Mouse *mouse = SDL_GetMouse(); 767 768 if (mouse->cur_cursor) { 769 SDL_CursorData *cdata = mouse->cur_cursor->internal; 770 cdata->current_frame = (cdata->current_frame + 1) % cdata->num_cursors; 771 772 SDL_Window *focus = SDL_GetMouseFocus(); 773 if (focus) { 774 SDL_CocoaWindowData *_data = (__bridge SDL_CocoaWindowData *)focus->internal; 775 [_data.nswindow invalidateCursorRectsForView:_data.sdlContentView]; 776 } 777 } 778} 779 780static NSCursor *Cocoa_GetDesiredCursor(void) 781{ 782 SDL_Mouse *mouse = SDL_GetMouse(); 783 784 if (mouse->cursor_visible && mouse->cur_cursor && !mouse->relative_mode) { 785 SDL_CursorData *cdata = mouse->cur_cursor->internal; 786 787 if (cdata) { 788 if (cdata->num_cursors > 1 && cdata->frames[cdata->current_frame].duration && !cdata->frameTimer) { 789 const NSTimeInterval interval = cdata->frames[cdata->current_frame].duration * 0.001; 790 cdata->frameTimer = [NSTimer timerWithTimeInterval:interval 791 repeats:NO 792 block:^(NSTimer *timer) { 793 cdata->frameTimer = nil; 794 Cocoa_IncrementCursorFrame(); 795 }]; 796 797 [[NSRunLoop currentRunLoop] addTimer:cdata->frameTimer forMode:NSRunLoopCommonModes]; 798 } 799 800 return (__bridge NSCursor *)cdata->frames[cdata->current_frame].cursor; 801 } 802 } 803 804 return [NSCursor invisibleCursor]; 805} 806 807@implementation SDL3Cocoa_WindowListener 808 809- (void)listen:(SDL_CocoaWindowData *)data 810{ 811 NSNotificationCenter *center; 812 NSWindow *window = data.nswindow; 813 NSView *view = data.sdlContentView; 814 815 _data = data; 816 observingVisible = YES; 817 wasCtrlLeft = NO; 818 wasVisible = [window isVisible]; 819 isFullscreenSpace = NO; 820 inFullscreenTransition = NO; 821 pendingWindowOperation = PENDING_OPERATION_NONE; 822 isMoving = NO; 823 isMiniaturizing = NO; 824 isDragAreaRunning = NO; 825 pendingWindowWarpX = pendingWindowWarpY = FLT_MAX; 826 liveResizeTimer = nil; 827 828 center = [NSNotificationCenter defaultCenter]; 829 830 if ([window delegate] != nil) { 831 [center addObserver:self selector:@selector(windowDidExpose:) name:NSWindowDidExposeNotification object:window]; 832 [center addObserver:self selector:@selector(windowDidChangeOcclusionState:) name:NSWindowDidChangeOcclusionStateNotification object:window]; 833 [center addObserver:self selector:@selector(windowWillStartLiveResize:) name:NSWindowWillStartLiveResizeNotification object:window]; 834 [center addObserver:self selector:@selector(windowDidEndLiveResize:) name:NSWindowDidEndLiveResizeNotification object:window]; 835 [center addObserver:self selector:@selector(windowWillMove:) name:NSWindowWillMoveNotification object:window]; 836 [center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:window]; 837 [center addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:window]; 838 [center addObserver:self selector:@selector(windowWillMiniaturize:) name:NSWindowWillMiniaturizeNotification object:window]; 839 [center addObserver:self selector:@selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:window]; 840 [center addObserver:self selector:@selector(windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:window]; 841 [center addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:window]; 842 [center addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:window]; 843 [center addObserver:self selector:@selector(windowDidChangeBackingProperties:) name:NSWindowDidChangeBackingPropertiesNotification object:window]; 844 [center addObserver:self selector:@selector(windowDidChangeScreenProfile:) name:NSWindowDidChangeScreenProfileNotification object:window]; 845 [center addObserver:self selector:@selector(windowDidChangeScreen:) name:NSWindowDidChangeScreenNotification object:window]; 846 [center addObserver:self selector:@selector(windowWillEnterFullScreen:) name:NSWindowWillEnterFullScreenNotification object:window]; 847 [center addObserver:self selector:@selector(windowDidEnterFullScreen:) name:NSWindowDidEnterFullScreenNotification object:window]; 848 [center addObserver:self selector:@selector(windowWillExitFullScreen:) name:NSWindowWillExitFullScreenNotification object:window]; 849 [center addObserver:self selector:@selector(windowDidExitFullScreen:) name:NSWindowDidExitFullScreenNotification object:window]; 850 [center addObserver:self selector:@selector(windowDidFailToEnterFullScreen:) name:@"NSWindowDidFailToEnterFullScreenNotification" object:window]; 851 [center addObserver:self selector:@selector(windowDidFailToExitFullScreen:) name:@"NSWindowDidFailToExitFullScreenNotification" object:window]; 852 } else { 853 [window setDelegate:self]; 854 } 855 856 /* Haven't found a delegate / notification that triggers when the window is 857 * ordered out (is not visible any more). You can be ordered out without 858 * minimizing, so DidMiniaturize doesn't work. (e.g. -[NSWindow orderOut:]) 859 */ 860 [window addObserver:self 861 forKeyPath:@"visible" 862 options:NSKeyValueObservingOptionNew 863 context:NULL]; 864 865 [window setNextResponder:self]; 866 [window setAcceptsMouseMovedEvents:YES]; 867 868 [view setNextResponder:self]; 869 870 [view setAcceptsTouchEvents:YES]; 871} 872 873- (void)observeValueForKeyPath:(NSString *)keyPath 874 ofObject:(id)object 875 change:(NSDictionary *)change 876 context:(void *)context 877{ 878 if (!observingVisible) { 879 return; 880 } 881 882 if (object == _data.nswindow && [keyPath isEqualToString:@"visible"]) { 883 int newVisibility = [[change objectForKey:@"new"] intValue]; 884 if (newVisibility) { 885 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_SHOWN, 0, 0); 886 } else if (![_data.nswindow isMiniaturized]) { 887 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); 888 } 889 } 890} 891 892- (void)pauseVisibleObservation 893{ 894 observingVisible = NO; 895 wasVisible = [_data.nswindow isVisible]; 896} 897 898- (void)resumeVisibleObservation 899{ 900 BOOL isVisible = [_data.nswindow isVisible]; 901 observingVisible = YES; 902 if (wasVisible != isVisible) { 903 if (isVisible) { 904 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_SHOWN, 0, 0); 905 } else if (![_data.nswindow isMiniaturized]) { 906 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); 907 } 908 909 wasVisible = isVisible; 910 } 911} 912 913- (BOOL)setFullscreenSpace:(BOOL)state 914{ 915 SDL_Window *window = _data.window; 916 NSWindow *nswindow = _data.nswindow; 917 SDL_CocoaVideoData *videodata = ((__bridge SDL_CocoaWindowData *)window->internal).videodata; 918 919 if (!videodata.allow_spaces) { 920 return NO; // Spaces are forcibly disabled. 921 } else if (state && window->fullscreen_exclusive) { 922 return NO; // we only allow you to make a Space on fullscreen desktop windows. 923 } else if (!state && window->last_fullscreen_exclusive_display) { 924 return NO; // we only handle leaving the Space on windows that were previously fullscreen desktop. 925 } else if (state == isFullscreenSpace && !inFullscreenTransition) { 926 return YES; // already there. 927 } 928 929 if (inFullscreenTransition) { 930 if (state) { 931 [self clearPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; 932 [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; 933 } else { 934 [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; 935 [self addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; 936 } 937 return YES; 938 } 939 inFullscreenTransition = YES; 940 941 // you need to be FullScreenPrimary, or toggleFullScreen doesn't work. Unset it again in windowDidExitFullScreen. 942 [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; 943 [nswindow performSelectorOnMainThread:@selector(toggleFullScreen:) withObject:nswindow waitUntilDone:NO]; 944 return YES; 945} 946 947- (BOOL)isInFullscreenSpace 948{ 949 return isFullscreenSpace; 950} 951 952- (BOOL)isInFullscreenSpaceTransition 953{ 954 return inFullscreenTransition; 955} 956 957- (void)clearPendingWindowOperation:(PendingWindowOperation)operation 958{ 959 pendingWindowOperation &= ~operation; 960} 961 962- (void)clearAllPendingWindowOperations 963{ 964 pendingWindowOperation = PENDING_OPERATION_NONE; 965} 966 967- (void)addPendingWindowOperation:(PendingWindowOperation)operation 968{ 969 pendingWindowOperation |= operation; 970} 971 972- (BOOL)windowOperationIsPending:(PendingWindowOperation)operation 973{ 974 return !!(pendingWindowOperation & operation); 975} 976 977- (BOOL)hasPendingWindowOperation 978{ 979 // A pending zoom may be deferred until leaving fullscreen, so don't block on it. 980 return (pendingWindowOperation & ~PENDING_OPERATION_ZOOM) != PENDING_OPERATION_NONE || 981 isMiniaturizing || inFullscreenTransition; 982} 983 984- (void)close 985{ 986 NSNotificationCenter *center; 987 NSWindow *window = _data.nswindow; 988 NSView *view = [window contentView]; 989 990 center = [NSNotificationCenter defaultCenter]; 991 992 if ([window delegate] != self) { 993 [center removeObserver:self name:NSWindowDidExposeNotification object:window]; 994 [center removeObserver:self name:NSWindowDidChangeOcclusionStateNotification object:window]; 995 [center removeObserver:self name:NSWindowWillStartLiveResizeNotification object:window]; 996 [center removeObserver:self name:NSWindowDidEndLiveResizeNotification object:window]; 997 [center removeObserver:self name:NSWindowWillMoveNotification object:window]; 998 [center removeObserver:self name:NSWindowDidMoveNotification object:window]; 999 [center removeObserver:self name:NSWindowDidResizeNotification object:window]; 1000 [center removeObserver:self name:NSWindowWillMiniaturizeNotification object:window]; 1001 [center removeObserver:self name:NSWindowDidMiniaturizeNotification object:window]; 1002 [center removeObserver:self name:NSWindowDidDeminiaturizeNotification object:window]; 1003 [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:window]; 1004 [center removeObserver:self name:NSWindowDidResignKeyNotification object:window]; 1005 [center removeObserver:self name:NSWindowDidChangeBackingPropertiesNotification object:window]; 1006 [center removeObserver:self name:NSWindowDidChangeScreenProfileNotification object:window]; 1007 [center removeObserver:self name:NSWindowDidChangeScreenNotification object:window]; 1008 [center removeObserver:self name:NSWindowWillEnterFullScreenNotification object:window]; 1009 [center removeObserver:self name:NSWindowDidEnterFullScreenNotification object:window]; 1010 [center removeObserver:self name:NSWindowWillExitFullScreenNotification object:window]; 1011 [center removeObserver:self name:NSWindowDidExitFullScreenNotification object:window]; 1012 [center removeObserver:self name:@"NSWindowDidFailToEnterFullScreenNotification" object:window]; 1013 [center removeObserver:self name:@"NSWindowDidFailToExitFullScreenNotification" object:window]; 1014 } else { 1015 [window setDelegate:nil]; 1016 } 1017 1018 [window removeObserver:self forKeyPath:@"visible"]; 1019 1020 if ([window nextResponder] == self) { 1021 [window setNextResponder:nil]; 1022 } 1023 if ([view nextResponder] == self) { 1024 [view setNextResponder:nil]; 1025 } 1026} 1027 1028- (BOOL)isMoving 1029{ 1030 return isMoving; 1031} 1032 1033- (BOOL)isMovingOrFocusClickPending 1034{ 1035 return isMoving || (focusClickPending != 0); 1036} 1037 1038- (void)setFocusClickPending:(NSInteger)button 1039{ 1040 focusClickPending |= (1 << button); 1041} 1042 1043- (void)clearFocusClickPending:(NSInteger)button 1044{ 1045 if (focusClickPending & (1 << button)) { 1046 focusClickPending &= ~(1 << button); 1047 if (focusClickPending == 0) { 1048 [self onMovingOrFocusClickPendingStateCleared]; 1049 } 1050 } 1051} 1052 1053- (void)updateIgnoreMouseState:(NSEvent *)theEvent 1054{ 1055 SDL_Window *window = _data.window; 1056 SDL_Surface *shape = (SDL_Surface *)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_SHAPE_POINTER, NULL); 1057 BOOL ignoresMouseEvents = NO; 1058 1059 if (shape) { 1060 NSPoint point = [theEvent locationInWindow]; 1061 NSRect windowRect = [[_data.nswindow contentView] frame]; 1062 if (NSMouseInRect(point, windowRect, NO)) { 1063 int x = (int)SDL_roundf((point.x / (window->w - 1)) * (shape->w - 1)); 1064 int y = (int)SDL_roundf(((window->h - point.y) / (window->h - 1)) * (shape->h - 1)); 1065 Uint8 a; 1066 1067 if (!SDL_ReadSurfacePixel(shape, x, y, NULL, NULL, NULL, &a) || a == SDL_ALPHA_TRANSPARENT) { 1068 ignoresMouseEvents = YES; 1069 } 1070 } 1071 } 1072 _data.nswindow.ignoresMouseEvents = ignoresMouseEvents; 1073} 1074 1075- (void)setPendingMoveX:(float)x Y:(float)y 1076{ 1077 pendingWindowWarpX = x; 1078 pendingWindowWarpY = y; 1079} 1080 1081- (void)windowDidFinishMoving 1082{ 1083 if (isMoving) { 1084 isMoving = NO; 1085 [self onMovingOrFocusClickPendingStateCleared]; 1086 } 1087} 1088 1089- (void)onMovingOrFocusClickPendingStateCleared 1090{ 1091 if (![self isMovingOrFocusClickPending]) { 1092 SDL_Mouse *mouse = SDL_GetMouse(); 1093 if (pendingWindowWarpX != FLT_MAX && pendingWindowWarpY != FLT_MAX) { 1094 mouse->WarpMouseGlobal(pendingWindowWarpX, pendingWindowWarpY); 1095 pendingWindowWarpX = pendingWindowWarpY = FLT_MAX; 1096 } 1097 if (mouse->relative_mode && mouse->focus == _data.window) { 1098 // Move the cursor to the nearest point in the window 1099 { 1100 float x, y; 1101 CGPoint cgpoint; 1102 1103 SDL_GetMouseState(&x, &y); 1104 cgpoint.x = _data.window->x + x; 1105 cgpoint.y = _data.window->y + y; 1106 1107 Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y); 1108 1109 DLog("Returning cursor to (%g, %g)", cgpoint.x, cgpoint.y); 1110 CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint); 1111 } 1112 1113 mouse->SetRelativeMouseMode(true); 1114 } else { 1115 Cocoa_UpdateClipCursor(_data.window); 1116 } 1117 } 1118} 1119 1120- (BOOL)windowShouldClose:(id)sender 1121{ 1122 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_CLOSE_REQUESTED, 0, 0); 1123 return NO; 1124} 1125 1126- (void)windowDidExpose:(NSNotification *)aNotification 1127{ 1128 Cocoa_SendExposedEventIfVisible(_data.window); 1129} 1130 1131- (void)windowDidChangeOcclusionState:(NSNotification *)aNotification 1132{ 1133 if ([_data.nswindow occlusionState] & NSWindowOcclusionStateVisible) { 1134 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_EXPOSED, 0, 0); 1135 } else { 1136 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_OCCLUDED, 0, 0); 1137 } 1138} 1139 1140- (void)windowWillStartLiveResize:(NSNotification *)aNotification 1141{ 1142 // We'll try to maintain 60 FPS during live resizing 1143 const NSTimeInterval interval = 1.0 / 60.0; 1144 liveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:interval 1145 repeats:TRUE 1146 block:^(NSTimer *unusedTimer) 1147 { 1148 SDL_OnWindowLiveResizeUpdate(_data.window); 1149 }]; 1150 1151 [[NSRunLoop currentRunLoop] addTimer:liveResizeTimer forMode:NSRunLoopCommonModes]; 1152} 1153 1154- (void)windowDidEndLiveResize:(NSNotification *)aNotification 1155{ 1156 [liveResizeTimer invalidate]; 1157 liveResizeTimer = nil; 1158} 1159 1160- (void)windowWillMove:(NSNotification *)aNotification 1161{ 1162 if ([_data.nswindow isKindOfClass:[SDL3Window class]]) { 1163 pendingWindowWarpX = pendingWindowWarpY = FLT_MAX; 1164 isMoving = YES; 1165 } 1166} 1167 1168- (void)windowDidMove:(NSNotification *)aNotification 1169{ 1170 int x, y; 1171 SDL_Window *window = _data.window; 1172 NSWindow *nswindow = _data.nswindow; 1173 NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; 1174 ConvertNSRect(&rect); 1175 1176 if (inFullscreenTransition || b_inModeTransition) { 1177 // We'll take care of this at the end of the transition 1178 return; 1179 } 1180 1181 x = (int)rect.origin.x; 1182 y = (int)rect.origin.y; 1183 1184 ScheduleContextUpdates(_data); 1185 1186 // Get the parent-relative coordinates for child windows. 1187 SDL_GlobalToRelativeForWindow(window, x, y, &x, &y); 1188 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y); 1189} 1190 1191- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize 1192{ 1193 SDL_Window *window = _data.window; 1194 1195 if (window->min_aspect != window->max_aspect) { 1196 NSWindow *nswindow = _data.nswindow; 1197 NSRect newContentRect = [nswindow contentRectForFrameRect:NSMakeRect(0, 0, frameSize.width, frameSize.height)]; 1198 NSSize newSize = newContentRect.size; 1199 CGFloat minAspectRatio = window->min_aspect; 1200 CGFloat maxAspectRatio = window->max_aspect; 1201 CGFloat aspectRatio; 1202 1203 if (newSize.height > 0) { 1204 aspectRatio = newSize.width / newSize.height; 1205 1206 if (maxAspectRatio > 0.0f && aspectRatio > maxAspectRatio) { 1207 newSize.width = SDL_roundf(newSize.height * maxAspectRatio); 1208 } else if (minAspectRatio > 0.0f && aspectRatio < minAspectRatio) { 1209 newSize.height = SDL_roundf(newSize.width / minAspectRatio); 1210 } 1211 1212 NSRect newFrameRect = [nswindow frameRectForContentRect:NSMakeRect(0, 0, newSize.width, newSize.height)]; 1213 frameSize = newFrameRect.size; 1214 } 1215 } 1216 return frameSize; 1217} 1218 1219- (void)windowDidResize:(NSNotification *)aNotification 1220{ 1221 SDL_Window *window; 1222 NSWindow *nswindow; 1223 NSRect rect; 1224 int x, y, w, h; 1225 BOOL zoomed; 1226 1227 if (inFullscreenTransition || b_inModeTransition) { 1228 // We'll take care of this at the end of the transition 1229 return; 1230 } 1231 1232 if (focusClickPending) { 1233 focusClickPending = 0; 1234 [self onMovingOrFocusClickPendingStateCleared]; 1235 } 1236 window = _data.window; 1237 nswindow = _data.nswindow; 1238 rect = [nswindow contentRectForFrameRect:[nswindow frame]]; 1239 ConvertNSRect(&rect); 1240 x = (int)rect.origin.x; 1241 y = (int)rect.origin.y; 1242 w = (int)rect.size.width; 1243 h = (int)rect.size.height; 1244 1245 _data.viewport = [_data.sdlContentView bounds]; 1246 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { 1247 // This gives us the correct viewport for a Retina-enabled view. 1248 _data.viewport = [_data.sdlContentView convertRectToBacking:_data.viewport]; 1249 } 1250 1251 ScheduleContextUpdates(_data); 1252 1253 /* The OS can resize the window automatically if the display density 1254 * changes while the window is miniaturized or hidden. 1255 */ 1256 if ([nswindow isVisible]) 1257 { 1258 /* isZoomed always returns true if the window is not resizable 1259 * and fullscreen windows are considered zoomed. 1260 */ 1261 if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed] && 1262 !(window->flags & SDL_WINDOW_FULLSCREEN) && ![self isInFullscreenSpace]) { 1263 zoomed = YES; 1264 } else { 1265 zoomed = NO; 1266 } 1267 if (!zoomed) { 1268 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESTORED, 0, 0); 1269 } else { 1270 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); 1271 if ([self windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) { 1272 [nswindow miniaturize:nil]; 1273 } 1274 } 1275 } 1276 1277 /* The window can move during a resize event, such as when maximizing 1278 or resizing from a corner */ 1279 SDL_GlobalToRelativeForWindow(window, x, y, &x, &y); 1280 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_MOVED, x, y); 1281 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_RESIZED, w, h); 1282} 1283 1284- (void)windowWillMiniaturize:(NSNotification *)aNotification 1285{ 1286 isMiniaturizing = YES; 1287 Cocoa_WaitForMiniaturizable(_data.window); 1288} 1289 1290- (void)windowDidMiniaturize:(NSNotification *)aNotification 1291{ 1292 if (focusClickPending) { 1293 focusClickPending = 0; 1294 [self onMovingOrFocusClickPendingStateCleared]; 1295 } 1296 isMiniaturizing = NO; 1297 [self clearPendingWindowOperation:PENDING_OPERATION_MINIMIZE]; 1298 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); 1299} 1300 1301- (void)windowDidDeminiaturize:(NSNotification *)aNotification 1302{ 1303 // Always send restored before maximized. 1304 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_RESTORED, 0, 0); 1305 1306 if (Cocoa_IsWindowZoomed(_data.window)) { 1307 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_MAXIMIZED, 0, 0); 1308 } 1309 1310 if ([self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) { 1311 SDL_UpdateFullscreenMode(_data.window, true, true); 1312 } 1313} 1314 1315- (void)windowDidBecomeKey:(NSNotification *)aNotification 1316{ 1317 SDL_Window *window = _data.window; 1318 1319 // We're going to get keyboard events, since we're key. 1320 // This needs to be done before restoring the relative mouse mode. 1321 Cocoa_SetKeyboardFocus(window->keyboard_focus ? window->keyboard_focus : window, true); 1322 1323 // If we just gained focus we need the updated mouse position 1324 if (!(window->flags & SDL_WINDOW_MOUSE_RELATIVE_MODE)) { 1325 NSPoint point; 1326 float x, y; 1327 1328 point = [_data.nswindow mouseLocationOutsideOfEventStream]; 1329 x = point.x; 1330 y = (window->h - point.y); 1331 1332 if (x >= 0.0f && x < (float)window->w && y >= 0.0f && y < (float)window->h) { 1333 SDL_SendMouseMotion(0, window, SDL_GLOBAL_MOUSE_ID, false, x, y); 1334 } 1335 } 1336 1337 // Check to see if someone updated the clipboard 1338 Cocoa_CheckClipboardUpdate(_data.videodata); 1339 1340 if (isFullscreenSpace && !window->fullscreen_exclusive) { 1341 Cocoa_ToggleFullscreenSpaceMenuVisibility(window); 1342 } 1343 { 1344 const unsigned int newflags = [NSEvent modifierFlags] & NSEventModifierFlagCapsLock; 1345 _data.videodata.modifierFlags = (_data.videodata.modifierFlags & ~NSEventModifierFlagCapsLock) | newflags; 1346 SDL_ToggleModState(SDL_KMOD_CAPS, newflags ? true : false); 1347 } 1348 1349 /* Restore fullscreen mode unless the window is deminiaturizing. 1350 * If it is, fullscreen will be restored when deminiaturization is complete. 1351 */ 1352 if (!(window->flags & SDL_WINDOW_MINIMIZED) && 1353 [self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) { 1354 SDL_UpdateFullscreenMode(window, true, true); 1355 } 1356} 1357 1358- (void)windowDidResignKey:(NSNotification *)aNotification 1359{ 1360 // Some other window will get mouse events, since we're not key. 1361 if (SDL_GetMouseFocus() == _data.window) { 1362 SDL_SetMouseFocus(NULL); 1363 } 1364 1365 // Some other window will get keyboard events, since we're not key. 1366 if (SDL_GetKeyboardFocus() == _data.window) { 1367 SDL_SetKeyboardFocus(NULL); 1368 } 1369 1370 if (isFullscreenSpace) { 1371 [NSMenu setMenuBarVisible:YES]; 1372 } 1373} 1374 1375- (void)windowDidChangeBackingProperties:(NSNotification *)aNotification 1376{ 1377 NSNumber *oldscale = [[aNotification userInfo] objectForKey:NSBackingPropertyOldScaleFactorKey]; 1378 1379 if (inFullscreenTransition) { 1380 return; 1381 } 1382 1383 if ([oldscale doubleValue] != [_data.nswindow backingScaleFactor]) { 1384 // Send a resize event when the backing scale factor changes. 1385 [self windowDidResize:aNotification]; 1386 } 1387} 1388 1389- (void)windowDidChangeScreenProfile:(NSNotification *)aNotification 1390{ 1391 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_ICCPROF_CHANGED, 0, 0); 1392} 1393 1394- (void)windowDidChangeScreen:(NSNotification *)aNotification 1395{ 1396 // printf("WINDOWDIDCHANGESCREEN\n"); 1397 1398#ifdef SDL_VIDEO_OPENGL 1399 1400 if (_data && _data.nscontexts) { 1401 for (SDL3OpenGLContext *context in _data.nscontexts) { 1402 [context movedToNewScreen]; 1403 } 1404 } 1405 1406#endif // SDL_VIDEO_OPENGL 1407} 1408 1409- (void)windowWillEnterFullScreen:(NSNotification *)aNotification 1410{ 1411 SDL_Window *window = _data.window; 1412 const NSUInteger flags = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled; 1413 1414 /* For some reason, the fullscreen window won't get any mouse button events 1415 * without the NSWindowStyleMaskTitled flag being set when entering fullscreen, 1416 * so it's needed even if the window is borderless. 1417 */ 1418 SetWindowStyle(window, flags); 1419 1420 _data.was_zoomed = !!(window->flags & SDL_WINDOW_MAXIMIZED); 1421 1422 isFullscreenSpace = YES; 1423 inFullscreenTransition = YES; 1424} 1425 1426/* This is usually sent after an unexpected windowDidExitFullscreen if the window 1427 * failed to become fullscreen. 1428 * 1429 * Since something went wrong and the current state is unknown, dump any pending events. 1430 */ 1431- (void)windowDidFailToEnterFullScreen:(NSNotification *)aNotification 1432{ 1433 [self clearAllPendingWindowOperations]; 1434} 1435 1436- (void)windowDidEnterFullScreen:(NSNotification *)aNotification 1437{ 1438 SDL_Window *window = _data.window; 1439 1440 inFullscreenTransition = NO; 1441 isFullscreenSpace = YES; 1442 [self clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; 1443 1444 if ([self windowOperationIsPending:PENDING_OPERATION_LEAVE_FULLSCREEN]) { 1445 [self setFullscreenSpace:NO]; 1446 } else { 1447 Cocoa_ToggleFullscreenSpaceMenuVisibility(window); 1448 1449 /* Don't recurse back into UpdateFullscreenMode() if this was hit in 1450 * a blocking transition, as the caller is already waiting in 1451 * UpdateFullscreenMode(). 1452 */ 1453 if (!_data.in_blocking_transition) { 1454 SDL_UpdateFullscreenMode(window, true, false); 1455 } 1456 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); 1457 1458 _data.pending_position = NO; 1459 _data.pending_size = NO; 1460 1461 /* Force the size change event in case it was delivered earlier 1462 while the window was still animating into place. 1463 */ 1464 window->w = 0; 1465 window->h = 0; 1466 [self windowDidMove:aNotification]; 1467 [self windowDidResize:aNotification]; 1468 1469 Cocoa_UpdateClipCursor(window); 1470 } 1471} 1472 1473- (void)windowWillExitFullScreen:(NSNotification *)aNotification 1474{ 1475 SDL_Window *window = _data.window; 1476 1477 /* If the windowed mode borders were toggled on while in a fullscreen space, 1478 * NSWindowStyleMaskTitled has to be cleared here, or the window can end up 1479 * in a weird, semi-decorated state upon returning to windowed mode. 1480 */ 1481 if (_data.border_toggled && !(window->flags & SDL_WINDOW_BORDERLESS)) { 1482 const NSUInteger flags = NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable; 1483 1484 SetWindowStyle(window, flags); 1485 _data.border_toggled = false; 1486 } 1487 1488 isFullscreenSpace = NO; 1489 inFullscreenTransition = YES; 1490} 1491 1492/* This may be sent before windowDidExitFullscreen to signal that the window was 1493 * dumped out of fullscreen with no animation. 1494 * 1495 * Since something went wrong and the state is unknown, dump any pending events. 1496 */ 1497- (void)windowDidFailToExitFullScreen:(NSNotification *)aNotification 1498{ 1499 [self clearAllPendingWindowOperations]; 1500} 1501 1502- (void)windowDidExitFullScreen:(NSNotification *)aNotification 1503{ 1504 SDL_Window *window = _data.window; 1505 NSWindow *nswindow = _data.nswindow; 1506 1507 inFullscreenTransition = NO; 1508 isFullscreenSpace = NO; 1509 _data.fullscreen_space_requested = NO; 1510 1511 /* As of macOS 10.15, the window decorations can go missing sometimes after 1512 certain fullscreen-desktop->exclusive-fullscreen->windowed mode flows 1513 sometimes. Making sure the style mask always uses the windowed mode style 1514 when returning to windowed mode from a space (instead of using a pending 1515 fullscreen mode style mask) seems to work around that issue. 1516 */ 1517 SetWindowStyle(window, GetWindowWindowedStyle(window)); 1518 1519 /* This can happen if the window failed to enter fullscreen, as this 1520 * may be called *before* windowDidFailToEnterFullScreen in that case. 1521 */ 1522 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { 1523 [self clearAllPendingWindowOperations]; 1524 } 1525 1526 /* Don't recurse back into UpdateFullscreenMode() if this was hit in 1527 * a blocking transition, as the caller is already waiting in 1528 * UpdateFullscreenMode(). 1529 */ 1530 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); 1531 if (!_data.in_blocking_transition) { 1532 SDL_UpdateFullscreenMode(window, false, false); 1533 } 1534 1535 if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { 1536 [nswindow setLevel:NSFloatingWindowLevel]; 1537 } else { 1538 [nswindow setLevel:kCGNormalWindowLevel]; 1539 } 1540 1541 [self clearPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; 1542 1543 if ([self windowOperationIsPending:PENDING_OPERATION_ENTER_FULLSCREEN]) { 1544 [self setFullscreenSpace:YES]; 1545 } else if ([self windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) { 1546 /* There's some state that isn't quite back to normal when 1547 * windowDidExitFullScreen triggers. For example, the minimize button on 1548 * the title bar doesn't actually enable for another 200 milliseconds or 1549 * so on this MacBook. Camp here and wait for that to happen before 1550 * going on, in case we're exiting fullscreen to minimize, which need 1551 * that window state to be normal before it will work. 1552 */ 1553 Cocoa_WaitForMiniaturizable(_data.window); 1554 [self addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; 1555 [nswindow miniaturize:nil]; 1556 } else { 1557 // Adjust the fullscreen toggle button and readd menu now that we're here. 1558 if (window->flags & SDL_WINDOW_RESIZABLE) { 1559 // resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. 1560 [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; 1561 } else { 1562 [nswindow setCollectionBehavior:NSWindowCollectionBehaviorManaged]; 1563 } 1564 [NSMenu setMenuBarVisible:YES]; 1565 1566 // Toggle zoom, if changed while fullscreen. 1567 if ([self windowOperationIsPending:PENDING_OPERATION_ZOOM]) { 1568 [self clearPendingWindowOperation:PENDING_OPERATION_ZOOM]; 1569 [nswindow zoom:nil]; 1570 _data.was_zoomed = !_data.was_zoomed; 1571 } 1572 1573 if (!_data.was_zoomed) { 1574 // Apply a pending window size, if not zoomed. 1575 NSRect rect; 1576 rect.origin.x = _data.pending_position ? window->pending.x : window->floating.x; 1577 rect.origin.y = _data.pending_position ? window->pending.y : window->floating.y; 1578 rect.size.width = _data.pending_size ? window->pending.w : window->floating.w; 1579 rect.size.height = _data.pending_size ? window->pending.h : window->floating.h; 1580 ConvertNSRect(&rect); 1581 1582 if (_data.pending_size) { 1583 [nswindow setContentSize:rect.size]; 1584 } 1585 if (_data.pending_position) { 1586 [nswindow setFrameOrigin:rect.origin]; 1587 } 1588 } 1589 1590 _data.pending_size = NO; 1591 _data.pending_position = NO; 1592 _data.was_zoomed = NO; 1593 1594 /* Force the size change event in case it was delivered earlier 1595 * while the window was still animating into place. 1596 */ 1597 window->w = 0; 1598 window->h = 0; 1599 [self windowDidMove:aNotification]; 1600 [self windowDidResize:aNotification]; 1601 1602 // FIXME: Why does the window get hidden? 1603 if (!(window->flags & SDL_WINDOW_HIDDEN)) { 1604 Cocoa_ShowWindow(SDL_GetVideoDevice(), window); 1605 } 1606 1607 Cocoa_UpdateClipCursor(window); 1608 } 1609} 1610 1611- (NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions 1612{ 1613 if (_data.window->fullscreen_exclusive) { 1614 return NSApplicationPresentationFullScreen | NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar; 1615 } else { 1616 return proposedOptions; 1617 } 1618} 1619 1620/* We'll respond to key events by mostly doing nothing so we don't beep. 1621 * We could handle key messages here, but we lose some in the NSApp dispatch, 1622 * where they get converted to action messages, etc. 1623 */ 1624- (void)flagsChanged:(NSEvent *)theEvent 1625{ 1626 // Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent); 1627 1628 /* Catch capslock in here as a special case: 1629 https://developer.apple.com/library/archive/qa/qa1519/_index.html 1630 Note that technote's check of keyCode doesn't work. At least on the 1631 10.15 beta, capslock comes through here as keycode 255, but it's safe 1632 to send duplicate key events; SDL filters them out quickly in 1633 SDL_SendKeyboardKey(). */ 1634 1635 /* Also note that SDL_SendKeyboardKey expects all capslock events to be 1636 keypresses; it won't toggle the mod state if you send a keyrelease. */ 1637 const bool osenabled = ([theEvent modifierFlags] & NSEventModifierFlagCapsLock) ? true : false; 1638 const bool sdlenabled = (SDL_GetModState() & SDL_KMOD_CAPS) ? true : false; 1639 if (osenabled ^ sdlenabled) { 1640 SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, 0, SDL_SCANCODE_CAPSLOCK, true); 1641 SDL_SendKeyboardKey(0, SDL_DEFAULT_KEYBOARD_ID, 0, SDL_SCANCODE_CAPSLOCK, false); 1642 } 1643} 1644- (void)keyDown:(NSEvent *)theEvent 1645{ 1646 // Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent); 1647} 1648- (void)keyUp:(NSEvent *)theEvent 1649{ 1650 // Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent); 1651} 1652 1653/* We'll respond to selectors by doing nothing so we don't beep. 1654 * The escape key gets converted to a "cancel" selector, etc. 1655 */ 1656- (void)doCommandBySelector:(SEL)aSelector 1657{ 1658 // NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector)); 1659} 1660 1661- (void)updateHitTest 1662{ 1663 SDL_Window *window = _data.window; 1664 BOOL draggable = NO; 1665 1666 if (window->hit_test) { 1667 float x, y; 1668 SDL_Point point; 1669 1670 SDL_GetGlobalMouseState(&x, &y); 1671 point.x = (int)SDL_roundf(x - window->x); 1672 point.y = (int)SDL_roundf(y - window->y); 1673 if (point.x >= 0 && point.x < window->w && point.y >= 0 && point.y < window->h) { 1674 if (window->hit_test(window, &point, window->hit_test_data) == SDL_HITTEST_DRAGGABLE) { 1675 draggable = YES; 1676 } 1677 } 1678 } 1679 1680 if (isDragAreaRunning != draggable) { 1681 isDragAreaRunning = draggable; 1682 [_data.nswindow setMovableByWindowBackground:draggable]; 1683 } 1684} 1685 1686- (BOOL)processHitTest:(NSEvent *)theEvent 1687{ 1688 SDL_Window *window = _data.window; 1689 1690 if (window->hit_test) { // if no hit-test, skip this. 1691 const NSPoint location = [theEvent locationInWindow]; 1692 const SDL_Point point = { (int)location.x, window->h - (((int)location.y) - 1) }; 1693 const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data); 1694 if (rc == SDL_HITTEST_DRAGGABLE) { 1695 if (!isDragAreaRunning) { 1696 isDragAreaRunning = YES; 1697 [_data.nswindow setMovableByWindowBackground:YES]; 1698 } 1699 return YES; // dragging! 1700 } else { 1701 if (isDragAreaRunning) { 1702 isDragAreaRunning = NO; 1703 [_data.nswindow setMovableByWindowBackground:NO]; 1704 return YES; // was dragging, drop event. 1705 } 1706 } 1707 } 1708 1709 return NO; // not a special area, carry on. 1710} 1711 1712static void Cocoa_SendMouseButtonClicks(SDL_Mouse *mouse, NSEvent *theEvent, SDL_Window *window, Uint8 button, bool down) 1713{ 1714 SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID; 1715 //const int clicks = (int)[theEvent clickCount]; 1716 SDL_Window *focus = SDL_GetKeyboardFocus(); 1717 1718 // macOS will send non-left clicks to background windows without raising them, so we need to 1719 // temporarily adjust the mouse position when this happens, as `mouse` will be tracking 1720 // the position in the currently-focused window. We don't (currently) send a mousemove 1721 // event for the background window, this just makes sure the button is reported at the 1722 // correct position in its own event. 1723 if (focus && ([theEvent window] == ((__bridge SDL_CocoaWindowData *)focus->internal).nswindow)) { 1724 //SDL_SendMouseButtonClicks(Cocoa_GetEventTimestamp([theEvent timestamp]), window, mouseID, button, down, clicks); 1725 SDL_SendMouseButton(Cocoa_GetEventTimestamp([theEvent timestamp]), window, mouseID, button, down); 1726 } else { 1727 const float orig_x = mouse->x; 1728 const float orig_y = mouse->y; 1729 const NSPoint point = [theEvent locationInWindow]; 1730 mouse->x = (int)point.x; 1731 mouse->y = (int)(window->h - point.y); 1732 //SDL_SendMouseButtonClicks(Cocoa_GetEventTimestamp([theEvent timestamp]), window, mouseID, button, down, clicks); 1733 SDL_SendMouseButton(Cocoa_GetEventTimestamp([theEvent timestamp]), window, mouseID, button, down); 1734 mouse->x = orig_x; 1735 mouse->y = orig_y; 1736 } 1737} 1738 1739- (void)mouseDown:(NSEvent *)theEvent 1740{ 1741 if (Cocoa_HandlePenEvent(_data, theEvent)) { 1742 return; // pen code handled it. 1743 } 1744 1745 SDL_Mouse *mouse = SDL_GetMouse(); 1746 int button; 1747 1748 if (!mouse) { 1749 return; 1750 } 1751 1752 // Ignore events that aren't inside the client area (i.e. title bar.) 1753 if ([theEvent window]) { 1754 NSRect windowRect = [[[theEvent window] contentView] frame]; 1755 if (!NSMouseInRect([theEvent locationInWindow], windowRect, NO)) { 1756 return; 1757 } 1758 } 1759 1760 switch ([theEvent buttonNumber]) { 1761 case 0: 1762 if (([theEvent modifierFlags] & NSEventModifierFlagControl) && 1763 GetHintCtrlClickEmulateRightClick()) { 1764 wasCtrlLeft = YES; 1765 button = SDL_BUTTON_RIGHT; 1766 } else { 1767 wasCtrlLeft = NO; 1768 button = SDL_BUTTON_LEFT; 1769 } 1770 break; 1771 case 1: 1772 button = SDL_BUTTON_RIGHT; 1773 break; 1774 case 2: 1775 button = SDL_BUTTON_MIDDLE; 1776 break; 1777 default: 1778 button = (int)[theEvent buttonNumber] + 1; 1779 break; 1780 } 1781 1782 if (button == SDL_BUTTON_LEFT && [self processHitTest:theEvent]) { 1783 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); 1784 return; // dragging, drop event. 1785 } 1786 1787 Cocoa_SendMouseButtonClicks(mouse, theEvent, _data.window, button, true); 1788} 1789 1790- (void)rightMouseDown:(NSEvent *)theEvent 1791{ 1792 [self mouseDown:theEvent]; 1793} 1794 1795- (void)otherMouseDown:(NSEvent *)theEvent 1796{ 1797 [self mouseDown:theEvent]; 1798} 1799 1800- (void)mouseUp:(NSEvent *)theEvent 1801{ 1802 if (Cocoa_HandlePenEvent(_data, theEvent)) { 1803 return; // pen code handled it. 1804 } 1805 1806 SDL_Mouse *mouse = SDL_GetMouse(); 1807 int button; 1808 1809 if (!mouse) { 1810 return; 1811 } 1812 1813 switch ([theEvent buttonNumber]) { 1814 case 0: 1815 if (wasCtrlLeft) { 1816 button = SDL_BUTTON_RIGHT; 1817 wasCtrlLeft = NO; 1818 } else { 1819 button = SDL_BUTTON_LEFT; 1820 } 1821 break; 1822 case 1: 1823 button = SDL_BUTTON_RIGHT; 1824 break; 1825 case 2: 1826 button = SDL_BUTTON_MIDDLE; 1827 break; 1828 default: 1829 button = (int)[theEvent buttonNumber] + 1; 1830 break; 1831 } 1832 1833 if (button == SDL_BUTTON_LEFT && [self processHitTest:theEvent]) { 1834 SDL_SendWindowEvent(_data.window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); 1835 return; // stopped dragging, drop event. 1836 } 1837 1838 Cocoa_SendMouseButtonClicks(mouse, theEvent, _data.window, button, false); 1839} 1840 1841- (void)rightMouseUp:(NSEvent *)theEvent 1842{ 1843 [self mouseUp:theEvent]; 1844} 1845 1846- (void)otherMouseUp:(NSEvent *)theEvent 1847{ 1848 [self mouseUp:theEvent]; 1849} 1850 1851- (void)mouseMoved:(NSEvent *)theEvent 1852{ 1853 if (Cocoa_HandlePenEvent(_data, theEvent)) { 1854 return; // pen code handled it. 1855 } 1856 1857 SDL_MouseID mouseID = SDL_DEFAULT_MOUSE_ID; 1858 SDL_Mouse *mouse = SDL_GetMouse(); 1859 NSPoint point; 1860 float x, y; 1861 SDL_Window *window; 1862 NSView *contentView; 1863 1864 if (!mouse) { 1865 return; 1866 } 1867 1868 if (!Cocoa_GetMouseFocus()) { 1869 // The mouse is no longer over any window in the application 1870 SDL_SetMouseFocus(NULL); 1871 return; 1872 } 1873 1874 window = _data.window; 1875 contentView = _data.sdlContentView; 1876 point = [theEvent locationInWindow]; 1877 1878 if ([contentView mouse:[contentView convertPoint:point fromView:nil] inRect:[contentView bounds]] && 1879 [NSCursor currentCursor] != Cocoa_GetDesiredCursor()) { 1880 // The wrong cursor is on screen, fix it. This fixes an macOS bug that is only known to 1881 // occur in fullscreen windows on the built-in displays of newer MacBooks with camera 1882 // notches. When the mouse is moved near the top of such a window (within about 44 units) 1883 // and then moved back down, the cursor rects aren't respected. 1884 [_data.nswindow invalidateCursorRectsForView:contentView]; 1885 } 1886 1887 if (window->flags & SDL_WINDOW_TRANSPARENT) { 1888 [self updateIgnoreMouseState:theEvent]; 1889 } 1890 1891 if ([self processHitTest:theEvent]) { 1892 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_HIT_TEST, 0, 0); 1893 return; // dragging, drop event. 1894 } 1895 1896 if (mouse->relative_mode) { 1897 return; 1898 } 1899 1900 x = point.x; 1901 y = (window->h - point.y); 1902 1903 // On macOS 26 if you move away from a space and then back, mouse motion events will have incorrect 1904 // values at the top of the screen. The global mouse position query is still correct, so we'll fall 1905 // back to that until this is fixed by Apple. Mouse button events are interestingly not affected. 1906 if (@available(macOS 26.0, *)) { 1907 if ([_data.listener isInFullscreenSpace]) { 1908 int posx = 0, posy = 0; 1909 SDL_GetWindowPosition(window, &posx, &posy); 1910 SDL_GetGlobalMouseState(&x, &y); 1911 x -= posx; 1912 y -= posy; 1913 } 1914 } 1915 1916 if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_13_2) { 1917 // Mouse grab is taken care of by the confinement rect 1918 } else { 1919 CGPoint cgpoint; 1920 if (ShouldAdjustCoordinatesForGrab(window) && 1921 AdjustCoordinatesForGrab(window, window->x + x, window->y + y, &cgpoint)) { 1922 Cocoa_HandleMouseWarp(cgpoint.x, cgpoint.y); 1923 CGDisplayMoveCursorToPoint(kCGDirectMainDisplay, cgpoint); 1924 CGAssociateMouseAndMouseCursorPosition(YES); 1925 } 1926 } 1927 1928 SDL_SendMouseMotion(Cocoa_GetEventTimestamp([theEvent timestamp]), window, mouseID, false, x, y); 1929} 1930 1931- (void)mouseDragged:(NSEvent *)theEvent 1932{ 1933 [self mouseMoved:theEvent]; 1934} 1935 1936- (void)rightMouseDragged:(NSEvent *)theEvent 1937{ 1938 [self mouseMoved:theEvent]; 1939} 1940 1941- (void)otherMouseDragged:(NSEvent *)theEvent 1942{ 1943 [self mouseMoved:theEvent]; 1944} 1945 1946- (void)scrollWheel:(NSEvent *)theEvent 1947{ 1948 Cocoa_HandleMouseWheel(_data.window, theEvent); 1949} 1950 1951- (BOOL)isTouchFromTrackpad:(NSEvent *)theEvent 1952{ 1953 SDL_Window *window = _data.window; 1954 SDL_CocoaVideoData *videodata = ((__bridge SDL_CocoaWindowData *)window->internal).videodata; 1955 1956 /* if this a MacBook trackpad, we'll make input look like a synthesized 1957 event. This is backwards from reality, but better matches user 1958 expectations. You can make it look like a generic touch device instead 1959 with the SDL_HINT_TRACKPAD_IS_TOUCH_ONLY hint. */ 1960 BOOL istrackpad = NO; 1961 if (!videodata.trackpad_is_touch_only) { 1962 @try { 1963 istrackpad = ([theEvent subtype] == NSEventSubtypeMouseEvent); 1964 } 1965 @catch (NSException *e) { 1966 /* if NSEvent type doesn't have subtype, such as NSEventTypeBeginGesture on 1967 * macOS 10.5 to 10.10, then NSInternalInconsistencyException is thrown. 1968 * This still prints a message to terminal so catching it's not an ideal solution. 1969 * 1970 * *** Assertion failure in -[NSEvent subtype] 1971 */ 1972 } 1973 } 1974 return istrackpad; 1975} 1976 1977- (void)touchesBeganWithEvent:(NSEvent *)theEvent 1978{ 1979 NSSet *touches; 1980 SDL_TouchID touchID; 1981 int existingTouchCount; 1982 const BOOL istrackpad = [self isTouchFromTrackpad:theEvent]; 1983 1984 touches = [theEvent touchesMatchingPhase:NSTouchPhaseAny inView:nil]; 1985 touchID = istrackpad ? SDL_MOUSE_TOUCHID : (SDL_TouchID)(intptr_t)[[touches anyObject] device]; 1986 existingTouchCount = 0; 1987 1988 for (NSTouch *touch in touches) { 1989 if ([touch phase] != NSTouchPhaseBegan) { 1990 existingTouchCount++; 1991 } 1992 } 1993 if (existingTouchCount == 0) { 1994 int numFingers; 1995 SDL_Finger **fingers = SDL_GetTouchFingers(touchID, &numFingers); 1996 if (fingers) { 1997 DLog("Reset Lost Fingers: %d", numFingers); 1998 for (--numFingers; numFingers >= 0; --numFingers) { 1999 const SDL_Finger *finger = fingers[numFingers]; 2000 /* trackpad touches have no window. If we really wanted one we could 2001 * use the window that has mouse or keyboard focus. 2002 * Sending a null window currently also prevents synthetic mouse 2003 * events from being generated from touch events. 2004 */ 2005 SDL_Window *window = NULL; 2006 SDL_SendTouch(Cocoa_GetEventTimestamp([theEvent timestamp]), touchID, finger->id, window, SDL_EVENT_FINGER_CANCELED, 0, 0, 0); 2007 } 2008 SDL_free(fingers); 2009 } 2010 } 2011 2012 DLog("Began Fingers: %lu .. existing: %d", (unsigned long)[touches count], existingTouchCount); 2013 [self handleTouches:NSTouchPhaseBegan withEvent:theEvent]; 2014} 2015 2016- (void)touchesMovedWithEvent:(NSEvent *)theEvent 2017{ 2018 [self handleTouches:NSTouchPhaseMoved withEvent:theEvent]; 2019} 2020 2021- (void)touchesEndedWithEvent:(NSEvent *)theEvent 2022{ 2023 [self handleTouches:NSTouchPhaseEnded withEvent:theEvent]; 2024} 2025 2026- (void)touchesCancelledWithEvent:(NSEvent *)theEvent 2027{ 2028 [self handleTouches:NSTouchPhaseCancelled withEvent:theEvent]; 2029} 2030 2031- (void)magnifyWithEvent:(NSEvent *)theEvent 2032{ 2033 switch ([theEvent phase]) { 2034 case NSEventPhaseBegan: 2035 SDL_SendPinch(SDL_EVENT_PINCH_BEGIN, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, 0); 2036 break; 2037 case NSEventPhaseChanged: 2038 { 2039 CGFloat scale = 1.0f + [theEvent magnification]; 2040 SDL_SendPinch(SDL_EVENT_PINCH_UPDATE, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, scale); 2041 } 2042 break; 2043 case NSEventPhaseEnded: 2044 case NSEventPhaseCancelled: 2045 SDL_SendPinch(SDL_EVENT_PINCH_END, Cocoa_GetEventTimestamp([theEvent timestamp]), NULL, 0); 2046 break; 2047 default: 2048 break; 2049 } 2050} 2051 2052- (void)handleTouches:(NSTouchPhase)phase withEvent:(NSEvent *)theEvent 2053{ 2054 NSSet *touches = [theEvent touchesMatchingPhase:phase inView:nil]; 2055 const BOOL istrackpad = [self isTouchFromTrackpad:theEvent]; 2056 SDL_FingerID fingerId; 2057 float x, y; 2058 2059 for (NSTouch *touch in touches) { 2060 const SDL_TouchID touchId = istrackpad ? SDL_MOUSE_TOUCHID : (SDL_TouchID)(uintptr_t)[touch device]; 2061 SDL_TouchDeviceType devtype = SDL_TOUCH_DEVICE_INDIRECT_ABSOLUTE; 2062 2063 /* trackpad touches have no window. If we really wanted one we could 2064 * use the window that has mouse or keyboard focus. 2065 * Sending a null window currently also prevents synthetic mouse events 2066 * from being generated from touch events. 2067 */ 2068 SDL_Window *window = NULL; 2069 2070 /* TODO: Before implementing direct touch support here, we need to 2071 * figure out whether the OS generates mouse events from them on its 2072 * own. If it does, we should prevent SendTouch from generating 2073 * synthetic mouse events for these touches itself (while also 2074 * sending a window.) It will also need to use normalized window- 2075 * relative coordinates via [touch locationInView:]. 2076 */ 2077 if ([touch type] == NSTouchTypeDirect) { 2078 continue; 2079 } 2080 2081 if (SDL_AddTouch(touchId, devtype, "") < 0) { 2082 return; 2083 } 2084 2085 fingerId = (SDL_FingerID)(uintptr_t)[touch identity]; 2086 x = [touch normalizedPosition].x; 2087 y = [touch normalizedPosition].y; 2088 // Make the origin the upper left instead of the lower left 2089 y = 1.0f - y; 2090 2091 switch (phase) { 2092 case NSTouchPhaseBegan: 2093 SDL_SendTouch(Cocoa_GetEventTimestamp([theEvent timestamp]), touchId, fingerId, window, SDL_EVENT_FINGER_DOWN, x, y, 1.0f); 2094 break; 2095 case NSTouchPhaseEnded: 2096 SDL_SendTouch(Cocoa_GetEventTimestamp([theEvent timestamp]), touchId, fingerId, window, SDL_EVENT_FINGER_UP, x, y, 1.0f); 2097 break; 2098 case NSTouchPhaseCancelled: 2099 SDL_SendTouch(Cocoa_GetEventTimestamp([theEvent timestamp]), touchId, fingerId, window, SDL_EVENT_FINGER_CANCELED, x, y, 1.0f); 2100 break; 2101 case NSTouchPhaseMoved: 2102 SDL_SendTouchMotion(Cocoa_GetEventTimestamp([theEvent timestamp]), touchId, fingerId, window, x, y, 1.0f); 2103 break; 2104 default: 2105 break; 2106 } 2107 } 2108} 2109 2110- (void)tabletProximity:(NSEvent *)theEvent 2111{ 2112 Cocoa_HandlePenEvent(_data, theEvent); 2113} 2114 2115- (void)tabletPoint:(NSEvent *)theEvent 2116{ 2117 Cocoa_HandlePenEvent(_data, theEvent); 2118} 2119 2120@end 2121 2122@interface SDL3View : NSView 2123{ 2124 SDL_Window *_sdlWindow; 2125 NSTrackingArea *_trackingArea; // only used on macOS <= 11.0 2126} 2127 2128- (void)setSDLWindow:(SDL_Window *)window; 2129 2130// The default implementation doesn't pass rightMouseDown to responder chain 2131- (void)rightMouseDown:(NSEvent *)theEvent; 2132- (BOOL)mouseDownCanMoveWindow; 2133- (void)drawRect:(NSRect)dirtyRect; 2134- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent; 2135- (BOOL)wantsUpdateLayer; 2136- (void)updateLayer; 2137- (void)updateTrackingAreas; 2138@end 2139 2140@implementation SDL3View 2141 2142- (void)setSDLWindow:(SDL_Window *)window 2143{ 2144 _sdlWindow = window; 2145} 2146 2147/* this is used on older macOS revisions, and newer ones which emulate old 2148 NSOpenGLContext behaviour while still using a layer under the hood. 10.8 and 2149 later use updateLayer, up until 10.14.2 or so, which uses drawRect without 2150 a GraphicsContext and with a layer active instead (for OpenGL contexts). */ 2151- (void)drawRect:(NSRect)dirtyRect 2152{ 2153 /* Force the graphics context to clear to black so we don't get a flash of 2154 white until the app is ready to draw. In practice on modern macOS, this 2155 only gets called for window creation and other extraordinary events. */ 2156 BOOL transparent = (_sdlWindow->flags & SDL_WINDOW_TRANSPARENT) != 0; 2157 if ([NSGraphicsContext currentContext]) { 2158 NSColor *fillColor = transparent ? [NSColor clearColor] : [NSColor blackColor]; 2159 [fillColor setFill]; 2160 NSRectFill(dirtyRect); 2161 } else if (self.layer) { 2162 CFStringRef color = transparent ? kCGColorClear : kCGColorBlack; 2163 self.layer.backgroundColor = CGColorGetConstantColor(color); 2164 } 2165 2166 Cocoa_SendExposedEventIfVisible(_sdlWindow); 2167} 2168 2169- (BOOL)wantsUpdateLayer 2170{ 2171 return YES; 2172} 2173 2174// This is also called when a Metal layer is active. 2175- (void)updateLayer 2176{ 2177 /* Force the graphics context to clear to black so we don't get a flash of 2178 white until the app is ready to draw. In practice on modern macOS, this 2179 only gets called for window creation and other extraordinary events. */ 2180 BOOL transparent = (_sdlWindow->flags & SDL_WINDOW_TRANSPARENT) != 0; 2181 CFStringRef color = transparent ? kCGColorClear : kCGColorBlack; 2182 self.layer.backgroundColor = CGColorGetConstantColor(color); 2183 ScheduleContextUpdates((__bridge SDL_CocoaWindowData *)_sdlWindow->internal); 2184 Cocoa_SendExposedEventIfVisible(_sdlWindow); 2185} 2186 2187- (void)rightMouseDown:(NSEvent *)theEvent 2188{ 2189 [[self nextResponder] rightMouseDown:theEvent]; 2190} 2191 2192- (BOOL)mouseDownCanMoveWindow 2193{ 2194 /* Always say YES, but this doesn't do anything until we call 2195 -[NSWindow setMovableByWindowBackground:YES], which we ninja-toggle 2196 during mouse events when we're using a drag area. */ 2197 return YES; 2198} 2199 2200- (void)resetCursorRects 2201{ 2202 [super resetCursorRects]; 2203 [self addCursorRect:[self bounds] 2204 cursor:Cocoa_GetDesiredCursor()]; 2205} 2206 2207- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent 2208{ 2209 if (_sdlWindow->flags & SDL_WINDOW_POPUP_MENU) { 2210 return YES; 2211 } else if (SDL_GetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH)) { 2212 return SDL_GetHintBoolean(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, false); 2213 } else { 2214 return SDL_GetHintBoolean("SDL_MAC_MOUSE_FOCUS_CLICKTHROUGH", false); 2215 } 2216} 2217 2218// NSTrackingArea is how Cocoa tells you when the mouse cursor has entered or 2219// left certain regions. We put one over our entire window so we know when 2220// it has "mouse focus." 2221- (void)updateTrackingAreas 2222{ 2223 [super updateTrackingAreas]; 2224 2225 SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)_sdlWindow->internal; 2226 if (_trackingArea) { 2227 [self removeTrackingArea:_trackingArea]; 2228 } 2229 _trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:NSTrackingMouseEnteredAndExited|NSTrackingActiveAlways owner:windata.listener userInfo:nil]; 2230 [self addTrackingArea:_trackingArea]; 2231} 2232@end 2233 2234static void Cocoa_UpdateMouseFocus() 2235{ 2236 const NSPoint mouseLocation = [NSEvent mouseLocation]; 2237 2238 // Find the topmost window under the pointer and send a motion event if it is an SDL window. 2239 [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack 2240 usingBlock:^(NSWindow *nswin, BOOL *stop) { 2241 NSRect r = [nswin contentRectForFrameRect:[nswin frame]]; 2242 if (NSPointInRect(mouseLocation, r)) { 2243 SDL_VideoDevice *vid = SDL_GetVideoDevice(); 2244 SDL_Window *sdlwindow; 2245 for (sdlwindow = vid->windows; sdlwindow; sdlwindow = sdlwindow->next) { 2246 if (nswin == ((__bridge SDL_CocoaWindowData *)sdlwindow->internal).nswindow) { 2247 break; 2248 } 2249 } 2250 *stop = YES; 2251 if (sdlwindow) { 2252 int wx, wy; 2253 SDL_RelativeToGlobalForWindow(sdlwindow, sdlwindow->x, sdlwindow->y, &wx, &wy); 2254 2255 // Calculate the cursor coordinates relative to the window. 2256 const float dx = mouseLocation.x - wx; 2257 const float dy = (CGDisplayPixelsHigh(kCGDirectMainDisplay) - mouseLocation.y) - wy; 2258 SDL_SendMouseMotion(0, sdlwindow, SDL_GLOBAL_MOUSE_ID, false, dx, dy); 2259 } 2260 } 2261 }]; 2262} 2263 2264static bool SetupWindowData(SDL_VideoDevice *_this, SDL_Window *window, NSWindow *nswindow, NSView *nsview) 2265{ 2266 @autoreleasepool { 2267 SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->internal; 2268 SDL_CocoaWindowData *data; 2269 2270 // Allocate the window data 2271 data = [[SDL_CocoaWindowData alloc] init]; 2272 if (!data) { 2273 return SDL_OutOfMemory(); 2274 } 2275 window->internal = (SDL_WindowData *)CFBridgingRetain(data); 2276 data.window = window; 2277 data.nswindow = nswindow; 2278 data.videodata = videodata; 2279 data.window_number = nswindow.windowNumber; 2280 data.nscontexts = [[NSMutableArray alloc] init]; 2281 data.sdlContentView = nsview; 2282 2283 data.viewport = [data.sdlContentView bounds]; 2284 if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) { 2285 // This gives us the correct viewport for a Retina-enabled view. 2286 data.viewport = [data.sdlContentView convertRectToBacking:data.viewport]; 2287 } 2288 2289 // Create an event listener for the window 2290 data.listener = [[SDL3Cocoa_WindowListener alloc] init]; 2291 2292 // Fill in the SDL window with the window data 2293 { 2294 int x, y; 2295 NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; 2296 ConvertNSRect(&rect); 2297 SDL_GlobalToRelativeForWindow(window, (int)rect.origin.x, (int)rect.origin.y, &x, &y); 2298 window->x = x; 2299 window->y = y; 2300 window->w = (int)rect.size.width; 2301 window->h = (int)rect.size.height; 2302 } 2303 2304 // Set up the listener after we create the view 2305 [data.listener listen:data]; 2306 2307 if ([nswindow isVisible]) { 2308 window->flags &= ~SDL_WINDOW_HIDDEN; 2309 } else { 2310 window->flags |= SDL_WINDOW_HIDDEN; 2311 } 2312 2313 { 2314 unsigned long style = [nswindow styleMask]; 2315 2316 /* NSWindowStyleMaskBorderless is zero, and it's possible to be 2317 Resizeable _and_ borderless, so we can't do a simple bitwise AND 2318 of NSWindowStyleMaskBorderless here. */ 2319 if ((style & ~(NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable)) == NSWindowStyleMaskBorderless) { 2320 window->flags |= SDL_WINDOW_BORDERLESS; 2321 } else { 2322 window->flags &= ~SDL_WINDOW_BORDERLESS; 2323 } 2324 if (style & NSWindowStyleMaskResizable) { 2325 window->flags |= SDL_WINDOW_RESIZABLE; 2326 } else { 2327 window->flags &= ~SDL_WINDOW_RESIZABLE; 2328 } 2329 } 2330 2331 // isZoomed always returns true if the window is not resizable 2332 if ((window->flags & SDL_WINDOW_RESIZABLE) && [nswindow isZoomed]) { 2333 window->flags |= SDL_WINDOW_MAXIMIZED; 2334 } else { 2335 window->flags &= ~SDL_WINDOW_MAXIMIZED; 2336 } 2337 2338 if ([nswindow isMiniaturized]) { 2339 window->flags |= SDL_WINDOW_MINIMIZED; 2340 } else { 2341 window->flags &= ~SDL_WINDOW_MINIMIZED; 2342 } 2343 2344 if (window->parent) { 2345 NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow; 2346 [nsparent addChildWindow:nswindow ordered:NSWindowAbove]; 2347 2348 /* FIXME: Should not need to call addChildWindow then orderOut. 2349 Attaching a hidden child window to a hidden parent window will cause the child window 2350 to show when the parent does. We therefore shouldn't attach the child window here as we're 2351 going to do so when the child window is explicitly shown later but skipping the addChildWindow 2352 entirely causes the child window to not get key focus correctly the first time it's shown. Adding 2353 then immediately ordering out (removing) the window does work. */ 2354 if (window->flags & SDL_WINDOW_HIDDEN) { 2355 [nswindow orderOut:nil]; 2356 } 2357 } 2358 2359 if (!SDL_WINDOW_IS_POPUP(window)) { 2360 if ([nswindow isKeyWindow]) { 2361 window->flags |= SDL_WINDOW_INPUT_FOCUS; 2362 Cocoa_SetKeyboardFocus(data.window, true); 2363 } 2364 } else { 2365 if (window->flags & SDL_WINDOW_TOOLTIP) { 2366 [nswindow setIgnoresMouseEvents:YES]; 2367 [nswindow setAcceptsMouseMovedEvents:NO]; 2368 } else if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_HIDDEN)) { 2369 if (!(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { 2370 Cocoa_SetKeyboardFocus(window, true); 2371 } 2372 Cocoa_UpdateMouseFocus(); 2373 } 2374 } 2375 2376 if (nswindow.isOpaque) { 2377 window->flags &= ~SDL_WINDOW_TRANSPARENT; 2378 } else { 2379 window->flags |= SDL_WINDOW_TRANSPARENT; 2380 } 2381 2382 /* SDL_CocoaWindowData will be holding a strong reference to the NSWindow, and 2383 * it will also call [NSWindow close] in DestroyWindow before releasing the 2384 * NSWindow, so the extra release provided by releasedWhenClosed isn't 2385 * necessary. */ 2386 nswindow.releasedWhenClosed = NO; 2387 2388 /* Prevents the window's "window device" from being destroyed when it is 2389 * hidden. See http://www.mikeash.com/pyblog/nsopenglcontext-and-one-shot.html 2390 */ 2391 [nswindow setOneShot:NO]; 2392 2393 if (window->flags & SDL_WINDOW_EXTERNAL) { 2394 // Query the title from the existing window 2395 NSString *title = [nswindow title]; 2396 if (title) { 2397 window->title = SDL_strdup([title UTF8String]); 2398 } 2399 } 2400 2401 SDL_PropertiesID props = SDL_GetWindowProperties(window); 2402 SDL_SetPointerProperty(props, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, (__bridge void *)data.nswindow); 2403 SDL_SetNumberProperty(props, SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER, SDL_METALVIEW_TAG); 2404 2405 // All done! 2406 return true; 2407 } 2408} 2409 2410bool Cocoa_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID create_props) 2411{ 2412 @autoreleasepool { 2413 SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->internal; 2414 const void *data = SDL_GetPointerProperty(create_props, "sdl2-compat.external_window", NULL); 2415 NSWindow *nswindow = nil; 2416 NSView *nsview = nil; 2417 2418 if (data) { 2419 if ([(__bridge id)data isKindOfClass:[NSWindow class]]) { 2420 nswindow = (__bridge NSWindow *)data; 2421 } else if ([(__bridge id)data isKindOfClass:[NSView class]]) { 2422 nsview = (__bridge NSView *)data; 2423 } else { 2424 SDL_assert(false); 2425 } 2426 } else { 2427 nswindow = (__bridge NSWindow *)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER, NULL); 2428 nsview = (__bridge NSView *)SDL_GetPointerProperty(create_props, SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER, NULL); 2429 } 2430 if (nswindow && !nsview) { 2431 nsview = [nswindow contentView]; 2432 } 2433 if (nsview && !nswindow) { 2434 nswindow = [nsview window]; 2435 } 2436 if (nswindow) { 2437 window->flags |= SDL_WINDOW_EXTERNAL; 2438 } else { 2439 int x, y; 2440 NSScreen *screen; 2441 NSRect rect, screenRect; 2442 NSUInteger style; 2443 SDL3View *contentView; 2444 2445 SDL_RelativeToGlobalForWindow(window, window->x, window->y, &x, &y); 2446 rect.origin.x = x; 2447 rect.origin.y = y; 2448 rect.size.width = window->w; 2449 rect.size.height = window->h; 2450 ConvertNSRect(&rect); 2451 2452 style = GetWindowStyle(window); 2453 2454 // Figure out which screen to place this window 2455 screen = ScreenForRect(&rect); 2456 screenRect = [screen frame]; 2457 rect.origin.x -= screenRect.origin.x; 2458 rect.origin.y -= screenRect.origin.y; 2459 2460 // Constrain the popup 2461 if (SDL_WINDOW_IS_POPUP(window) && window->constrain_popup) { 2462 if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { 2463 rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width); 2464 } 2465 if (rect.origin.y + rect.size.height > screenRect.origin.y + screenRect.size.height) { 2466 rect.origin.y -= (rect.origin.y + rect.size.height) - (screenRect.origin.y + screenRect.size.height); 2467 } 2468 rect.origin.x = SDL_max(rect.origin.x, screenRect.origin.x); 2469 rect.origin.y = SDL_max(rect.origin.y, screenRect.origin.y); 2470 } 2471 2472 @try { 2473 nswindow = [[SDL3Window alloc] initWithContentRect:rect styleMask:style backing:NSBackingStoreBuffered defer:NO screen:screen]; 2474 } 2475 @catch (NSException *e) { 2476 return SDL_SetError("%s", [[e reason] UTF8String]); 2477 } 2478 2479 [nswindow setColorSpace:[NSColorSpace sRGBColorSpace]]; 2480 2481 [nswindow setTabbingMode:NSWindowTabbingModeDisallowed]; 2482 2483 if (videodata.allow_spaces) { 2484 // we put fullscreen desktop windows in their own Space, without a toggle button or menubar, later 2485 if (window->flags & SDL_WINDOW_RESIZABLE) { 2486 // resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. 2487 [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; 2488 } 2489 } 2490 2491 // Create a default view for this window 2492 rect = [nswindow contentRectForFrameRect:[nswindow frame]]; 2493 contentView = [[SDL3View alloc] initWithFrame:rect]; 2494 [contentView setSDLWindow:window]; 2495 nsview = contentView; 2496 } 2497 2498 if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { 2499 [nswindow setLevel:NSFloatingWindowLevel]; 2500 } 2501 2502 if (window->flags & SDL_WINDOW_TRANSPARENT) { 2503 nswindow.opaque = NO; 2504 nswindow.hasShadow = NO; 2505 nswindow.backgroundColor = [NSColor clearColor]; 2506 } 2507 2508// We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. 2509#ifdef __clang__ 2510#pragma clang diagnostic push 2511#pragma clang diagnostic ignored "-Wdeprecated-declarations" 2512#endif 2513 /* Note: as of the macOS 10.15 SDK, this defaults to YES instead of NO when 2514 * the NSHighResolutionCapable boolean is set in Info.plist. */ 2515 BOOL highdpi = (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) ? YES : NO; 2516 [nsview setWantsBestResolutionOpenGLSurface:highdpi]; 2517#ifdef __clang__ 2518#pragma clang diagnostic pop 2519#endif 2520 2521#ifdef SDL_VIDEO_OPENGL_ES2 2522#ifdef SDL_VIDEO_OPENGL_EGL 2523 if ((window->flags & SDL_WINDOW_OPENGL) && 2524 _this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) { 2525 [nsview setWantsLayer:TRUE]; 2526 if ((window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY)) { 2527 nsview.layer.contentsScale = nswindow.screen.backingScaleFactor; 2528 } else { 2529 nsview.layer.contentsScale = 1; 2530 } 2531 } 2532#endif // SDL_VIDEO_OPENGL_EGL 2533#endif // SDL_VIDEO_OPENGL_ES2 2534 [nswindow setContentView:nsview]; 2535 2536 if (!SetupWindowData(_this, window, nswindow, nsview)) { 2537 return false; 2538 } 2539 2540 if (!(window->flags & SDL_WINDOW_OPENGL)) { 2541 return true; 2542 } 2543 2544 // The rest of this macro mess is for OpenGL or OpenGL ES windows 2545#ifdef SDL_VIDEO_OPENGL_ES2 2546 if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) { 2547#ifdef SDL_VIDEO_OPENGL_EGL 2548 if (!Cocoa_GLES_SetupWindow(_this, window)) { 2549 Cocoa_DestroyWindow(_this, window); 2550 return false; 2551 } 2552 return true; 2553#else 2554 return SDL_SetError("Could not create GLES window surface (EGL support not configured)"); 2555#endif // SDL_VIDEO_OPENGL_EGL 2556 } 2557#endif // SDL_VIDEO_OPENGL_ES2 2558 return true; 2559 } 2560} 2561 2562void Cocoa_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) 2563{ 2564 @autoreleasepool { 2565 const char *title = window->title ? window->title : ""; 2566 NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow; 2567 NSString *string = [[NSString alloc] initWithUTF8String:title]; 2568 [nswindow setTitle:string]; 2569 } 2570} 2571 2572bool Cocoa_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) 2573{ 2574 @autoreleasepool { 2575 NSImage *nsimage = Cocoa_CreateImage(icon); 2576 2577 if (nsimage) { 2578 [NSApp setApplicationIconImage:nsimage]; 2579 2580 return true; 2581 } 2582 2583 return SDL_SetError("Unable to set the window's icon"); 2584 } 2585} 2586 2587bool Cocoa_SetWindowPosition(SDL_VideoDevice *_this, SDL_Window *window) 2588{ 2589 @autoreleasepool { 2590 SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; 2591 NSWindow *nswindow = windata.nswindow; 2592 NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; 2593 BOOL fullscreen = (window->flags & SDL_WINDOW_FULLSCREEN) ? YES : NO; 2594 int x, y; 2595 2596 if ([windata.listener isInFullscreenSpaceTransition]) { 2597 windata.pending_position = YES; 2598 return true; 2599 } 2600 2601 if (!(window->flags & SDL_WINDOW_MAXIMIZED)) { 2602 if (fullscreen) { 2603 SDL_VideoDisplay *display = SDL_GetVideoDisplayForFullscreenWindow(window); 2604 SDL_Rect r; 2605 SDL_GetDisplayBounds(display->id, &r); 2606 2607 rect.origin.x = r.x; 2608 rect.origin.y = r.y; 2609 } else { 2610 SDL_RelativeToGlobalForWindow(window, window->pending.x, window->pending.y, &x, &y); 2611 rect.origin.x = x; 2612 rect.origin.y = y; 2613 } 2614 ConvertNSRect(&rect); 2615 2616 // Position and constrain the popup 2617 if (SDL_WINDOW_IS_POPUP(window) && window->constrain_popup) { 2618 NSRect screenRect = [ScreenForRect(&rect) frame]; 2619 2620 if (rect.origin.x + rect.size.width > screenRect.origin.x + screenRect.size.width) { 2621 rect.origin.x -= (rect.origin.x + rect.size.width) - (screenRect.origin.x + screenRect.size.width); 2622 } 2623 if (rect.origin.y + rect.size.height > screenRect.origin.y + screenRect.size.height) { 2624 rect.origin.y -= (rect.origin.y + rect.size.height) - (screenRect.origin.y + screenRect.size.height); 2625 } 2626 rect.origin.x = SDL_max(rect.origin.x, screenRect.origin.x); 2627 rect.origin.y = SDL_max(rect.origin.y, screenRect.origin.y); 2628 } 2629 2630 [nswindow setFrameOrigin:rect.origin]; 2631 2632 ScheduleContextUpdates(windata); 2633 } 2634 } 2635 return true; 2636} 2637 2638void Cocoa_SetWindowSize(SDL_VideoDevice *_this, SDL_Window *window) 2639{ 2640 @autoreleasepool { 2641 SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; 2642 NSWindow *nswindow = windata.nswindow; 2643 2644 if ([windata.listener isInFullscreenSpaceTransition]) { 2645 windata.pending_size = YES; 2646 return; 2647 } 2648 2649 if (!Cocoa_IsWindowZoomed(window)) { 2650 int x, y; 2651 NSRect rect = [nswindow contentRectForFrameRect:[nswindow frame]]; 2652 2653 /* Cocoa will resize the window from the bottom-left rather than the 2654 * top-left when -[nswindow setContentSize:] is used, so we must set the 2655 * entire frame based on the new size, in order to preserve the position. 2656 */ 2657 SDL_RelativeToGlobalForWindow(window, window->floating.x, window->floating.y, &x, &y); 2658 rect.origin.x = x; 2659 rect.origin.y = y; 2660 rect.size.width = window->pending.w; 2661 rect.size.height = window->pending.h; 2662 ConvertNSRect(&rect); 2663 2664 [nswindow setFrame:[nswindow frameRectForContentRect:rect] display:YES]; 2665 ScheduleContextUpdates(windata); 2666 } else { 2667 // Can't set the window size. 2668 window->last_size_pending = false; 2669 } 2670 } 2671} 2672 2673void Cocoa_SetWindowMinimumSize(SDL_VideoDevice *_this, SDL_Window *window) 2674{ 2675 @autoreleasepool { 2676 SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; 2677 2678 NSSize minSize; 2679 minSize.width = window->min_w; 2680 minSize.height = window->min_h; 2681 2682 [windata.nswindow setContentMinSize:minSize]; 2683 } 2684} 2685 2686void Cocoa_SetWindowMaximumSize(SDL_VideoDevice *_this, SDL_Window *window) 2687{ 2688 @autoreleasepool { 2689 SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; 2690 2691 NSSize maxSize; 2692 maxSize.width = window->max_w ? window->max_w : CGFLOAT_MAX; 2693 maxSize.height = window->max_h ? window->max_h : CGFLOAT_MAX; 2694 2695 [windata.nswindow setContentMaxSize:maxSize]; 2696 } 2697} 2698 2699void Cocoa_SetWindowAspectRatio(SDL_VideoDevice *_this, SDL_Window *window) 2700{ 2701 @autoreleasepool { 2702 SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; 2703 2704 if (window->min_aspect > 0.0f && window->min_aspect == window->max_aspect) { 2705 int numerator = 0, denominator = 1; 2706 SDL_CalculateFraction(window->max_aspect, &numerator, &denominator); 2707 [windata.nswindow setContentAspectRatio:NSMakeSize(numerator, denominator)]; 2708 } else { 2709 [windata.nswindow setContentAspectRatio:NSMakeSize(0, 0)]; 2710 } 2711 } 2712} 2713 2714void Cocoa_GetWindowSizeInPixels(SDL_VideoDevice *_this, SDL_Window *window, int *w, int *h) 2715{ 2716 @autoreleasepool { 2717 SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; 2718 2719 *w = (int)windata.viewport.size.width; 2720 *h = (int)windata.viewport.size.height; 2721 } 2722} 2723 2724void Cocoa_ShowWindow(SDL_VideoDevice *_this, SDL_Window *window) 2725{ 2726 @autoreleasepool { 2727 SDL_CocoaWindowData *windowData = ((__bridge SDL_CocoaWindowData *)window->internal); 2728 NSWindow *nswindow = windowData.nswindow; 2729 bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, true); 2730 2731 if (![nswindow isMiniaturized]) { 2732 [windowData.listener pauseVisibleObservation]; 2733 if (window->parent) { 2734 NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow; 2735 [nsparent addChildWindow:nswindow ordered:NSWindowAbove]; 2736 2737 if (window->flags & SDL_WINDOW_MODAL) { 2738 Cocoa_SetWindowModal(_this, window, true); 2739 } 2740 } 2741 if (!SDL_WINDOW_IS_POPUP(window)) { 2742 if (bActivate) { 2743 [nswindow makeKeyAndOrderFront:nil]; 2744 } else { 2745 // Order this window below the key window if we're not activating it 2746 if ([NSApp keyWindow]) { 2747 [nswindow orderWindow:NSWindowBelow relativeTo:[[NSApp keyWindow] windowNumber]]; 2748 } 2749 } 2750 } else if (window->flags & SDL_WINDOW_POPUP_MENU) { 2751 if (!(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { 2752 Cocoa_SetKeyboardFocus(window, true); 2753 } 2754 Cocoa_UpdateMouseFocus(); 2755 } 2756 } 2757 [nswindow setIsVisible:YES]; 2758 [windowData.listener resumeVisibleObservation]; 2759 } 2760} 2761 2762void Cocoa_HideWindow(SDL_VideoDevice *_this, SDL_Window *window) 2763{ 2764 @autoreleasepool { 2765 NSWindow *nswindow = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow; 2766 const BOOL waskey = [nswindow isKeyWindow]; 2767 2768 /* orderOut has no effect on miniaturized windows, so close must be used to remove 2769 * the window from the desktop and window list in this case. 2770 * 2771 * SDL holds a strong reference to the window (oneShot/releasedWhenClosed are 'NO'), 2772 * and calling 'close' doesn't send a 'windowShouldClose' message, so it's safe to 2773 * use for this purpose as nothing is implicitly released. 2774 */ 2775 if (![nswindow isMiniaturized]) { 2776 [nswindow orderOut:nil]; 2777 } else { 2778 [nswindow close]; 2779 } 2780 2781 /* If this window is the source of a modal session, end it when 2782 * hidden, or other windows will be prevented from closing. 2783 */ 2784 Cocoa_SetWindowModal(_this, window, false); 2785 2786 // Transfer keyboard focus back to the parent when closing a popup menu 2787 if ((window->flags & SDL_WINDOW_POPUP_MENU) && !(window->flags & SDL_WINDOW_NOT_FOCUSABLE)) { 2788 SDL_Window *new_focus; 2789 const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); 2790 Cocoa_SetKeyboardFocus(new_focus, set_focus); 2791 Cocoa_UpdateMouseFocus(); 2792 } else if (window->parent && waskey) { 2793 /* Key status is not automatically set on the parent when a child is hidden. Check if the 2794 * child window was key, and set the first visible parent to be key if so. 2795 */ 2796 SDL_Window *new_focus = window->parent; 2797 2798 while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) { 2799 new_focus = new_focus->parent; 2800 } 2801 2802 if (new_focus) { 2803 NSWindow *newkey = ((__bridge SDL_CocoaWindowData *)window->internal).nswindow; 2804 [newkey makeKeyAndOrderFront:nil]; 2805 } 2806 } 2807 } 2808} 2809 2810void Cocoa_RaiseWindow(SDL_VideoDevice *_this, SDL_Window *window) 2811{ 2812 @autoreleasepool { 2813 SDL_CocoaWindowData *windowData = ((__bridge SDL_CocoaWindowData *)window->internal); 2814 NSWindow *nswindow = windowData.nswindow; 2815 bool bActivate = SDL_GetHintBoolean(SDL_HINT_WINDOW_ACTIVATE_WHEN_RAISED, true); 2816 2817 /* makeKeyAndOrderFront: has the side-effect of deminiaturizing and showing 2818 a minimized or hidden window, so check for that before showing it. 2819 */ 2820 [windowData.listener pauseVisibleObservation]; 2821 if (![nswindow isMiniaturized] && [nswindow isVisible]) { 2822 if (window->parent) { 2823 NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow; 2824 [nsparent addChildWindow:nswindow ordered:NSWindowAbove]; 2825 } 2826 if (!SDL_WINDOW_IS_POPUP(window)) { 2827 if (bActivate) { 2828 [NSApp activateIgnoringOtherApps:YES]; 2829 [nswindow makeKeyAndOrderFront:nil]; 2830 } else { 2831 [nswindow orderFront:nil]; 2832 } 2833 } else { 2834 if (bActivate) { 2835 [nswindow makeKeyWindow]; 2836 } 2837 } 2838 } 2839 [windowData.listener resumeVisibleObservation]; 2840 } 2841} 2842 2843void Cocoa_MaximizeWindow(SDL_VideoDevice *_this, SDL_Window *window) 2844{ 2845 @autoreleasepool { 2846 SDL_CocoaWindowData *windata = (__bridge SDL_CocoaWindowData *)window->internal; 2847 NSWindow *nswindow = windata.nswindow; 2848 2849 if ([windata.listener windowOperationIsPending:(PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN)] || 2850 [windata.listener isInFullscreenSpaceTransition]) { 2851 Cocoa_SyncWindow(_this, window); 2852 } 2853 2854 if (!(window->flags & SDL_WINDOW_FULLSCREEN) && 2855 ![windata.listener isInFullscreenSpaceTransition] && 2856 ![windata.listener isInFullscreenSpace]) { 2857 [nswindow zoom:nil]; 2858 ScheduleContextUpdates(windata); 2859 } else if (!windata.was_zoomed) { 2860 [windata.listener addPendingWindowOperation:PENDING_OPERATION_ZOOM]; 2861 } else { 2862 [windata.listener clearPendingWindowOperation:PENDING_OPERATION_ZOOM]; 2863 } 2864 } 2865} 2866 2867void Cocoa_MinimizeWindow(SDL_VideoDevice *_this, SDL_Window *window) 2868{ 2869 @autoreleasepool { 2870 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 2871 NSWindow *nswindow = data.nswindow; 2872 2873 [data.listener addPendingWindowOperation:PENDING_OPERATION_MINIMIZE]; 2874 if ([data.listener isInFullscreenSpace] || (window->flags & SDL_WINDOW_FULLSCREEN)) { 2875 [data.listener addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; 2876 SDL_UpdateFullscreenMode(window, false, true); 2877 } else if ([data.listener isInFullscreenSpaceTransition]) { 2878 [data.listener addPendingWindowOperation:PENDING_OPERATION_LEAVE_FULLSCREEN]; 2879 } else { 2880 [nswindow miniaturize:nil]; 2881 } 2882 } 2883} 2884 2885void Cocoa_RestoreWindow(SDL_VideoDevice *_this, SDL_Window *window) 2886{ 2887 @autoreleasepool { 2888 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 2889 NSWindow *nswindow = data.nswindow; 2890 2891 if (([data.listener windowOperationIsPending:(PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN)] && 2892 ![data.nswindow isMiniaturized]) || [data.listener isInFullscreenSpaceTransition]) { 2893 Cocoa_SyncWindow(_this, window); 2894 } 2895 2896 [data.listener clearPendingWindowOperation:(PENDING_OPERATION_MINIMIZE)]; 2897 2898 if (!(window->flags & SDL_WINDOW_FULLSCREEN) && 2899 ![data.listener isInFullscreenSpaceTransition] && 2900 ![data.listener isInFullscreenSpace]) { 2901 if ([nswindow isMiniaturized]) { 2902 [nswindow deminiaturize:nil]; 2903 } else if (Cocoa_IsWindowZoomed(window)) { 2904 [nswindow zoom:nil]; 2905 } 2906 } else if (data.was_zoomed) { 2907 [data.listener addPendingWindowOperation:PENDING_OPERATION_ZOOM]; 2908 } else { 2909 [data.listener clearPendingWindowOperation:PENDING_OPERATION_ZOOM]; 2910 } 2911 } 2912} 2913 2914void Cocoa_SetWindowBordered(SDL_VideoDevice *_this, SDL_Window *window, bool bordered) 2915{ 2916 @autoreleasepool { 2917 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 2918 2919 // If the window is in or transitioning to/from fullscreen, this will be set on leave. 2920 if (!(window->flags & SDL_WINDOW_FULLSCREEN) && ![data.listener isInFullscreenSpaceTransition]) { 2921 if (SetWindowStyle(window, GetWindowStyle(window))) { 2922 if (bordered) { 2923 Cocoa_SetWindowTitle(_this, window); // this got blanked out. 2924 } 2925 } 2926 } else { 2927 data.border_toggled = true; 2928 } 2929 Cocoa_UpdateClipCursor(window); 2930 } 2931} 2932 2933void Cocoa_SetWindowResizable(SDL_VideoDevice *_this, SDL_Window *window, bool resizable) 2934{ 2935 @autoreleasepool { 2936 /* Don't set this if we're in or transitioning to/from a space! 2937 * The window will get permanently stuck if resizable is false. 2938 * -flibit 2939 */ 2940 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 2941 SDL3Cocoa_WindowListener *listener = data.listener; 2942 NSWindow *nswindow = data.nswindow; 2943 SDL_CocoaVideoData *videodata = data.videodata; 2944 if (![listener isInFullscreenSpace] && ![listener isInFullscreenSpaceTransition]) { 2945 SetWindowStyle(window, GetWindowStyle(window)); 2946 } 2947 if (videodata.allow_spaces) { 2948 if (resizable) { 2949 // resizable windows are Spaces-friendly: they get the "go fullscreen" toggle button on their titlebar. 2950 [nswindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; 2951 } else { 2952 [nswindow setCollectionBehavior:NSWindowCollectionBehaviorManaged]; 2953 } 2954 } 2955 } 2956} 2957 2958void Cocoa_SetWindowAlwaysOnTop(SDL_VideoDevice *_this, SDL_Window *window, bool on_top) 2959{ 2960 @autoreleasepool { 2961 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 2962 NSWindow *nswindow = data.nswindow; 2963 2964 // If the window is in or transitioning to/from fullscreen, this will be set on leave. 2965 if (!(window->flags & SDL_WINDOW_FULLSCREEN) && ![data.listener isInFullscreenSpaceTransition]) { 2966 if (on_top) { 2967 [nswindow setLevel:NSFloatingWindowLevel]; 2968 } else { 2969 [nswindow setLevel:kCGNormalWindowLevel]; 2970 } 2971 } 2972 } 2973} 2974 2975SDL_FullscreenResult Cocoa_SetWindowFullscreen(SDL_VideoDevice *_this, SDL_Window *window, SDL_VideoDisplay *display, SDL_FullscreenOp fullscreen) 2976{ 2977 @autoreleasepool { 2978 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 2979 NSWindow *nswindow = data.nswindow; 2980 NSRect rect; 2981 2982 // This is a synchronous operation, so always clear the pending flags. 2983 [data.listener clearPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN | PENDING_OPERATION_LEAVE_FULLSCREEN]; 2984 2985 // The view responder chain gets messed with during setStyleMask 2986 if ([data.sdlContentView nextResponder] == data.listener) { 2987 [data.sdlContentView setNextResponder:nil]; 2988 } 2989 2990 if (fullscreen) { 2991 SDL_Rect bounds; 2992 2993 if (!(window->flags & SDL_WINDOW_FULLSCREEN)) { 2994 data.was_zoomed = !!(window->flags & SDL_WINDOW_MAXIMIZED); 2995 } 2996 2997 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_ENTER_FULLSCREEN, 0, 0); 2998 Cocoa_GetDisplayBounds(_this, display, &bounds); 2999 rect.origin.x = bounds.x; 3000 rect.origin.y = bounds.y; 3001 rect.size.width = bounds.w; 3002 rect.size.height = bounds.h; 3003 ConvertNSRect(&rect); 3004 3005 /* Hack to fix origin on macOS 10.4 3006 This is no longer needed as of macOS 10.15, according to bug 4822. 3007 */ 3008 if (SDL_floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_14) { 3009 NSRect screenRect = [[nswindow screen] frame]; 3010 if (screenRect.size.height >= 1.0f) { 3011 rect.origin.y += (screenRect.size.height - rect.size.height); 3012 } 3013 } 3014 3015 [nswindow setStyleMask:NSWindowStyleMaskBorderless]; 3016 } else { 3017 NSRect frameRect; 3018 3019 SDL_SendWindowEvent(window, SDL_EVENT_WINDOW_LEAVE_FULLSCREEN, 0, 0); 3020 3021 rect.origin.x = data.was_zoomed ? window->windowed.x : window->floating.x; 3022 rect.origin.y = data.was_zoomed ? window->windowed.y : window->floating.y; 3023 rect.size.width = data.was_zoomed ? window->windowed.w : window->floating.w; 3024 rect.size.height = data.was_zoomed ? window->windowed.h : window->floating.h; 3025 3026 ConvertNSRect(&rect); 3027 3028 /* The window is not meant to be fullscreen, but its flags might have a 3029 * fullscreen bit set if it's scheduled to go fullscreen immediately 3030 * after. Always using the windowed mode style here works around bugs in 3031 * macOS 10.15 where the window doesn't properly restore the windowed 3032 * mode decorations after exiting fullscreen-desktop, when the window 3033 * was created as fullscreen-desktop. */ 3034 [nswindow setStyleMask:GetWindowWindowedStyle(window)]; 3035 3036 // Hack to restore window decorations on macOS 10.10 3037 frameRect = [nswindow frame]; 3038 [nswindow setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO]; 3039 [nswindow setFrame:frameRect display:NO]; 3040 } 3041 3042 // The view responder chain gets messed with during setStyleMask 3043 if ([data.sdlContentView nextResponder] != data.listener) { 3044 [data.sdlContentView setNextResponder:data.listener]; 3045 } 3046 3047 [nswindow setContentSize:rect.size]; 3048 [nswindow setFrameOrigin:rect.origin]; 3049 3050 // When the window style changes the title is cleared 3051 if (!fullscreen) { 3052 Cocoa_SetWindowTitle(_this, window); 3053 data.was_zoomed = NO; 3054 if ([data.listener windowOperationIsPending:PENDING_OPERATION_ZOOM]) { 3055 [data.listener clearPendingWindowOperation:PENDING_OPERATION_ZOOM]; 3056 [nswindow zoom:nil]; 3057 } 3058 } 3059 3060 if (SDL_ShouldAllowTopmost() && fullscreen) { 3061 // OpenGL is rendering to the window, so make it visible! 3062 [nswindow setLevel:kCGMainMenuWindowLevel + 1]; 3063 } else if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { 3064 [nswindow setLevel:NSFloatingWindowLevel]; 3065 } else { 3066 [nswindow setLevel:kCGNormalWindowLevel]; 3067 } 3068 3069 if ([nswindow isVisible] || fullscreen) { 3070 [data.listener pauseVisibleObservation]; 3071 [nswindow makeKeyAndOrderFront:nil]; 3072 [data.listener resumeVisibleObservation]; 3073 } 3074 3075 // Update the safe area insets 3076 // The view never seems to reflect the safe area, so we'll use the screen instead 3077 if (@available(macOS 12.0, *)) { 3078 if (fullscreen) { 3079 NSScreen *screen = [nswindow screen]; 3080 3081 SDL_SetWindowSafeAreaInsets(data.window, 3082 (int)SDL_ceilf(screen.safeAreaInsets.left), 3083 (int)SDL_ceilf(screen.safeAreaInsets.right), 3084 (int)SDL_ceilf(screen.safeAreaInsets.top), 3085 (int)SDL_ceilf(screen.safeAreaInsets.bottom)); 3086 } else { 3087 SDL_SetWindowSafeAreaInsets(data.window, 0, 0, 0, 0); 3088 } 3089 } 3090 3091 /* When coming out of fullscreen to minimize, this needs to happen after the window 3092 * is made key again, or it won't minimize on 15.0 (Sequoia). 3093 */ 3094 if (!fullscreen && [data.listener windowOperationIsPending:PENDING_OPERATION_MINIMIZE]) { 3095 Cocoa_WaitForMiniaturizable(window); 3096 [data.listener addPendingWindowOperation:PENDING_OPERATION_ENTER_FULLSCREEN]; 3097 [data.listener clearPendingWindowOperation:PENDING_OPERATION_MINIMIZE]; 3098 [nswindow miniaturize:nil]; 3099 } 3100 3101 ScheduleContextUpdates(data); 3102 Cocoa_SyncWindow(_this, window); 3103 Cocoa_UpdateClipCursor(window); 3104 } 3105 3106 return SDL_FULLSCREEN_SUCCEEDED; 3107} 3108 3109void *Cocoa_GetWindowICCProfile(SDL_VideoDevice *_this, SDL_Window *window, size_t *size) 3110{ 3111 @autoreleasepool { 3112 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3113 NSWindow *nswindow = data.nswindow; 3114 NSScreen *screen = [nswindow screen]; 3115 NSData *iccProfileData = nil; 3116 void *retIccProfileData = NULL; 3117 3118 if (screen == nil) { 3119 SDL_SetError("Could not get screen of window."); 3120 return NULL; 3121 } 3122 3123 if ([screen colorSpace] == nil) { 3124 SDL_SetError("Could not get colorspace information of screen."); 3125 return NULL; 3126 } 3127 3128 iccProfileData = [[screen colorSpace] ICCProfileData]; 3129 if (iccProfileData == nil) { 3130 SDL_SetError("Could not get ICC profile data."); 3131 return NULL; 3132 } 3133 3134 retIccProfileData = SDL_malloc([iccProfileData length]); 3135 if (!retIccProfileData) { 3136 return NULL; 3137 } 3138 3139 [iccProfileData getBytes:retIccProfileData length:[iccProfileData length]]; 3140 *size = [iccProfileData length]; 3141 return retIccProfileData; 3142 } 3143} 3144 3145SDL_DisplayID Cocoa_GetDisplayForWindow(SDL_VideoDevice *_this, SDL_Window *window) 3146{ 3147 @autoreleasepool { 3148 NSScreen *screen; 3149 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3150 3151 // Not recognized via CHECK_WINDOW_MAGIC 3152 if (data == nil) { 3153 // Don't set the error here, it hides other errors and is ignored anyway 3154 // return SDL_SetError("Window data not set"); 3155 return 0; 3156 } 3157 3158 // NSWindow.screen may be nil when the window is off-screen. 3159 screen = data.nswindow.screen; 3160 3161 if (screen != nil) { 3162 // https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc 3163 CGDirectDisplayID displayid = [[screen.deviceDescription objectForKey:@"NSScreenNumber"] unsignedIntValue]; 3164 SDL_VideoDisplay *display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid); 3165 if (display) { 3166 return display->id; 3167 } 3168 } 3169 3170 // The higher level code will use other logic to find the display 3171 return 0; 3172 } 3173} 3174 3175bool Cocoa_SetWindowMouseRect(SDL_VideoDevice *_this, SDL_Window *window) 3176{ 3177 Cocoa_UpdateClipCursor(window); 3178 return true; 3179} 3180 3181bool Cocoa_SetWindowMouseGrab(SDL_VideoDevice *_this, SDL_Window *window, bool grabbed) 3182{ 3183 @autoreleasepool { 3184 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3185 3186 Cocoa_UpdateClipCursor(window); 3187 3188 if (data && (window->flags & SDL_WINDOW_FULLSCREEN) != 0) { 3189 if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_INPUT_FOCUS) && ![data.listener isInFullscreenSpace]) { 3190 // OpenGL is rendering to the window, so make it visible! 3191 // Doing this in 10.11 while in a Space breaks things (bug #3152) 3192 [data.nswindow setLevel:kCGMainMenuWindowLevel + 1]; 3193 } else if (window->flags & SDL_WINDOW_ALWAYS_ON_TOP) { 3194 [data.nswindow setLevel:NSFloatingWindowLevel]; 3195 } else { 3196 [data.nswindow setLevel:kCGNormalWindowLevel]; 3197 } 3198 } 3199 } 3200 3201 return true; 3202} 3203 3204void Cocoa_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) 3205{ 3206 @autoreleasepool { 3207 SDL_CocoaWindowData *data = (SDL_CocoaWindowData *)CFBridgingRelease(window->internal); 3208 3209 if (data) { 3210#ifdef SDL_VIDEO_OPENGL 3211 3212 NSArray *contexts; 3213 3214#endif // SDL_VIDEO_OPENGL 3215 SDL_Window *topmost = GetParentToplevelWindow(window); 3216 3217 /* Reset the input focus of the root window if this window is still set as keyboard focus. 3218 * SDL_DestroyWindow will have already taken care of reassigning focus if this is the SDL 3219 * keyboard focus, this ensures that an inactive window with this window set as input focus 3220 * does not try to reference it the next time it gains focus. 3221 */ 3222 if (topmost->keyboard_focus == window) { 3223 SDL_Window *new_focus = window; 3224 while (SDL_WINDOW_IS_POPUP(new_focus) && (new_focus->is_hiding || new_focus->is_destroying)) { 3225 new_focus = new_focus->parent; 3226 } 3227 3228 topmost->keyboard_focus = new_focus; 3229 } 3230 3231 if ([data.listener isInFullscreenSpace]) { 3232 [NSMenu setMenuBarVisible:YES]; 3233 } 3234 [data.listener close]; 3235 data.listener = nil; 3236 3237 if (!(window->flags & SDL_WINDOW_EXTERNAL)) { 3238 // Release the content view to avoid further updateLayer callbacks 3239 [data.nswindow setContentView:nil]; 3240 [data.nswindow close]; 3241 } 3242 3243#ifdef SDL_VIDEO_OPENGL 3244 3245 contexts = [data.nscontexts copy]; 3246 for (SDL3OpenGLContext *context in contexts) { 3247 // Calling setWindow:NULL causes the context to remove itself from the context list. 3248 [context setWindow:NULL]; 3249 } 3250 3251#endif // SDL_VIDEO_OPENGL 3252 } 3253 window->internal = NULL; 3254 } 3255} 3256 3257bool Cocoa_SetWindowFullscreenSpace(SDL_Window *window, bool state, bool blocking) 3258{ 3259 @autoreleasepool { 3260 bool succeeded = false; 3261 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3262 3263 if (state) { 3264 data.fullscreen_space_requested = YES; 3265 } 3266 data.in_blocking_transition = blocking; 3267 if ([data.listener setFullscreenSpace:(state ? YES : NO)]) { 3268 if (blocking) { 3269 const int maxattempts = 3; 3270 int attempt = 0; 3271 while (++attempt <= maxattempts) { 3272 /* Wait for the transition to complete, so application changes 3273 take effect properly (e.g. setting the window size, etc.) 3274 */ 3275 const int limit = 10000; 3276 int count = 0; 3277 while ([data.listener isInFullscreenSpaceTransition]) { 3278 if (++count == limit) { 3279 // Uh oh, transition isn't completing. Should we assert? 3280 break; 3281 } 3282 SDL_Delay(1); 3283 SDL_PumpEvents(); 3284 } 3285 if ([data.listener isInFullscreenSpace] == (state ? YES : NO)) { 3286 break; 3287 } 3288 // Try again, the last attempt was interrupted by user gestures 3289 if (![data.listener setFullscreenSpace:(state ? YES : NO)]) { 3290 break; // ??? 3291 } 3292 } 3293 } 3294 3295 // Return TRUE to prevent non-space fullscreen logic from running 3296 succeeded = true; 3297 } 3298 3299 data.in_blocking_transition = NO; 3300 return succeeded; 3301 } 3302} 3303 3304bool Cocoa_SetWindowHitTest(SDL_Window *window, bool enabled) 3305{ 3306 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3307 3308 [data.listener updateHitTest]; 3309 return true; 3310} 3311 3312void Cocoa_AcceptDragAndDrop(SDL_Window *window, bool accept) 3313{ 3314 @autoreleasepool { 3315 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3316 if (accept) { 3317 [data.nswindow registerForDraggedTypes:@[ (NSString *)kUTTypeFileURL, 3318 (NSString *)kUTTypeUTF8PlainText ]]; 3319 } else { 3320 [data.nswindow unregisterDraggedTypes]; 3321 } 3322 } 3323} 3324 3325bool Cocoa_SetWindowParent(SDL_VideoDevice *_this, SDL_Window *window, SDL_Window *parent) 3326{ 3327 @autoreleasepool { 3328 SDL_CocoaWindowData *child_data = (__bridge SDL_CocoaWindowData *)window->internal; 3329 3330 // Remove an existing parent. 3331 if (child_data.nswindow.parentWindow) { 3332 NSWindow *nsparent = ((__bridge SDL_CocoaWindowData *)window->parent->internal).nswindow; 3333 [nsparent removeChildWindow:child_data.nswindow]; 3334 } 3335 3336 if (parent) { 3337 SDL_CocoaWindowData *parent_data = (__bridge SDL_CocoaWindowData *)parent->internal; 3338 [parent_data.nswindow addChildWindow:child_data.nswindow ordered:NSWindowAbove]; 3339 } 3340 } 3341 3342 return true; 3343} 3344 3345bool Cocoa_SetWindowModal(SDL_VideoDevice *_this, SDL_Window *window, bool modal) 3346{ 3347 @autoreleasepool { 3348 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3349 3350 if (data.modal_session) { 3351 [NSApp endModalSession:data.modal_session]; 3352 data.modal_session = nil; 3353 } 3354 3355 if (modal) { 3356 data.modal_session = [NSApp beginModalSessionForWindow:data.nswindow]; 3357 } 3358 } 3359 3360 return true; 3361} 3362 3363bool Cocoa_FlashWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_FlashOperation operation) 3364{ 3365 @autoreleasepool { 3366 // Note that this is app-wide and not window-specific! 3367 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3368 3369 if (data.flash_request) { 3370 [NSApp cancelUserAttentionRequest:data.flash_request]; 3371 data.flash_request = 0; 3372 } 3373 3374 switch (operation) { 3375 case SDL_FLASH_CANCEL: 3376 // Canceled above 3377 break; 3378 case SDL_FLASH_BRIEFLY: 3379 data.flash_request = [NSApp requestUserAttention:NSInformationalRequest]; 3380 break; 3381 case SDL_FLASH_UNTIL_FOCUSED: 3382 data.flash_request = [NSApp requestUserAttention:NSCriticalRequest]; 3383 break; 3384 default: 3385 return SDL_Unsupported(); 3386 } 3387 return true; 3388 } 3389} 3390 3391bool Cocoa_SetWindowFocusable(SDL_VideoDevice *_this, SDL_Window *window, bool focusable) 3392{ 3393 if (window->flags & SDL_WINDOW_POPUP_MENU) { 3394 if (!(window->flags & SDL_WINDOW_HIDDEN)) { 3395 if (!focusable && (window->flags & SDL_WINDOW_INPUT_FOCUS)) { 3396 SDL_Window *new_focus; 3397 const bool set_focus = SDL_ShouldRelinquishPopupFocus(window, &new_focus); 3398 Cocoa_SetKeyboardFocus(new_focus, set_focus); 3399 } else if (focusable) { 3400 if (SDL_ShouldFocusPopup(window)) { 3401 Cocoa_SetKeyboardFocus(window, true); 3402 } 3403 } 3404 } 3405 } 3406 3407 return true; // just succeed, the real work is done elsewhere. 3408} 3409 3410bool Cocoa_SetWindowOpacity(SDL_VideoDevice *_this, SDL_Window *window, float opacity) 3411{ 3412 @autoreleasepool { 3413 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3414 [data.nswindow setAlphaValue:opacity]; 3415 return true; 3416 } 3417} 3418 3419bool Cocoa_SyncWindow(SDL_VideoDevice *_this, SDL_Window *window) 3420{ 3421 bool result = false; 3422 3423 @autoreleasepool { 3424 const Uint64 timeout = SDL_GetTicksNS() + SDL_MS_TO_NS(2500); 3425 SDL_CocoaWindowData *data = (__bridge SDL_CocoaWindowData *)window->internal; 3426 3427 for (;;) { 3428 SDL_PumpEvents(); 3429 3430 result = ![data.listener hasPendingWindowOperation]; 3431 if (result || SDL_GetTicksNS() >= timeout) { 3432 break; 3433 } 3434 3435 // Small delay before going again. 3436 SDL_Delay(10); 3437 } 3438 } 3439 3440 return result; 3441} 3442 3443#endif // SDL_VIDEO_DRIVER_COCOA 3444[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.