sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_cocoamodes.m (24980B)


      1 /*
      2   Simple DirectMedia Layer
      3   Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org>
      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 #if SDL_VIDEO_DRIVER_COCOA
     24 
     25 #include "SDL_cocoavideo.h"
     26 
     27 /* We need this for IODisplayCreateInfoDictionary and kIODisplayOnlyPreferredName */
     28 #include <IOKit/graphics/IOGraphicsLib.h>
     29 
     30 /* We need this for CVDisplayLinkGetNominalOutputVideoRefreshPeriod */
     31 #include <CoreVideo/CVBase.h>
     32 #include <CoreVideo/CVDisplayLink.h>
     33 
     34 /* we need this for ShowMenuBar() and HideMenuBar(). */
     35 #include <Carbon/Carbon.h>
     36 
     37 /* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */
     38 #include <AvailabilityMacros.h>
     39 
     40 #ifndef MAC_OS_X_VERSION_10_13
     41 #define NSAppKitVersionNumber10_12 1504
     42 #endif
     43 
     44 
     45 static void
     46 Cocoa_ToggleMenuBar(const BOOL show)
     47 {
     48     /* !!! FIXME: keep an eye on this.
     49      * ShowMenuBar/HideMenuBar is officially unavailable for 64-bit binaries.
     50      *  It happens to work, as of 10.7, but we're going to see if
     51      *  we can just simply do without it on newer OSes...
     52      */
     53 #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070) && !defined(__LP64__)
     54     if (show) {
     55         ShowMenuBar();
     56     } else {
     57         HideMenuBar();
     58     }
     59 #endif
     60 }
     61 
     62 static int
     63 CG_SetError(const char *prefix, CGDisplayErr result)
     64 {
     65     const char *error;
     66 
     67     switch (result) {
     68     case kCGErrorFailure:
     69         error = "kCGErrorFailure";
     70         break;
     71     case kCGErrorIllegalArgument:
     72         error = "kCGErrorIllegalArgument";
     73         break;
     74     case kCGErrorInvalidConnection:
     75         error = "kCGErrorInvalidConnection";
     76         break;
     77     case kCGErrorInvalidContext:
     78         error = "kCGErrorInvalidContext";
     79         break;
     80     case kCGErrorCannotComplete:
     81         error = "kCGErrorCannotComplete";
     82         break;
     83     case kCGErrorNotImplemented:
     84         error = "kCGErrorNotImplemented";
     85         break;
     86     case kCGErrorRangeCheck:
     87         error = "kCGErrorRangeCheck";
     88         break;
     89     case kCGErrorTypeCheck:
     90         error = "kCGErrorTypeCheck";
     91         break;
     92     case kCGErrorInvalidOperation:
     93         error = "kCGErrorInvalidOperation";
     94         break;
     95     case kCGErrorNoneAvailable:
     96         error = "kCGErrorNoneAvailable";
     97         break;
     98     default:
     99         error = "Unknown Error";
    100         break;
    101     }
    102     return SDL_SetError("%s: %s", prefix, error);
    103 }
    104 
    105 static int
    106 GetDisplayModeRefreshRate(CGDisplayModeRef vidmode, CVDisplayLinkRef link)
    107 {
    108     int refreshRate = (int) (CGDisplayModeGetRefreshRate(vidmode) + 0.5);
    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 = (int) ((time.timeScale / (double) time.timeValue) + 0.5);
    115         }
    116     }
    117 
    118     return refreshRate;
    119 }
    120 
    121 static SDL_bool
    122 HasValidDisplayModeFlags(CGDisplayModeRef vidmode)
    123 {
    124     uint32_t ioflags = CGDisplayModeGetIOFlags(vidmode);
    125 
    126     /* Filter out modes which have flags that we don't want. */
    127     if (ioflags & (kDisplayModeNeverShowFlag | kDisplayModeNotGraphicsQualityFlag)) {
    128         return SDL_FALSE;
    129     }
    130 
    131     /* Filter out modes which don't have flags that we want. */
    132     if (!(ioflags & kDisplayModeValidFlag) || !(ioflags & kDisplayModeSafeFlag)) {
    133         return SDL_FALSE;
    134     }
    135 
    136     return SDL_TRUE;
    137 }
    138 
    139 static Uint32
    140 GetDisplayModePixelFormat(CGDisplayModeRef vidmode)
    141 {
    142     /* This API is deprecated in 10.11 with no good replacement (as of 10.15). */
    143     CFStringRef fmt = CGDisplayModeCopyPixelEncoding(vidmode);
    144     Uint32 pixelformat = SDL_PIXELFORMAT_UNKNOWN;
    145 
    146     if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels),
    147                         kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
    148         pixelformat = SDL_PIXELFORMAT_ARGB8888;
    149     } else if (CFStringCompare(fmt, CFSTR(IO16BitDirectPixels),
    150                         kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
    151         pixelformat = SDL_PIXELFORMAT_ARGB1555;
    152     } else if (CFStringCompare(fmt, CFSTR(kIO30BitDirectPixels),
    153                         kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
    154         pixelformat = SDL_PIXELFORMAT_ARGB2101010;
    155     } else {
    156         /* ignore 8-bit and such for now. */
    157     }
    158 
    159     CFRelease(fmt);
    160 
    161     return pixelformat;
    162 }
    163 
    164 static SDL_bool
    165 GetDisplayMode(_THIS, CGDisplayModeRef vidmode, CFArrayRef modelist, CVDisplayLinkRef link, SDL_DisplayMode *mode)
    166 {
    167     SDL_DisplayModeData *data;
    168     bool usableForGUI = CGDisplayModeIsUsableForDesktopGUI(vidmode);
    169     int width = (int) CGDisplayModeGetWidth(vidmode);
    170     int height = (int) CGDisplayModeGetHeight(vidmode);
    171     uint32_t ioflags = CGDisplayModeGetIOFlags(vidmode);
    172     int refreshrate = GetDisplayModeRefreshRate(vidmode, link);
    173     Uint32 format = GetDisplayModePixelFormat(vidmode);
    174     bool interlaced = (ioflags & kDisplayModeInterlacedFlag) != 0;
    175     CFMutableArrayRef modes;
    176 
    177     if (format == SDL_PIXELFORMAT_UNKNOWN) {
    178         return SDL_FALSE;
    179     }
    180 
    181     if (!HasValidDisplayModeFlags(vidmode)) {
    182         return SDL_FALSE;
    183     }
    184 
    185     modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    186     CFArrayAppendValue(modes, vidmode);
    187 
    188     /* If a list of possible diplay modes is passed in, use it to filter out
    189      * modes that have duplicate sizes. We don't just rely on SDL's higher level
    190      * duplicate filtering because this code can choose what properties are
    191      * prefered, and it can add CGDisplayModes to the DisplayModeData's list of
    192      * modes to try (see comment below for why that's necessary).
    193      * CGDisplayModeGetPixelWidth and friends are only available in 10.8+. */
    194 #ifdef MAC_OS_X_VERSION_10_8
    195     if (modelist != NULL && floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) {
    196         int pixelW = (int) CGDisplayModeGetPixelWidth(vidmode);
    197         int pixelH = (int) CGDisplayModeGetPixelHeight(vidmode);
    198 
    199         CFIndex modescount = CFArrayGetCount(modelist);
    200         int  i;
    201 
    202         for (i = 0; i < modescount; i++) {
    203             CGDisplayModeRef othermode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modelist, i);
    204             uint32_t otherioflags = CGDisplayModeGetIOFlags(othermode);
    205 
    206             if (CFEqual(vidmode, othermode)) {
    207                 continue;
    208             }
    209 
    210             if (!HasValidDisplayModeFlags(othermode)) {
    211                 continue;
    212             }
    213 
    214             int otherW = (int) CGDisplayModeGetWidth(othermode);
    215             int otherH = (int) CGDisplayModeGetHeight(othermode);
    216             int otherpixelW = (int) CGDisplayModeGetPixelWidth(othermode);
    217             int otherpixelH = (int) CGDisplayModeGetPixelHeight(othermode);
    218             int otherrefresh = GetDisplayModeRefreshRate(othermode, link);
    219             Uint32 otherformat = GetDisplayModePixelFormat(othermode);
    220             bool otherGUI = CGDisplayModeIsUsableForDesktopGUI(othermode);
    221 
    222             /* Ignore this mode if it's low-dpi (@1x) and we have a high-dpi
    223              * mode in the list with the same size in points.
    224              */
    225             if (width == pixelW && height == pixelH
    226                 && width == otherW && height == otherH
    227                 && refreshrate == otherrefresh && format == otherformat
    228                 && (otherpixelW != otherW || otherpixelH != otherH)) {
    229                 CFRelease(modes);
    230                 return SDL_FALSE;
    231             }
    232 
    233             /* Ignore this mode if it's interlaced and there's a non-interlaced
    234              * mode in the list with the same properties.
    235              */
    236             if (interlaced && ((otherioflags & kDisplayModeInterlacedFlag) == 0)
    237                 && width == otherW && height == otherH && pixelW == otherpixelW
    238                 && pixelH == otherpixelH && refreshrate == otherrefresh
    239                 && format == otherformat && usableForGUI == otherGUI) {
    240                 CFRelease(modes);
    241                 return SDL_FALSE;
    242             }
    243 
    244             /* Ignore this mode if it's not usable for desktop UI and its
    245              * properties are equal to another GUI-capable mode in the list.
    246              */
    247             if (width == otherW && height == otherH && pixelW == otherpixelW
    248                 && pixelH == otherpixelH && !usableForGUI && otherGUI
    249                 && refreshrate == otherrefresh && format == otherformat) {
    250                 CFRelease(modes);
    251                 return SDL_FALSE;
    252             }
    253 
    254             /* If multiple modes have the exact same properties, they'll all
    255              * go in the list of modes to try when SetDisplayMode is called.
    256              * This is needed because kCGDisplayShowDuplicateLowResolutionModes
    257              * (which is used to expose highdpi display modes) can make the
    258              * list of modes contain duplicates (according to their properties
    259              * obtained via public APIs) which don't work with SetDisplayMode.
    260              * Those duplicate non-functional modes *do* have different pixel
    261              * formats according to their internal data structure viewed with
    262              * NSLog, but currently no public API can detect that.
    263              * https://bugzilla.libsdl.org/show_bug.cgi?id=4822
    264              *
    265              * As of macOS 10.15.0, those duplicates have the exact same
    266              * properties via public APIs in every way (even their IO flags and
    267              * CGDisplayModeGetIODisplayModeID is the same), so we could test
    268              * those for equality here too, but I'm intentionally not doing that
    269              * in case there are duplicate modes with different IO flags or IO
    270              * display mode IDs in the future. In that case I think it's better
    271              * to try them all in SetDisplayMode than to risk one of them being
    272              * correct but it being filtered out by SDL_AddDisplayMode as being
    273              * a duplicate.
    274              */
    275             if (width == otherW && height == otherH && pixelW == otherpixelW
    276                 && pixelH == otherpixelH && usableForGUI == otherGUI
    277                 && refreshrate == otherrefresh && format == otherformat) {
    278                 CFArrayAppendValue(modes, othermode);
    279             }
    280         }
    281     }
    282 #endif
    283 
    284     data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data));
    285     if (!data) {
    286         CFRelease(modes);
    287         return SDL_FALSE;
    288     }
    289     data->modes = modes;
    290     mode->format = format;
    291     mode->w = width;
    292     mode->h = height;
    293     mode->refresh_rate = refreshrate;
    294     mode->driverdata = data;
    295     return SDL_TRUE;
    296 }
    297 
    298 static const char *
    299 Cocoa_GetDisplayName(CGDirectDisplayID displayID)
    300 {
    301     /* This API is deprecated in 10.9 with no good replacement (as of 10.15). */
    302     io_service_t servicePort = CGDisplayIOServicePort(displayID);
    303     CFDictionaryRef deviceInfo = IODisplayCreateInfoDictionary(servicePort, kIODisplayOnlyPreferredName);
    304     NSDictionary *localizedNames = [(NSDictionary *)deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
    305     const char* displayName = NULL;
    306 
    307     if ([localizedNames count] > 0) {
    308         displayName = SDL_strdup([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]);
    309     }
    310     CFRelease(deviceInfo);
    311     return displayName;
    312 }
    313 
    314 void
    315 Cocoa_InitModes(_THIS)
    316 { @autoreleasepool
    317 {
    318     CGDisplayErr result;
    319     CGDirectDisplayID *displays;
    320     CGDisplayCount numDisplays;
    321     SDL_bool isstack;
    322     int pass, i;
    323 
    324     result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
    325     if (result != kCGErrorSuccess) {
    326         CG_SetError("CGGetOnlineDisplayList()", result);
    327         return;
    328     }
    329     displays = SDL_small_alloc(CGDirectDisplayID, numDisplays, &isstack);
    330     result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
    331     if (result != kCGErrorSuccess) {
    332         CG_SetError("CGGetOnlineDisplayList()", result);
    333         SDL_small_free(displays, isstack);
    334         return;
    335     }
    336 
    337     /* Pick up the primary display in the first pass, then get the rest */
    338     for (pass = 0; pass < 2; ++pass) {
    339         for (i = 0; i < numDisplays; ++i) {
    340             SDL_VideoDisplay display;
    341             SDL_DisplayData *displaydata;
    342             SDL_DisplayMode mode;
    343             CGDisplayModeRef moderef = NULL;
    344             CVDisplayLinkRef link = NULL;
    345 
    346             if (pass == 0) {
    347                 if (!CGDisplayIsMain(displays[i])) {
    348                     continue;
    349                 }
    350             } else {
    351                 if (CGDisplayIsMain(displays[i])) {
    352                     continue;
    353                 }
    354             }
    355 
    356             if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) {
    357                 continue;
    358             }
    359 
    360             moderef = CGDisplayCopyDisplayMode(displays[i]);
    361 
    362             if (!moderef) {
    363                 continue;
    364             }
    365 
    366             displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
    367             if (!displaydata) {
    368                 CGDisplayModeRelease(moderef);
    369                 continue;
    370             }
    371             displaydata->display = displays[i];
    372 
    373             CVDisplayLinkCreateWithCGDisplay(displays[i], &link);
    374 
    375             SDL_zero(display);
    376             /* this returns a stddup'ed string */
    377             display.name = (char *)Cocoa_GetDisplayName(displays[i]);
    378             if (!GetDisplayMode(_this, moderef, NULL, link, &mode)) {
    379                 CVDisplayLinkRelease(link);
    380                 CGDisplayModeRelease(moderef);
    381                 SDL_free(display.name);
    382                 SDL_free(displaydata);
    383                 continue;
    384             }
    385 
    386             CVDisplayLinkRelease(link);
    387             CGDisplayModeRelease(moderef);
    388 
    389             display.desktop_mode = mode;
    390             display.current_mode = mode;
    391             display.driverdata = displaydata;
    392             SDL_AddVideoDisplay(&display, SDL_FALSE);
    393             SDL_free(display.name);
    394         }
    395     }
    396     SDL_small_free(displays, isstack);
    397 }}
    398 
    399 int
    400 Cocoa_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
    401 {
    402     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
    403     CGRect cgrect;
    404 
    405     cgrect = CGDisplayBounds(displaydata->display);
    406     rect->x = (int)cgrect.origin.x;
    407     rect->y = (int)cgrect.origin.y;
    408     rect->w = (int)cgrect.size.width;
    409     rect->h = (int)cgrect.size.height;
    410     return 0;
    411 }
    412 
    413 int
    414 Cocoa_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
    415 {
    416     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
    417     const CGDirectDisplayID cgdisplay = displaydata->display;
    418     NSArray *screens = [NSScreen screens];
    419     NSScreen *screen = nil;
    420 
    421     /* !!! FIXME: maybe track the NSScreen in SDL_DisplayData? */
    422     for (NSScreen *i in screens) {
    423         const CGDirectDisplayID thisDisplay = (CGDirectDisplayID) [[[i deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
    424         if (thisDisplay == cgdisplay) {
    425             screen = i;
    426             break;
    427         }
    428     }
    429 
    430     SDL_assert(screen != nil);  /* didn't find it?! */
    431     if (screen == nil) {
    432         return -1;
    433     }
    434 
    435     const NSRect frame = [screen visibleFrame];
    436     rect->x = (int)frame.origin.x;
    437     rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height);
    438     rect->w = (int)frame.size.width;
    439     rect->h = (int)frame.size.height;
    440 
    441     return 0;
    442 }
    443 
    444 int
    445 Cocoa_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi)
    446 { @autoreleasepool
    447 {
    448     const float MM_IN_INCH = 25.4f;
    449 
    450     SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
    451 
    452     /* we need the backingScaleFactor for Retina displays, which is only exposed through NSScreen, not CGDisplay, afaik, so find our screen... */
    453     CGFloat scaleFactor = 1.0f;
    454     NSArray *screens = [NSScreen screens];
    455     for (NSScreen *screen in screens) {
    456         const CGDirectDisplayID dpyid = (const CGDirectDisplayID ) [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
    457         if (dpyid == data->display) {
    458             if ([screen respondsToSelector:@selector(backingScaleFactor)]) {  // Mac OS X 10.7 and later
    459                 scaleFactor = [screen backingScaleFactor];
    460                 break;
    461             }
    462         }
    463     }
    464 
    465     const CGSize displaySize = CGDisplayScreenSize(data->display);
    466     const int pixelWidth =  (int) CGDisplayPixelsWide(data->display);
    467     const int pixelHeight = (int) CGDisplayPixelsHigh(data->display);
    468 
    469     if (ddpi) {
    470         *ddpi = (SDL_ComputeDiagonalDPI(pixelWidth, pixelHeight, displaySize.width / MM_IN_INCH, displaySize.height / MM_IN_INCH)) * scaleFactor;
    471     }
    472     if (hdpi) {
    473         *hdpi = (pixelWidth * MM_IN_INCH / displaySize.width) * scaleFactor;
    474     }
    475     if (vdpi) {
    476         *vdpi = (pixelHeight * MM_IN_INCH / displaySize.height) * scaleFactor;
    477     }
    478 
    479     return 0;
    480 }}
    481 
    482 void
    483 Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
    484 {
    485     SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
    486     CVDisplayLinkRef link = NULL;
    487     CGDisplayModeRef desktopmoderef;
    488     SDL_DisplayMode desktopmode;
    489     CFArrayRef modes;
    490     CFDictionaryRef dict = NULL;
    491 
    492     CVDisplayLinkCreateWithCGDisplay(data->display, &link);
    493 
    494     desktopmoderef = CGDisplayCopyDisplayMode(data->display);
    495 
    496     /* CopyAllDisplayModes won't always contain the desktop display mode (if
    497      * NULL is passed in) - for example on a retina 15" MBP, System Preferences
    498      * allows choosing 1920x1200 but it's not in the list. AddDisplayMode makes
    499      * sure there are no duplicates so it's safe to always add the desktop mode
    500      * even in cases where it is in the CopyAllDisplayModes list.
    501      */
    502     if (desktopmoderef && GetDisplayMode(_this, desktopmoderef, NULL, link, &desktopmode)) {
    503         if (!SDL_AddDisplayMode(display, &desktopmode)) {
    504             CFRelease(((SDL_DisplayModeData*)desktopmode.driverdata)->modes);
    505             SDL_free(desktopmode.driverdata);
    506         }
    507     }
    508 
    509     CGDisplayModeRelease(desktopmoderef);
    510 
    511     /* By default, CGDisplayCopyAllDisplayModes will only get a subset of the
    512      * system's available modes. For example on a 15" 2016 MBP, users can
    513      * choose 1920x1080@2x in System Preferences but it won't show up here,
    514      * unless we specify the option below.
    515      * The display modes returned by CGDisplayCopyAllDisplayModes are also not
    516      * high dpi-capable unless this option is set.
    517      * macOS 10.15 also seems to have a bug where entering, exiting, and
    518      * re-entering exclusive fullscreen with a low dpi display mode can cause
    519      * the content of the screen to move up, which this setting avoids:
    520      * https://bugzilla.libsdl.org/show_bug.cgi?id=4822
    521      */
    522 #ifdef MAC_OS_X_VERSION_10_8
    523     if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) {
    524         const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
    525         const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
    526         dict = CFDictionaryCreate(NULL,
    527                                   (const void **)dictkeys,
    528                                   (const void **)dictvalues,
    529                                   1,
    530                                   &kCFCopyStringDictionaryKeyCallBacks,
    531                                   &kCFTypeDictionaryValueCallBacks);
    532     }
    533 #endif
    534 
    535     modes = CGDisplayCopyAllDisplayModes(data->display, dict);
    536 
    537     if (dict) {
    538         CFRelease(dict);
    539     }
    540 
    541     if (modes) {
    542         CFIndex i;
    543         const CFIndex count = CFArrayGetCount(modes);
    544 
    545         for (i = 0; i < count; i++) {
    546             CGDisplayModeRef moderef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
    547             SDL_DisplayMode mode;
    548 
    549             if (GetDisplayMode(_this, moderef, modes, link, &mode)) {
    550                 if (!SDL_AddDisplayMode(display, &mode)) {
    551                     CFRelease(((SDL_DisplayModeData*)mode.driverdata)->modes);
    552                     SDL_free(mode.driverdata);
    553                 }
    554             }
    555         }
    556 
    557         CFRelease(modes);
    558     }
    559 
    560     CVDisplayLinkRelease(link);
    561 }
    562 
    563 static CGError
    564 SetDisplayModeForDisplay(CGDirectDisplayID display, SDL_DisplayModeData *data)
    565 {
    566     /* SDL_DisplayModeData can contain multiple CGDisplayModes to try (with
    567      * identical properties), some of which might not work. See GetDisplayMode.
    568      */
    569     CGError result = kCGErrorFailure;
    570     for (CFIndex i = 0; i < CFArrayGetCount(data->modes); i++) {
    571         CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(data->modes, i);
    572         result = CGDisplaySetDisplayMode(display, moderef, NULL);
    573         if (result == kCGErrorSuccess) {
    574             /* If this mode works, try it first next time. */
    575             CFArrayExchangeValuesAtIndices(data->modes, i, 0);
    576             break;
    577         }
    578     }
    579     return result;
    580 }
    581 
    582 int
    583 Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
    584 {
    585     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
    586     SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata;
    587     CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
    588     CGError result;
    589 
    590     /* Fade to black to hide resolution-switching flicker */
    591     if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
    592         CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
    593     }
    594 
    595     if (data == display->desktop_mode.driverdata) {
    596         /* Restoring desktop mode */
    597         SetDisplayModeForDisplay(displaydata->display, data);
    598 
    599         if (CGDisplayIsMain(displaydata->display)) {
    600             CGReleaseAllDisplays();
    601         } else {
    602             CGDisplayRelease(displaydata->display);
    603         }
    604 
    605         if (CGDisplayIsMain(displaydata->display)) {
    606             Cocoa_ToggleMenuBar(YES);
    607         }
    608     } else {
    609         /* Put up the blanking window (a window above all other windows) */
    610         if (CGDisplayIsMain(displaydata->display)) {
    611             /* If we don't capture all displays, Cocoa tries to rearrange windows... *sigh* */
    612             result = CGCaptureAllDisplays();
    613         } else {
    614             result = CGDisplayCapture(displaydata->display);
    615         }
    616         if (result != kCGErrorSuccess) {
    617             CG_SetError("CGDisplayCapture()", result);
    618             goto ERR_NO_CAPTURE;
    619         }
    620 
    621         /* Do the physical switch */
    622         result =  SetDisplayModeForDisplay(displaydata->display, data);
    623         if (result != kCGErrorSuccess) {
    624             CG_SetError("CGDisplaySwitchToMode()", result);
    625             goto ERR_NO_SWITCH;
    626         }
    627 
    628         /* Hide the menu bar so it doesn't intercept events */
    629         if (CGDisplayIsMain(displaydata->display)) {
    630             Cocoa_ToggleMenuBar(NO);
    631         }
    632     }
    633 
    634     /* Fade in again (asynchronously) */
    635     if (fade_token != kCGDisplayFadeReservationInvalidToken) {
    636         CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
    637         CGReleaseDisplayFadeReservation(fade_token);
    638     }
    639 
    640     return 0;
    641 
    642     /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
    643 ERR_NO_SWITCH:
    644     if (CGDisplayIsMain(displaydata->display)) {
    645         CGReleaseAllDisplays();
    646     } else {
    647         CGDisplayRelease(displaydata->display);
    648     }
    649 ERR_NO_CAPTURE:
    650     if (fade_token != kCGDisplayFadeReservationInvalidToken) {
    651         CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
    652         CGReleaseDisplayFadeReservation(fade_token);
    653     }
    654     return -1;
    655 }
    656 
    657 void
    658 Cocoa_QuitModes(_THIS)
    659 {
    660     int i, j;
    661 
    662     for (i = 0; i < _this->num_displays; ++i) {
    663         SDL_VideoDisplay *display = &_this->displays[i];
    664         SDL_DisplayModeData *mode;
    665 
    666         if (display->current_mode.driverdata != display->desktop_mode.driverdata) {
    667             Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
    668         }
    669 
    670         mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata;
    671         CFRelease(mode->modes);
    672 
    673         for (j = 0; j < display->num_display_modes; j++) {
    674             mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata;
    675             CFRelease(mode->modes);
    676         }
    677     }
    678     Cocoa_ToggleMenuBar(YES);
    679 }
    680 
    681 #endif /* SDL_VIDEO_DRIVER_COCOA */
    682 
    683 /* vi: set ts=4 sw=4 expandtab: */