SDL  2.0
SDL_cocoamodes.m
Go to the documentation of this file.
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 
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 
41 static void
42 Cocoa_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 
67 static BOOL
68 IS_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 
77 static int
78 CG_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 
120 static SDL_bool
121 GetDisplayMode(_THIS, const void *moderef, CVDisplayLinkRef link, SDL_DisplayMode *mode)
122 {
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 
184  switch (bpp) {
185  case 16:
187  break;
188  case 30:
190  break;
191  case 32:
193  break;
194  case 8: /* We don't support palettized modes now */
195  default: /* Totally unrecognizable bit depth. */
196  SDL_free(data);
197  return SDL_FALSE;
198  }
199  mode->w = width;
200  mode->h = height;
201  mode->refresh_rate = refreshRate;
202  mode->driverdata = data;
203  return SDL_TRUE;
204 }
205 
206 static void
207 Cocoa_ReleaseDisplayMode(_THIS, const void *moderef)
208 {
209  if (IS_SNOW_LEOPARD_OR_LATER()) {
210  CGDisplayModeRelease((CGDisplayModeRef) moderef); /* NULL is ok */
211  }
212 }
213 
214 static void
215 Cocoa_ReleaseDisplayModeList(_THIS, CFArrayRef modelist)
216 {
217  if (IS_SNOW_LEOPARD_OR_LATER()) {
218  CFRelease(modelist); /* NULL is ok */
219  }
220 }
221 
222 static const char *
223 Cocoa_GetDisplayName(CGDirectDisplayID displayID)
224 {
225  CFDictionaryRef deviceInfo = IODisplayCreateInfoDictionary(CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName);
226  NSDictionary *localizedNames = [(NSDictionary *)deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
227  const char* displayName = NULL;
228 
229  if ([localizedNames count] > 0) {
230  displayName = SDL_strdup([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]);
231  }
232  CFRelease(deviceInfo);
233  return displayName;
234 }
235 
236 void
238 { @autoreleasepool
239 {
240  CGDisplayErr result;
241  CGDirectDisplayID *displays;
242  CGDisplayCount numDisplays;
243  int pass, i;
244 
245  result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
246  if (result != kCGErrorSuccess) {
247  CG_SetError("CGGetOnlineDisplayList()", result);
248  return;
249  }
250  displays = SDL_stack_alloc(CGDirectDisplayID, numDisplays);
251  result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
252  if (result != kCGErrorSuccess) {
253  CG_SetError("CGGetOnlineDisplayList()", result);
254  SDL_stack_free(displays);
255  return;
256  }
257 
258  /* Pick up the primary display in the first pass, then get the rest */
259  for (pass = 0; pass < 2; ++pass) {
260  for (i = 0; i < numDisplays; ++i) {
261  SDL_VideoDisplay display;
262  SDL_DisplayData *displaydata;
264  const void *moderef = NULL;
265  CVDisplayLinkRef link = NULL;
266 
267  if (pass == 0) {
268  if (!CGDisplayIsMain(displays[i])) {
269  continue;
270  }
271  } else {
272  if (CGDisplayIsMain(displays[i])) {
273  continue;
274  }
275  }
276 
277  if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) {
278  continue;
279  }
280 
281  if (IS_SNOW_LEOPARD_OR_LATER()) {
282  moderef = CGDisplayCopyDisplayMode(displays[i]);
283  }
284 
285  #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
286  if (!IS_SNOW_LEOPARD_OR_LATER()) {
287  moderef = CGDisplayCurrentMode(displays[i]);
288  }
289  #endif
290 
291  if (!moderef) {
292  continue;
293  }
294 
295  displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
296  if (!displaydata) {
297  Cocoa_ReleaseDisplayMode(_this, moderef);
298  continue;
299  }
300  displaydata->display = displays[i];
301 
302  CVDisplayLinkCreateWithCGDisplay(displays[i], &link);
303 
304  SDL_zero(display);
305  /* this returns a stddup'ed string */
306  display.name = (char *)Cocoa_GetDisplayName(displays[i]);
307  if (!GetDisplayMode(_this, moderef, link, &mode)) {
308  CVDisplayLinkRelease(link);
309  Cocoa_ReleaseDisplayMode(_this, moderef);
310  SDL_free(display.name);
311  SDL_free(displaydata);
312  continue;
313  }
314 
315  CVDisplayLinkRelease(link);
316 
317  display.desktop_mode = mode;
318  display.current_mode = mode;
319  display.driverdata = displaydata;
320  SDL_AddVideoDisplay(&display);
321  SDL_free(display.name);
322  }
323  }
324  SDL_stack_free(displays);
325 }}
326 
327 int
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
343 {
344  SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
345  CFArrayRef modes = NULL;
346 
347  if (IS_SNOW_LEOPARD_OR_LATER()) {
348  modes = CGDisplayCopyAllDisplayModes(data->display, NULL);
349  }
350 
351  #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
352  if (!IS_SNOW_LEOPARD_OR_LATER()) {
353  modes = CGDisplayAvailableModes(data->display);
354  }
355  #endif
356 
357  if (modes) {
358  CVDisplayLinkRef link = NULL;
359  const CFIndex count = CFArrayGetCount(modes);
360  CFIndex i;
361 
362  CVDisplayLinkCreateWithCGDisplay(data->display, &link);
363 
364  for (i = 0; i < count; i++) {
365  const void *moderef = CFArrayGetValueAtIndex(modes, i);
367  if (GetDisplayMode(_this, moderef, link, &mode)) {
368  if (IS_SNOW_LEOPARD_OR_LATER()) {
369  CGDisplayModeRetain((CGDisplayModeRef) moderef);
370  }
371  SDL_AddDisplayMode(display, &mode);
372  }
373  }
374 
375  CVDisplayLinkRelease(link);
376  Cocoa_ReleaseDisplayModeList(_this, modes);
377  }
378 }
379 
380 static CGError
381 Cocoa_SwitchMode(_THIS, CGDirectDisplayID display, const void *mode)
382 {
383  if (IS_SNOW_LEOPARD_OR_LATER()) {
384  return CGDisplaySetDisplayMode(display, (CGDisplayModeRef) mode, NULL);
385  }
386 
387  #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
388  if (!IS_SNOW_LEOPARD_OR_LATER()) {
389  return CGDisplaySwitchToMode(display, (CFDictionaryRef) mode);
390  }
391  #endif
392 
393  return kCGErrorFailure;
394 }
395 
396 int
398 {
399  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
401  CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
402  CGError result;
403 
404  /* Fade to black to hide resolution-switching flicker */
405  if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
406  CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
407  }
408 
409  if (data == display->desktop_mode.driverdata) {
410  /* Restoring desktop mode */
411  Cocoa_SwitchMode(_this, displaydata->display, data->moderef);
412 
413  if (CGDisplayIsMain(displaydata->display)) {
414  CGReleaseAllDisplays();
415  } else {
416  CGDisplayRelease(displaydata->display);
417  }
418 
419  if (CGDisplayIsMain(displaydata->display)) {
420  Cocoa_ToggleMenuBar(YES);
421  }
422  } else {
423  /* Put up the blanking window (a window above all other windows) */
424  if (CGDisplayIsMain(displaydata->display)) {
425  /* If we don't capture all displays, Cocoa tries to rearrange windows... *sigh* */
426  result = CGCaptureAllDisplays();
427  } else {
428  result = CGDisplayCapture(displaydata->display);
429  }
430  if (result != kCGErrorSuccess) {
431  CG_SetError("CGDisplayCapture()", result);
432  goto ERR_NO_CAPTURE;
433  }
434 
435  /* Do the physical switch */
436  result = Cocoa_SwitchMode(_this, displaydata->display, data->moderef);
437  if (result != kCGErrorSuccess) {
438  CG_SetError("CGDisplaySwitchToMode()", result);
439  goto ERR_NO_SWITCH;
440  }
441 
442  /* Hide the menu bar so it doesn't intercept events */
443  if (CGDisplayIsMain(displaydata->display)) {
444  Cocoa_ToggleMenuBar(NO);
445  }
446  }
447 
448  /* Fade in again (asynchronously) */
449  if (fade_token != kCGDisplayFadeReservationInvalidToken) {
450  CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
451  CGReleaseDisplayFadeReservation(fade_token);
452  }
453 
454  return 0;
455 
456  /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
457 ERR_NO_SWITCH:
458  CGDisplayRelease(displaydata->display);
459 ERR_NO_CAPTURE:
460  if (fade_token != kCGDisplayFadeReservationInvalidToken) {
461  CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
462  CGReleaseDisplayFadeReservation(fade_token);
463  }
464  return -1;
465 }
466 
467 void
469 {
470  int i, j;
471 
472  for (i = 0; i < _this->num_displays; ++i) {
473  SDL_VideoDisplay *display = &_this->displays[i];
475 
476  if (display->current_mode.driverdata != display->desktop_mode.driverdata) {
477  Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
478  }
479 
480  mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata;
481  Cocoa_ReleaseDisplayMode(_this, mode->moderef);
482 
483  for (j = 0; j < display->num_display_modes; j++) {
484  mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata;
485  Cocoa_ReleaseDisplayMode(_this, mode->moderef);
486  }
487 
488  }
489  Cocoa_ToggleMenuBar(YES);
490 }
491 
492 #endif /* SDL_VIDEO_DRIVER_COCOA */
493 
494 /* vi: set ts=4 sw=4 expandtab: */
void Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay *display)
GLuint64EXT * result
const void * moderef
GLint GLint GLsizei width
Definition: SDL_opengl.h:1565
GLuint GLuint GLsizei count
Definition: SDL_opengl.h:1564
SDL_Rect rect
Definition: testrelative.c:27
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1967
The structure that defines a display mode.
Definition: SDL_video.h:53
GLint GLint GLsizei GLsizei height
Definition: SDL_opengl.h:1565
CGDirectDisplayID display
int SDL_AddVideoDisplay(const SDL_VideoDisplay *display)
Definition: SDL_video.c:593
static SDL_VideoDevice * _this
Definition: SDL_video.c:114
SDL_bool
Definition: SDL_stdinc.h:126
int Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
#define _THIS
#define SDL_stack_alloc(type, count)
Definition: SDL_stdinc.h:324
void SDL_free(void *mem)
void * driverdata
Definition: SDL_video.h:59
#define TRUE
Definition: edid-parse.c:31
SDL_DisplayMode * display_modes
Definition: SDL_sysvideo.h:125
SDL_DisplayMode current_mode
Definition: SDL_sysvideo.h:127
GLenum mode
SDL_VideoDisplay * displays
Definition: SDL_sysvideo.h:280
#define SDL_zero(x)
Definition: SDL_stdinc.h:355
int x
Definition: SDL_rect.h:66
int w
Definition: SDL_rect.h:67
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:42
#define NULL
Definition: begin_code.h:143
SDL_DisplayMode desktop_mode
Definition: SDL_sysvideo.h:126
#define SDL_SetError
int h
Definition: SDL_rect.h:67
#define SDL_strdup
SDL_bool SDL_AddDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode)
Definition: SDL_video.c:709
#define SDL_malloc
int Cocoa_GetDisplayBounds(_THIS, SDL_VideoDisplay *display, SDL_Rect *rect)
Uint32 format
Definition: SDL_video.h:55
#define SDL_stack_free(data)
Definition: SDL_stdinc.h:325
#define FALSE
Definition: edid-parse.c:32
void Cocoa_QuitModes(_THIS)
#define floor
Definition: math_private.h:37
void Cocoa_InitModes(_THIS)
int y
Definition: SDL_rect.h:66
A rectangle, with the origin at the upper left.
Definition: SDL_rect.h:64
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int int in j)
Definition: SDL_x11sym.h:42