Atlas - SDL_cocoamodes.m

Home / ext / SDL / src / video / cocoa Lines: 1 | Size: 27618 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_VIDEO_DRIVER_COCOA 24 25#include "SDL_cocoavideo.h" 26#include "../../events/SDL_events_c.h" 27 28// We need this for IODisplayCreateInfoDictionary and kIODisplayOnlyPreferredName 29#include <IOKit/graphics/IOGraphicsLib.h> 30 31// We need this for CVDisplayLinkGetNominalOutputVideoRefreshPeriod 32#include <CoreVideo/CVBase.h> 33#include <CoreVideo/CVDisplayLink.h> 34 35#if (IOGRAPHICSTYPES_REV < 40) 36#define kDisplayModeNativeFlag 0x02000000 37#endif 38 39static bool CG_SetError(const char *prefix, CGDisplayErr result) 40{ 41 const char *error; 42 43 switch (result) { 44 case kCGErrorFailure: 45 error = "kCGErrorFailure"; 46 break; 47 case kCGErrorIllegalArgument: 48 error = "kCGErrorIllegalArgument"; 49 break; 50 case kCGErrorInvalidConnection: 51 error = "kCGErrorInvalidConnection"; 52 break; 53 case kCGErrorInvalidContext: 54 error = "kCGErrorInvalidContext"; 55 break; 56 case kCGErrorCannotComplete: 57 error = "kCGErrorCannotComplete"; 58 break; 59 case kCGErrorNotImplemented: 60 error = "kCGErrorNotImplemented"; 61 break; 62 case kCGErrorRangeCheck: 63 error = "kCGErrorRangeCheck"; 64 break; 65 case kCGErrorTypeCheck: 66 error = "kCGErrorTypeCheck"; 67 break; 68 case kCGErrorInvalidOperation: 69 error = "kCGErrorInvalidOperation"; 70 break; 71 case kCGErrorNoneAvailable: 72 error = "kCGErrorNoneAvailable"; 73 break; 74 default: 75 error = "Unknown Error"; 76 break; 77 } 78 return SDL_SetError("%s: %s", prefix, error); 79} 80 81static NSScreen *GetNSScreenForDisplayID(CGDirectDisplayID displayID) 82{ 83 NSArray *screens = [NSScreen screens]; 84 85 // !!! FIXME: maybe track the NSScreen in SDL_DisplayData? 86 for (NSScreen *screen in screens) { 87 const CGDirectDisplayID thisDisplay = (CGDirectDisplayID)[[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue]; 88 if (thisDisplay == displayID) { 89 return screen; 90 } 91 } 92 return nil; 93} 94 95SDL_VideoDisplay *Cocoa_FindSDLDisplayByCGDirectDisplayID(SDL_VideoDevice *_this, CGDirectDisplayID displayid) 96{ 97 for (int i = 0; i < _this->num_displays; i++) { 98 const SDL_DisplayData *displaydata = _this->displays[i]->internal; 99 if (displaydata && (displaydata->display == displayid)) { 100 return _this->displays[i]; 101 } 102 } 103 return NULL; 104} 105 106static float GetDisplayModeRefreshRate(CGDisplayModeRef vidmode, CVDisplayLinkRef link) 107{ 108 float refreshRate = (float)CGDisplayModeGetRefreshRate(vidmode); 109 110 // CGDisplayModeGetRefreshRate can return 0 (eg for built-in displays). 111 if (refreshRate == 0 && link != NULL) { 112 CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); 113 if ((time.flags & kCVTimeIsIndefinite) == 0 && time.timeValue != 0) { 114 refreshRate = (float)time.timeScale / time.timeValue; 115 } 116 } 117 118 return refreshRate; 119} 120 121static bool HasValidDisplayModeFlags(CGDisplayModeRef vidmode) 122{ 123 uint32_t ioflags = CGDisplayModeGetIOFlags(vidmode); 124 125 // Filter out modes which have flags that we don't want. 126 if (ioflags & (kDisplayModeNeverShowFlag | kDisplayModeNotGraphicsQualityFlag)) { 127 return false; 128 } 129 130 // Filter out modes which don't have flags that we want. 131 if (!(ioflags & kDisplayModeValidFlag) || !(ioflags & kDisplayModeSafeFlag)) { 132 return false; 133 } 134 135 return true; 136} 137 138static Uint32 GetDisplayModePixelFormat(CGDisplayModeRef vidmode) 139{ 140 // This API is deprecated in 10.11 with no good replacement (as of 10.15). 141 CFStringRef fmt = CGDisplayModeCopyPixelEncoding(vidmode); 142 Uint32 pixelformat = SDL_PIXELFORMAT_UNKNOWN; 143 144 if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels), 145 kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 146 pixelformat = SDL_PIXELFORMAT_ARGB8888; 147 } else if (CFStringCompare(fmt, CFSTR(IO16BitDirectPixels), 148 kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 149 pixelformat = SDL_PIXELFORMAT_ARGB1555; 150 } else if (CFStringCompare(fmt, CFSTR(kIO30BitDirectPixels), 151 kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 152 pixelformat = SDL_PIXELFORMAT_ARGB2101010; 153 } else { 154 // ignore 8-bit and such for now. 155 } 156 157 CFRelease(fmt); 158 159 return pixelformat; 160} 161 162static bool GetDisplayMode(CGDisplayModeRef vidmode, bool vidmodeCurrent, CFArrayRef modelist, CVDisplayLinkRef link, SDL_DisplayMode *mode) 163{ 164 SDL_DisplayModeData *data; 165 bool usableForGUI = CGDisplayModeIsUsableForDesktopGUI(vidmode); 166 size_t width = CGDisplayModeGetWidth(vidmode); 167 size_t height = CGDisplayModeGetHeight(vidmode); 168 size_t pixelW = width; 169 size_t pixelH = height; 170 uint32_t ioflags = CGDisplayModeGetIOFlags(vidmode); 171 float refreshrate = GetDisplayModeRefreshRate(vidmode, link); 172 Uint32 format = GetDisplayModePixelFormat(vidmode); 173 bool interlaced = (ioflags & kDisplayModeInterlacedFlag) != 0; 174 CFMutableArrayRef modes; 175 176 if (format == SDL_PIXELFORMAT_UNKNOWN) { 177 return false; 178 } 179 180 /* Don't fail the current mode based on flags because this could prevent Cocoa_InitModes from 181 * succeeding if the current mode lacks certain flags (esp kDisplayModeSafeFlag). */ 182 if (!vidmodeCurrent && !HasValidDisplayModeFlags(vidmode)) { 183 return false; 184 } 185 186 modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 187 CFArrayAppendValue(modes, vidmode); 188 189 /* If a list of possible display modes is passed in, use it to filter out 190 * modes that have duplicate sizes. We don't just rely on SDL's higher level 191 * duplicate filtering because this code can choose what properties are 192 * preferred, and it can add CGDisplayModes to the DisplayModeData's list of 193 * modes to try (see comment below for why that's necessary). */ 194 pixelW = CGDisplayModeGetPixelWidth(vidmode); 195 pixelH = CGDisplayModeGetPixelHeight(vidmode); 196 197 if (modelist != NULL) { 198 CFIndex modescount = CFArrayGetCount(modelist); 199 int i; 200 201 for (i = 0; i < modescount; i++) { 202 size_t otherW, otherH, otherpixelW, otherpixelH; 203 float otherrefresh; 204 Uint32 otherformat; 205 bool otherGUI; 206 CGDisplayModeRef othermode = (CGDisplayModeRef)CFArrayGetValueAtIndex(modelist, i); 207 uint32_t otherioflags = CGDisplayModeGetIOFlags(othermode); 208 209 if (CFEqual(vidmode, othermode)) { 210 continue; 211 } 212 213 if (!HasValidDisplayModeFlags(othermode)) { 214 continue; 215 } 216 217 otherW = CGDisplayModeGetWidth(othermode); 218 otherH = CGDisplayModeGetHeight(othermode); 219 otherpixelW = CGDisplayModeGetPixelWidth(othermode); 220 otherpixelH = CGDisplayModeGetPixelHeight(othermode); 221 otherrefresh = GetDisplayModeRefreshRate(othermode, link); 222 otherformat = GetDisplayModePixelFormat(othermode); 223 otherGUI = CGDisplayModeIsUsableForDesktopGUI(othermode); 224 225 /* Ignore this mode if it's interlaced and there's a non-interlaced 226 * mode in the list with the same properties. 227 */ 228 if (interlaced && ((otherioflags & kDisplayModeInterlacedFlag) == 0) && width == otherW && height == otherH && pixelW == otherpixelW && pixelH == otherpixelH && refreshrate == otherrefresh && format == otherformat && usableForGUI == otherGUI) { 229 CFRelease(modes); 230 return false; 231 } 232 233 /* Ignore this mode if it's not usable for desktop UI and its 234 * properties are equal to another GUI-capable mode in the list. 235 */ 236 if (width == otherW && height == otherH && pixelW == otherpixelW && pixelH == otherpixelH && !usableForGUI && otherGUI && refreshrate == otherrefresh && format == otherformat) { 237 CFRelease(modes); 238 return false; 239 } 240 241 /* If multiple modes have the exact same properties, they'll all 242 * go in the list of modes to try when SetDisplayMode is called. 243 * This is needed because kCGDisplayShowDuplicateLowResolutionModes 244 * (which is used to expose highdpi display modes) can make the 245 * list of modes contain duplicates (according to their properties 246 * obtained via public APIs) which don't work with SetDisplayMode. 247 * Those duplicate non-functional modes *do* have different pixel 248 * formats according to their internal data structure viewed with 249 * NSLog, but currently no public API can detect that. 250 * https://bugzilla.libsdl.org/show_bug.cgi?id=4822 251 * 252 * As of macOS 10.15.0, those duplicates have the exact same 253 * properties via public APIs in every way (even their IO flags and 254 * CGDisplayModeGetIODisplayModeID is the same), so we could test 255 * those for equality here too, but I'm intentionally not doing that 256 * in case there are duplicate modes with different IO flags or IO 257 * display mode IDs in the future. In that case I think it's better 258 * to try them all in SetDisplayMode than to risk one of them being 259 * correct but it being filtered out by SDL_AddFullscreenDisplayMode 260 * as being a duplicate. 261 */ 262 if (width == otherW && height == otherH && pixelW == otherpixelW && pixelH == otherpixelH && usableForGUI == otherGUI && refreshrate == otherrefresh && format == otherformat) { 263 CFArrayAppendValue(modes, othermode); 264 } 265 } 266 } 267 268 SDL_zerop(mode); 269 data = (SDL_DisplayModeData *)SDL_malloc(sizeof(*data)); 270 if (!data) { 271 CFRelease(modes); 272 return false; 273 } 274 data->modes = modes; 275 mode->format = format; 276 mode->w = (int)width; 277 mode->h = (int)height; 278 mode->pixel_density = (float)pixelW / width; 279 mode->refresh_rate = refreshrate; 280 mode->internal = data; 281 return true; 282} 283 284static char *Cocoa_GetDisplayName(CGDirectDisplayID displayID) 285{ 286 if (@available(macOS 10.15, *)) { 287 NSScreen *screen = GetNSScreenForDisplayID(displayID); 288 if (screen) { 289 const char *name = [screen.localizedName UTF8String]; 290 if (name) { 291 return SDL_strdup(name); 292 } 293 } 294 } 295 296 // This API is deprecated in 10.9 with no good replacement (as of 10.15). 297 io_service_t servicePort = CGDisplayIOServicePort(displayID); 298 CFDictionaryRef deviceInfo = IODisplayCreateInfoDictionary(servicePort, kIODisplayOnlyPreferredName); 299 NSDictionary *localizedNames = [(__bridge NSDictionary *)deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; 300 char *displayName = NULL; 301 302 if ([localizedNames count] > 0) { 303 displayName = SDL_strdup([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]); 304 } 305 CFRelease(deviceInfo); 306 return displayName; 307} 308 309static void Cocoa_GetHDRProperties(CGDirectDisplayID displayID, SDL_HDROutputProperties *HDR) 310{ 311 HDR->SDR_white_level = 1.0f; 312 HDR->HDR_headroom = 1.0f; 313 314 if (@available(macOS 10.15, *)) { 315 NSScreen *screen = GetNSScreenForDisplayID(displayID); 316 if (screen) { 317 if (screen.maximumExtendedDynamicRangeColorComponentValue > 1.0f) { 318 HDR->HDR_headroom = screen.maximumExtendedDynamicRangeColorComponentValue; 319 } else { 320 HDR->HDR_headroom = screen.maximumPotentialExtendedDynamicRangeColorComponentValue; 321 } 322 } 323 } 324} 325 326static bool Cocoa_GetUsableBounds(CGDirectDisplayID displayID, SDL_Rect *rect) 327{ 328 NSScreen *screen = GetNSScreenForDisplayID(displayID); 329 330 if (screen == nil) { 331 return false; 332 } 333 334 SDL_VideoDevice *device = SDL_GetVideoDevice(); 335 SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)device->internal; 336 337 const NSRect frame = [screen visibleFrame]; 338 rect->x = (int)frame.origin.x; 339 rect->y = (int)(data.mainDisplayHeight - frame.origin.y - frame.size.height); 340 rect->w = (int)frame.size.width; 341 rect->h = (int)frame.size.height; 342 return true; 343} 344 345bool Cocoa_AddDisplay(CGDirectDisplayID display, bool send_event) 346{ 347 CGDisplayModeRef moderef = CGDisplayCopyDisplayMode(display); 348 if (!moderef) { 349 return false; 350 } 351 352 SDL_DisplayData *displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata)); 353 if (!displaydata) { 354 CGDisplayModeRelease(moderef); 355 return false; 356 } 357 displaydata->display = display; 358 359 CVDisplayLinkRef link = NULL; 360 CVDisplayLinkCreateWithCGDisplay(display, &link); 361 362 SDL_VideoDisplay viddisplay; 363 SDL_zero(viddisplay); 364 viddisplay.name = Cocoa_GetDisplayName(display); // this returns a strdup'ed string 365 366 SDL_DisplayMode mode; 367 if (!GetDisplayMode(moderef, true, NULL, link, &mode)) { 368 CVDisplayLinkRelease(link); 369 CGDisplayModeRelease(moderef); 370 SDL_free(viddisplay.name); 371 SDL_free(displaydata); 372 return false; 373 } 374 375 CVDisplayLinkRelease(link); 376 CGDisplayModeRelease(moderef); 377 378 Cocoa_GetHDRProperties(displaydata->display, &viddisplay.HDR); 379 380 Cocoa_GetUsableBounds(displaydata->display, &displaydata->usable_bounds); 381 382 viddisplay.desktop_mode = mode; 383 viddisplay.internal = displaydata; 384 const bool retval = SDL_AddVideoDisplay(&viddisplay, send_event); 385 SDL_free(viddisplay.name); 386 return retval; 387} 388 389static void Cocoa_DisplayReconfigurationCallback(CGDirectDisplayID displayid, CGDisplayChangeSummaryFlags flags, void *userInfo) 390{ 391 #if 0 392 SDL_Log("COCOA DISPLAY RECONFIG CALLBACK! display=%u", (unsigned int) displayid); 393 #define CHECK_DISPLAY_RECONFIG_FLAG(x) if (flags & x) { SDL_Log(" - " #x); } 394 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayBeginConfigurationFlag); 395 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayMovedFlag); 396 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplaySetMainFlag); 397 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplaySetModeFlag); 398 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayAddFlag); 399 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayRemoveFlag); 400 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayEnabledFlag); 401 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayDisabledFlag); 402 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayMirrorFlag); 403 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayUnMirrorFlag); 404 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayDesktopShapeChangedFlag); 405 #undef CHECK_DISPLAY_RECONFIG_FLAG 406 #endif 407 408 SDL_VideoDevice *_this = (SDL_VideoDevice *) userInfo; 409 SDL_VideoDisplay *display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid); // will be NULL for newly-added (or newly-unmirrored) displays! 410 411 if (flags & kCGDisplayDisabledFlag) { 412 flags |= kCGDisplayRemoveFlag; // treat this like a display leaving, even though it's still plugged in. 413 } 414 415 if (flags & kCGDisplayEnabledFlag) { 416 flags |= kCGDisplayAddFlag; // treat this like a display leaving, even though it's still plugged in. 417 } 418 419 if (flags & kCGDisplayMirrorFlag) { 420 flags |= kCGDisplayRemoveFlag; // treat this like a display leaving, even though it's still actually here. 421 } 422 423 if (flags & kCGDisplayUnMirrorFlag) { 424 flags |= kCGDisplayAddFlag; // treat this like a new display arriving, even though it was here all along. 425 } 426 427 if ((flags & kCGDisplayAddFlag) && (flags & kCGDisplayRemoveFlag)) { 428 // sometimes you get a removed device that gets Add and Remove flags at the same time but the display dimensions are 0x0 or 1x1, hence the `> 1` test. 429 // Mirrored things are always removed, since they don't represent a discrete display in this state. 430 if (((flags & kCGDisplayMirrorFlag) == 0) && (CGDisplayPixelsWide(displayid) > 1)) { 431 // Final state is connected 432 flags &= ~kCGDisplayRemoveFlag; 433 } else { 434 // Final state is disconnected 435 flags &= ~kCGDisplayAddFlag; 436 } 437 } 438 439 if (flags & kCGDisplayAddFlag) { 440 if (!display) { 441 if (!Cocoa_AddDisplay(displayid, true)) { 442 return; // oh well. 443 } 444 display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid); 445 SDL_assert(display != NULL); 446 } 447 } 448 449 if (flags & kCGDisplayRemoveFlag) { 450 if (display) { 451 SDL_DelVideoDisplay(display->id, true); 452 display = NULL; 453 } 454 } 455 456 if (flags & kCGDisplaySetModeFlag) { 457 if (display) { 458 CGDisplayModeRef moderef = CGDisplayCopyDisplayMode(displayid); 459 if (moderef) { 460 CVDisplayLinkRef link = NULL; 461 CVDisplayLinkCreateWithCGDisplay(displayid, &link); 462 if (link) { 463 SDL_DisplayMode mode; 464 if (GetDisplayMode(moderef, true, NULL, link, &mode)) { 465 SDL_SetDesktopDisplayMode(display, &mode); 466 } 467 CVDisplayLinkRelease(link); 468 } 469 CGDisplayModeRelease(moderef); 470 } 471 } 472 } 473 474 if (flags & kCGDisplaySetMainFlag) { 475 if (display) { 476 for (int i = 0; i < _this->num_displays; i++) { 477 if (_this->displays[i] == display) { 478 if (i > 0) { 479 // move this display to the front of _this->displays so it's treated as primary. 480 SDL_memmove(&_this->displays[1], &_this->displays[0], sizeof (*_this->displays) * i); 481 _this->displays[0] = display; 482 } 483 flags |= kCGDisplayMovedFlag; // we don't have an SDL event atm for "this display became primary," so at least let everyone know it "moved". 484 break; 485 } 486 } 487 } 488 } 489 490 if (flags & kCGDisplayMovedFlag) { 491 if (display) { 492 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_MOVED, 0, 0); 493 } 494 } 495 496 if (flags & kCGDisplayDesktopShapeChangedFlag) { 497 SDL_UpdateDesktopBounds(); 498 } 499 500 SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; 501 data.mainDisplayHeight = CGDisplayPixelsHigh(kCGDirectMainDisplay); 502} 503 504void Cocoa_InitModes(SDL_VideoDevice *_this) 505{ 506 @autoreleasepool { 507 SDL_CocoaVideoData *data = (__bridge SDL_CocoaVideoData *)_this->internal; 508 CGDisplayErr result; 509 CGDisplayCount numDisplays = 0; 510 511 data.mainDisplayHeight = CGDisplayPixelsHigh(kCGDirectMainDisplay); 512 513 result = CGGetOnlineDisplayList(0, NULL, &numDisplays); 514 if (result != kCGErrorSuccess) { 515 CG_SetError("CGGetOnlineDisplayList()", result); 516 return; 517 } 518 519 bool isstack; 520 CGDirectDisplayID *displays = SDL_small_alloc(CGDirectDisplayID, numDisplays, &isstack); 521 522 result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays); 523 if (result != kCGErrorSuccess) { 524 CG_SetError("CGGetOnlineDisplayList()", result); 525 SDL_small_free(displays, isstack); 526 return; 527 } 528 529 // future updates to the display graph will come through this callback. 530 CGDisplayRegisterReconfigurationCallback(Cocoa_DisplayReconfigurationCallback, _this); 531 532 // Pick up the primary display in the first pass, then get the rest 533 for (int pass = 0; pass < 2; ++pass) { 534 for (int i = 0; i < numDisplays; ++i) { 535 if (pass == 0) { 536 if (!CGDisplayIsMain(displays[i])) { 537 continue; 538 } 539 } else { 540 if (CGDisplayIsMain(displays[i])) { 541 continue; 542 } 543 } 544 545 if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) { 546 continue; 547 } 548 549 Cocoa_AddDisplay(displays[i], false); 550 } 551 } 552 SDL_small_free(displays, isstack); 553 } 554} 555 556void Cocoa_UpdateDisplays(SDL_VideoDevice *_this) 557{ 558 SDL_HDROutputProperties HDR; 559 int i; 560 561 for (i = 0; i < _this->num_displays; ++i) { 562 SDL_VideoDisplay *display = _this->displays[i]; 563 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 564 565 Cocoa_GetHDRProperties(displaydata->display, &HDR); 566 SDL_SetDisplayHDRProperties(display, &HDR); 567 568 SDL_Rect rect; 569 if (Cocoa_GetUsableBounds(displaydata->display, &rect) && 570 SDL_memcmp(&displaydata->usable_bounds, &rect, sizeof(rect)) != 0) { 571 SDL_memcpy(&displaydata->usable_bounds, &rect, sizeof(rect)); 572 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, 0, 0); 573 } 574 } 575} 576 577bool Cocoa_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) 578{ 579 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 580 CGRect cgrect; 581 582 cgrect = CGDisplayBounds(displaydata->display); 583 rect->x = (int)cgrect.origin.x; 584 rect->y = (int)cgrect.origin.y; 585 rect->w = (int)cgrect.size.width; 586 rect->h = (int)cgrect.size.height; 587 return true; 588} 589 590bool Cocoa_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) 591{ 592 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 593 594 SDL_memcpy(rect, &displaydata->usable_bounds, sizeof(*rect)); 595 return true; 596} 597 598bool Cocoa_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) 599{ 600 SDL_DisplayData *data = (SDL_DisplayData *)display->internal; 601 CVDisplayLinkRef link = NULL; 602 CFArrayRef modes; 603 CFDictionaryRef dict = NULL; 604 const CFStringRef dictkeys[] = { kCGDisplayShowDuplicateLowResolutionModes }; 605 const CFBooleanRef dictvalues[] = { kCFBooleanTrue }; 606 607 CVDisplayLinkCreateWithCGDisplay(data->display, &link); 608 609 /* By default, CGDisplayCopyAllDisplayModes will only get a subset of the 610 * system's available modes. For example on a 15" 2016 MBP, users can 611 * choose 1920x1080@2x in System Preferences but it won't show up here, 612 * unless we specify the option below. 613 * The display modes returned by CGDisplayCopyAllDisplayModes are also not 614 * high dpi-capable unless this option is set. 615 * macOS 10.15 also seems to have a bug where entering, exiting, and 616 * re-entering exclusive fullscreen with a low dpi display mode can cause 617 * the content of the screen to move up, which this setting avoids: 618 * https://bugzilla.libsdl.org/show_bug.cgi?id=4822 619 */ 620 621 dict = CFDictionaryCreate(NULL, 622 (const void **)dictkeys, 623 (const void **)dictvalues, 624 1, 625 &kCFCopyStringDictionaryKeyCallBacks, 626 &kCFTypeDictionaryValueCallBacks); 627 628 modes = CGDisplayCopyAllDisplayModes(data->display, dict); 629 630 if (dict) { 631 CFRelease(dict); 632 } 633 634 if (modes) { 635 CFIndex i; 636 const CFIndex count = CFArrayGetCount(modes); 637 638 for (i = 0; i < count; i++) { 639 CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i); 640 SDL_DisplayMode mode; 641 642 if (GetDisplayMode(moderef, false, modes, link, &mode)) { 643 if (!SDL_AddFullscreenDisplayMode(display, &mode)) { 644 CFRelease(mode.internal->modes); 645 SDL_free(mode.internal); 646 } 647 } 648 } 649 650 CFRelease(modes); 651 } 652 653 CVDisplayLinkRelease(link); 654 return true; 655} 656 657static CGError SetDisplayModeForDisplay(CGDirectDisplayID display, SDL_DisplayModeData *data) 658{ 659 /* SDL_DisplayModeData can contain multiple CGDisplayModes to try (with 660 * identical properties), some of which might not work. See GetDisplayMode. 661 */ 662 CGError result = kCGErrorFailure; 663 for (CFIndex i = 0; i < CFArrayGetCount(data->modes); i++) { 664 CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(data->modes, i); 665 result = CGDisplaySetDisplayMode(display, moderef, NULL); 666 if (result == kCGErrorSuccess) { 667 // If this mode works, try it first next time. 668 if (i > 0) { 669 CFArrayExchangeValuesAtIndices(data->modes, i, 0); 670 } 671 break; 672 } 673 } 674 return result; 675} 676 677bool Cocoa_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode) 678{ 679 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 680 SDL_DisplayModeData *data = mode->internal; 681 CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken; 682 CGError result = kCGErrorSuccess; 683 684 b_inModeTransition = true; 685 686 // Fade to black to hide resolution-switching flicker 687 if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) { 688 CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE); 689 } 690 691 if (data == display->desktop_mode.internal) { 692 // Restoring desktop mode 693 SetDisplayModeForDisplay(displaydata->display, data); 694 } else { 695 // Do the physical switch 696 result = SetDisplayModeForDisplay(displaydata->display, data); 697 } 698 699 // Fade in again (asynchronously) 700 if (fade_token != kCGDisplayFadeReservationInvalidToken) { 701 CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE); 702 CGReleaseDisplayFadeReservation(fade_token); 703 } 704 705 b_inModeTransition = false; 706 707 if (result != kCGErrorSuccess) { 708 return CG_SetError("CGDisplaySwitchToMode()", result); 709 } 710 return true; 711} 712 713void Cocoa_QuitModes(SDL_VideoDevice *_this) 714{ 715 int i, j; 716 717 CGDisplayRemoveReconfigurationCallback(Cocoa_DisplayReconfigurationCallback, _this); 718 719 for (i = 0; i < _this->num_displays; ++i) { 720 SDL_VideoDisplay *display = _this->displays[i]; 721 SDL_DisplayModeData *mode; 722 723 if (display->current_mode->internal != display->desktop_mode.internal) { 724 Cocoa_SetDisplayMode(_this, display, &display->desktop_mode); 725 } 726 727 mode = display->desktop_mode.internal; 728 CFRelease(mode->modes); 729 730 for (j = 0; j < display->num_fullscreen_modes; j++) { 731 mode = display->fullscreen_modes[j].internal; 732 CFRelease(mode->modes); 733 } 734 } 735} 736 737#endif // SDL_VIDEO_DRIVER_COCOA 738
[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.