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