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 42static void 43Cocoa_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 59static int 60CG_SetError(const char *prefix, CGDisplayErr result) 61{ 62 const char *error; 63 64 switch (result) { 65 case kCGErrorFailure: 66 error = "kCGErrorFailure"; 67 break; 68 case kCGErrorIllegalArgument: 69 error = "kCGErrorIllegalArgument"; 70 break; 71 case kCGErrorInvalidConnection: 72 error = "kCGErrorInvalidConnection"; 73 break; 74 case kCGErrorInvalidContext: 75 error = "kCGErrorInvalidContext"; 76 break; 77 case kCGErrorCannotComplete: 78 error = "kCGErrorCannotComplete"; 79 break; 80 case kCGErrorNotImplemented: 81 error = "kCGErrorNotImplemented"; 82 break; 83 case kCGErrorRangeCheck: 84 error = "kCGErrorRangeCheck"; 85 break; 86 case kCGErrorTypeCheck: 87 error = "kCGErrorTypeCheck"; 88 break; 89 case kCGErrorInvalidOperation: 90 error = "kCGErrorInvalidOperation"; 91 break; 92 case kCGErrorNoneAvailable: 93 error = "kCGErrorNoneAvailable"; 94 break; 95 default: 96 error = "Unknown Error"; 97 break; 98 } 99 return SDL_SetError("%s: %s", prefix, error); 100} 101 102static SDL_bool 103GetDisplayMode(_THIS, CGDisplayModeRef vidmode, CVDisplayLinkRef link, SDL_DisplayMode *mode) 104{ 105 SDL_DisplayModeData *data; 106 int width = 0; 107 int height = 0; 108 int bpp = 0; 109 int refreshRate = 0; 110 CFStringRef fmt; 111 112 data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data)); 113 if (!data) { 114 return SDL_FALSE; 115 } 116 data->moderef = vidmode; 117 118 fmt = CGDisplayModeCopyPixelEncoding(vidmode); 119 width = (int) CGDisplayModeGetWidth(vidmode); 120 height = (int) CGDisplayModeGetHeight(vidmode); 121 refreshRate = (int) (CGDisplayModeGetRefreshRate(vidmode) + 0.5); 122 123 if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels), 124 kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 125 bpp = 32; 126 } else if (CFStringCompare(fmt, CFSTR(IO16BitDirectPixels), 127 kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 128 bpp = 16; 129 } else if (CFStringCompare(fmt, CFSTR(kIO30BitDirectPixels), 130 kCFCompareCaseInsensitive) == kCFCompareEqualTo) { 131 bpp = 30; 132 } else { 133 bpp = 0; /* ignore 8-bit and such for now. */ 134 } 135 136 CFRelease(fmt); 137 138 /* CGDisplayModeGetRefreshRate returns 0 for many non-CRT displays. */ 139 if (refreshRate == 0 && link != NULL) { 140 CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link); 141 if ((time.flags & kCVTimeIsIndefinite) == 0 && time.timeValue != 0) { 142 refreshRate = (int) ((time.timeScale / (double) time.timeValue) + 0.5); 143 } 144 } 145 146 mode->format = SDL_PIXELFORMAT_UNKNOWN; 147 switch (bpp) { 148 case 16: 149 mode->format = SDL_PIXELFORMAT_ARGB1555; 150 break; 151 case 30: 152 mode->format = SDL_PIXELFORMAT_ARGB2101010; 153 break; 154 case 32: 155 mode->format = SDL_PIXELFORMAT_ARGB8888; 156 break; 157 case 8: /* We don't support palettized modes now */ 158 default: /* Totally unrecognizable bit depth. */ 159 SDL_free(data); 160 return SDL_FALSE; 161 } 162 mode->w = width; 163 mode->h = height; 164 mode->refresh_rate = refreshRate; 165 mode->driverdata = data; 166 return SDL_TRUE; 167} 168 169static const char * 170Cocoa_GetDisplayName(CGDirectDisplayID displayID) 171{ 172 CFDictionaryRef deviceInfo = IODisplayCreateInfoDictionary(CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName); 173 NSDictionary *localizedNames = [(NSDictionary *)deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]]; 174 const char* displayName = NULL; 175 176 if ([localizedNames count] > 0) { 177 displayName = SDL_strdup([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]); 178 } 179 CFRelease(deviceInfo); 180 return displayName; 181} 182 183void 184Cocoa_InitModes(_THIS) 185{ @autoreleasepool 186{ 187 CGDisplayErr result; 188 CGDirectDisplayID *displays; 189 CGDisplayCount numDisplays; 190 int pass, i; 191 192 result = CGGetOnlineDisplayList(0, NULL, &numDisplays); 193 if (result != kCGErrorSuccess) { 194 CG_SetError("CGGetOnlineDisplayList()", result); 195 return; 196 } 197 displays = SDL_stack_alloc(CGDirectDisplayID, numDisplays); 198 result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays); 199 if (result != kCGErrorSuccess) { 200 CG_SetError("CGGetOnlineDisplayList()", result); 201 SDL_stack_free(displays); 202 return; 203 } 204 205 /* Pick up the primary display in the first pass, then get the rest */ 206 for (pass = 0; pass < 2; ++pass) { 207 for (i = 0; i < numDisplays; ++i) { 208 SDL_VideoDisplay display; 209 SDL_DisplayData *displaydata; 210 SDL_DisplayMode mode; 211 CGDisplayModeRef moderef = NULL; 212 CVDisplayLinkRef link = NULL; 213 214 if (pass == 0) { 215 if (!CGDisplayIsMain(displays[i])) { 216 continue; 217 } 218 } else { 219 if (CGDisplayIsMain(displays[i])) { 220 continue; 221 } 222 } 223 224 if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) { 225 continue; 226 } 227 228 moderef = CGDisplayCopyDisplayMode(displays[i]); 229 230 if (!moderef) { 231 continue; 232 } 233 234 displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata)); 235 if (!displaydata) { 236 CGDisplayModeRelease(moderef); 237 continue; 238 } 239 displaydata->display = displays[i]; 240 241 CVDisplayLinkCreateWithCGDisplay(displays[i], &link); 242 243 SDL_zero(display); 244 /* this returns a stddup'ed string */ 245 display.name = (char *)Cocoa_GetDisplayName(displays[i]); 246 if (!GetDisplayMode(_this, moderef, link, &mode)) { 247 CVDisplayLinkRelease(link); 248 CGDisplayModeRelease(moderef); 249 SDL_free(display.name); 250 SDL_free(displaydata); 251 continue; 252 } 253 254 CVDisplayLinkRelease(link); 255 256 display.desktop_mode = mode; 257 display.current_mode = mode; 258 display.driverdata = displaydata; 259 SDL_AddVideoDisplay(&display); 260 SDL_free(display.name); 261 } 262 } 263 SDL_stack_free(displays); 264}} 265 266int 267Cocoa_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect) 268{ 269 SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata; 270 CGRect cgrect; 271 272 cgrect = CGDisplayBounds(displaydata->display); 273 rect->x = (int)cgrect.origin.x; 274 rect->y = (int)cgrect.origin.y; 275 rect->w = (int)cgrect.size.width; 276 rect->h = (int)cgrect.size.height; 277 return 0; 278} 279 280int 281Cocoa_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect) 282{ 283 SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata; 284 const CGDirectDisplayID cgdisplay = displaydata->display; 285 NSArray *screens = [NSScreen screens]; 286 NSScreen *screen = nil; 287 288 /* !!! FIXME: maybe track the NSScreen in SDL_DisplayData? */ 289 for (NSScreen *i in screens) { 290 const CGDirectDisplayID thisDisplay = (CGDirectDisplayID) [[[i deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue]; 291 if (thisDisplay == cgdisplay) { 292 screen = i; 293 break; 294 } 295 } 296 297 SDL_assert(screen != nil); /* didn't find it?! */ 298 if (screen == nil) { 299 return -1; 300 } 301 302 const CGRect cgrect = CGDisplayBounds(cgdisplay); 303 const NSRect frame = [screen visibleFrame]; 304 305 // !!! FIXME: I assume -[NSScreen visibleFrame] is relative to the origin of the screen in question and not the whole desktop. 306 // !!! FIXME: The math vs CGDisplayBounds might be incorrect if that's not the case, though. Check this. 307 rect->x = (int)(cgrect.origin.x + frame.origin.x); 308 rect->y = (int)(cgrect.origin.y + frame.origin.y); 309 rect->w = (int)frame.size.width; 310 rect->h = (int)frame.size.height; 311 312 return 0; 313} 314 315int 316Cocoa_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi) 317{ 318 const float MM_IN_INCH = 25.4f; 319 320 SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata; 321 322 CGSize displaySize = CGDisplayScreenSize(data->display); 323 int pixelWidth = (int) CGDisplayPixelsWide(data->display); 324 int pixelHeight = (int) CGDisplayPixelsHigh(data->display); 325 326 if (ddpi) { 327 *ddpi = SDL_ComputeDiagonalDPI(pixelWidth, pixelHeight, displaySize.width / MM_IN_INCH, displaySize.height / MM_IN_INCH); 328 } 329 if (hdpi) { 330 *hdpi = pixelWidth * MM_IN_INCH / displaySize.width; 331 } 332 if (vdpi) { 333 *vdpi = pixelHeight * MM_IN_INCH / displaySize.height; 334 } 335 336 return 0; 337} 338 339void 340Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display) 341{ 342 SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata; 343 CFArrayRef modes = CGDisplayCopyAllDisplayModes(data->display, NULL); 344 345 if (modes) { 346 CVDisplayLinkRef link = NULL; 347 const CFIndex count = CFArrayGetCount(modes); 348 CFIndex i; 349 350 CVDisplayLinkCreateWithCGDisplay(data->display, &link); 351 352 for (i = 0; i < count; i++) { 353 CGDisplayModeRef moderef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i); 354 SDL_DisplayMode mode; 355 if (GetDisplayMode(_this, moderef, link, &mode)) { 356 CGDisplayModeRetain(moderef); 357 SDL_AddDisplayMode(display, &mode); 358 } 359 } 360 361 CVDisplayLinkRelease(link); 362 CFRelease(modes); 363 } 364} 365 366int 367Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode) 368{ 369 SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata; 370 SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata; 371 CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken; 372 CGError result; 373 374 /* Fade to black to hide resolution-switching flicker */ 375 if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) { 376 CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE); 377 } 378 379 if (data == display->desktop_mode.driverdata) { 380 /* Restoring desktop mode */ 381 CGDisplaySetDisplayMode(displaydata->display, data->moderef, NULL); 382 383 if (CGDisplayIsMain(displaydata->display)) { 384 CGReleaseAllDisplays(); 385 } else { 386 CGDisplayRelease(displaydata->display); 387 } 388 389 if (CGDisplayIsMain(displaydata->display)) { 390 Cocoa_ToggleMenuBar(YES); 391 } 392 } else { 393 /* Put up the blanking window (a window above all other windows) */ 394 if (CGDisplayIsMain(displaydata->display)) { 395 /* If we don't capture all displays, Cocoa tries to rearrange windows... *sigh* */ 396 result = CGCaptureAllDisplays(); 397 } else { 398 result = CGDisplayCapture(displaydata->display); 399 } 400 if (result != kCGErrorSuccess) { 401 CG_SetError("CGDisplayCapture()", result); 402 goto ERR_NO_CAPTURE; 403 } 404 405 /* Do the physical switch */ 406 result = CGDisplaySetDisplayMode(displaydata->display, data->moderef, NULL); 407 if (result != kCGErrorSuccess) { 408 CG_SetError("CGDisplaySwitchToMode()", result); 409 goto ERR_NO_SWITCH; 410 } 411 412 /* Hide the menu bar so it doesn't intercept events */ 413 if (CGDisplayIsMain(displaydata->display)) { 414 Cocoa_ToggleMenuBar(NO); 415 } 416 } 417 418 /* Fade in again (asynchronously) */ 419 if (fade_token != kCGDisplayFadeReservationInvalidToken) { 420 CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE); 421 CGReleaseDisplayFadeReservation(fade_token); 422 } 423 424 return 0; 425 426 /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */ 427ERR_NO_SWITCH: 428 CGDisplayRelease(displaydata->display); 429ERR_NO_CAPTURE: 430 if (fade_token != kCGDisplayFadeReservationInvalidToken) { 431 CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE); 432 CGReleaseDisplayFadeReservation(fade_token); 433 } 434 return -1; 435} 436 437void 438Cocoa_QuitModes(_THIS) 439{ 440 int i, j; 441 442 for (i = 0; i < _this->num_displays; ++i) { 443 SDL_VideoDisplay *display = &_this->displays[i]; 444 SDL_DisplayModeData *mode; 445 446 if (display->current_mode.driverdata != display->desktop_mode.driverdata) { 447 Cocoa_SetDisplayMode(_this, display, &display->desktop_mode); 448 } 449 450 mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata; 451 CGDisplayModeRelease(mode->moderef); 452 453 for (j = 0; j < display->num_display_modes; j++) { 454 mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata; 455 CGDisplayModeRelease(mode->moderef); 456 } 457 458 } 459 Cocoa_ToggleMenuBar(YES); 460} 461 462#endif /* SDL_VIDEO_DRIVER_COCOA */ 463 464/* vi: set ts=4 sw=4 expandtab: */ 465