summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamodes.m
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamodes.m')
-rw-r--r--contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamodes.m716
1 files changed, 716 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamodes.m b/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamodes.m
new file mode 100644
index 0000000..b3c34ba
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoamodes.m
@@ -0,0 +1,716 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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
23#ifdef SDL_VIDEO_DRIVER_COCOA
24
25#include "SDL_cocoavideo.h"
26#include "../../events/SDL_events_c.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#if (IOGRAPHICSTYPES_REV < 40)
36#define kDisplayModeNativeFlag 0x02000000
37#endif
38
39static bool CG_SetError(const char *prefix, CGDisplayErr result)
40{
41 const char *error;
42
43 switch (result) {
44 case kCGErrorFailure:
45 error = "kCGErrorFailure";
46 break;
47 case kCGErrorIllegalArgument:
48 error = "kCGErrorIllegalArgument";
49 break;
50 case kCGErrorInvalidConnection:
51 error = "kCGErrorInvalidConnection";
52 break;
53 case kCGErrorInvalidContext:
54 error = "kCGErrorInvalidContext";
55 break;
56 case kCGErrorCannotComplete:
57 error = "kCGErrorCannotComplete";
58 break;
59 case kCGErrorNotImplemented:
60 error = "kCGErrorNotImplemented";
61 break;
62 case kCGErrorRangeCheck:
63 error = "kCGErrorRangeCheck";
64 break;
65 case kCGErrorTypeCheck:
66 error = "kCGErrorTypeCheck";
67 break;
68 case kCGErrorInvalidOperation:
69 error = "kCGErrorInvalidOperation";
70 break;
71 case kCGErrorNoneAvailable:
72 error = "kCGErrorNoneAvailable";
73 break;
74 default:
75 error = "Unknown Error";
76 break;
77 }
78 return SDL_SetError("%s: %s", prefix, error);
79}
80
81static NSScreen *GetNSScreenForDisplayID(CGDirectDisplayID displayID)
82{
83 NSArray *screens = [NSScreen screens];
84
85 // !!! FIXME: maybe track the NSScreen in SDL_DisplayData?
86 for (NSScreen *screen in screens) {
87 const CGDirectDisplayID thisDisplay = (CGDirectDisplayID)[[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
88 if (thisDisplay == displayID) {
89 return screen;
90 }
91 }
92 return nil;
93}
94
95SDL_VideoDisplay *Cocoa_FindSDLDisplayByCGDirectDisplayID(SDL_VideoDevice *_this, CGDirectDisplayID displayid)
96{
97 for (int i = 0; i < _this->num_displays; i++) {
98 const SDL_DisplayData *displaydata = _this->displays[i]->internal;
99 if (displaydata && (displaydata->display == displayid)) {
100 return _this->displays[i];
101 }
102 }
103 return NULL;
104}
105
106static float GetDisplayModeRefreshRate(CGDisplayModeRef vidmode, CVDisplayLinkRef link)
107{
108 float refreshRate = (float)CGDisplayModeGetRefreshRate(vidmode);
109
110 // CGDisplayModeGetRefreshRate can return 0 (eg for built-in displays).
111 if (refreshRate == 0 && link != NULL) {
112 CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
113 if ((time.flags & kCVTimeIsIndefinite) == 0 && time.timeValue != 0) {
114 refreshRate = (float)time.timeScale / time.timeValue;
115 }
116 }
117
118 return refreshRate;
119}
120
121static bool HasValidDisplayModeFlags(CGDisplayModeRef vidmode)
122{
123 uint32_t ioflags = CGDisplayModeGetIOFlags(vidmode);
124
125 // Filter out modes which have flags that we don't want.
126 if (ioflags & (kDisplayModeNeverShowFlag | kDisplayModeNotGraphicsQualityFlag)) {
127 return false;
128 }
129
130 // Filter out modes which don't have flags that we want.
131 if (!(ioflags & kDisplayModeValidFlag) || !(ioflags & kDisplayModeSafeFlag)) {
132 return false;
133 }
134
135 return true;
136}
137
138static Uint32 GetDisplayModePixelFormat(CGDisplayModeRef vidmode)
139{
140 // This API is deprecated in 10.11 with no good replacement (as of 10.15).
141 CFStringRef fmt = CGDisplayModeCopyPixelEncoding(vidmode);
142 Uint32 pixelformat = SDL_PIXELFORMAT_UNKNOWN;
143
144 if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels),
145 kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
146 pixelformat = SDL_PIXELFORMAT_ARGB8888;
147 } else if (CFStringCompare(fmt, CFSTR(IO16BitDirectPixels),
148 kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
149 pixelformat = SDL_PIXELFORMAT_ARGB1555;
150 } else if (CFStringCompare(fmt, CFSTR(kIO30BitDirectPixels),
151 kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
152 pixelformat = SDL_PIXELFORMAT_ARGB2101010;
153 } else {
154 // ignore 8-bit and such for now.
155 }
156
157 CFRelease(fmt);
158
159 return pixelformat;
160}
161
162static bool GetDisplayMode(CGDisplayModeRef vidmode, bool vidmodeCurrent, CFArrayRef modelist, CVDisplayLinkRef link, SDL_DisplayMode *mode)
163{
164 SDL_DisplayModeData *data;
165 bool usableForGUI = CGDisplayModeIsUsableForDesktopGUI(vidmode);
166 size_t width = CGDisplayModeGetWidth(vidmode);
167 size_t height = CGDisplayModeGetHeight(vidmode);
168 size_t pixelW = width;
169 size_t pixelH = height;
170 uint32_t ioflags = CGDisplayModeGetIOFlags(vidmode);
171 float refreshrate = GetDisplayModeRefreshRate(vidmode, link);
172 Uint32 format = GetDisplayModePixelFormat(vidmode);
173 bool interlaced = (ioflags & kDisplayModeInterlacedFlag) != 0;
174 CFMutableArrayRef modes;
175
176 if (format == SDL_PIXELFORMAT_UNKNOWN) {
177 return false;
178 }
179
180 /* Don't fail the current mode based on flags because this could prevent Cocoa_InitModes from
181 * succeeding if the current mode lacks certain flags (esp kDisplayModeSafeFlag). */
182 if (!vidmodeCurrent && !HasValidDisplayModeFlags(vidmode)) {
183 return false;
184 }
185
186 modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
187 CFArrayAppendValue(modes, vidmode);
188
189 /* If a list of possible display modes is passed in, use it to filter out
190 * modes that have duplicate sizes. We don't just rely on SDL's higher level
191 * duplicate filtering because this code can choose what properties are
192 * preferred, and it can add CGDisplayModes to the DisplayModeData's list of
193 * modes to try (see comment below for why that's necessary). */
194 pixelW = CGDisplayModeGetPixelWidth(vidmode);
195 pixelH = CGDisplayModeGetPixelHeight(vidmode);
196
197 if (modelist != NULL) {
198 CFIndex modescount = CFArrayGetCount(modelist);
199 int i;
200
201 for (i = 0; i < modescount; i++) {
202 size_t otherW, otherH, otherpixelW, otherpixelH;
203 float otherrefresh;
204 Uint32 otherformat;
205 bool otherGUI;
206 CGDisplayModeRef othermode = (CGDisplayModeRef)CFArrayGetValueAtIndex(modelist, i);
207 uint32_t otherioflags = CGDisplayModeGetIOFlags(othermode);
208
209 if (CFEqual(vidmode, othermode)) {
210 continue;
211 }
212
213 if (!HasValidDisplayModeFlags(othermode)) {
214 continue;
215 }
216
217 otherW = CGDisplayModeGetWidth(othermode);
218 otherH = CGDisplayModeGetHeight(othermode);
219 otherpixelW = CGDisplayModeGetPixelWidth(othermode);
220 otherpixelH = CGDisplayModeGetPixelHeight(othermode);
221 otherrefresh = GetDisplayModeRefreshRate(othermode, link);
222 otherformat = GetDisplayModePixelFormat(othermode);
223 otherGUI = CGDisplayModeIsUsableForDesktopGUI(othermode);
224
225 /* Ignore this mode if it's interlaced and there's a non-interlaced
226 * mode in the list with the same properties.
227 */
228 if (interlaced && ((otherioflags & kDisplayModeInterlacedFlag) == 0) && width == otherW && height == otherH && pixelW == otherpixelW && pixelH == otherpixelH && refreshrate == otherrefresh && format == otherformat && usableForGUI == otherGUI) {
229 CFRelease(modes);
230 return false;
231 }
232
233 /* Ignore this mode if it's not usable for desktop UI and its
234 * properties are equal to another GUI-capable mode in the list.
235 */
236 if (width == otherW && height == otherH && pixelW == otherpixelW && pixelH == otherpixelH && !usableForGUI && otherGUI && refreshrate == otherrefresh && format == otherformat) {
237 CFRelease(modes);
238 return false;
239 }
240
241 /* If multiple modes have the exact same properties, they'll all
242 * go in the list of modes to try when SetDisplayMode is called.
243 * This is needed because kCGDisplayShowDuplicateLowResolutionModes
244 * (which is used to expose highdpi display modes) can make the
245 * list of modes contain duplicates (according to their properties
246 * obtained via public APIs) which don't work with SetDisplayMode.
247 * Those duplicate non-functional modes *do* have different pixel
248 * formats according to their internal data structure viewed with
249 * NSLog, but currently no public API can detect that.
250 * https://bugzilla.libsdl.org/show_bug.cgi?id=4822
251 *
252 * As of macOS 10.15.0, those duplicates have the exact same
253 * properties via public APIs in every way (even their IO flags and
254 * CGDisplayModeGetIODisplayModeID is the same), so we could test
255 * those for equality here too, but I'm intentionally not doing that
256 * in case there are duplicate modes with different IO flags or IO
257 * display mode IDs in the future. In that case I think it's better
258 * to try them all in SetDisplayMode than to risk one of them being
259 * correct but it being filtered out by SDL_AddFullscreenDisplayMode
260 * as being a duplicate.
261 */
262 if (width == otherW && height == otherH && pixelW == otherpixelW && pixelH == otherpixelH && usableForGUI == otherGUI && refreshrate == otherrefresh && format == otherformat) {
263 CFArrayAppendValue(modes, othermode);
264 }
265 }
266 }
267
268 SDL_zerop(mode);
269 data = (SDL_DisplayModeData *)SDL_malloc(sizeof(*data));
270 if (!data) {
271 CFRelease(modes);
272 return false;
273 }
274 data->modes = modes;
275 mode->format = format;
276 mode->w = (int)width;
277 mode->h = (int)height;
278 mode->pixel_density = (float)pixelW / width;
279 mode->refresh_rate = refreshrate;
280 mode->internal = data;
281 return true;
282}
283
284static char *Cocoa_GetDisplayName(CGDirectDisplayID displayID)
285{
286 if (@available(macOS 10.15, *)) {
287 NSScreen *screen = GetNSScreenForDisplayID(displayID);
288 if (screen) {
289 const char *name = [screen.localizedName UTF8String];
290 if (name) {
291 return SDL_strdup(name);
292 }
293 }
294 }
295
296 // This API is deprecated in 10.9 with no good replacement (as of 10.15).
297 io_service_t servicePort = CGDisplayIOServicePort(displayID);
298 CFDictionaryRef deviceInfo = IODisplayCreateInfoDictionary(servicePort, kIODisplayOnlyPreferredName);
299 NSDictionary *localizedNames = [(__bridge NSDictionary *)deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
300 char *displayName = NULL;
301
302 if ([localizedNames count] > 0) {
303 displayName = SDL_strdup([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]);
304 }
305 CFRelease(deviceInfo);
306 return displayName;
307}
308
309static void Cocoa_GetHDRProperties(CGDirectDisplayID displayID, SDL_HDROutputProperties *HDR)
310{
311 HDR->SDR_white_level = 1.0f;
312 HDR->HDR_headroom = 1.0f;
313
314 if (@available(macOS 10.15, *)) {
315 NSScreen *screen = GetNSScreenForDisplayID(displayID);
316 if (screen) {
317 if (screen.maximumExtendedDynamicRangeColorComponentValue > 1.0f) {
318 HDR->HDR_headroom = screen.maximumExtendedDynamicRangeColorComponentValue;
319 } else {
320 HDR->HDR_headroom = screen.maximumPotentialExtendedDynamicRangeColorComponentValue;
321 }
322 }
323 }
324}
325
326
327bool Cocoa_AddDisplay(CGDirectDisplayID display, bool send_event)
328{
329 CGDisplayModeRef moderef = CGDisplayCopyDisplayMode(display);
330 if (!moderef) {
331 return false;
332 }
333
334 SDL_DisplayData *displaydata = (SDL_DisplayData *)SDL_malloc(sizeof(*displaydata));
335 if (!displaydata) {
336 CGDisplayModeRelease(moderef);
337 return false;
338 }
339 displaydata->display = display;
340
341 CVDisplayLinkRef link = NULL;
342 CVDisplayLinkCreateWithCGDisplay(display, &link);
343
344 SDL_VideoDisplay viddisplay;
345 SDL_zero(viddisplay);
346 viddisplay.name = Cocoa_GetDisplayName(display); // this returns a strdup'ed string
347
348 SDL_DisplayMode mode;
349 if (!GetDisplayMode(moderef, true, NULL, link, &mode)) {
350 CVDisplayLinkRelease(link);
351 CGDisplayModeRelease(moderef);
352 SDL_free(viddisplay.name);
353 SDL_free(displaydata);
354 return false;
355 }
356
357 CVDisplayLinkRelease(link);
358 CGDisplayModeRelease(moderef);
359
360 Cocoa_GetHDRProperties(displaydata->display, &viddisplay.HDR);
361
362 viddisplay.desktop_mode = mode;
363 viddisplay.internal = displaydata;
364 const bool retval = SDL_AddVideoDisplay(&viddisplay, send_event);
365 SDL_free(viddisplay.name);
366 return retval;
367}
368
369static void Cocoa_DisplayReconfigurationCallback(CGDirectDisplayID displayid, CGDisplayChangeSummaryFlags flags, void *userInfo)
370{
371 #if 0
372 SDL_Log("COCOA DISPLAY RECONFIG CALLBACK! display=%u", (unsigned int) displayid);
373 #define CHECK_DISPLAY_RECONFIG_FLAG(x) if (flags & x) { SDL_Log(" - " #x); }
374 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayBeginConfigurationFlag);
375 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayMovedFlag);
376 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplaySetMainFlag);
377 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplaySetModeFlag);
378 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayAddFlag);
379 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayRemoveFlag);
380 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayEnabledFlag);
381 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayDisabledFlag);
382 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayMirrorFlag);
383 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayUnMirrorFlag);
384 CHECK_DISPLAY_RECONFIG_FLAG(kCGDisplayDesktopShapeChangedFlag);
385 #undef CHECK_DISPLAY_RECONFIG_FLAG
386 #endif
387
388 SDL_VideoDevice *_this = (SDL_VideoDevice *) userInfo;
389 SDL_VideoDisplay *display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid); // will be NULL for newly-added (or newly-unmirrored) displays!
390
391 if (flags & kCGDisplayDisabledFlag) {
392 flags |= kCGDisplayRemoveFlag; // treat this like a display leaving, even though it's still plugged in.
393 }
394
395 if (flags & kCGDisplayEnabledFlag) {
396 flags |= kCGDisplayAddFlag; // treat this like a display leaving, even though it's still plugged in.
397 }
398
399 if (flags & kCGDisplayMirrorFlag) {
400 flags |= kCGDisplayRemoveFlag; // treat this like a display leaving, even though it's still actually here.
401 }
402
403 if (flags & kCGDisplayUnMirrorFlag) {
404 flags |= kCGDisplayAddFlag; // treat this like a new display arriving, even though it was here all along.
405 }
406
407 if ((flags & kCGDisplayAddFlag) && (flags & kCGDisplayRemoveFlag)) {
408 // sometimes you get a removed device that gets Add and Remove flags at the same time but the display dimensions are 0x0 or 1x1, hence the `> 1` test.
409 // Mirrored things are always removed, since they don't represent a discrete display in this state.
410 if (((flags & kCGDisplayMirrorFlag) == 0) && (CGDisplayPixelsWide(displayid) > 1)) {
411 // Final state is connected
412 flags &= ~kCGDisplayRemoveFlag;
413 } else {
414 // Final state is disconnected
415 flags &= ~kCGDisplayAddFlag;
416 }
417 }
418
419 if (flags & kCGDisplayAddFlag) {
420 if (!display) {
421 if (!Cocoa_AddDisplay(displayid, true)) {
422 return; // oh well.
423 }
424 display = Cocoa_FindSDLDisplayByCGDirectDisplayID(_this, displayid);
425 SDL_assert(display != NULL);
426 }
427 }
428
429 if (flags & kCGDisplayRemoveFlag) {
430 if (display) {
431 SDL_DelVideoDisplay(display->id, true);
432 display = NULL;
433 }
434 }
435
436 if (flags & kCGDisplaySetModeFlag) {
437 if (display) {
438 CGDisplayModeRef moderef = CGDisplayCopyDisplayMode(displayid);
439 if (moderef) {
440 CVDisplayLinkRef link = NULL;
441 CVDisplayLinkCreateWithCGDisplay(displayid, &link);
442 if (link) {
443 SDL_DisplayMode mode;
444 if (GetDisplayMode(moderef, true, NULL, link, &mode)) {
445 SDL_SetDesktopDisplayMode(display, &mode);
446 }
447 CVDisplayLinkRelease(link);
448 }
449 CGDisplayModeRelease(moderef);
450 }
451 }
452 }
453
454 if (flags & kCGDisplaySetMainFlag) {
455 if (display) {
456 for (int i = 0; i < _this->num_displays; i++) {
457 if (_this->displays[i] == display) {
458 if (i > 0) {
459 // move this display to the front of _this->displays so it's treated as primary.
460 SDL_memmove(&_this->displays[1], &_this->displays[0], sizeof (*_this->displays) * i);
461 _this->displays[0] = display;
462 }
463 flags |= kCGDisplayMovedFlag; // we don't have an SDL event atm for "this display became primary," so at least let everyone know it "moved".
464 break;
465 }
466 }
467 }
468 }
469
470 if (flags & kCGDisplayMovedFlag) {
471 if (display) {
472 SDL_SendDisplayEvent(display, SDL_EVENT_DISPLAY_MOVED, 0, 0);
473 }
474 }
475
476 if (flags & kCGDisplayDesktopShapeChangedFlag) {
477 SDL_UpdateDesktopBounds();
478 }
479}
480
481void Cocoa_InitModes(SDL_VideoDevice *_this)
482{
483 @autoreleasepool {
484 CGDisplayErr result;
485 CGDisplayCount numDisplays = 0;
486
487 result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
488 if (result != kCGErrorSuccess) {
489 CG_SetError("CGGetOnlineDisplayList()", result);
490 return;
491 }
492
493 bool isstack;
494 CGDirectDisplayID *displays = SDL_small_alloc(CGDirectDisplayID, numDisplays, &isstack);
495
496 result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
497 if (result != kCGErrorSuccess) {
498 CG_SetError("CGGetOnlineDisplayList()", result);
499 SDL_small_free(displays, isstack);
500 return;
501 }
502
503 // future updates to the display graph will come through this callback.
504 CGDisplayRegisterReconfigurationCallback(Cocoa_DisplayReconfigurationCallback, _this);
505
506 // Pick up the primary display in the first pass, then get the rest
507 for (int pass = 0; pass < 2; ++pass) {
508 for (int i = 0; i < numDisplays; ++i) {
509 if (pass == 0) {
510 if (!CGDisplayIsMain(displays[i])) {
511 continue;
512 }
513 } else {
514 if (CGDisplayIsMain(displays[i])) {
515 continue;
516 }
517 }
518
519 if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) {
520 continue;
521 }
522
523 Cocoa_AddDisplay(displays[i], false);
524 }
525 }
526 SDL_small_free(displays, isstack);
527 }
528}
529
530void Cocoa_UpdateDisplays(SDL_VideoDevice *_this)
531{
532 SDL_HDROutputProperties HDR;
533 int i;
534
535 for (i = 0; i < _this->num_displays; ++i) {
536 SDL_VideoDisplay *display = _this->displays[i];
537 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
538
539 Cocoa_GetHDRProperties(displaydata->display, &HDR);
540 SDL_SetDisplayHDRProperties(display, &HDR);
541 }
542}
543
544bool Cocoa_GetDisplayBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
545{
546 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
547 CGRect cgrect;
548
549 cgrect = CGDisplayBounds(displaydata->display);
550 rect->x = (int)cgrect.origin.x;
551 rect->y = (int)cgrect.origin.y;
552 rect->w = (int)cgrect.size.width;
553 rect->h = (int)cgrect.size.height;
554 return true;
555}
556
557bool Cocoa_GetDisplayUsableBounds(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_Rect *rect)
558{
559 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
560 NSScreen *screen = GetNSScreenForDisplayID(displaydata->display);
561
562 if (screen == nil) {
563 return SDL_SetError("Couldn't get NSScreen for display");
564 }
565
566 {
567 const NSRect frame = [screen visibleFrame];
568 rect->x = (int)frame.origin.x;
569 rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height);
570 rect->w = (int)frame.size.width;
571 rect->h = (int)frame.size.height;
572 }
573
574 return true;
575}
576
577bool Cocoa_GetDisplayModes(SDL_VideoDevice *_this, SDL_VideoDisplay *display)
578{
579 SDL_DisplayData *data = (SDL_DisplayData *)display->internal;
580 CVDisplayLinkRef link = NULL;
581 CFArrayRef modes;
582 CFDictionaryRef dict = NULL;
583 const CFStringRef dictkeys[] = { kCGDisplayShowDuplicateLowResolutionModes };
584 const CFBooleanRef dictvalues[] = { kCFBooleanTrue };
585
586 CVDisplayLinkCreateWithCGDisplay(data->display, &link);
587
588 /* By default, CGDisplayCopyAllDisplayModes will only get a subset of the
589 * system's available modes. For example on a 15" 2016 MBP, users can
590 * choose 1920x1080@2x in System Preferences but it won't show up here,
591 * unless we specify the option below.
592 * The display modes returned by CGDisplayCopyAllDisplayModes are also not
593 * high dpi-capable unless this option is set.
594 * macOS 10.15 also seems to have a bug where entering, exiting, and
595 * re-entering exclusive fullscreen with a low dpi display mode can cause
596 * the content of the screen to move up, which this setting avoids:
597 * https://bugzilla.libsdl.org/show_bug.cgi?id=4822
598 */
599
600 dict = CFDictionaryCreate(NULL,
601 (const void **)dictkeys,
602 (const void **)dictvalues,
603 1,
604 &kCFCopyStringDictionaryKeyCallBacks,
605 &kCFTypeDictionaryValueCallBacks);
606
607 modes = CGDisplayCopyAllDisplayModes(data->display, dict);
608
609 if (dict) {
610 CFRelease(dict);
611 }
612
613 if (modes) {
614 CFIndex i;
615 const CFIndex count = CFArrayGetCount(modes);
616
617 for (i = 0; i < count; i++) {
618 CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i);
619 SDL_DisplayMode mode;
620
621 if (GetDisplayMode(moderef, false, modes, link, &mode)) {
622 if (!SDL_AddFullscreenDisplayMode(display, &mode)) {
623 CFRelease(mode.internal->modes);
624 SDL_free(mode.internal);
625 }
626 }
627 }
628
629 CFRelease(modes);
630 }
631
632 CVDisplayLinkRelease(link);
633 return true;
634}
635
636static CGError SetDisplayModeForDisplay(CGDirectDisplayID display, SDL_DisplayModeData *data)
637{
638 /* SDL_DisplayModeData can contain multiple CGDisplayModes to try (with
639 * identical properties), some of which might not work. See GetDisplayMode.
640 */
641 CGError result = kCGErrorFailure;
642 for (CFIndex i = 0; i < CFArrayGetCount(data->modes); i++) {
643 CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(data->modes, i);
644 result = CGDisplaySetDisplayMode(display, moderef, NULL);
645 if (result == kCGErrorSuccess) {
646 // If this mode works, try it first next time.
647 if (i > 0) {
648 CFArrayExchangeValuesAtIndices(data->modes, i, 0);
649 }
650 break;
651 }
652 }
653 return result;
654}
655
656bool Cocoa_SetDisplayMode(SDL_VideoDevice *_this, SDL_VideoDisplay *display, SDL_DisplayMode *mode)
657{
658 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
659 SDL_DisplayModeData *data = mode->internal;
660 CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
661 CGError result = kCGErrorSuccess;
662
663 b_inModeTransition = true;
664
665 // Fade to black to hide resolution-switching flicker
666 if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
667 CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
668 }
669
670 if (data == display->desktop_mode.internal) {
671 // Restoring desktop mode
672 SetDisplayModeForDisplay(displaydata->display, data);
673 } else {
674 // Do the physical switch
675 result = SetDisplayModeForDisplay(displaydata->display, data);
676 }
677
678 // Fade in again (asynchronously)
679 if (fade_token != kCGDisplayFadeReservationInvalidToken) {
680 CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
681 CGReleaseDisplayFadeReservation(fade_token);
682 }
683
684 b_inModeTransition = false;
685
686 if (result != kCGErrorSuccess) {
687 return CG_SetError("CGDisplaySwitchToMode()", result);
688 }
689 return true;
690}
691
692void Cocoa_QuitModes(SDL_VideoDevice *_this)
693{
694 int i, j;
695
696 CGDisplayRemoveReconfigurationCallback(Cocoa_DisplayReconfigurationCallback, _this);
697
698 for (i = 0; i < _this->num_displays; ++i) {
699 SDL_VideoDisplay *display = _this->displays[i];
700 SDL_DisplayModeData *mode;
701
702 if (display->current_mode->internal != display->desktop_mode.internal) {
703 Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
704 }
705
706 mode = display->desktop_mode.internal;
707 CFRelease(mode->modes);
708
709 for (j = 0; j < display->num_fullscreen_modes; j++) {
710 mode = display->fullscreen_modes[j].internal;
711 CFRelease(mode->modes);
712 }
713 }
714}
715
716#endif // SDL_VIDEO_DRIVER_COCOA