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