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 | 92 ++++++++++++++++++++++++++++++-- dlls/winemac.drv/display.c | 8 +-- dlls/winemac.drv/macdrv_cocoa.h | 4 +- 3 files changed, 97 insertions(+), 7 deletions(-)
diff --git a/dlls/winemac.drv/cocoa_display.m b/dlls/winemac.drv/cocoa_display.m index 372d89381f1..88d0fb100d9 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" @@ -581,6 +582,68 @@ void macdrv_free_adapters(struct macdrv_adapter* adapters) free(adapters); }
+static CFDataRef get_edid_from_dcpav_service_proxy(uint32_t vendor_number, uint32_t model_number, uint32_t serial_number) +{ + typedef CFTypeRef IOAVServiceRef; + static IOAVServiceRef (*pIOAVServiceCreateWithService)(CFAllocatorRef, io_service_t); + static IOReturn (*pIOAVServiceCopyEDID)(IOAVServiceRef, CFDataRef*); + static dispatch_once_t once; + io_iterator_t iterator; + CFDataRef edid = NULL; + kern_return_t result; + io_service_t service; + + 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 NULL; + + result = IOServiceGetMatchingServices(0, IOServiceMatching("DCPAVServiceProxy"), &iterator); + if (result != KERN_SUCCESS) + return NULL; + + while((service = IOIteratorNext(iterator))) + { + uint32_t vendor_number_edid, model_number_edid, serial_number_edid; + const unsigned char *edid_ptr; + IOAVServiceRef avservice; + IOReturn edid_result; + + avservice = pIOAVServiceCreateWithService(kCFAllocatorDefault, service); + IOObjectRelease(service); + if (!avservice) + continue; + + edid_result = pIOAVServiceCopyEDID(avservice, &edid); + CFRelease(avservice); + if (edid_result != kIOReturnSuccess || !edid || CFDataGetLength(edid) < 13) + { + if (edid) + CFRelease(edid); + continue; + } + + edid_ptr = CFDataGetBytePtr(edid); + vendor_number_edid = (uint16_t)(edid_ptr[9] | (edid_ptr[8] << 8)); + model_number_edid = *((uint16_t *)&edid_ptr[10]); + serial_number_edid = *((uint32_t *)&edid_ptr[12]); + if (vendor_number == vendor_number_edid && + model_number == model_number_edid && + serial_number == serial_number_edid) + break; + } + + IOObjectRelease(iterator); + return edid; +} + /*********************************************************************** * macdrv_get_monitors * @@ -597,7 +660,7 @@ int macdrv_get_monitors(CGDirectDisplayID adapter_id, struct macdrv_monitor** ne struct macdrv_monitor* monitors = NULL; struct macdrv_monitor* realloc_monitors; CGDirectDisplayID display_ids[16]; - uint32_t display_id_count; + uint32_t display_id_count, vendor_number, model_number, serial_number; NSArray<NSScreen *> *screens = [NSScreen screens]; NSRect primary_frame; int primary_index = 0; @@ -634,6 +697,8 @@ int macdrv_get_monitors(CGDirectDisplayID adapter_id, struct macdrv_monitor** ne { NSScreen* screen = screens[j]; CGDirectDisplayID screen_displayID = [[screen deviceDescription][@"NSScreenNumber"] unsignedIntValue]; + CFDataRef edid_data; + size_t length;
if (screen_displayID == display_ids[i] || CGDisplayMirrorsDisplay(display_ids[i]) == screen_displayID) @@ -654,6 +719,24 @@ int macdrv_get_monitors(CGDirectDisplayID adapter_id, struct macdrv_monitor** ne monitors[monitor_count].id = display_ids[i]; monitors[monitor_count].rc_monitor = convert_display_rect(screen.frame, primary_frame); monitors[monitor_count].rc_work = convert_display_rect(screen.visibleFrame, primary_frame); + + vendor_number = CGDisplayVendorNumber(monitors[monitor_count].id); + model_number = CGDisplayModelNumber(monitors[monitor_count].id); + serial_number = CGDisplaySerialNumber(monitors[monitor_count].id); + + edid_data = get_edid_from_dcpav_service_proxy(vendor_number, model_number, serial_number); + if (edid_data && (length = CFDataGetLength(edid_data))) + { + const unsigned char *edid_ptr = CFDataGetBytePtr(edid_data); + + if ((monitors[monitor_count].edid = malloc(length))) + { + monitors[monitor_count].edid_len = length; + memcpy(monitors[monitor_count].edid, edid_ptr, length); + } + CFRelease(edid_data); + } + monitor_count++; break; } @@ -674,7 +757,7 @@ int macdrv_get_monitors(CGDirectDisplayID adapter_id, struct macdrv_monitor** ne ret = 0; done: if (ret) - macdrv_free_monitors(monitors); + macdrv_free_monitors(monitors, capacity); return ret; } } @@ -684,8 +767,11 @@ int macdrv_get_monitors(CGDirectDisplayID adapter_id, struct macdrv_monitor** ne * * 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 8b012379b2e..2a5c55093bd 100644 --- a/dlls/winemac.drv/display.c +++ b/dlls/winemac.drv/display.c @@ -213,7 +213,7 @@ static void init_original_display_mode(void) HKEY mac_driver_hkey, parent_hkey; DWORD disposition; struct macdrv_monitor *monitors = NULL; - int num_monitors, i; + int num_monitors = 0, i;
if (inited_original_display_mode) return; @@ -252,7 +252,7 @@ done: success = TRUE;
fail: - macdrv_free_monitors(monitors); + macdrv_free_monitors(monitors, num_monitors); NtClose(parent_hkey); if (!success && parent_hkey) reg_delete_tree(mac_driver_hkey, initial_mode_keyW, sizeof(initial_mode_keyW)); @@ -1093,6 +1093,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 );
@@ -1104,7 +1106,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 954acb0fc67..82ef110dce5 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -253,6 +253,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_set_display_mode(CGDirectDisplayID id, CGDisplayModeRef display_mode); @@ -261,7 +263,7 @@ static inline CGPoint cgpoint_win_from_mac(CGPoint point) 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(CGDirectDisplayID 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 */