From 59beaccd50f7bbc9e6cca8a32d93081b5eb66935 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Thu, 24 Oct 2019 20:15:54 -0300 Subject: [PATCH] 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. --- src/video/cocoa/SDL_cocoamodes.m | 98 +++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 9 deletions(-) diff --git a/src/video/cocoa/SDL_cocoamodes.m b/src/video/cocoa/SDL_cocoamodes.m index 95e88da23..fd882bcca 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 +#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 {