src/video/cocoa/SDL_cocoamodes.m
author Sam Lantinga <slouken@libsdl.org>
Mon, 31 Dec 2012 12:15:25 -0800
changeset 6788 036f53f2f5aa
parent 6787 95a4c5a5464c
child 6809 54cca01b6006
permissions -rw-r--r--
Added SDL_SetWindowMaximumSize() and SDL_GetWindowMaximumSize()
Also fixed Cocoa implementation so that it affects client area, not the whole window area.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2012 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 
   229     if ([localizedNames count] > 0) {
   230         return [[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String];
   231     }
   232     return NULL;
   233 }
   234 
   235 void
   236 Cocoa_InitModes(_THIS)
   237 {
   238     CGDisplayErr result;
   239     CGDirectDisplayID *displays;
   240     CGDisplayCount numDisplays;
   241     int pass, i;
   242 
   243     result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
   244     if (result != kCGErrorSuccess) {
   245         CG_SetError("CGGetOnlineDisplayList()", result);
   246         return;
   247     }
   248     displays = SDL_stack_alloc(CGDirectDisplayID, numDisplays);
   249     result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
   250     if (result != kCGErrorSuccess) {
   251         CG_SetError("CGGetOnlineDisplayList()", result);
   252         SDL_stack_free(displays);
   253         return;
   254     }
   255 
   256     /* Pick up the primary display in the first pass, then get the rest */
   257     for (pass = 0; pass < 2; ++pass) {
   258         for (i = 0; i < numDisplays; ++i) {
   259             SDL_VideoDisplay display;
   260             SDL_DisplayData *displaydata;
   261             SDL_DisplayMode mode;
   262             const void *moderef = NULL;
   263 
   264             if (pass == 0) {
   265                 if (!CGDisplayIsMain(displays[i])) {
   266                     continue;
   267                 }
   268             } else {
   269                 if (CGDisplayIsMain(displays[i])) {
   270                     continue;
   271                 }
   272             }
   273 
   274             if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) {
   275                 continue;
   276             }
   277 
   278             #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
   279             if (IS_SNOW_LEOPARD_OR_LATER(_this)) {
   280                 moderef = CGDisplayCopyDisplayMode(displays[i]);
   281             }
   282             #endif
   283 
   284             #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
   285             if (!IS_SNOW_LEOPARD_OR_LATER(_this)) {
   286                 moderef = CGDisplayCurrentMode(displays[i]);
   287             }
   288             #endif
   289 
   290             if (!moderef) {
   291                 continue;
   292             }
   293 
   294             displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
   295             if (!displaydata) {
   296                 Cocoa_ReleaseDisplayMode(_this, moderef);
   297                 continue;
   298             }
   299             displaydata->display = displays[i];
   300 
   301             SDL_zero(display);
   302             display.name = (char *)Cocoa_GetDisplayName(displays[i]);
   303             if (!GetDisplayMode (_this, moderef, &mode)) {
   304                 Cocoa_ReleaseDisplayMode(_this, moderef);
   305                 SDL_free(displaydata);
   306                 continue;
   307             }
   308 
   309             display.desktop_mode = mode;
   310             display.current_mode = mode;
   311             display.driverdata = displaydata;
   312             SDL_AddVideoDisplay(&display);
   313         }
   314     }
   315     SDL_stack_free(displays);
   316 }
   317 
   318 int
   319 Cocoa_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
   320 {
   321     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
   322     CGRect cgrect;
   323 
   324     cgrect = CGDisplayBounds(displaydata->display);
   325     rect->x = (int)cgrect.origin.x;
   326     rect->y = (int)cgrect.origin.y;
   327     rect->w = (int)cgrect.size.width;
   328     rect->h = (int)cgrect.size.height;
   329     return 0;
   330 }
   331 
   332 void
   333 Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
   334 {
   335     SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
   336     CFArrayRef modes = NULL;
   337 
   338     #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
   339     if (IS_SNOW_LEOPARD_OR_LATER(_this)) {
   340         modes = CGDisplayCopyAllDisplayModes(data->display, NULL);
   341     }
   342     #endif
   343 
   344     #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
   345     if (!IS_SNOW_LEOPARD_OR_LATER(_this)) {
   346         modes = CGDisplayAvailableModes(data->display);
   347     }
   348     #endif
   349 
   350     if (modes) {
   351         const CFIndex count = CFArrayGetCount(modes);
   352         CFIndex i;
   353 
   354         for (i = 0; i < count; i++) {
   355             const void *moderef = CFArrayGetValueAtIndex(modes, i);
   356             SDL_DisplayMode mode;
   357             if (GetDisplayMode(_this, moderef, &mode)) {
   358                 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
   359                 if (IS_SNOW_LEOPARD_OR_LATER(_this)) {
   360                     CGDisplayModeRetain((CGDisplayModeRef) moderef);
   361                 }
   362                 #endif
   363                 SDL_AddDisplayMode(display, &mode);
   364             }
   365         }
   366 
   367         Cocoa_ReleaseDisplayModeList(_this, modes);
   368     }
   369 }
   370 
   371 static CGError
   372 Cocoa_SwitchMode(_THIS, CGDirectDisplayID display, const void *mode)
   373 {
   374     #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
   375     if (IS_SNOW_LEOPARD_OR_LATER(_this)) {
   376         return CGDisplaySetDisplayMode(display, (CGDisplayModeRef) mode, NULL);
   377     }
   378     #endif
   379 
   380     #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
   381     if (!IS_SNOW_LEOPARD_OR_LATER(_this)) {
   382         return CGDisplaySwitchToMode(display, (CFDictionaryRef) mode);
   383     }
   384     #endif
   385 
   386     return kCGErrorFailure;
   387 }
   388 
   389 int
   390 Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
   391 {
   392     SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
   393     SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata;
   394     CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
   395     CGError result;
   396 
   397     /* Fade to black to hide resolution-switching flicker */
   398     if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
   399         CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
   400     }
   401 
   402     if (data == display->desktop_mode.driverdata) {
   403         /* Restoring desktop mode */
   404         Cocoa_SwitchMode(_this, displaydata->display, data->moderef);
   405 
   406         if (CGDisplayIsMain(displaydata->display)) {
   407             CGReleaseAllDisplays();
   408         } else {
   409             CGDisplayRelease(displaydata->display);
   410         }
   411 
   412         if (CGDisplayIsMain(displaydata->display)) {
   413             Cocoa_ToggleMenuBar(YES);
   414         }
   415     } else {
   416         /* Put up the blanking window (a window above all other windows) */
   417         if (CGDisplayIsMain(displaydata->display)) {
   418             /* If we don't capture all displays, Cocoa tries to rearrange windows... *sigh* */
   419             result = CGCaptureAllDisplays();
   420         } else {
   421             result = CGDisplayCapture(displaydata->display);
   422         }
   423         if (result != kCGErrorSuccess) {
   424             CG_SetError("CGDisplayCapture()", result);
   425             goto ERR_NO_CAPTURE;
   426         }
   427 
   428         /* Do the physical switch */
   429         result = Cocoa_SwitchMode(_this, displaydata->display, data->moderef);
   430         if (result != kCGErrorSuccess) {
   431             CG_SetError("CGDisplaySwitchToMode()", result);
   432             goto ERR_NO_SWITCH;
   433         }
   434 
   435         /* Hide the menu bar so it doesn't intercept events */
   436         if (CGDisplayIsMain(displaydata->display)) {
   437             Cocoa_ToggleMenuBar(NO);
   438         }
   439     }
   440 
   441     /* Fade in again (asynchronously) */
   442     if (fade_token != kCGDisplayFadeReservationInvalidToken) {
   443         CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
   444         CGReleaseDisplayFadeReservation(fade_token);
   445     }
   446 
   447     return 0;
   448 
   449     /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
   450 ERR_NO_SWITCH:
   451     CGDisplayRelease(displaydata->display);
   452 ERR_NO_CAPTURE:
   453     if (fade_token != kCGDisplayFadeReservationInvalidToken) {
   454         CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
   455         CGReleaseDisplayFadeReservation(fade_token);
   456     }
   457     return -1;
   458 }
   459 
   460 void
   461 Cocoa_QuitModes(_THIS)
   462 {
   463     int i, j;
   464 
   465     for (i = 0; i < _this->num_displays; ++i) {
   466         SDL_VideoDisplay *display = &_this->displays[i];
   467         SDL_DisplayModeData *mode;
   468 
   469         if (display->current_mode.driverdata != display->desktop_mode.driverdata) {
   470             Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
   471         }
   472 
   473         mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata;
   474         Cocoa_ReleaseDisplayMode(_this, mode->moderef);
   475 
   476         for (j = 0; j < display->num_display_modes; j++) {
   477             mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata;
   478             Cocoa_ReleaseDisplayMode(_this, mode->moderef);
   479         }
   480 
   481     }
   482     Cocoa_ToggleMenuBar(YES);
   483 }
   484 
   485 #endif /* SDL_VIDEO_DRIVER_COCOA */
   486 
   487 /* vi: set ts=4 sw=4 expandtab: */