src/video/cocoa/SDL_cocoamodes.m
author Ryan C. Gordon <icculus@icculus.org>
Mon, 04 Jan 2016 23:52:40 -0500
changeset 10019 36f7e8084508
parent 9998 f67cf37e9cd4
child 10038 2bab6f8d403e
permissions -rw-r--r--
Added SDL_GetDisplayUsableBounds().
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2016 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 #include "SDL_assert.h"
    23 
    24 #if SDL_VIDEO_DRIVER_COCOA
    25 
    26 #include "SDL_cocoavideo.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 /* we need this for ShowMenuBar() and HideMenuBar(). */
    36 #include <Carbon/Carbon.h>
    37 
    38 /* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */
    39 #include <AvailabilityMacros.h>
    40 
    41 
    42 static void
    43 Cocoa_ToggleMenuBar(const BOOL show)
    44 {
    45     /* !!! FIXME: keep an eye on this.
    46      * ShowMenuBar/HideMenuBar is officially unavailable for 64-bit binaries.
    47      *  It happens to work, as of 10.7, but we're going to see if
    48      *  we can just simply do without it on newer OSes...
    49      */
    50 #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070) && !defined(__LP64__)
    51     if (show) {
    52         ShowMenuBar();
    53     } else {
    54         HideMenuBar();
    55     }
    56 #endif
    57 }
    58 
    59 
    60 /* !!! FIXME: clean out the pre-10.6 code when it makes sense to do so. */
    61 #define FORCE_OLD_API 0
    62 
    63 #if FORCE_OLD_API
    64 #undef MAC_OS_X_VERSION_MIN_REQUIRED
    65 #define MAC_OS_X_VERSION_MIN_REQUIRED 1050
    66 #endif
    67 
    68 static BOOL
    69 IS_SNOW_LEOPARD_OR_LATER()
    70 {
    71 #if FORCE_OLD_API
    72     return NO;
    73 #else
    74     return floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5;
    75 #endif
    76 }
    77 
    78 static int
    79 CG_SetError(const char *prefix, CGDisplayErr result)
    80 {
    81     const char *error;
    82 
    83     switch (result) {
    84     case kCGErrorFailure:
    85         error = "kCGErrorFailure";
    86         break;
    87     case kCGErrorIllegalArgument:
    88         error = "kCGErrorIllegalArgument";
    89         break;
    90     case kCGErrorInvalidConnection:
    91         error = "kCGErrorInvalidConnection";
    92         break;
    93     case kCGErrorInvalidContext:
    94         error = "kCGErrorInvalidContext";
    95         break;
    96     case kCGErrorCannotComplete:
    97         error = "kCGErrorCannotComplete";
    98         break;
    99     case kCGErrorNotImplemented:
   100         error = "kCGErrorNotImplemented";
   101         break;
   102     case kCGErrorRangeCheck:
   103         error = "kCGErrorRangeCheck";
   104         break;
   105     case kCGErrorTypeCheck:
   106         error = "kCGErrorTypeCheck";
   107         break;
   108     case kCGErrorInvalidOperation:
   109         error = "kCGErrorInvalidOperation";
   110         break;
   111     case kCGErrorNoneAvailable:
   112         error = "kCGErrorNoneAvailable";
   113         break;
   114     default:
   115         error = "Unknown Error";
   116         break;
   117     }
   118     return SDL_SetError("%s: %s", prefix, error);
   119 }
   120 
   121 static SDL_bool
   122 GetDisplayMode(_THIS, const void *moderef, CVDisplayLinkRef link, SDL_DisplayMode *mode)
   123 {
   124     SDL_DisplayModeData *data;
   125     long width = 0;
   126     long height = 0;
   127     long bpp = 0;
   128     long refreshRate = 0;
   129 
   130     data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data));
   131     if (!data) {
   132         return SDL_FALSE;
   133     }
   134     data->moderef = moderef;
   135 
   136     if (IS_SNOW_LEOPARD_OR_LATER()) {
   137         CGDisplayModeRef vidmode = (CGDisplayModeRef) moderef;
   138         CFStringRef fmt = CGDisplayModeCopyPixelEncoding(vidmode);
   139         width = (long) CGDisplayModeGetWidth(vidmode);
   140         height = (long) CGDisplayModeGetHeight(vidmode);
   141         refreshRate = (long) (CGDisplayModeGetRefreshRate(vidmode) + 0.5);
   142 
   143         if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels),
   144                             kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
   145             bpp = 32;
   146         } else if (CFStringCompare(fmt, CFSTR(IO16BitDirectPixels),
   147                             kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
   148             bpp = 16;
   149         } else if (CFStringCompare(fmt, CFSTR(kIO30BitDirectPixels),
   150                             kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
   151             bpp = 30;
   152         } else {
   153             bpp = 0;  /* ignore 8-bit and such for now. */
   154         }
   155 
   156         CFRelease(fmt);
   157     }
   158 
   159     #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
   160     if (!IS_SNOW_LEOPARD_OR_LATER()) {
   161         CFNumberRef number;
   162         double refresh;
   163         CFDictionaryRef vidmode = (CFDictionaryRef) moderef;
   164         number = CFDictionaryGetValue(vidmode, kCGDisplayWidth);
   165         CFNumberGetValue(number, kCFNumberLongType, &width);
   166         number = CFDictionaryGetValue(vidmode, kCGDisplayHeight);
   167         CFNumberGetValue(number, kCFNumberLongType, &height);
   168         number = CFDictionaryGetValue(vidmode, kCGDisplayBitsPerPixel);
   169         CFNumberGetValue(number, kCFNumberLongType, &bpp);
   170         number = CFDictionaryGetValue(vidmode, kCGDisplayRefreshRate);
   171         CFNumberGetValue(number, kCFNumberDoubleType, &refresh);
   172         refreshRate = (long) (refresh + 0.5);
   173     }
   174     #endif
   175 
   176     /* CGDisplayModeGetRefreshRate returns 0 for many non-CRT displays. */
   177     if (refreshRate == 0 && link != NULL) {
   178         CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
   179         if ((time.flags & kCVTimeIsIndefinite) == 0 && time.timeValue != 0) {
   180             refreshRate = (long) ((time.timeScale / (double) time.timeValue) + 0.5);
   181         }
   182     }
   183 
   184     mode->format = SDL_PIXELFORMAT_UNKNOWN;
   185     switch (bpp) {
   186     case 16:
   187         mode->format = SDL_PIXELFORMAT_ARGB1555;
   188         break;
   189     case 30:
   190         mode->format = SDL_PIXELFORMAT_ARGB2101010;
   191         break;
   192     case 32:
   193         mode->format = SDL_PIXELFORMAT_ARGB8888;
   194         break;
   195     case 8: /* We don't support palettized modes now */
   196     default: /* Totally unrecognizable bit depth. */
   197         SDL_free(data);
   198         return SDL_FALSE;
   199     }
   200     mode->w = width;
   201     mode->h = height;
   202     mode->refresh_rate = refreshRate;
   203     mode->driverdata = data;
   204     return SDL_TRUE;
   205 }
   206 
   207 static void
   208 Cocoa_ReleaseDisplayMode(_THIS, const void *moderef)
   209 {
   210     if (IS_SNOW_LEOPARD_OR_LATER()) {
   211         CGDisplayModeRelease((CGDisplayModeRef) moderef);  /* NULL is ok */
   212     }
   213 }
   214 
   215 static void
   216 Cocoa_ReleaseDisplayModeList(_THIS, CFArrayRef modelist)
   217 {
   218     if (IS_SNOW_LEOPARD_OR_LATER()) {
   219         CFRelease(modelist);  /* NULL is ok */
   220     }
   221 }
   222 
   223 static const char *
   224 Cocoa_GetDisplayName(CGDirectDisplayID displayID)
   225 {
   226     CFDictionaryRef deviceInfo = IODisplayCreateInfoDictionary(CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName);
   227     NSDictionary *localizedNames = [(NSDictionary *)deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
   228     const char* displayName = NULL;
   229 
   230     if ([localizedNames count] > 0) {
   231         displayName = SDL_strdup([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]);
   232     }
   233     CFRelease(deviceInfo);
   234     return displayName;
   235 }
   236 
   237 void
   238 Cocoa_InitModes(_THIS)
   239 { @autoreleasepool
   240 {
   241     CGDisplayErr result;
   242     CGDirectDisplayID *displays;
   243     CGDisplayCount numDisplays;
   244     int pass, i;
   245 
   246     result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
   247     if (result != kCGErrorSuccess) {
   248         CG_SetError("CGGetOnlineDisplayList()", result);
   249         return;
   250     }
   251     displays = SDL_stack_alloc(CGDirectDisplayID, numDisplays);
   252     result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
   253     if (result != kCGErrorSuccess) {
   254         CG_SetError("CGGetOnlineDisplayList()", result);
   255         SDL_stack_free(displays);
   256         return;
   257     }
   258 
   259     /* Pick up the primary display in the first pass, then get the rest */
   260     for (pass = 0; pass < 2; ++pass) {
   261         for (i = 0; i < numDisplays; ++i) {
   262             SDL_VideoDisplay display;
   263             SDL_DisplayData *displaydata;
   264             SDL_DisplayMode mode;
   265             const void *moderef = NULL;
   266             CVDisplayLinkRef link = NULL;
   267 
   268             if (pass == 0) {
   269                 if (!CGDisplayIsMain(displays[i])) {
   270                     continue;
   271                 }
   272             } else {
   273                 if (CGDisplayIsMain(displays[i])) {
   274                     continue;
   275                 }
   276             }
   277 
   278             if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) {
   279                 continue;
   280             }
   281 
   282             if (IS_SNOW_LEOPARD_OR_LATER()) {
   283                 moderef = CGDisplayCopyDisplayMode(displays[i]);
   284             }
   285 
   286             #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
   287             if (!IS_SNOW_LEOPARD_OR_LATER()) {
   288                 moderef = CGDisplayCurrentMode(displays[i]);
   289             }
   290             #endif
   291 
   292             if (!moderef) {
   293                 continue;
   294             }
   295 
   296             displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
   297             if (!displaydata) {
   298                 Cocoa_ReleaseDisplayMode(_this, moderef);
   299                 continue;
   300             }
   301             displaydata->display = displays[i];
   302 
   303             CVDisplayLinkCreateWithCGDisplay(displays[i], &link);
   304 
   305             SDL_zero(display);
   306             /* this returns a stddup'ed string */
   307             display.name = (char *)Cocoa_GetDisplayName(displays[i]);
   308             if (!GetDisplayMode(_this, moderef, link, &mode)) {
   309                 CVDisplayLinkRelease(link);
   310                 Cocoa_ReleaseDisplayMode(_this, moderef);
   311                 SDL_free(display.name);
   312                 SDL_free(displaydata);
   313                 continue;
   314             }
   315 
   316             CVDisplayLinkRelease(link);
   317 
   318             display.desktop_mode = mode;
   319             display.current_mode = mode;
   320             display.driverdata = displaydata;
   321             SDL_AddVideoDisplay(&display);
   322             SDL_free(display.name);
   323         }
   324     }
   325     SDL_stack_free(displays);
   326 }}
   327 
   328 int
   329 Cocoa_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
   330 {
   331     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
   332     CGRect cgrect;
   333 
   334     cgrect = CGDisplayBounds(displaydata->display);
   335     rect->x = (int)cgrect.origin.x;
   336     rect->y = (int)cgrect.origin.y;
   337     rect->w = (int)cgrect.size.width;
   338     rect->h = (int)cgrect.size.height;
   339     return 0;
   340 }
   341 
   342 int
   343 Cocoa_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
   344 {
   345     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
   346     const CGDirectDisplayID cgdisplay = displaydata->display;
   347     NSArray *screens = [NSScreen screens];
   348     NSScreen *screen = nil;
   349 
   350     /* !!! FIXME: maybe track the NSScreen in SDL_DisplayData? */
   351     for (NSScreen *i in screens) {
   352         const CGDirectDisplayID thisDisplay = (CGDirectDisplayID) [[[i deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
   353         if (thisDisplay == cgdisplay) {
   354             screen = i;
   355             break;
   356         }
   357     }
   358 
   359     SDL_assert(screen != nil);  /* didn't find it?! */
   360     if (screen == nil) {
   361         return -1;
   362     }
   363 
   364     const CGRect cgrect = CGDisplayBounds(cgdisplay);
   365     const NSRect frame = [screen visibleFrame];
   366 
   367     // !!! FIXME: I assume -[NSScreen visibleFrame] is relative to the origin of the screen in question and not the whole desktop.
   368     // !!! FIXME: The math vs CGDisplayBounds might be incorrect if that's not the case, though. Check this.
   369     rect->x = (int)(cgrect.origin.x + frame.origin.x);
   370     rect->y = (int)(cgrect.origin.y + frame.origin.y);
   371     rect->w = (int)frame.size.width;
   372     rect->h = (int)frame.size.height;
   373 
   374     return 0;
   375 }
   376 
   377 void
   378 Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
   379 {
   380     SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
   381     CFArrayRef modes = NULL;
   382 
   383     if (IS_SNOW_LEOPARD_OR_LATER()) {
   384         modes = CGDisplayCopyAllDisplayModes(data->display, NULL);
   385     }
   386 
   387     #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
   388     if (!IS_SNOW_LEOPARD_OR_LATER()) {
   389         modes = CGDisplayAvailableModes(data->display);
   390     }
   391     #endif
   392 
   393     if (modes) {
   394         CVDisplayLinkRef link = NULL;
   395         const CFIndex count = CFArrayGetCount(modes);
   396         CFIndex i;
   397 
   398         CVDisplayLinkCreateWithCGDisplay(data->display, &link);
   399 
   400         for (i = 0; i < count; i++) {
   401             const void *moderef = CFArrayGetValueAtIndex(modes, i);
   402             SDL_DisplayMode mode;
   403             if (GetDisplayMode(_this, moderef, link, &mode)) {
   404                 if (IS_SNOW_LEOPARD_OR_LATER()) {
   405                     CGDisplayModeRetain((CGDisplayModeRef) moderef);
   406                 }
   407                 SDL_AddDisplayMode(display, &mode);
   408             }
   409         }
   410 
   411         CVDisplayLinkRelease(link);
   412         Cocoa_ReleaseDisplayModeList(_this, modes);
   413     }
   414 }
   415 
   416 static CGError
   417 Cocoa_SwitchMode(_THIS, CGDirectDisplayID display, const void *mode)
   418 {
   419     if (IS_SNOW_LEOPARD_OR_LATER()) {
   420         return CGDisplaySetDisplayMode(display, (CGDisplayModeRef) mode, NULL);
   421     }
   422  
   423     #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
   424     if (!IS_SNOW_LEOPARD_OR_LATER()) {
   425         return CGDisplaySwitchToMode(display, (CFDictionaryRef) mode);
   426     }
   427     #endif
   428 
   429     return kCGErrorFailure;
   430 }
   431 
   432 int
   433 Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
   434 {
   435     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
   436     SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata;
   437     CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
   438     CGError result;
   439 
   440     /* Fade to black to hide resolution-switching flicker */
   441     if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
   442         CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
   443     }
   444 
   445     if (data == display->desktop_mode.driverdata) {
   446         /* Restoring desktop mode */
   447         Cocoa_SwitchMode(_this, displaydata->display, data->moderef);
   448 
   449         if (CGDisplayIsMain(displaydata->display)) {
   450             CGReleaseAllDisplays();
   451         } else {
   452             CGDisplayRelease(displaydata->display);
   453         }
   454 
   455         if (CGDisplayIsMain(displaydata->display)) {
   456             Cocoa_ToggleMenuBar(YES);
   457         }
   458     } else {
   459         /* Put up the blanking window (a window above all other windows) */
   460         if (CGDisplayIsMain(displaydata->display)) {
   461             /* If we don't capture all displays, Cocoa tries to rearrange windows... *sigh* */
   462             result = CGCaptureAllDisplays();
   463         } else {
   464             result = CGDisplayCapture(displaydata->display);
   465         }
   466         if (result != kCGErrorSuccess) {
   467             CG_SetError("CGDisplayCapture()", result);
   468             goto ERR_NO_CAPTURE;
   469         }
   470 
   471         /* Do the physical switch */
   472         result = Cocoa_SwitchMode(_this, displaydata->display, data->moderef);
   473         if (result != kCGErrorSuccess) {
   474             CG_SetError("CGDisplaySwitchToMode()", result);
   475             goto ERR_NO_SWITCH;
   476         }
   477 
   478         /* Hide the menu bar so it doesn't intercept events */
   479         if (CGDisplayIsMain(displaydata->display)) {
   480             Cocoa_ToggleMenuBar(NO);
   481         }
   482     }
   483 
   484     /* Fade in again (asynchronously) */
   485     if (fade_token != kCGDisplayFadeReservationInvalidToken) {
   486         CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
   487         CGReleaseDisplayFadeReservation(fade_token);
   488     }
   489 
   490     return 0;
   491 
   492     /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
   493 ERR_NO_SWITCH:
   494     CGDisplayRelease(displaydata->display);
   495 ERR_NO_CAPTURE:
   496     if (fade_token != kCGDisplayFadeReservationInvalidToken) {
   497         CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
   498         CGReleaseDisplayFadeReservation(fade_token);
   499     }
   500     return -1;
   501 }
   502 
   503 void
   504 Cocoa_QuitModes(_THIS)
   505 {
   506     int i, j;
   507 
   508     for (i = 0; i < _this->num_displays; ++i) {
   509         SDL_VideoDisplay *display = &_this->displays[i];
   510         SDL_DisplayModeData *mode;
   511 
   512         if (display->current_mode.driverdata != display->desktop_mode.driverdata) {
   513             Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
   514         }
   515 
   516         mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata;
   517         Cocoa_ReleaseDisplayMode(_this, mode->moderef);
   518 
   519         for (j = 0; j < display->num_display_modes; j++) {
   520             mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata;
   521             Cocoa_ReleaseDisplayMode(_this, mode->moderef);
   522         }
   523 
   524     }
   525     Cocoa_ToggleMenuBar(YES);
   526 }
   527 
   528 #endif /* SDL_VIDEO_DRIVER_COCOA */
   529 
   530 /* vi: set ts=4 sw=4 expandtab: */