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 #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 
42 static void
43 Cocoa_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 
59 static int
60 CG_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 
102 static SDL_bool
103 GetDisplayMode(_THIS, CGDisplayModeRef vidmode, CVDisplayLinkRef link, SDL_DisplayMode *mode)
104 {
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 
147  switch (bpp) {
148  case 16:
150  break;
151  case 30:
153  break;
154  case 32:
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 
169 static const char *
170 Cocoa_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 
183 void
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;
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 
266 int
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 
280 int
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 
315 int
316 Cocoa_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 
339 void
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);
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 
366 int
368 {
369  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->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 */
427 ERR_NO_SWITCH:
428  CGDisplayRelease(displaydata->display);
429 ERR_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 
437 void
439 {
440  int i, j;
441 
442  for (i = 0; i < _this->num_displays; ++i) {
443  SDL_VideoDisplay *display = &_this->displays[i];
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: */
void Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay *display)
GLuint64EXT * result
GLint GLint GLsizei width
Definition: SDL_opengl.h:1565
GLuint GLuint GLsizei count
Definition: SDL_opengl.h:1564
CGDisplayModeRef moderef
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:591
int Cocoa_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay *display, SDL_Rect *rect)
static SDL_VideoDevice * _this
Definition: SDL_video.c:118
int Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
#define _THIS
#define SDL_stack_alloc(type, count)
Definition: SDL_stdinc.h:328
int frame
Definition: teststreaming.c:60
void SDL_free(void *mem)
void * driverdata
Definition: SDL_video.h:59
#define TRUE
Definition: edid-parse.c:33
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:50
SDL_DisplayMode * display_modes
Definition: SDL_sysvideo.h:128
int Cocoa_GetDisplayDPI(_THIS, SDL_VideoDisplay *display, float *ddpi, float *hpdi, float *vdpi)
SDL_DisplayMode current_mode
Definition: SDL_sysvideo.h:130
GLenum mode
SDL_VideoDisplay * displays
Definition: SDL_sysvideo.h:293
#define SDL_zero(x)
Definition: SDL_stdinc.h:359
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:50
#define SDL_assert(condition)
Definition: SDL_assert.h:167
#define NULL
Definition: begin_code.h:143
SDL_bool
Definition: SDL_stdinc.h:130
SDL_DisplayMode desktop_mode
Definition: SDL_sysvideo.h:129
#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:728
#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:329
#define FALSE
Definition: edid-parse.c:34
void Cocoa_QuitModes(_THIS)
GLuint in
SDL_Renderer * screen
void Cocoa_InitModes(_THIS)
int y
Definition: SDL_rect.h:66
float SDL_ComputeDiagonalDPI(int hpix, int vpix, float hinches, float vinches)
Definition: SDL_video.c:3783
A rectangle, with the origin at the upper left.
Definition: SDL_rect.h:64