macOS: Expose high dpi-capable display modes on macOS 10.13+. Fixes an issue in macOS 10.15 where the displayed content would move up after entering, exiting and re-entering exclusive fullscreen when certain display modes were used (bug #4822). Bug #3949 is also related to this change.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
diff --git a/src/video/cocoa/SDL_cocoamodes.m b/src/video/cocoa/SDL_cocoamodes.m
index 95e88da..fd882bc 100644
--- a/src/video/cocoa/SDL_cocoamodes.m
+++ b/src/video/cocoa/SDL_cocoamodes.m
@@ -38,6 +38,10 @@
/* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */
#include <AvailabilityMacros.h>
+#ifndef MAC_OS_X_VERSION_10_13
+#define NSAppKitVersionNumber10_12 1504
+#endif
+
static void
Cocoa_ToggleMenuBar(const BOOL show)
@@ -100,15 +104,64 @@ CG_SetError(const char *prefix, CGDisplayErr result)
}
static SDL_bool
-GetDisplayMode(_THIS, CGDisplayModeRef vidmode, CVDisplayLinkRef link, SDL_DisplayMode *mode)
+GetDisplayMode(_THIS, CGDisplayModeRef vidmode, CFArrayRef modelist, CVDisplayLinkRef link, SDL_DisplayMode *mode)
{
SDL_DisplayModeData *data;
- int width = 0;
- int height = 0;
+ bool usableForDesktop = CGDisplayModeIsUsableForDesktopGUI(vidmode);
+ int width = (int) CGDisplayModeGetWidth(vidmode);
+ int height = (int) CGDisplayModeGetHeight(vidmode);
int bpp = 0;
int refreshRate = 0;
CFStringRef fmt;
+ /* If a list of possible diplay modes is passed in, use it to filter out
+ * modes that have duplicate sizes. We don't just rely on SDL's higher level
+ * duplicate filtering because this code can choose what properties are
+ * prefered.
+ * CGDisplayModeGetPixelWidth and friends are only available in 10.8+. */
+#ifdef MAC_OS_X_VERSION_10_8
+ if (modelist != NULL && floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) {
+ int pixelW = (int) CGDisplayModeGetPixelWidth(vidmode);
+ int pixelH = (int) CGDisplayModeGetPixelHeight(vidmode);
+
+ if (width == pixelW && height == pixelH) {
+ CFIndex modescount = CFArrayGetCount(modelist);
+
+ for (int i = 0; i < modescount; i++) {
+ CGDisplayModeRef othermode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modelist, i);
+
+ if (CFEqual(vidmode, othermode)) {
+ continue;
+ }
+
+ int otherW = (int) CGDisplayModeGetWidth(othermode);
+ int otherH = (int) CGDisplayModeGetHeight(othermode);
+
+ int otherpixelW = (int) CGDisplayModeGetPixelWidth(othermode);
+ int otherpixelH = (int) CGDisplayModeGetPixelHeight(othermode);
+
+ /* Ignore this mode if it's low-dpi (@1x) and we have a high-dpi
+ * mode in the list with the same size in points.
+ */
+ if (width == otherW && height == otherH
+ && (otherpixelW != otherW || otherpixelH != otherH)) {
+ return SDL_FALSE;
+ }
+
+ /* Ignore this mode if it's not usable for desktop UI and its
+ * pixel and point dimensions are equal to another GUI-capable
+ * mode in the list.
+ */
+ if (width == otherW && height == otherH && pixelW == otherpixelW
+ && pixelH == otherpixelH && usableForDesktop
+ && CGDisplayModeIsUsableForDesktopGUI(othermode)) {
+ return SDL_FALSE;
+ }
+ }
+ }
+ }
+#endif
+
data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data));
if (!data) {
return SDL_FALSE;
@@ -116,8 +169,6 @@ GetDisplayMode(_THIS, CGDisplayModeRef vidmode, CVDisplayLinkRef link, SDL_Displ
data->moderef = vidmode;
fmt = CGDisplayModeCopyPixelEncoding(vidmode);
- width = (int) CGDisplayModeGetWidth(vidmode);
- height = (int) CGDisplayModeGetHeight(vidmode);
refreshRate = (int) (CGDisplayModeGetRefreshRate(vidmode) + 0.5);
if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels),
@@ -244,7 +295,7 @@ Cocoa_InitModes(_THIS)
SDL_zero(display);
/* this returns a stddup'ed string */
display.name = (char *)Cocoa_GetDisplayName(displays[i]);
- if (!GetDisplayMode(_this, moderef, link, &mode)) {
+ if (!GetDisplayMode(_this, moderef, NULL, link, &mode)) {
CVDisplayLinkRelease(link);
CGDisplayModeRelease(moderef);
SDL_free(display.name);
@@ -341,6 +392,7 @@ Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
CGDisplayModeRef desktopmoderef;
SDL_DisplayMode desktopmode;
CFArrayRef modes;
+ CFDictionaryRef dict = NULL;
CVDisplayLinkCreateWithCGDisplay(data->display, &link);
@@ -352,7 +404,7 @@ Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
* sure there are no duplicates so it's safe to always add the desktop mode
* even in cases where it is in the CopyAllDisplayModes list.
*/
- if (desktopmoderef && GetDisplayMode(_this, desktopmoderef, link, &desktopmode)) {
+ if (desktopmoderef && GetDisplayMode(_this, desktopmoderef, NULL, link, &desktopmode)) {
if (!SDL_AddDisplayMode(display, &desktopmode)) {
CGDisplayModeRelease(desktopmoderef);
SDL_free(desktopmode.driverdata);
@@ -361,7 +413,35 @@ Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
CGDisplayModeRelease(desktopmoderef);
}
- modes = CGDisplayCopyAllDisplayModes(data->display, NULL);
+ /* By default, CGDisplayCopyAllDisplayModes will only get a subset of the
+ * system's available modes. For example on a 15" 2016 MBP, users can
+ * choose 1920x1080@2x in System Preferences but it won't show up here,
+ * unless we specify the option below.
+ * The display modes returned by CGDisplayCopyAllDisplayModes are also not
+ * high dpi-capable unless this option is set.
+ * kCGDisplayShowDuplicateLowResolutionModes exists since 10.8, but macOS
+ * 10.11 and 10.12 have bugs with the modes returned when it's used:
+ * https://bugzilla.libsdl.org/show_bug.cgi?id=3949
+ * macOS 10.15 also seems to have a bug where entering, exiting, and
+ * re-entering exclusive fullscreen with a low dpi display mode can cause
+ * the content of the screen to move up, which this setting avoids:
+ * https://bugzilla.libsdl.org/show_bug.cgi?id=4822
+ */
+#ifdef MAC_OS_X_VERSION_10_8
+ if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_12) {
+ const CFStringRef dictkeys[] = {kCGDisplayShowDuplicateLowResolutionModes};
+ const CFBooleanRef dictvalues[] = {kCFBooleanTrue};
+ dict = CFDictionaryCreate(NULL,
+ (const void **)dictkeys,
+ (const void **)dictvalues,
+ 1,
+ &kCFCopyStringDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ }
+#endif
+
+ modes = CGDisplayCopyAllDisplayModes(data->display, dict);
+ CFRelease(dict);
if (modes) {
CFIndex i;
@@ -371,7 +451,7 @@ Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
CGDisplayModeRef moderef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
SDL_DisplayMode mode;
- if (GetDisplayMode(_this, moderef, link, &mode)) {
+ if (GetDisplayMode(_this, moderef, modes, link, &mode)) {
if (SDL_AddDisplayMode(display, &mode)) {
CGDisplayModeRetain(moderef);
} else {