SDL  2.0
SDL_cocoamodes.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2017 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, CFArrayRef modelist, CVDisplayLinkRef link, SDL_DisplayMode *mode)
104 {
106  int width = (int) CGDisplayModeGetWidth(vidmode);
107  int height = (int) CGDisplayModeGetHeight(vidmode);
108  int bpp = 0;
109  int refreshRate = 0;
110  CFStringRef fmt;
111 
112  /* Ignore this mode if it's low-dpi (@1x) and we have a high-dpi mode in the
113  * list with the same size in points.
114  */
115 #ifdef MAC_OS_X_VERSION_10_8
116  if (modelist != NULL && floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) {
117  int pixelW = (int) CGDisplayModeGetPixelWidth(vidmode);
118  int pixelH = (int) CGDisplayModeGetPixelHeight(vidmode);
119 
120  if (width == pixelW && height == pixelH) {
121  CFIndex modescount = CFArrayGetCount(modelist);
122 
123  for (int i = 0; i < modescount; i++) {
124  CGDisplayModeRef othermode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modelist, i);
125 
126  if (CFEqual(vidmode, othermode)) {
127  continue;
128  }
129 
130  int otherW = (int) CGDisplayModeGetWidth(othermode);
131  int otherH = (int) CGDisplayModeGetHeight(othermode);
132 
133  int otherpixelW = (int) CGDisplayModeGetPixelWidth(othermode);
134  int otherpixelH = (int) CGDisplayModeGetPixelHeight(othermode);
135 
136  if (width == otherW && height == otherH && (otherpixelW != otherW || otherpixelH != otherH)) {
137  return SDL_FALSE;
138  }
139  }
140  }
141  }
142 #endif
143 
144  data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data));
145  if (!data) {
146  return SDL_FALSE;
147  }
148  data->moderef = vidmode;
149 
150  fmt = CGDisplayModeCopyPixelEncoding(vidmode);
151  refreshRate = (int) (CGDisplayModeGetRefreshRate(vidmode) + 0.5);
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 if (CFStringCompare(fmt, CFSTR(kIO30BitDirectPixels),
160  kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
161  bpp = 30;
162  } else {
163  bpp = 0; /* ignore 8-bit and such for now. */
164  }
165 
166  CFRelease(fmt);
167 
168  /* CGDisplayModeGetRefreshRate returns 0 for many non-CRT displays. */
169  if (refreshRate == 0 && link != NULL) {
170  CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
171  if ((time.flags & kCVTimeIsIndefinite) == 0 && time.timeValue != 0) {
172  refreshRate = (int) ((time.timeScale / (double) time.timeValue) + 0.5);
173  }
174  }
175 
177  switch (bpp) {
178  case 16:
180  break;
181  case 30:
183  break;
184  case 32:
186  break;
187  case 8: /* We don't support palettized modes now */
188  default: /* Totally unrecognizable bit depth. */
189  SDL_free(data);
190  return SDL_FALSE;
191  }
192  mode->w = width;
193  mode->h = height;
194  mode->refresh_rate = refreshRate;
195  mode->driverdata = data;
196  return SDL_TRUE;
197 }
198 
199 static const char *
200 Cocoa_GetDisplayName(CGDirectDisplayID displayID)
201 {
202  CFDictionaryRef deviceInfo = IODisplayCreateInfoDictionary(CGDisplayIOServicePort(displayID), kIODisplayOnlyPreferredName);
203  NSDictionary *localizedNames = [(NSDictionary *)deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
204  const char* displayName = NULL;
205 
206  if ([localizedNames count] > 0) {
207  displayName = SDL_strdup([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]);
208  }
209  CFRelease(deviceInfo);
210  return displayName;
211 }
212 
213 void
215 { @autoreleasepool
216 {
217  CGDisplayErr result;
218  CGDirectDisplayID *displays;
219  CGDisplayCount numDisplays;
220  int pass, i;
221 
222  result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
223  if (result != kCGErrorSuccess) {
224  CG_SetError("CGGetOnlineDisplayList()", result);
225  return;
226  }
227  displays = SDL_stack_alloc(CGDirectDisplayID, numDisplays);
228  result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
229  if (result != kCGErrorSuccess) {
230  CG_SetError("CGGetOnlineDisplayList()", result);
231  SDL_stack_free(displays);
232  return;
233  }
234 
235  /* Pick up the primary display in the first pass, then get the rest */
236  for (pass = 0; pass < 2; ++pass) {
237  for (i = 0; i < numDisplays; ++i) {
238  SDL_VideoDisplay display;
239  SDL_DisplayData *displaydata;
241  CGDisplayModeRef moderef = NULL;
242  CVDisplayLinkRef link = NULL;
243 
244  if (pass == 0) {
245  if (!CGDisplayIsMain(displays[i])) {
246  continue;
247  }
248  } else {
249  if (CGDisplayIsMain(displays[i])) {
250  continue;
251  }
252  }
253 
254  if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) {
255  continue;
256  }
257 
258  moderef = CGDisplayCopyDisplayMode(displays[i]);
259 
260  if (!moderef) {
261  continue;
262  }
263 
264  displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
265  if (!displaydata) {
266  CGDisplayModeRelease(moderef);
267  continue;
268  }
269  displaydata->display = displays[i];
270 
271  CVDisplayLinkCreateWithCGDisplay(displays[i], &link);
272 
273  SDL_zero(display);
274  /* this returns a stddup'ed string */
275  display.name = (char *)Cocoa_GetDisplayName(displays[i]);
276  if (!GetDisplayMode(_this, moderef, NULL, link, &mode)) {
277  CVDisplayLinkRelease(link);
278  CGDisplayModeRelease(moderef);
279  SDL_free(display.name);
280  SDL_free(displaydata);
281  continue;
282  }
283 
284  CVDisplayLinkRelease(link);
285 
286  display.desktop_mode = mode;
287  display.current_mode = mode;
288  display.driverdata = displaydata;
289  SDL_AddVideoDisplay(&display);
290  SDL_free(display.name);
291  }
292  }
293  SDL_stack_free(displays);
294 }}
295 
296 int
298 {
299  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
300  CGRect cgrect;
301 
302  cgrect = CGDisplayBounds(displaydata->display);
303  rect->x = (int)cgrect.origin.x;
304  rect->y = (int)cgrect.origin.y;
305  rect->w = (int)cgrect.size.width;
306  rect->h = (int)cgrect.size.height;
307  return 0;
308 }
309 
310 int
312 {
313  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
314  const CGDirectDisplayID cgdisplay = displaydata->display;
315  NSArray *screens = [NSScreen screens];
316  NSScreen *screen = nil;
317 
318  /* !!! FIXME: maybe track the NSScreen in SDL_DisplayData? */
319  for (NSScreen *i in screens) {
320  const CGDirectDisplayID thisDisplay = (CGDirectDisplayID) [[[i deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
321  if (thisDisplay == cgdisplay) {
322  screen = i;
323  break;
324  }
325  }
326 
327  SDL_assert(screen != nil); /* didn't find it?! */
328  if (screen == nil) {
329  return -1;
330  }
331 
332  const CGRect cgrect = CGDisplayBounds(cgdisplay);
333  const NSRect frame = [screen visibleFrame];
334 
335  // !!! FIXME: I assume -[NSScreen visibleFrame] is relative to the origin of the screen in question and not the whole desktop.
336  // !!! FIXME: The math vs CGDisplayBounds might be incorrect if that's not the case, though. Check this.
337  rect->x = (int)(cgrect.origin.x + frame.origin.x);
338  rect->y = (int)(cgrect.origin.y + frame.origin.y);
339  rect->w = (int)frame.size.width;
340  rect->h = (int)frame.size.height;
341 
342  return 0;
343 }
344 
345 int
346 Cocoa_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi)
347 {
348  const float MM_IN_INCH = 25.4f;
349 
350  SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
351 
352  CGSize displaySize = CGDisplayScreenSize(data->display);
353  int pixelWidth = (int) CGDisplayPixelsWide(data->display);
354  int pixelHeight = (int) CGDisplayPixelsHigh(data->display);
355 
356  if (ddpi) {
357  *ddpi = SDL_ComputeDiagonalDPI(pixelWidth, pixelHeight, displaySize.width / MM_IN_INCH, displaySize.height / MM_IN_INCH);
358  }
359  if (hdpi) {
360  *hdpi = pixelWidth * MM_IN_INCH / displaySize.width;
361  }
362  if (vdpi) {
363  *vdpi = pixelHeight * MM_IN_INCH / displaySize.height;
364  }
365 
366  return 0;
367 }
368 
369 void
371 {
372  SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
373  CFDictionaryRef dict = NULL;
374  CFArrayRef modes;
375 
376  /* By default, CGDisplayCopyAllDisplayModes will only get a subset of the
377  * system's available modes. For example on a 15" 2016 MBP, users can
378  * choose 1920x1080@2x in System Preferences but it won't show up here,
379  * unless we specify the option below.
380  */
381 #ifdef MAC_OS_X_VERSION_10_8
382  if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) {
383  const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
384  const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
385  dict = CFDictionaryCreate(NULL,
386  (const void **)dictkeys,
387  (const void **)dictvalues,
388  1,
389  &kCFCopyStringDictionaryKeyCallBacks,
390  &kCFTypeDictionaryValueCallBacks);
391  }
392 #endif
393 
394  modes = CGDisplayCopyAllDisplayModes(data->display, dict);
395 
396  if (dict != NULL) {
397  CFRelease(dict);
398  }
399 
400  if (modes) {
401  CVDisplayLinkRef link = NULL;
402  const CFIndex count = CFArrayGetCount(modes);
403  CFIndex i;
404 
405  CVDisplayLinkCreateWithCGDisplay(data->display, &link);
406 
407  for (i = 0; i < count; i++) {
408  CGDisplayModeRef moderef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
410  if (GetDisplayMode(_this, moderef, modes, link, &mode)) {
411  CGDisplayModeRetain(moderef);
412  SDL_AddDisplayMode(display, &mode);
413  }
414  }
415 
416  CVDisplayLinkRelease(link);
417  CFRelease(modes);
418  }
419 }
420 
421 int
423 {
424  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
426  CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
427  CGError result;
428 
429  /* Fade to black to hide resolution-switching flicker */
430  if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
431  CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
432  }
433 
434  if (data == display->desktop_mode.driverdata) {
435  /* Restoring desktop mode */
436  CGDisplaySetDisplayMode(displaydata->display, data->moderef, NULL);
437 
438  if (CGDisplayIsMain(displaydata->display)) {
439  CGReleaseAllDisplays();
440  } else {
441  CGDisplayRelease(displaydata->display);
442  }
443 
444  if (CGDisplayIsMain(displaydata->display)) {
445  Cocoa_ToggleMenuBar(YES);
446  }
447  } else {
448  /* Put up the blanking window (a window above all other windows) */
449  if (CGDisplayIsMain(displaydata->display)) {
450  /* If we don't capture all displays, Cocoa tries to rearrange windows... *sigh* */
451  result = CGCaptureAllDisplays();
452  } else {
453  result = CGDisplayCapture(displaydata->display);
454  }
455  if (result != kCGErrorSuccess) {
456  CG_SetError("CGDisplayCapture()", result);
457  goto ERR_NO_CAPTURE;
458  }
459 
460  /* Do the physical switch */
461  result = CGDisplaySetDisplayMode(displaydata->display, data->moderef, NULL);
462  if (result != kCGErrorSuccess) {
463  CG_SetError("CGDisplaySwitchToMode()", result);
464  goto ERR_NO_SWITCH;
465  }
466 
467  /* Hide the menu bar so it doesn't intercept events */
468  if (CGDisplayIsMain(displaydata->display)) {
469  Cocoa_ToggleMenuBar(NO);
470  }
471  }
472 
473  /* Fade in again (asynchronously) */
474  if (fade_token != kCGDisplayFadeReservationInvalidToken) {
475  CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
476  CGReleaseDisplayFadeReservation(fade_token);
477  }
478 
479  return 0;
480 
481  /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
482 ERR_NO_SWITCH:
483  CGDisplayRelease(displaydata->display);
484 ERR_NO_CAPTURE:
485  if (fade_token != kCGDisplayFadeReservationInvalidToken) {
486  CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
487  CGReleaseDisplayFadeReservation(fade_token);
488  }
489  return -1;
490 }
491 
492 void
494 {
495  int i, j;
496 
497  for (i = 0; i < _this->num_displays; ++i) {
498  SDL_VideoDisplay *display = &_this->displays[i];
500 
501  if (display->current_mode.driverdata != display->desktop_mode.driverdata) {
502  Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
503  }
504 
505  mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata;
506  CGDisplayModeRelease(mode->moderef);
507 
508  for (j = 0; j < display->num_display_modes; j++) {
509  mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata;
510  CGDisplayModeRelease(mode->moderef);
511  }
512 
513  }
514  Cocoa_ToggleMenuBar(YES);
515 }
516 
517 #endif /* SDL_VIDEO_DRIVER_COCOA */
518 
519 /* vi: set ts=4 sw=4 expandtab: */
void Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay *display)
GLuint64EXT * result
EGLSurface EGLnsecsANDROID time
Definition: eglext.h:518
GLuint GLuint GLsizei count
Definition: SDL_opengl.h:1571
CGDisplayModeRef moderef
SDL_Rect rect
Definition: testrelative.c:27
The structure that defines a display mode.
Definition: SDL_video.h:53
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
CGDirectDisplayID display
int SDL_AddVideoDisplay(const SDL_VideoDisplay *display)
Definition: SDL_video.c:606
GLint GLint GLsizei width
Definition: SDL_opengl.h:1572
int Cocoa_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay *display, SDL_Rect *rect)
static SDL_VideoDevice * _this
Definition: SDL_video.c:121
int Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
#define _THIS
#define SDL_stack_alloc(type, count)
Definition: SDL_stdinc.h:354
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:130
int Cocoa_GetDisplayDPI(_THIS, SDL_VideoDisplay *display, float *ddpi, float *hpdi, float *vdpi)
SDL_DisplayMode current_mode
Definition: SDL_sysvideo.h:132
GLenum mode
SDL_VideoDisplay * displays
Definition: SDL_sysvideo.h:312
#define SDL_zero(x)
Definition: SDL_stdinc.h:385
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:169
#define NULL
Definition: begin_code.h:164
SDL_bool
Definition: SDL_stdinc.h:139
SDL_DisplayMode desktop_mode
Definition: SDL_sysvideo.h:131
#define SDL_SetError
GLint GLint GLsizei GLsizei height
Definition: SDL_opengl.h:1572
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:743
#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:355
#define FALSE
Definition: edid-parse.c:34
void Cocoa_QuitModes(_THIS)
GLuint in
SDL_Renderer * screen
#define floor
Definition: math_private.h:37
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:3911
A rectangle, with the origin at the upper left.
Definition: SDL_rect.h:64