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