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