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 
146  mode->format = SDL_PIXELFORMAT_UNKNOWN;
147  switch (bpp) {
148  case 16:
149  mode->format = SDL_PIXELFORMAT_ARGB1555;
150  break;
151  case 30:
153  break;
154  case 32:
155  mode->format = SDL_PIXELFORMAT_ARGB8888;
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 
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 {
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 
474  CGDisplayModeRelease(mode->moderef);
475 
476  for (j = 0; j < display->num_display_modes; j++) {
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: */
SDL_zero
#define SDL_zero(x)
Definition: SDL_stdinc.h:416
SDL_PIXELFORMAT_ARGB1555
@ SDL_PIXELFORMAT_ARGB1555
Definition: SDL_pixels.h:212
SDL_VideoDisplay::display_modes
SDL_DisplayMode * display_modes
Definition: SDL_sysvideo.h:130
screen
SDL_Renderer * screen
Definition: testgamecontroller.c:64
SDL_small_free
#define SDL_small_free(ptr, isstack)
Definition: SDL_internal.h:40
SDL_VideoDisplay::name
char * name
Definition: SDL_sysvideo.h:127
time
EGLSurface EGLnsecsANDROID time
Definition: eglext.h:518
in
GLuint in
Definition: SDL_opengl_glext.h:7940
NULL
#define NULL
Definition: begin_code.h:167
width
GLint GLint GLsizei width
Definition: SDL_opengl.h:1572
TRUE
#define TRUE
Definition: edid-parse.c:33
mode
GLenum mode
Definition: SDL_opengl_glext.h:1122
count
GLuint GLuint GLsizei count
Definition: SDL_opengl.h:1571
Cocoa_GetDisplayDPI
int Cocoa_GetDisplayDPI(_THIS, SDL_VideoDisplay *display, float *ddpi, float *hpdi, float *vdpi)
SDL_VideoDisplay::desktop_mode
SDL_DisplayMode desktop_mode
Definition: SDL_sysvideo.h:131
SDL_cocoavideo.h
Cocoa_GetDisplayBounds
int Cocoa_GetDisplayBounds(_THIS, SDL_VideoDisplay *display, SDL_Rect *rect)
SDL_small_alloc
#define SDL_small_alloc(type, count, pisstack)
Definition: SDL_internal.h:39
SDL_Rect::x
int x
Definition: SDL_rect.h:79
result
GLuint64EXT * result
Definition: SDL_opengl_glext.h:9432
data
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
SDL_Rect::w
int w
Definition: SDL_rect.h:80
SDL_DisplayMode
The structure that defines a display mode.
Definition: SDL_video.h:53
SDL_AddDisplayMode
SDL_bool SDL_AddDisplayMode(SDL_VideoDisplay *display, const SDL_DisplayMode *mode)
Definition: SDL_video.c:751
SDL_FALSE
@ SDL_FALSE
Definition: SDL_stdinc.h:163
_this
static SDL_VideoDevice * _this
Definition: SDL_video.c:118
Cocoa_GetDisplayUsableBounds
int Cocoa_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay *display, SDL_Rect *rect)
SDL_VideoDevice::num_displays
int num_displays
Definition: SDL_sysvideo.h:315
Cocoa_SetDisplayMode
int Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
SDL_Rect::y
int y
Definition: SDL_rect.h:79
SDL_Rect::h
int h
Definition: SDL_rect.h:80
SDL_free
#define SDL_free
Definition: SDL_dynapi_overrides.h:377
height
GLint GLint GLsizei GLsizei height
Definition: SDL_opengl.h:1572
SDL_PIXELFORMAT_ARGB2101010
@ SDL_PIXELFORMAT_ARGB2101010
Definition: SDL_pixels.h:260
SDL_VideoDevice::displays
SDL_VideoDisplay * displays
Definition: SDL_sysvideo.h:316
rect
SDL_Rect rect
Definition: testrelative.c:27
Cocoa_GetDisplayModes
void Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay *display)
frame
int frame
Definition: teststreaming.c:60
SDL_assert.h
Cocoa_QuitModes
void Cocoa_QuitModes(_THIS)
_THIS
#define _THIS
Definition: SDL_alsa_audio.h:31
SDL_PIXELFORMAT_ARGB8888
@ SDL_PIXELFORMAT_ARGB8888
Definition: SDL_pixels.h:248
SDL_AddVideoDisplay
int SDL_AddVideoDisplay(const SDL_VideoDisplay *display)
Definition: SDL_video.c:603
SDL_assert
#define SDL_assert(condition)
Definition: SDL_assert.h:169
SDL_VideoDisplay::driverdata
void * driverdata
Definition: SDL_sysvideo.h:139
SDL_DisplayData
Definition: SDL_cocoamodes.h:26
size
GLsizeiptr size
Definition: SDL_opengl_glext.h:537
y
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
SDL_VideoDisplay::num_display_modes
int num_display_modes
Definition: SDL_sysvideo.h:129
SDL_TRUE
@ SDL_TRUE
Definition: SDL_stdinc.h:164
SDL_VideoDisplay
Definition: SDL_sysvideo.h:125
SDL_SetError
#define SDL_SetError
Definition: SDL_dynapi_overrides.h:30
SDL_Rect
A rectangle, with the origin at the upper left (integer).
Definition: SDL_rect.h:77
j
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_PIXELFORMAT_UNKNOWN
@ SDL_PIXELFORMAT_UNKNOWN
Definition: SDL_pixels.h:173
SDL_strdup
#define SDL_strdup
Definition: SDL_dynapi_overrides.h:397
SDL_ComputeDiagonalDPI
float SDL_ComputeDiagonalDPI(int hpix, int vpix, float hinches, float vinches)
Definition: SDL_video.c:4015
SDL_DisplayMode::driverdata
void * driverdata
Definition: SDL_video.h:59
Cocoa_InitModes
void Cocoa_InitModes(_THIS)
SDL_VideoDisplay::current_mode
SDL_DisplayMode current_mode
Definition: SDL_sysvideo.h:132
SDL_malloc
#define SDL_malloc
Definition: SDL_dynapi_overrides.h:374
SDL_DisplayModeData
Definition: SDL_cocoamodes.h:31
i
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
SDL_DisplayData::display
CGDirectDisplayID display
Definition: SDL_cocoamodes.h:28
FALSE
#define FALSE
Definition: edid-parse.c:34
SDL_bool
SDL_bool
Definition: SDL_stdinc.h:161