2022-09-06 15:42:37 +02:00
|
|
|
#include "displayserver.h"
|
2026-01-06 09:48:38 +08:00
|
|
|
#include "common/apple/cf_helpers.h"
|
2026-05-28 15:58:08 +08:00
|
|
|
#include "common/strutil.h"
|
2026-01-06 09:48:38 +08:00
|
|
|
#include "common/edidHelper.h"
|
2025-06-10 10:56:13 +08:00
|
|
|
#include "detection/os/os.h"
|
2022-09-06 08:04:45 -07:00
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
2022-09-15 01:58:31 -07:00
|
|
|
#include <string.h>
|
2022-11-16 18:19:23 +08:00
|
|
|
#include <assert.h>
|
|
|
|
|
#include <CoreGraphics/CGDirectDisplay.h>
|
2023-01-24 11:21:08 +08:00
|
|
|
#include <CoreVideo/CVDisplayLink.h>
|
2026-06-22 10:24:02 +08:00
|
|
|
#include <IOKit/IOKitLib.h>
|
2022-09-06 08:04:45 -07:00
|
|
|
|
2024-10-12 19:45:14 +08:00
|
|
|
#ifdef MAC_OS_X_VERSION_10_15
|
2026-04-09 16:20:59 +08:00
|
|
|
extern Boolean CoreDisplay_Display_SupportsHDRMode(CGDirectDisplayID display) FF_A_WEAK_IMPORT;
|
|
|
|
|
extern Boolean CoreDisplay_Display_IsHDRModeEnabled(CGDirectDisplayID display) FF_A_WEAK_IMPORT;
|
|
|
|
|
extern CFDictionaryRef CoreDisplay_DisplayCreateInfoDictionary(CGDirectDisplayID display) FF_A_WEAK_IMPORT;
|
2024-08-12 16:23:01 +08:00
|
|
|
#else
|
2026-04-15 16:16:46 +08:00
|
|
|
#include <IOKit/graphics/IOGraphicsLib.h>
|
2024-05-19 09:31:08 +08:00
|
|
|
#endif
|
2023-03-22 16:04:48 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
static void detectDisplays(FFDisplayServerResult* ds) {
|
2022-11-16 18:19:23 +08:00
|
|
|
CGDirectDisplayID screens[128];
|
|
|
|
|
uint32_t screenCount;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (CGGetOnlineDisplayList(ARRAY_SIZE(screens), screens, &screenCount) != kCGErrorSuccess) {
|
2022-09-06 08:04:45 -07:00
|
|
|
return;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2022-09-06 08:04:45 -07:00
|
|
|
|
2024-10-05 17:22:32 +08:00
|
|
|
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
2026-03-29 09:28:49 +08:00
|
|
|
for (uint32_t i = 0; i < screenCount; i++) {
|
2022-11-16 18:19:23 +08:00
|
|
|
CGDirectDisplayID screen = screens[i];
|
2026-06-22 10:24:02 +08:00
|
|
|
boolean_t builtin = CGDisplayIsBuiltin(screen);
|
2022-11-16 18:19:23 +08:00
|
|
|
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(screen);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (mode) {
|
|
|
|
|
// https://github.com/glfw/glfw/commit/aab08712dd8142b642e2042e7b7ba563acd07a45
|
2023-01-24 11:21:08 +08:00
|
|
|
double refreshRate = CGDisplayModeGetRefreshRate(mode);
|
|
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (refreshRate == 0) {
|
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
2023-01-24 11:21:08 +08:00
|
|
|
CVDisplayLinkRef link;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (CVDisplayLinkCreateWithCGDisplay(screen, &link) == kCVReturnSuccess) {
|
2023-01-24 11:21:08 +08:00
|
|
|
const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!(time.flags & kCVTimeIsIndefinite)) {
|
|
|
|
|
refreshRate = time.timeScale / (double) time.timeValue; // 59.97...
|
|
|
|
|
}
|
2023-01-24 11:21:08 +08:00
|
|
|
CVDisplayLinkRelease(link);
|
|
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
#pragma clang diagnostic pop
|
2023-01-24 11:21:08 +08:00
|
|
|
}
|
|
|
|
|
|
2024-10-05 17:22:32 +08:00
|
|
|
ffStrbufClear(&buffer);
|
2026-07-10 18:08:04 +08:00
|
|
|
CFDictionaryRef FF_CFTYPE_AUTO_RELEASE displayInfo = nullptr;
|
2026-03-29 09:28:49 +08:00
|
|
|
#ifdef MAC_OS_X_VERSION_10_15
|
|
|
|
|
if (CoreDisplay_DisplayCreateInfoDictionary) {
|
2024-08-12 16:23:01 +08:00
|
|
|
displayInfo = CoreDisplay_DisplayCreateInfoDictionary(screen);
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
|
|
|
|
#else
|
2024-05-19 09:31:08 +08:00
|
|
|
{
|
|
|
|
|
io_service_t servicePort = CGDisplayIOServicePort(screen);
|
2024-08-12 16:23:01 +08:00
|
|
|
displayInfo = IODisplayCreateInfoDictionary(servicePort, kIODisplayOnlyPreferredName);
|
2024-05-19 09:31:08 +08:00
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
#endif
|
2024-11-20 15:53:12 +08:00
|
|
|
|
2024-10-05 17:22:32 +08:00
|
|
|
uint32_t physicalWidth = 0, physicalHeight = 0;
|
2024-11-20 15:53:12 +08:00
|
|
|
uint32_t preferredWidth = 0, preferredHeight = 0;
|
|
|
|
|
double preferredRefreshRate = 0;
|
2026-06-20 01:19:13 +08:00
|
|
|
FF_STRBUF_AUTO_DESTROY serial = ffStrbufCreate();
|
2024-11-20 15:53:12 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (displayInfo) {
|
2024-10-05 17:22:32 +08:00
|
|
|
// CGDisplayScreenSize reports invalid result for external displays on old Intel MacBook Pro
|
|
|
|
|
CFDataRef edidRef = (CFDataRef) CFDictionaryGetValue(displayInfo, CFSTR(kIODisplayEDIDKey));
|
2026-03-29 09:28:49 +08:00
|
|
|
if (edidRef && CFGetTypeID(edidRef) == CFDataGetTypeID()) {
|
2024-10-05 17:22:32 +08:00
|
|
|
const uint8_t* edidData = CFDataGetBytePtr(edidRef);
|
|
|
|
|
uint32_t edidLength = (uint32_t) CFDataGetLength(edidRef);
|
2026-06-20 01:19:13 +08:00
|
|
|
if (ffEdidIsValid(edidData, edidLength)) {
|
2024-10-05 17:22:32 +08:00
|
|
|
ffEdidGetPhysicalSize(edidData, &physicalWidth, &physicalHeight);
|
2026-06-20 01:19:13 +08:00
|
|
|
ffEdidGetSerial(edidData, &serial);
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2026-07-10 18:08:04 +08:00
|
|
|
} else if (!builtin && ffCfDictGetString(displayInfo, CFSTR(kIODisplayLocationKey), &buffer) == nullptr) {
|
2026-06-22 10:24:02 +08:00
|
|
|
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t ioDisplay = IORegistryEntryFromPath(MACH_PORT_NULL, buffer.chars);
|
|
|
|
|
ffStrbufClear(&buffer);
|
|
|
|
|
if (ioDisplay) {
|
|
|
|
|
FF_CFTYPE_AUTO_RELEASE CFDictionaryRef displayAttrRef = IORegistryEntryCreateCFProperty(ioDisplay, CFSTR("DisplayAttributes"), kCFAllocatorDefault, kNilOptions);
|
|
|
|
|
if (displayAttrRef && CFGetTypeID(displayAttrRef) == CFDictionaryGetTypeID()) {
|
|
|
|
|
CFDictionaryRef productAttrs;
|
2026-07-10 18:08:04 +08:00
|
|
|
if (ffCfDictGetDict(displayAttrRef, CFSTR("ProductAttributes"), &productAttrs) == nullptr) {
|
2026-06-22 10:24:02 +08:00
|
|
|
ffCfDictGetString(productAttrs, CFSTR("AlphanumericSerialNumber"), &serial);
|
|
|
|
|
ffCfDictGetString(productAttrs, CFSTR("ProductName"), &buffer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!buffer.length) {
|
|
|
|
|
CFDictionaryRef productNames;
|
2026-07-10 18:08:04 +08:00
|
|
|
if (ffCfDictGetDict(displayInfo, CFSTR(kDisplayProductName), &productNames) == nullptr) {
|
2026-06-22 10:24:02 +08:00
|
|
|
ffCfDictGetString(productNames, CFSTR("en_US"), &buffer);
|
|
|
|
|
}
|
2024-10-05 17:22:32 +08:00
|
|
|
}
|
2024-11-20 15:53:12 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (!physicalWidth || !physicalHeight) {
|
2026-07-10 18:08:04 +08:00
|
|
|
if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayHorizontalImageSize), (int*) &physicalWidth) == nullptr) {
|
2024-11-20 15:53:12 +08:00
|
|
|
ffCfDictGetInt(displayInfo, CFSTR(kDisplayVerticalImageSize), (int*) &physicalHeight);
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-11-20 15:53:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ffCfDictGetInt(displayInfo, CFSTR("kCGDisplayPixelWidth"), (int*) &preferredWidth);
|
|
|
|
|
ffCfDictGetInt(displayInfo, CFSTR("kCGDisplayPixelHeight"), (int*) &preferredHeight);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (preferredWidth && preferredHeight) {
|
2026-07-10 18:08:04 +08:00
|
|
|
FF_CFTYPE_AUTO_RELEASE CFArrayRef allModes = CGDisplayCopyAllDisplayModes(screen, nullptr);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (allModes) {
|
|
|
|
|
for (CFIndex i = 0, count = CFArrayGetCount(allModes); i < count; i++) {
|
2024-11-20 15:53:12 +08:00
|
|
|
CGDisplayModeRef modeInfo = (CGDisplayModeRef) CFArrayGetValueAtIndex(allModes, i);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (CGDisplayModeGetPixelWidth(modeInfo) == preferredWidth && CGDisplayModeGetPixelHeight(modeInfo) == preferredHeight) {
|
2024-11-20 15:53:12 +08:00
|
|
|
double rr = CGDisplayModeGetRefreshRate(modeInfo);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (rr > preferredRefreshRate) {
|
|
|
|
|
preferredRefreshRate = rr;
|
|
|
|
|
}
|
2024-11-20 15:53:12 +08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-12 16:23:01 +08:00
|
|
|
}
|
2023-03-22 16:04:48 +08:00
|
|
|
|
2024-11-21 15:29:14 +08:00
|
|
|
if ((!physicalWidth || !physicalHeight) && CGDisplayPrimaryDisplay(screen) == screen) // #1406
|
2024-10-05 17:22:32 +08:00
|
|
|
{
|
|
|
|
|
CGSize size = CGDisplayScreenSize(screen);
|
|
|
|
|
physicalWidth = (uint32_t) (size.width + 0.5);
|
|
|
|
|
physicalHeight = (uint32_t) (size.height + 0.5);
|
|
|
|
|
}
|
2024-07-11 14:05:34 +08:00
|
|
|
|
2026-03-13 17:46:39 +08:00
|
|
|
uint32_t pixelWidth = (uint32_t) CGDisplayModeGetPixelWidth(mode);
|
|
|
|
|
uint32_t pixelHeight = (uint32_t) CGDisplayModeGetPixelHeight(mode);
|
|
|
|
|
|
2024-07-18 16:41:31 +08:00
|
|
|
FFDisplayResult* display = ffdsAppendDisplay(ds,
|
2026-03-13 17:46:39 +08:00
|
|
|
pixelWidth,
|
|
|
|
|
pixelHeight,
|
2023-03-22 14:59:24 +08:00
|
|
|
refreshRate,
|
2026-03-29 09:28:49 +08:00
|
|
|
pixelHeight * 96 / (uint32_t) CGDisplayModeGetHeight(mode),
|
2024-11-20 15:53:12 +08:00
|
|
|
preferredWidth,
|
|
|
|
|
preferredHeight,
|
|
|
|
|
preferredRefreshRate,
|
2026-03-29 09:28:49 +08:00
|
|
|
(uint32_t) CGDisplayRotation(screen),
|
2024-10-05 17:22:32 +08:00
|
|
|
&buffer,
|
2026-06-22 10:24:02 +08:00
|
|
|
builtin ? FF_DISPLAY_TYPE_BUILTIN : FF_DISPLAY_TYPE_EXTERNAL,
|
2023-08-11 14:56:17 +08:00
|
|
|
CGDisplayIsMain(screen),
|
2026-03-29 09:28:49 +08:00
|
|
|
(uint64_t) screen,
|
2024-10-05 17:22:32 +08:00
|
|
|
physicalWidth,
|
2024-10-14 14:42:11 +08:00
|
|
|
physicalHeight,
|
2026-03-29 09:28:49 +08:00
|
|
|
"CoreGraphics");
|
|
|
|
|
if (display) {
|
|
|
|
|
#ifndef MAC_OS_X_VERSION_10_11
|
2024-11-09 21:27:16 +08:00
|
|
|
FF_CFTYPE_AUTO_RELEASE CFStringRef pe = CGDisplayModeCopyPixelEncoding(mode);
|
2026-03-29 09:28:49 +08:00
|
|
|
if (pe) {
|
|
|
|
|
display->bitDepth = (uint8_t) (CFStringGetLength(pe) - CFStringFind(pe, CFSTR("B"), 0).location);
|
|
|
|
|
}
|
|
|
|
|
#else
|
2024-08-01 16:02:41 +08:00
|
|
|
// https://stackoverflow.com/a/33519316/9976392
|
|
|
|
|
// Also shitty, but better than parsing `CFCopyDescription(mode)`
|
2026-03-29 09:28:49 +08:00
|
|
|
CFDictionaryRef dict = (CFDictionaryRef) * ((int64_t*) mode + 2);
|
|
|
|
|
if (CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
|
2024-08-01 16:02:41 +08:00
|
|
|
int32_t bitDepth;
|
|
|
|
|
ffCfDictGetInt(dict, kCGDisplayBitsPerSample, &bitDepth);
|
|
|
|
|
display->bitDepth = (uint8_t) bitDepth;
|
2024-07-18 16:41:31 +08:00
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
#endif
|
2024-08-12 16:23:01 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (display->type == FF_DISPLAY_TYPE_BUILTIN && displayInfo) {
|
2024-09-30 20:16:24 +08:00
|
|
|
display->hdrStatus = CFDictionaryContainsKey(displayInfo, CFSTR("ReferencePeakHDRLuminance"))
|
2026-03-29 09:28:49 +08:00
|
|
|
? FF_DISPLAY_HDR_STATUS_SUPPORTED
|
|
|
|
|
: FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
|
|
|
|
|
}
|
|
|
|
|
#ifdef MAC_OS_X_VERSION_10_15
|
|
|
|
|
else if (CoreDisplay_Display_SupportsHDRMode) {
|
|
|
|
|
if (CoreDisplay_Display_SupportsHDRMode(screen)) {
|
2024-10-05 17:22:32 +08:00
|
|
|
display->hdrStatus = FF_DISPLAY_HDR_STATUS_SUPPORTED;
|
2026-03-29 09:28:49 +08:00
|
|
|
if (CoreDisplay_Display_IsHDRModeEnabled && CoreDisplay_Display_IsHDRModeEnabled(screen)) {
|
2024-10-05 17:22:32 +08:00
|
|
|
display->hdrStatus = FF_DISPLAY_HDR_STATUS_ENABLED;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
2024-10-05 17:22:32 +08:00
|
|
|
display->hdrStatus = FF_DISPLAY_HDR_STATUS_UNSUPPORTED;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-08-12 16:23:01 +08:00
|
|
|
}
|
2026-03-29 09:28:49 +08:00
|
|
|
#endif
|
2024-10-05 17:22:32 +08:00
|
|
|
|
2026-06-20 01:19:13 +08:00
|
|
|
if (serial.length) {
|
|
|
|
|
ffStrbufInitMove(&display->serial, &serial);
|
|
|
|
|
} else {
|
|
|
|
|
uint32_t int_serial = CGDisplaySerialNumber(screen);
|
|
|
|
|
if (int_serial != 0 && int_serial != 0xFFFFFFFF) {
|
|
|
|
|
ffStrbufSetF(&display->serial, "0x%08X", int_serial);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-25 22:21:53 +08:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
if (displayInfo) {
|
2024-11-25 22:21:53 +08:00
|
|
|
int value;
|
2026-07-10 18:08:04 +08:00
|
|
|
if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayYearOfManufacture), &value) == nullptr) {
|
2024-11-25 22:21:53 +08:00
|
|
|
display->manufactureYear = (uint16_t) value;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2026-07-10 18:08:04 +08:00
|
|
|
if (ffCfDictGetInt(displayInfo, CFSTR(kDisplayWeekOfManufacture), &value) == nullptr) {
|
2024-11-25 22:21:53 +08:00
|
|
|
display->manufactureWeek = (uint16_t) value;
|
2026-03-29 09:28:49 +08:00
|
|
|
}
|
2024-11-25 22:21:53 +08:00
|
|
|
}
|
2024-07-18 16:41:31 +08:00
|
|
|
}
|
2022-11-16 18:19:23 +08:00
|
|
|
CGDisplayModeRelease(mode);
|
|
|
|
|
}
|
|
|
|
|
CGDisplayRelease(screen);
|
2022-09-06 08:04:45 -07:00
|
|
|
}
|
|
|
|
|
}
|
2022-09-06 15:42:37 +02:00
|
|
|
|
2026-03-29 09:28:49 +08:00
|
|
|
void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) {
|
2023-08-11 14:56:17 +08:00
|
|
|
{
|
|
|
|
|
FF_CFTYPE_AUTO_RELEASE CFMachPortRef port = CGWindowServerCreateServerPort();
|
2026-03-29 09:28:49 +08:00
|
|
|
if (port) {
|
2023-12-30 18:13:54 +08:00
|
|
|
ffStrbufSetStatic(&ds->wmProcessName, "WindowServer");
|
|
|
|
|
ffStrbufSetStatic(&ds->wmPrettyName, "Quartz Compositor");
|
2023-08-11 14:56:17 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-06-10 10:56:13 +08:00
|
|
|
|
2023-06-12 10:35:28 +08:00
|
|
|
detectDisplays(ds);
|
2022-09-06 15:42:37 +02:00
|
|
|
}
|