Atlas - SDL_cocoamodes.m
Home / ext / SDL / src / video / cocoa Lines: 1 | Size: 27192 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#ifdef SDL_VIDEO_DRIVER_COCOA 24 25#include "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 const NSRect frame = [screen visibleFrame]; 335 rect->x = (int)frame.origin.x; 336 rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height); 337 rect->w = (int)frame.size.width; 338 rect->h = (int)frame.size.height; 339 return true; 340} 341 342bool Cocoa_AddDisplay(CGDirectDisplayID display, bool send_event) 343{ 344 CGDisplayModeRef moderef = CGDisplayCopyDisplayMode(display); 345 if (!moderef) { 346 return false; 347 } 348 349 SDL_DisplayData *displaydata = (SDL_DisplayData *)SDL_calloc(1, sizeof(*displaydata)); 350 if (!displaydata) { 351 CGDisplayModeRelease(moderef); 352 return false; 353 } 354 displaydata->display = display; 355 356 CVDisplayLinkRef link = NULL; 357 CVDisplayLinkCreateWithCGDisplay(display, &link); 358 359 SDL_VideoDisplay viddisplay; 360 SDL_zero(viddisplay); 361 viddisplay.name = Cocoa_GetDisplayName(display); // this returns a strdup'ed string 362 363 SDL_DisplayMode mode; 364 if (!GetDisplayMode(moderef, true, NULL, link, &mode)) { 365 CVDisplayLinkRelease(link); 366 CGDisplayModeRelease(moderef); 367 SDL_free(viddisplay.name); 368 SDL_free(displaydata); 369 return false; 370 } 371 372 CVDisplayLinkRelease(link); 373 CGDisplayModeRelease(moderef); 374 375 Cocoa_GetHDRProperties(displaydata->display, &viddisplay.HDR); 376 377 Cocoa_GetUsableBounds(displaydata->display, &displaydata->usable_bounds); 378 379 viddisplay.desktop_mode = mode; 380 viddisplay.internal = displaydata; 381 const bool retval = SDL_AddVideoDisplay(&viddisplay, send_event); 382 SDL_free(viddisplay.name); 383 return retval; 384} 385 386static void Cocoa_DisplayReconfigurationCallback(CGDirectDisplayID displayid, CGDisplayChangeSummaryFlags flags, void *userInfo) 387{ 388 #if 0 389 SDL_Log("COCOA DISPLAY RECONFIG CALLBACK! display=%u", (unsigned int) displayid); 390 #define CHECK_DISPLAY_RECONFIG_FLAG(x) if (flags & x) { SDL_Log(" - " #x); } 391 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayBeginConfigurationFlag); 392 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayMovedFlag); 393 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplaySetMainFlag); 394 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplaySetModeFlag); 395 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayAddFlag); 396 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayRemoveFlag); 397 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayEnabledFlag); 398 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayDisabledFlag); 399 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayMirrorFlag); 400 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayUnMirrorFlag); 401 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayDesktopShapeChangedFlag); 402 #undef CHECK_DISPLAY_RECONFIG_FLAG 403 #endif 404 405 SDL_VideoDevice *_this = (SDL_VideoDevice *) userInfo; 406 SDL_VideoDisplay *display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid); // will be NULL for newly-added (or newly-unmirrored) displays! 407 408 if (flags & kCGDisplayDisabledFlag) { 409 flags |= kCGDisplayRemoveFlag; // treat this like a display leaving, even though it's still plugged in. 410 } 411 412 if (flags & kCGDisplayEnabledFlag) { 413 flags |= kCGDisplayAddFlag; // treat this like a display leaving, even though it's still plugged in. 414 } 415 416 if (flags & kCGDisplayMirrorFlag) { 417 flags |= kCGDisplayRemoveFlag; // treat this like a display leaving, even though it's still actually here. 418 } 419 420 if (flags & kCGDisplayUnMirrorFlag) { 421 flags |= kCGDisplayAddFlag; // treat this like a new display arriving, even though it was here all along. 422 } 423 424 if ((flags & kCGDisplayAddFlag) && (flags & kCGDisplayRemoveFlag)) { 425 // 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. 426 // Mirrored things are always removed, since they don't represent a discrete display in this state. 427 if (((flags & kCGDisplayMirrorFlag) == 0) && (CGDisplayPixelsWide(displayid) > 1)) { 428 // Final state is connected 429 flags &= ~kCGDisplayRemoveFlag; 430 } else { 431 // Final state is disconnected 432 flags &= ~kCGDisplayAddFlag; 433 } 434 } 435 436 if (flags & kCGDisplayAddFlag) { 437 if (!display) { 438 if (!Cocoa_AddDisplay(displayid, true)) { 439 return; // oh well. 440 } 441 display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid); 442 SDL_assert(display != NULL); 443 } 444 } 445 446 if (flags & kCGDisplayRemoveFlag) { 447 if (display) { 448 SDL_DelVideoDisplay(display->id, true); 449 display = NULL; 450 } 451 } 452 453 if (flags & kCGDisplaySetModeFlag) { 454 if (display) { 455 CGDisplayModeRef moderef = CGDisplayCopyDisplayMode(displayid); 456 if (moderef) { 457 CVDisplayLinkRef link = NULL; 458 CVDisplayLinkCreateWithCGDisplay(displayid, &link); 459 if (link) { 460 SDL_DisplayMode mode; 461 if (GetDisplayMode(moderef, true, NULL, link, &mode)) { 462 SDL_SetDesktopDisplayMode(display, &mode); 463 } 464 CVDisplayLinkRelease(link); 465 } 466 CGDisplayModeRelease(moderef); 467 } 468 } 469 } 470 471 if (flags & kCGDisplaySetMainFlag) { 472 if (display) { 473 for (int i = 0; i < _this->num_displays; i++) { 474 if (_this->displays[i] == display) { 475 if (i > 0) { 476 // move this display to the front of _this->displays so it's treated as primary. 477 SDL_memmove(&_this->displays[1], &_this->displays[0], sizeof (*_this->displays) * i); 478 _this->displays[0] = display; 479 } 480 flags |= kCGDisplayMovedFlag; // we don't have an SDL event atm for "this display became primary," so at least let everyone know it "moved". 481 break; 482 } 483 } 484 } 485 } 486 487 if (flags & kCGDisplayMovedFlag) { 488 if (display) { 489 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_MOVED, 0, 0); 490 } 491 } 492 493 if (flags & kCGDisplayDesktopShapeChangedFlag) { 494 SDL_UpdateDesktopBounds(); 495 } 496} 497 498void Cocoa_InitModes(SDL_VideoDevice *_this) 499{ 500 @autoreleasepool { 501 CGDisplayErr result; 502 CGDisplayCount numDisplays = 0; 503 504 result = CGGetOnlineDisplayList(0, NULL, &numDisplays); 505 if (result != kCGErrorSuccess) { 506 CG_SetError("CGGetOnlineDisplayList()", result); 507 return; 508 } 509 510 bool isstack; 511 CGDirectDisplayID *displays = SDL_small_alloc(CGDirectDisplayID, numDisplays, &isstack); 512 513 result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays); 514 if (result != kCGErrorSuccess) { 515 CG_SetError("CGGetOnlineDisplayList()", result); 516 SDL_small_free(displays, isstack); 517 return; 518 } 519 520 // future updates to the display graph will come through this callback. 521 CGDisplayRegisterReconfigurationCallback(Cocoa_DisplayReconfigurationCallback, _this); 522 523 // Pick up the primary display in the first pass, then get the rest 524 for (int pass = 0; pass < 2; ++pass) { 525 for (int i = 0; i < numDisplays; ++i) { 526 if (pass == 0) { 527 if (!CGDisplayIsMain(displays[i])) { 528 continue; 529 } 530 } else { 531 if (CGDisplayIsMain(displays[i])) { 532 continue; 533 } 534 } 535 536 if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) { 537 continue; 538 } 539 540 Cocoa_AddDisplay(displays[i], false); 541 } 542 } 543 SDL_small_free(displays, isstack); 544 } 545} 546 547void Cocoa_UpdateDisplays(SDL_VideoDevice *_this) 548{ 549 SDL_HDROutputProperties HDR; 550 int i; 551 552 for (i = 0; i < _this->num_displays; ++i) { 553 SDL_VideoDisplay *display = _this->displays[i]; 554 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 555 556 Cocoa_GetHDRProperties(displaydata->display, &HDR); 557 SDL_SetDisplayHDRProperties(display, &HDR); 558 559 SDL_Rect rect; 560 if (Cocoa_GetUsableBounds(displaydata->display, &rect) && 561 SDL_memcmp(&displaydata->usable_bounds, &rect, sizeof(rect)) != 0) { 562 SDL_memcpy(&displaydata->usable_bounds, &rect, sizeof(rect)); 563 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_USABLE_BOUNDS_CHANGED, 0, 0); 564 } 565 } 566} 567 568bool Cocoa_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) 569{ 570 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 571 CGRect cgrect; 572 573 cgrect = CGDisplayBounds(displaydata->display); 574 rect->x = (int)cgrect.origin.x; 575 rect->y = (int)cgrect.origin.y; 576 rect->w = (int)cgrect.size.width; 577 rect->h = (int)cgrect.size.height; 578 return true; 579} 580 581bool Cocoa_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect) 582{ 583 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 584 585 SDL_memcpy(rect, &displaydata->usable_bounds, sizeof(*rect)); 586 return true; 587} 588 589bool Cocoa_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display) 590{ 591 SDL_DisplayData *data = (SDL_DisplayData *)display->internal; 592 CVDisplayLinkRef link = NULL; 593 CFArrayRef modes; 594 CFDictionaryRef dict = NULL; 595 const CFStringRef dictkeys[] = { kCGDisplayShowDuplicateLowResolutionModes }; 596 const CFBooleanRef dictvalues[] = { kCFBooleanTrue }; 597 598 CVDisplayLinkCreateWithCGDisplay(data->display, &link); 599 600 /* By default, CGDisplayCopyAllDisplayModes will only get a subset of the 601 * system's available modes. For example on a 15" 2016 MBP, users can 602 * choose 1920x1080@2x in System Preferences but it won't show up here, 603 * unless we specify the option below. 604 * The display modes returned by CGDisplayCopyAllDisplayModes are also not 605 * high dpi-capable unless this option is set. 606 * macOS 10.15 also seems to have a bug where entering, exiting, and 607 * re-entering exclusive fullscreen with a low dpi display mode can cause 608 * the content of the screen to move up, which this setting avoids: 609 * https://bugzilla.libsdl.org/show_bug.cgi?id=4822 610 */ 611 612 dict = CFDictionaryCreate(NULL, 613 (const void **)dictkeys, 614 (const void **)dictvalues, 615 1, 616 &kCFCopyStringDictionaryKeyCallBacks, 617 &kCFTypeDictionaryValueCallBacks); 618 619 modes = CGDisplayCopyAllDisplayModes(data->display, dict); 620 621 if (dict) { 622 CFRelease(dict); 623 } 624 625 if (modes) { 626 CFIndex i; 627 const CFIndex count = CFArrayGetCount(modes); 628 629 for (i = 0; i < count; i++) { 630 CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i); 631 SDL_DisplayMode mode; 632 633 if (GetDisplayMode(moderef, false, modes, link, &mode)) { 634 if (!SDL_AddFullscreenDisplayMode(display, &mode)) { 635 CFRelease(mode.internal->modes); 636 SDL_free(mode.internal); 637 } 638 } 639 } 640 641 CFRelease(modes); 642 } 643 644 CVDisplayLinkRelease(link); 645 return true; 646} 647 648static CGError SetDisplayModeForDisplay(CGDirectDisplayID display, SDL_DisplayModeData *data) 649{ 650 /* SDL_DisplayModeData can contain multiple CGDisplayModes to try (with 651 * identical properties), some of which might not work. See GetDisplayMode. 652 */ 653 CGError result = kCGErrorFailure; 654 for (CFIndex i = 0; i < CFArrayGetCount(data->modes); i++) { 655 CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(data->modes, i); 656 result = CGDisplaySetDisplayMode(display, moderef, NULL); 657 if (result == kCGErrorSuccess) { 658 // If this mode works, try it first next time. 659 if (i > 0) { 660 CFArrayExchangeValuesAtIndices(data->modes, i, 0); 661 } 662 break; 663 } 664 } 665 return result; 666} 667 668bool Cocoa_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode) 669{ 670 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 671 SDL_DisplayModeData *data = mode->internal; 672 CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken; 673 CGError result = kCGErrorSuccess; 674 675 b_inModeTransition = true; 676 677 // Fade to black to hide resolution-switching flicker 678 if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) { 679 CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE); 680 } 681 682 if (data == display->desktop_mode.internal) { 683 // Restoring desktop mode 684 SetDisplayModeForDisplay(displaydata->display, data); 685 } else { 686 // Do the physical switch 687 result = SetDisplayModeForDisplay(displaydata->display, data); 688 } 689 690 // Fade in again (asynchronously) 691 if (fade_token != kCGDisplayFadeReservationInvalidToken) { 692 CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE); 693 CGReleaseDisplayFadeReservation(fade_token); 694 } 695 696 b_inModeTransition = false; 697 698 if (result != kCGErrorSuccess) { 699 return CG_SetError("CGDisplaySwitchToMode()", result); 700 } 701 return true; 702} 703 704void Cocoa_QuitModes(SDL_VideoDevice *_this) 705{ 706 int i, j; 707 708 CGDisplayRemoveReconfigurationCallback(Cocoa_DisplayReconfigurationCallback, _this); 709 710 for (i = 0; i < _this->num_displays; ++i) { 711 SDL_VideoDisplay *display = _this->displays[i]; 712 SDL_DisplayModeData *mode; 713 714 if (display->current_mode->internal != display->desktop_mode.internal) { 715 Cocoa_SetDisplayMode(_this, display, &display->desktop_mode); 716 } 717 718 mode = display->desktop_mode.internal; 719 CFRelease(mode->modes); 720 721 for (j = 0; j < display->num_fullscreen_modes; j++) { 722 mode = display->fullscreen_modes[j].internal; 723 CFRelease(mode->modes); 724 } 725 } 726} 727 728#endif // SDL_VIDEO_DRIVER_COCOA 729[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.