SDL  2.0
SDL_cocoamodes.m
Go to the documentation of this file.
1 /*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2019 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  SDL_bool isstack;
191  int pass, i;
192 
193  result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
194  if (result != kCGErrorSuccess) {
195  CG_SetError("CGGetOnlineDisplayList()", result);
196  return;
197  }
198  displays = SDL_small_alloc(CGDirectDisplayID, numDisplays, &isstack);
199  result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
200  if (result != kCGErrorSuccess) {
201  CG_SetError("CGGetOnlineDisplayList()", result);
202  SDL_small_free(displays, isstack);
203  return;
204  }
205 
206  /* Pick up the primary display in the first pass, then get the rest */
207  for (pass = 0; pass < 2; ++pass) {
208  for (i = 0; i < numDisplays; ++i) {
209  SDL_VideoDisplay display;
210  SDL_DisplayData *displaydata;
212  CGDisplayModeRef moderef = NULL;
213  CVDisplayLinkRef link = NULL;
214 
215  if (pass == 0) {
216  if (!CGDisplayIsMain(displays[i])) {
217  continue;
218  }
219  } else {
220  if (CGDisplayIsMain(displays[i])) {
221  continue;
222  }
223  }
224 
225  if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) {
226  continue;
227  }
228 
229  moderef = CGDisplayCopyDisplayMode(displays[i]);
230 
231  if (!moderef) {
232  continue;
233  }
234 
235  displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
236  if (!displaydata) {
237  CGDisplayModeRelease(moderef);
238  continue;
239  }
240  displaydata->display = displays[i];
241 
242  CVDisplayLinkCreateWithCGDisplay(displays[i], &link);
243 
244  SDL_zero(display);
245  /* this returns a stddup'ed string */
246  display.name = (char *)Cocoa_GetDisplayName(displays[i]);
247  if (!GetDisplayMode(_this, moderef, link, &mode)) {
248  CVDisplayLinkRelease(link);
249  CGDisplayModeRelease(moderef);
250  SDL_free(display.name);
251  SDL_free(displaydata);
252  continue;
253  }
254 
255  CVDisplayLinkRelease(link);
256 
257  display.desktop_mode = mode;
258  display.current_mode = mode;
259  display.driverdata = displaydata;
260  SDL_AddVideoDisplay(&display);
261  SDL_free(display.name);
262  }
263  }
264  SDL_small_free(displays, isstack);
265 }}
266 
267 int
269 {
270  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
271  CGRect cgrect;
272 
273  cgrect = CGDisplayBounds(displaydata->display);
274  rect->x = (int)cgrect.origin.x;
275  rect->y = (int)cgrect.origin.y;
276  rect->w = (int)cgrect.size.width;
277  rect->h = (int)cgrect.size.height;
278  return 0;
279 }
280 
281 int
283 {
284  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
285  const CGDirectDisplayID cgdisplay = displaydata->display;
286  NSArray *screens = [NSScreen screens];
287  NSScreen *screen = nil;
288 
289  /* !!! FIXME: maybe track the NSScreen in SDL_DisplayData? */
290  for (NSScreen *i in screens) {
291  const CGDirectDisplayID thisDisplay = (CGDirectDisplayID) [[[i deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
292  if (thisDisplay == cgdisplay) {
293  screen = i;
294  break;
295  }
296  }
297 
298  SDL_assert(screen != nil); /* didn't find it?! */
299  if (screen == nil) {
300  return -1;
301  }
302 
303  const NSRect frame = [screen visibleFrame];
304  rect->x = (int)frame.origin.x;
305  rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height);
306  rect->w = (int)frame.size.width;
307  rect->h = (int)frame.size.height;
308 
309  return 0;
310 }
311 
312 int
313 Cocoa_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi)
314 {
315  const float MM_IN_INCH = 25.4f;
316 
317  SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
318 
319  CGSize displaySize = CGDisplayScreenSize(data->display);
320  int pixelWidth = (int) CGDisplayPixelsWide(data->display);
321  int pixelHeight = (int) CGDisplayPixelsHigh(data->display);
322 
323  if (ddpi) {
324  *ddpi = SDL_ComputeDiagonalDPI(pixelWidth, pixelHeight, displaySize.width / MM_IN_INCH, displaySize.height / MM_IN_INCH);
325  }
326  if (hdpi) {
327  *hdpi = pixelWidth * MM_IN_INCH / displaySize.width;
328  }
329  if (vdpi) {
330  *vdpi = pixelHeight * MM_IN_INCH / displaySize.height;
331  }
332 
333  return 0;
334 }
335 
336 void
338 {
339  SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
340  CVDisplayLinkRef link = NULL;
341  CGDisplayModeRef desktopmoderef;
342  SDL_DisplayMode desktopmode;
343  CFArrayRef modes;
344 
345  CVDisplayLinkCreateWithCGDisplay(data->display, &link);
346 
347  desktopmoderef = CGDisplayCopyDisplayMode(data->display);
348 
349  /* CopyAllDisplayModes won't always contain the desktop display mode (if
350  * NULL is passed in) - for example on a retina 15" MBP, System Preferences
351  * allows choosing 1920x1200 but it's not in the list. AddDisplayMode makes
352  * sure there are no duplicates so it's safe to always add the desktop mode
353  * even in cases where it is in the CopyAllDisplayModes list.
354  */
355  if (desktopmoderef && GetDisplayMode(_this, desktopmoderef, link, &desktopmode)) {
356  if (!SDL_AddDisplayMode(display, &desktopmode)) {
357  CGDisplayModeRelease(desktopmoderef);
358  SDL_free(desktopmode.driverdata);
359  }
360  } else {
361  CGDisplayModeRelease(desktopmoderef);
362  }
363 
364  modes = CGDisplayCopyAllDisplayModes(data->display, NULL);
365 
366  if (modes) {
367  CFIndex i;
368  const CFIndex count = CFArrayGetCount(modes);
369 
370  for (i = 0; i < count; i++) {
371  CGDisplayModeRef moderef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
373 
374  if (GetDisplayMode(_this, moderef, link, &mode)) {
375  if (SDL_AddDisplayMode(display, &mode)) {
376  CGDisplayModeRetain(moderef);
377  } else {
378  SDL_free(mode.driverdata);
379  }
380  }
381  }
382 
383  CFRelease(modes);
384  }
385 
386  CVDisplayLinkRelease(link);
387 }
388 
389 int
391 {
392  SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
394  CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
395  CGError result;
396 
397  /* Fade to black to hide resolution-switching flicker */
398  if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
399  CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
400  }
401 
402  if (data == display->desktop_mode.driverdata) {
403  /* Restoring desktop mode */
404  CGDisplaySetDisplayMode(displaydata->display, data->moderef, NULL);
405 
406  if (CGDisplayIsMain(displaydata->display)) {
407  CGReleaseAllDisplays();
408  } else {
409  CGDisplayRelease(displaydata->display);
410  }
411 
412  if (CGDisplayIsMain(displaydata->display)) {
413  Cocoa_ToggleMenuBar(YES);
414  }
415  } else {
416  /* Put up the blanking window (a window above all other windows) */
417  if (CGDisplayIsMain(displaydata->display)) {
418  /* If we don't capture all displays, Cocoa tries to rearrange windows... *sigh* */
419  result = CGCaptureAllDisplays();
420  } else {
421  result = CGDisplayCapture(displaydata->display);
422  }
423  if (result != kCGErrorSuccess) {
424  CG_SetError("CGDisplayCapture()", result);
425  goto ERR_NO_CAPTURE;
426  }
427 
428  /* Do the physical switch */
429  result = CGDisplaySetDisplayMode(displaydata->display, data->moderef, NULL);
430  if (result != kCGErrorSuccess) {
431  CG_SetError("CGDisplaySwitchToMode()", result);
432  goto ERR_NO_SWITCH;
433  }
434 
435  /* Hide the menu bar so it doesn't intercept events */
436  if (CGDisplayIsMain(displaydata->display)) {
437  Cocoa_ToggleMenuBar(NO);
438  }
439  }
440 
441  /* Fade in again (asynchronously) */
442  if (fade_token != kCGDisplayFadeReservationInvalidToken) {
443  CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
444  CGReleaseDisplayFadeReservation(fade_token);
445  }
446 
447  return 0;
448 
449  /* Since the blanking window covers *all* windows (even force quit) correct recovery is crucial */
450 ERR_NO_SWITCH:
451  CGDisplayRelease(displaydata->display);
452 ERR_NO_CAPTURE:
453  if (fade_token != kCGDisplayFadeReservationInvalidToken) {
454  CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
455  CGReleaseDisplayFadeReservation(fade_token);
456  }
457  return -1;
458 }
459 
460 void
462 {
463  int i, j;
464 
465  for (i = 0; i < _this->num_displays; ++i) {
466  SDL_VideoDisplay *display = &_this->displays[i];
468 
469  if (display->current_mode.driverdata != display->desktop_mode.driverdata) {
470  Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
471  }
472 
473  mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata;
474  CGDisplayModeRelease(mode->moderef);
475 
476  for (j = 0; j < display->num_display_modes; j++) {
477  mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata;
478  CGDisplayModeRelease(mode->moderef);
479  }
480 
481  }
482  Cocoa_ToggleMenuBar(YES);
483 }
484 
485 #endif /* SDL_VIDEO_DRIVER_COCOA */
486 
487 /* 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:603
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:118
#define SDL_small_alloc(type, count, pisstack)
Definition: SDL_internal.h:39
int Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
#define _THIS
#define SDL_free
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:316
#define SDL_zero(x)
Definition: SDL_stdinc.h:416
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
int x
Definition: SDL_rect.h:79
int w
Definition: SDL_rect.h:80
GLsizeiptr size
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:167
SDL_bool
Definition: SDL_stdinc.h:161
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:80
#define SDL_strdup
#define SDL_small_free(ptr, isstack)
Definition: SDL_internal.h:40
SDL_bool SDL_AddDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode)
Definition: SDL_video.c:751
#define SDL_malloc
int Cocoa_GetDisplayBounds(_THIS, SDL_VideoDisplay *display, SDL_Rect *rect)
Uint32 format
Definition: SDL_video.h:55
#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:79
float SDL_ComputeDiagonalDPI(int hpix, int vpix, float hinches, float vinches)
Definition: SDL_video.c:4015
A rectangle, with the origin at the upper left (integer).
Definition: SDL_rect.h:77