#include "../../SDL_internal.h"
#if SDL_VIDEO_DRIVER_COCOA
#include "SDL_cocoavideo.h"
#include <IOKit/graphics/IOGraphicsLib.h>
#include <CoreVideo/CVBase.h>
#include <CoreVideo/CVDisplayLink.h>
#include <AvailabilityMacros.h>
#ifndef MAC_OS_X_VERSION_10_13
#define NSAppKitVersionNumber10_12 1504
#endif
#if (IOGRAPHICSTYPES_REV < 40)
#define kDisplayModeNativeFlag 0x02000000
#endif
static int
CG_SetError(const char *prefix, CGDisplayErr result)
{
const char *error;
switch (result) {
case kCGErrorFailure:
error = "kCGErrorFailure";
break;
case kCGErrorIllegalArgument:
error = "kCGErrorIllegalArgument";
break;
case kCGErrorInvalidConnection:
error = "kCGErrorInvalidConnection";
break;
case kCGErrorInvalidContext:
error = "kCGErrorInvalidContext";
break;
case kCGErrorCannotComplete:
error = "kCGErrorCannotComplete";
break;
case kCGErrorNotImplemented:
error = "kCGErrorNotImplemented";
break;
case kCGErrorRangeCheck:
error = "kCGErrorRangeCheck";
break;
case kCGErrorTypeCheck:
error = "kCGErrorTypeCheck";
break;
case kCGErrorInvalidOperation:
error = "kCGErrorInvalidOperation";
break;
case kCGErrorNoneAvailable:
error = "kCGErrorNoneAvailable";
break;
default:
error = "Unknown Error";
break;
}
return SDL_SetError("%s: %s", prefix, error);
}
static int
GetDisplayModeRefreshRate(CGDisplayModeRef vidmode, CVDisplayLinkRef link)
{
int refreshRate = (int) (CGDisplayModeGetRefreshRate(vidmode) + 0.5);
if (refreshRate == 0 && link != NULL) {
CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
if ((time.flags & kCVTimeIsIndefinite) == 0 && time.timeValue != 0) {
refreshRate = (int) ((time.timeScale / (double) time.timeValue) + 0.5);
}
}
return refreshRate;
}
static SDL_bool
HasValidDisplayModeFlags(CGDisplayModeRef vidmode)
{
uint32_t ioflags = CGDisplayModeGetIOFlags(vidmode);
if (ioflags & (kDisplayModeNeverShowFlag | kDisplayModeNotGraphicsQualityFlag)) {
return SDL_FALSE;
}
if (!(ioflags & kDisplayModeValidFlag) || !(ioflags & kDisplayModeSafeFlag)) {
return SDL_FALSE;
}
return SDL_TRUE;
}
static Uint32
GetDisplayModePixelFormat(CGDisplayModeRef vidmode)
{
CFStringRef fmt = CGDisplayModeCopyPixelEncoding(vidmode);
Uint32 pixelformat = SDL_PIXELFORMAT_UNKNOWN;
if (CFStringCompare(fmt, CFSTR(IO32BitDirectPixels),
kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
pixelformat = SDL_PIXELFORMAT_ARGB8888;
} else if (CFStringCompare(fmt, CFSTR(IO16BitDirectPixels),
kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
pixelformat = SDL_PIXELFORMAT_ARGB1555;
} else if (CFStringCompare(fmt, CFSTR(kIO30BitDirectPixels),
kCFCompareCaseInsensitive) == kCFCompareEqualTo) {
pixelformat = SDL_PIXELFORMAT_ARGB2101010;
} else {
}
CFRelease(fmt);
return pixelformat;
}
static SDL_bool
GetDisplayMode(_THIS, CGDisplayModeRef vidmode, SDL_bool vidmodeCurrent, CFArrayRef modelist, CVDisplayLinkRef link, SDL_DisplayMode *mode)
{
SDL_DisplayModeData *data;
bool usableForGUI = CGDisplayModeIsUsableForDesktopGUI(vidmode);
int width = (int) CGDisplayModeGetWidth(vidmode);
int height = (int) CGDisplayModeGetHeight(vidmode);
uint32_t ioflags = CGDisplayModeGetIOFlags(vidmode);
int refreshrate = GetDisplayModeRefreshRate(vidmode, link);
Uint32 format = GetDisplayModePixelFormat(vidmode);
bool interlaced = (ioflags & kDisplayModeInterlacedFlag) != 0;
CFMutableArrayRef modes;
if (format == SDL_PIXELFORMAT_UNKNOWN) {
return SDL_FALSE;
}
if (!vidmodeCurrent && !HasValidDisplayModeFlags(vidmode)) {
return SDL_FALSE;
}
modes = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
CFArrayAppendValue(modes, vidmode);
#ifdef MAC_OS_X_VERSION_10_8
if (modelist != NULL && floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) {
int pixelW = (int) CGDisplayModeGetPixelWidth(vidmode);
int pixelH = (int) CGDisplayModeGetPixelHeight(vidmode);
CFIndex modescount = CFArrayGetCount(modelist);
int i;
for (i = 0; i < modescount; i++) {
int otherW, otherH, otherpixelW, otherpixelH, otherrefresh;
Uint32 otherformat;
bool otherGUI;
CGDisplayModeRef othermode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modelist, i);
uint32_t otherioflags = CGDisplayModeGetIOFlags(othermode);
if (CFEqual(vidmode, othermode)) {
continue;
}
if (!HasValidDisplayModeFlags(othermode)) {
continue;
}
otherW = (int) CGDisplayModeGetWidth(othermode);
otherH = (int) CGDisplayModeGetHeight(othermode);
otherpixelW = (int) CGDisplayModeGetPixelWidth(othermode);
otherpixelH = (int) CGDisplayModeGetPixelHeight(othermode);
otherrefresh = GetDisplayModeRefreshRate(othermode, link);
otherformat = GetDisplayModePixelFormat(othermode);
otherGUI = CGDisplayModeIsUsableForDesktopGUI(othermode);
if (width == pixelW && height == pixelH
&& width == otherW && height == otherH
&& refreshrate == otherrefresh && format == otherformat
&& (otherpixelW != otherW || otherpixelH != otherH)) {
CFRelease(modes);
return SDL_FALSE;
}
if (interlaced && ((otherioflags & kDisplayModeInterlacedFlag) == 0)
&& width == otherW && height == otherH && pixelW == otherpixelW
&& pixelH == otherpixelH && refreshrate == otherrefresh
&& format == otherformat && usableForGUI == otherGUI) {
CFRelease(modes);
return SDL_FALSE;
}
if (width == otherW && height == otherH && pixelW == otherpixelW
&& pixelH == otherpixelH && !usableForGUI && otherGUI
&& refreshrate == otherrefresh && format == otherformat) {
CFRelease(modes);
return SDL_FALSE;
}
if (width == otherW && height == otherH && pixelW == otherpixelW
&& pixelH == otherpixelH && usableForGUI == otherGUI
&& refreshrate == otherrefresh && format == otherformat) {
CFArrayAppendValue(modes, othermode);
}
}
}
#endif
data = (SDL_DisplayModeData *) SDL_malloc(sizeof(*data));
if (!data) {
CFRelease(modes);
return SDL_FALSE;
}
data->modes = modes;
mode->format = format;
mode->w = width;
mode->h = height;
mode->refresh_rate = refreshrate;
mode->driverdata = data;
return SDL_TRUE;
}
static const char *
Cocoa_GetDisplayName(CGDirectDisplayID displayID)
{
io_service_t servicePort = CGDisplayIOServicePort(displayID);
CFDictionaryRef deviceInfo = IODisplayCreateInfoDictionary(servicePort, kIODisplayOnlyPreferredName);
NSDictionary *localizedNames = [(__bridge NSDictionary *)deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
const char* displayName = NULL;
if ([localizedNames count] > 0) {
displayName = SDL_strdup([[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] UTF8String]);
}
CFRelease(deviceInfo);
return displayName;
}
void
Cocoa_InitModes(_THIS)
{ @autoreleasepool
{
CGDisplayErr result;
CGDirectDisplayID *displays;
CGDisplayCount numDisplays;
SDL_bool isstack;
int pass, i;
result = CGGetOnlineDisplayList(0, NULL, &numDisplays);
if (result != kCGErrorSuccess) {
CG_SetError("CGGetOnlineDisplayList()", result);
return;
}
displays = SDL_small_alloc(CGDirectDisplayID, numDisplays, &isstack);
result = CGGetOnlineDisplayList(numDisplays, displays, &numDisplays);
if (result != kCGErrorSuccess) {
CG_SetError("CGGetOnlineDisplayList()", result);
SDL_small_free(displays, isstack);
return;
}
for (pass = 0; pass < 2; ++pass) {
for (i = 0; i < numDisplays; ++i) {
SDL_VideoDisplay display;
SDL_DisplayData *displaydata;
SDL_DisplayMode mode;
CGDisplayModeRef moderef = NULL;
CVDisplayLinkRef link = NULL;
if (pass == 0) {
if (!CGDisplayIsMain(displays[i])) {
continue;
}
} else {
if (CGDisplayIsMain(displays[i])) {
continue;
}
}
if (CGDisplayMirrorsDisplay(displays[i]) != kCGNullDirectDisplay) {
continue;
}
moderef = CGDisplayCopyDisplayMode(displays[i]);
if (!moderef) {
continue;
}
displaydata = (SDL_DisplayData *) SDL_malloc(sizeof(*displaydata));
if (!displaydata) {
CGDisplayModeRelease(moderef);
continue;
}
displaydata->display = displays[i];
CVDisplayLinkCreateWithCGDisplay(displays[i], &link);
SDL_zero(display);
display.name = (char *)Cocoa_GetDisplayName(displays[i]);
if (!GetDisplayMode(_this, moderef, SDL_TRUE, NULL, link, &mode)) {
CVDisplayLinkRelease(link);
CGDisplayModeRelease(moderef);
SDL_free(display.name);
SDL_free(displaydata);
continue;
}
CVDisplayLinkRelease(link);
CGDisplayModeRelease(moderef);
display.desktop_mode = mode;
display.current_mode = mode;
display.driverdata = displaydata;
SDL_AddVideoDisplay(&display, SDL_FALSE);
SDL_free(display.name);
}
}
SDL_small_free(displays, isstack);
}}
int
Cocoa_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
{
SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
CGRect cgrect;
cgrect = CGDisplayBounds(displaydata->display);
rect->x = (int)cgrect.origin.x;
rect->y = (int)cgrect.origin.y;
rect->w = (int)cgrect.size.width;
rect->h = (int)cgrect.size.height;
return 0;
}
int
Cocoa_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
{
SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
const CGDirectDisplayID cgdisplay = displaydata->display;
NSArray *screens = [NSScreen screens];
NSScreen *screen = nil;
for (NSScreen *i in screens) {
const CGDirectDisplayID thisDisplay = (CGDirectDisplayID) [[[i deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
if (thisDisplay == cgdisplay) {
screen = i;
break;
}
}
SDL_assert(screen != nil);
if (screen == nil) {
return -1;
}
{
const NSRect frame = [screen visibleFrame];
rect->x = (int)frame.origin.x;
rect->y = (int)(CGDisplayPixelsHigh(kCGDirectMainDisplay) - frame.origin.y - frame.size.height);
rect->w = (int)frame.size.width;
rect->h = (int)frame.size.height;
}
return 0;
}
int
Cocoa_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi)
{ @autoreleasepool
{
const float MM_IN_INCH = 25.4f;
SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
CGFloat scaleFactor = 1.0f;
NSArray *screens = [NSScreen screens];
NSSize displayNativeSize;
displayNativeSize.width = (int) CGDisplayPixelsWide(data->display);
displayNativeSize.height = (int) CGDisplayPixelsHigh(data->display);
for (NSScreen *screen in screens) {
const CGDirectDisplayID dpyid = (const CGDirectDisplayID ) [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
if (dpyid == data->display) {
#ifdef MAC_OS_X_VERSION_10_8
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) {
CFStringRef dmKeys[1] = { kCGDisplayShowDuplicateLowResolutionModes };
CFBooleanRef dmValues[1] = { kCFBooleanTrue };
CFDictionaryRef dmOptions = CFDictionaryCreate(kCFAllocatorDefault, (const void**) dmKeys, (const void**) dmValues, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
CFArrayRef allDisplayModes = CGDisplayCopyAllDisplayModes(dpyid, dmOptions);
CFIndex n = CFArrayGetCount(allDisplayModes);
for(CFIndex i = 0; i < n; ++i) {
CGDisplayModeRef m = (CGDisplayModeRef)CFArrayGetValueAtIndex(allDisplayModes, i);
CGFloat width = CGDisplayModeGetPixelWidth(m);
CGFloat height = CGDisplayModeGetPixelHeight(m);
CGFloat HiDPIWidth = CGDisplayModeGetWidth(m);
if(width == HiDPIWidth) {
if (CGDisplayModeGetIOFlags(m) & kDisplayModeNativeFlag) {
displayNativeSize.width = width;
displayNativeSize.height = height;
break;
}
if(width > displayNativeSize.width) {
displayNativeSize.width = width;
displayNativeSize.height = height;
}
}
}
CFRelease(allDisplayModes);
CFRelease(dmOptions);
} else
#endif
{
scaleFactor = [screen backingScaleFactor];
displayNativeSize.width = displayNativeSize.width * scaleFactor;
displayNativeSize.height = displayNativeSize.height * scaleFactor;
break;
}
}
}
{
const CGSize displaySize = CGDisplayScreenSize(data->display);
const int pixelWidth = displayNativeSize.width;
const int pixelHeight = displayNativeSize.height;
if (ddpi) {
*ddpi = (SDL_ComputeDiagonalDPI(pixelWidth, pixelHeight, displaySize.width / MM_IN_INCH, displaySize.height / MM_IN_INCH));
}
if (hdpi) {
*hdpi = (pixelWidth * MM_IN_INCH / displaySize.width);
}
if (vdpi) {
*vdpi = (pixelHeight * MM_IN_INCH / displaySize.height);
}
}
return 0;
}}
void
Cocoa_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
{
SDL_DisplayData *data = (SDL_DisplayData *) display->driverdata;
CVDisplayLinkRef link = NULL;
CGDisplayModeRef desktopmoderef;
SDL_DisplayMode desktopmode;
CFArrayRef modes;
CFDictionaryRef dict = NULL;
CVDisplayLinkCreateWithCGDisplay(data->display, &link);
desktopmoderef = CGDisplayCopyDisplayMode(data->display);
if (desktopmoderef && GetDisplayMode(_this, desktopmoderef, SDL_TRUE, NULL, link, &desktopmode)) {
if (!SDL_AddDisplayMode(display, &desktopmode)) {
CFRelease(((SDL_DisplayModeData*)desktopmode.driverdata)->modes);
SDL_free(desktopmode.driverdata);
}
}
CGDisplayModeRelease(desktopmoderef);
#ifdef MAC_OS_X_VERSION_10_8
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_7) {
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);
if (dict) {
CFRelease(dict);
}
if (modes) {
CFIndex i;
const CFIndex count = CFArrayGetCount(modes);
for (i = 0; i < count; i++) {
CGDisplayModeRef moderef = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
SDL_DisplayMode mode;
if (GetDisplayMode(_this, moderef, SDL_FALSE, modes, link, &mode)) {
if (!SDL_AddDisplayMode(display, &mode)) {
CFRelease(((SDL_DisplayModeData*)mode.driverdata)->modes);
SDL_free(mode.driverdata);
}
}
}
CFRelease(modes);
}
CVDisplayLinkRelease(link);
}
static CGError
SetDisplayModeForDisplay(CGDirectDisplayID display, SDL_DisplayModeData *data)
{
CGError result = kCGErrorFailure;
for (CFIndex i = 0; i < CFArrayGetCount(data->modes); i++) {
CGDisplayModeRef moderef = (CGDisplayModeRef)CFArrayGetValueAtIndex(data->modes, i);
result = CGDisplaySetDisplayMode(display, moderef, NULL);
if (result == kCGErrorSuccess) {
CFArrayExchangeValuesAtIndices(data->modes, i, 0);
break;
}
}
return result;
}
int
Cocoa_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
{
SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata;
CGDisplayFadeReservationToken fade_token = kCGDisplayFadeReservationInvalidToken;
CGError result;
if (CGAcquireDisplayFadeReservation(5, &fade_token) == kCGErrorSuccess) {
CGDisplayFade(fade_token, 0.3, kCGDisplayBlendNormal, kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, TRUE);
}
if (data == display->desktop_mode.driverdata) {
SetDisplayModeForDisplay(displaydata->display, data);
if (CGDisplayIsMain(displaydata->display)) {
CGReleaseAllDisplays();
} else {
CGDisplayRelease(displaydata->display);
}
} else {
if (CGDisplayIsMain(displaydata->display)) {
result = CGCaptureAllDisplays();
} else {
result = CGDisplayCapture(displaydata->display);
}
if (result != kCGErrorSuccess) {
CG_SetError("CGDisplayCapture()", result);
goto ERR_NO_CAPTURE;
}
result = SetDisplayModeForDisplay(displaydata->display, data);
if (result != kCGErrorSuccess) {
CG_SetError("CGDisplaySwitchToMode()", result);
goto ERR_NO_SWITCH;
}
}
if (fade_token != kCGDisplayFadeReservationInvalidToken) {
CGDisplayFade(fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
CGReleaseDisplayFadeReservation(fade_token);
}
return 0;
ERR_NO_SWITCH:
if (CGDisplayIsMain(displaydata->display)) {
CGReleaseAllDisplays();
} else {
CGDisplayRelease(displaydata->display);
}
ERR_NO_CAPTURE:
if (fade_token != kCGDisplayFadeReservationInvalidToken) {
CGDisplayFade (fade_token, 0.5, kCGDisplayBlendSolidColor, kCGDisplayBlendNormal, 0.0, 0.0, 0.0, FALSE);
CGReleaseDisplayFadeReservation(fade_token);
}
return -1;
}
void
Cocoa_QuitModes(_THIS)
{
int i, j;
for (i = 0; i < _this->num_displays; ++i) {
SDL_VideoDisplay *display = &_this->displays[i];
SDL_DisplayModeData *mode;
if (display->current_mode.driverdata != display->desktop_mode.driverdata) {
Cocoa_SetDisplayMode(_this, display, &display->desktop_mode);
}
mode = (SDL_DisplayModeData *) display->desktop_mode.driverdata;
CFRelease(mode->modes);
for (j = 0; j < display->num_display_modes; j++) {
mode = (SDL_DisplayModeData*) display->display_modes[j].driverdata;
CFRelease(mode->modes);
}
}
}
#endif