From: Jactry Zeng jzeng@codeweavers.com
IOAVServiceCopyEDID() is a private API of macOS; ideally, maybe we should avoid using it. But this is the most straightforward way to get the EDID of an external monitor on Apple Silicon. --- dlls/winemac.drv/cocoa_display.m | 88 +++++++++++++++++++++++++++++++- dlls/winemac.drv/display.c | 4 +- dlls/winemac.drv/macdrv_cocoa.h | 4 +- 3 files changed, 92 insertions(+), 4 deletions(-)
diff --git a/dlls/winemac.drv/cocoa_display.m b/dlls/winemac.drv/cocoa_display.m index bb91d921b7c..04b75227f52 100644 --- a/dlls/winemac.drv/cocoa_display.m +++ b/dlls/winemac.drv/cocoa_display.m @@ -24,6 +24,7 @@ #ifdef HAVE_MTLDEVICE_REGISTRYID #import <Metal/Metal.h> #endif +#include <dlfcn.h> #include "macdrv_cocoa.h"
#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" @@ -651,6 +652,84 @@ void macdrv_free_adapters(struct macdrv_adapter* adapters) free(adapters); }
+static unsigned int get_edid_from_dcpav_service_proxy(const struct macdrv_display *display, unsigned char **prop) +{ + typedef CFTypeRef IOAVServiceRef; + static IOAVServiceRef (*pIOAVServiceCreateWithService)(CFAllocatorRef, io_service_t); + static IOReturn (*pIOAVServiceCopyEDID)(IOAVServiceRef, CFDataRef*); + static dispatch_once_t once; + io_iterator_t iterator; + kern_return_t result; + io_service_t service; + int len = 0; + + *prop = NULL; + dispatch_once(&once, ^{ + void *handle = dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", RTLD_LAZY | RTLD_LOCAL); + if (handle) + { + pIOAVServiceCreateWithService = dlsym(handle, "IOAVServiceCreateWithService"); + pIOAVServiceCopyEDID = dlsym(handle, "IOAVServiceCopyEDID"); + } + }); + + if (!pIOAVServiceCreateWithService || !pIOAVServiceCopyEDID) + return len; + + result = IOServiceGetMatchingServices(0, IOServiceMatching("DCPAVServiceProxy"), &iterator); + if (result != KERN_SUCCESS) + return len; + + while((service = IOIteratorNext(iterator))) + { + uint32_t vendor_number, model_number, serial_number; + const unsigned char *edid_ptr; + IOAVServiceRef avservice; + CFDataRef edid = NULL; + IOReturn edid_result; + + avservice = pIOAVServiceCreateWithService(kCFAllocatorDefault, service); + if (!avservice) + { + IOObjectRelease(service); + continue; + } + + edid_result = pIOAVServiceCopyEDID(avservice, &edid); + if (edid_result != kIOReturnSuccess || !edid) + { + CFRelease(avservice); + IOObjectRelease(service); + continue; + } + + edid_ptr = CFDataGetBytePtr(edid); + vendor_number = (uint16_t)(edid_ptr[9] | (edid_ptr[8] << 8)); + model_number = *((uint16_t *)&edid_ptr[10]); + serial_number = *((uint32_t *)&edid_ptr[12]); + if (display->vendor_number == vendor_number && + display->model_number == model_number && + display->serial_number == serial_number) + { + len = CFDataGetLength(edid); + if (len && (*prop = malloc(len))) + memcpy(*prop, edid_ptr, len); + else + len = 0; + } + + CFRelease(edid); + CFRelease(avservice); + IOObjectRelease(service); + + if (len) + break; + } + + IOObjectRelease(iterator); + return len; +} + /*********************************************************************** * macdrv_get_monitors * @@ -712,6 +791,8 @@ int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** new_monitor
monitors[monitor_count].rc_monitor = displays[j].frame; monitors[monitor_count].rc_work = displays[j].work_frame; + monitors[monitor_count].edid_len = get_edid_from_dcpav_service_proxy(&displays[j], + &monitors[monitor_count].edid); monitor_count++; break; } @@ -734,7 +815,7 @@ int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** new_monitor if (displays) macdrv_free_displays(displays); if (ret) - macdrv_free_monitors(monitors); + macdrv_free_monitors(monitors, capacity); return ret; }
@@ -743,8 +824,11 @@ int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** new_monitor * * Frees an monitor list allocated from macdrv_get_monitors() */ -void macdrv_free_monitors(struct macdrv_monitor* monitors) +void macdrv_free_monitors(struct macdrv_monitor* monitors, int monitor_count) { + while (monitor_count--) + if (monitors[monitor_count].edid) + free(monitors[monitor_count].edid); if (monitors) free(monitors); } diff --git a/dlls/winemac.drv/display.c b/dlls/winemac.drv/display.c index 7628ca32ec2..f0207bf3399 100644 --- a/dlls/winemac.drv/display.c +++ b/dlls/winemac.drv/display.c @@ -1122,6 +1122,8 @@ UINT macdrv_UpdateDisplayDevices(const struct gdi_device_manager *device_manager { .rc_monitor = rect_from_cgrect(monitor->rc_monitor), .rc_work = rect_from_cgrect(monitor->rc_work), + .edid_len = monitor->edid_len, + .edid = monitor->edid, }; device_manager->add_monitor( &gdi_monitor, param ); } @@ -1142,7 +1144,7 @@ UINT macdrv_UpdateDisplayDevices(const struct gdi_device_manager *device_manager if (!(modes = display_get_modes(adapter->id, &mode_count))) break; device_manager->add_modes( ¤t_mode, mode_count, modes, param ); free(modes); - macdrv_free_monitors(monitors); + macdrv_free_monitors(monitors, monitor_count); }
macdrv_free_adapters(adapters); diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index 4da4c14d446..7ba67871d18 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -241,6 +241,8 @@ static inline CGPoint cgpoint_win_from_mac(CGPoint point) CGRect rc_monitor; /* as RcWork in MONITORINFO struct after conversion by rect_from_cgrect */ CGRect rc_work; + unsigned char *edid; + uint32_t edid_len; };
extern int macdrv_get_displays(struct macdrv_display** displays, int* count); @@ -252,7 +254,7 @@ extern int macdrv_set_display_mode(const struct macdrv_display* display, extern int macdrv_get_adapters(uint64_t gpu_id, struct macdrv_adapter** adapters, int* count); extern void macdrv_free_adapters(struct macdrv_adapter* adapters); extern int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** monitors, int* count); -extern void macdrv_free_monitors(struct macdrv_monitor* monitors); +extern void macdrv_free_monitors(struct macdrv_monitor* monitors, int monitor_count);
/* event */