SDL  2.0
SDL_cocoamodes.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2018 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  CVDisplayLinkRef link = NULL;
344  CGDisplayModeRef desktopmoderef;
345  SDL_DisplayMode desktopmode;
346  CFArrayRef modes;
347 
348  CVDisplayLinkCreateWithCGDisplay(data->display, &link);
349 
350  desktopmoderef = CGDisplayCopyDisplayMode(data->display);
351 
352  /* CopyAllDisplayModes won't always contain the desktop display mode (if
353  * NULL is passed in) - for example on a retina 15" MBP, System Preferences
354  * allows choosing 1920x1200 but it's not in the list. AddDisplayMode makes
355  * sure there are no duplicates so it's safe to always add the desktop mode
356  * even in cases where it is in the CopyAllDisplayModes list.
357  */
358  if (desktopmoderef && GetDisplayMode(_this, desktopmoderef, link, &desktopmode)) {
359  if (!SDL_AddDisplayMode(display, &desktopmode)) {
360  CGDisplayModeRelease(desktopmoderef);
361  SDL_free(desktopmode.driverdata);
362  }
363  } else {
364  CGDisplayModeRelease(desktopmoderef);
365  }
366 
367  modes = CGDisplayCopyAllDisplayModes(data->display, NULL);
368 
369  if (modes) {
370  CFIndex i;
371  const CFIndex count = CFArrayGetCount(modes);
372 
373  for (i = 0; i < count; i++) {
374  CGDisplayModeRef moderef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
376 
377  if (GetDisplayMode(_this, moderef, link, &mode)) {
378  if (SDL_AddDisplayMode(display, &mode)) {
379  CGDisplayModeRetain(moderef);
380  } else {
381  SDL_free(mode.driverdata);
382  }
383  }
384  }
385 
386  CFRelease(modes);
387  }
388 
389  CVDisplayLinkRelease(link);
390 }
391 
392 int
394 {
395  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
397  CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
398  CGError result;
399 
400  /* Fade to black to hide resolution-switching flicker */
401  if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
402  CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
403  }
404 
405  if (data == display->desktop_mode.driverdata) {
406  /* Restoring desktop mode */
407  CGDisplaySetDisplayMode(displaydata->display, data->moderef, NULL);
408 
409  if (CGDisplayIsMain(displaydata->display)) {
410  CGReleaseAllDisplays();
411  } else {
412  CGDisplayRelease(displaydata->display);
413  }
414 
415  if (CGDisplayIsMain(displaydata->display)) {
416  Cocoa_ToggleMenuBar(YES);
417  }
418  } else {
419  /* Put up the blanking window (a window above all other windows) */
420  if (CGDisplayIsMain(displaydata->display)) {
421  /* If we don't capture all displays, Cocoa tries to rearrange windows... *sigh* */
422  result = CGCaptureAllDisplays();
423  } else {
424  result = CGDisplayCapture(displaydata->display);
425  }
426  if (result != kCGErrorSuccess) {
427  CG_SetError("CGDisplayCapture()", result);
428  goto ERR_NO_CAPTURE;
429  }
430 
431  /* Do the physical switch */
432  result = CGDisplaySetDisplayMode(displaydata->display, data->moderef, NULL);
433  if (result != kCGErrorSuccess) {
434  CG_SetError("CGDisplaySwitchToMode()", result);
435  goto ERR_NO_SWITCH;
436  }
437 
438  /* Hide the menu bar so it doesn't intercept events */
439  if (CGDisplayIsMain(displaydata->display)) {
440  Cocoa_ToggleMenuBar(NO);
441  }
442  }
443 
444  /* Fade in again (asynchronously) */
445  if (fade_token != kCGDisplayFadeReservationInvalidToken) {
446  CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
447  CGReleaseDisplayFadeReservation(fade_token);
448  }
449 
450  return 0;
451 
452  /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
453 ERR_NO_SWITCH:
454  CGDisplayRelease(displaydata->display);
455 ERR_NO_CAPTURE:
456  if (fade_token != kCGDisplayFadeReservationInvalidToken) {
457  CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
458  CGReleaseDisplayFadeReservation(fade_token);
459  }
460  return -1;
461 }
462 
463 void
465 {
466  int i, j;
467 
468  for (i = 0; i < _this->num_displays; ++i) {
469  SDL_VideoDisplay *display = &_this->displays[i];
471 
472  if (display->current_mode.driverdata != display->desktop_mode.driverdata) {
473  Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
474  }
475 
476  mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata;
477  CGDisplayModeRelease(mode->moderef);
478 
479  for (j = 0; j < display->num_display_modes; j++) {
480  mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata;
481  CGDisplayModeRelease(mode->moderef);
482  }
483 
484  }
485  Cocoa_ToggleMenuBar(YES);
486 }
487 
488 #endif /* SDL_VIDEO_DRIVER_COCOA */
489 
490 /* 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_free
#define SDL_stack_alloc(type, count)
Definition: SDL_stdinc.h:354
int frame
Definition: teststreaming.c:60
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:416
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
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