-- v3: winemac.drv: Support to generate EDID from display parameters from Core Graphics APIs. winemac.drv: Support to get EDID from IODisplayConnect. winemac.drv: Support to get EDID from DCPAVServiceProxy.
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 */
From: Jactry Zeng jzeng@codeweavers.com
IOAVServiceCopyEDID() isn't available on Intel Mac, let's try to get EDID from IODisplayConnect. --- dlls/winemac.drv/cocoa_display.m | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+)
diff --git a/dlls/winemac.drv/cocoa_display.m b/dlls/winemac.drv/cocoa_display.m index 88d0fb100d9..9e0f0f2785c 100644 --- a/dlls/winemac.drv/cocoa_display.m +++ b/dlls/winemac.drv/cocoa_display.m @@ -21,6 +21,7 @@ #include "config.h"
#import <AppKit/AppKit.h> +#import <IOKit/graphics/IOGraphicsLib.h> #ifdef HAVE_MTLDEVICE_REGISTRYID #import <Metal/Metal.h> #endif @@ -644,6 +645,49 @@ static CFDataRef get_edid_from_dcpav_service_proxy(uint32_t vendor_number, uint3 return edid; }
+static CFDataRef get_edid_from_io_display_edid(uint32_t vendor_number, uint32_t model_number, uint32_t serial_number) +{ + io_iterator_t iterator; + CFDataRef data = NULL; + kern_return_t result; + io_service_t service; + + result = IOServiceGetMatchingServices(0, IOServiceMatching("IODisplayConnect"), &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; + CFDictionaryRef display_dict; + CFDataRef edid; + + display_dict = IOCreateDisplayInfoDictionary(service, 0); + if (display_dict) + { + edid = CFDictionaryGetValue(display_dict, CFSTR(kIODisplayEDIDKey)); + if (edid && (CFDataGetLength(edid) >= 13)) + { + 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 && + /* CGDisplaySerialNumber() isn't reliable on Intel machines; it returns 0 sometimes. */ + (!serial_number || serial_number == serial_number_edid)) + data = CFDataCreateCopy(NULL, edid); + } + CFRelease(display_dict); + } + IOObjectRelease(service); + } + + IOObjectRelease(iterator); + return data; +} + /*********************************************************************** * macdrv_get_monitors * @@ -725,6 +769,8 @@ int macdrv_get_monitors(CGDirectDisplayID adapter_id, struct macdrv_monitor** ne serial_number = CGDisplaySerialNumber(monitors[monitor_count].id);
edid_data = get_edid_from_dcpav_service_proxy(vendor_number, model_number, serial_number); + if (!edid_data) + edid_data = get_edid_from_io_display_edid(vendor_number, model_number, serial_number); if (edid_data && (length = CFDataGetLength(edid_data))) { const unsigned char *edid_ptr = CFDataGetBytePtr(edid_data);
From: Jactry Zeng jzeng@codeweavers.com
Neither IOAVServiceCopyEDID() nor "IODisplayEDID" is available for Apple Silicon MacBook's built-in screen, let's try to generate EDID data based on display parameters from Core Graphics APIs. This isn't perfect since some parameters are missing, but at least it satisfies some Unreal games. --- dlls/winemac.drv/cocoa_display.m | 195 +++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+)
diff --git a/dlls/winemac.drv/cocoa_display.m b/dlls/winemac.drv/cocoa_display.m index 9e0f0f2785c..faa2b426867 100644 --- a/dlls/winemac.drv/cocoa_display.m +++ b/dlls/winemac.drv/cocoa_display.m @@ -688,6 +688,199 @@ static CFDataRef get_edid_from_io_display_edid(uint32_t vendor_number, uint32_t return data; }
+static uint16_t get_manufacturer_from_vendor(uint32_t vendor) +{ + uint16_t manufacturer = 0; + + manufacturer |= ((vendor >> 10) & 0x1f) << 10; + manufacturer |= ((vendor >> 5) & 0x1f) << 5; + manufacturer |= (vendor & 0x1f); + manufacturer = (manufacturer >> 8) | (manufacturer << 8); + return manufacturer; +} + +static void fill_detailed_timing_desc(uint8_t *data, int horizontal, int vertical, + double refresh, size_t mwidth, size_t mheight) +{ + int h_front_porch = 8, h_sync_pulse = 32, h_back_porch = 40; + int v_front_porch = 6, v_sync_pulse = 8, v_back_porch = 40; + int h_blanking, v_blanking, h_total, v_total, pixel_clock; + + h_blanking = h_front_porch + h_sync_pulse + h_back_porch; + v_blanking = v_front_porch + v_sync_pulse + v_back_porch; + h_total = horizontal + h_blanking; + v_total = vertical + v_blanking; + + if (refresh <= 0.0) refresh = 60.0; + pixel_clock = (int)round(h_total * v_total * refresh / 10000.0); + + memset(data, 0, 18); + data[0] = pixel_clock & 0xff; + data[1] = (pixel_clock >> 8) & 0xff; + data[2] = horizontal; + data[3] = h_total - horizontal; + data[4] = (((h_total - horizontal) >> 8) & 0xf) | (((horizontal >> 8) & 0xf) << 4); + data[5] = vertical; + data[6] = v_total - vertical; + data[7] = (((v_total - vertical) >> 8) & 0xf) | (((vertical >> 8) & 0xf) << 4); + data[8] = h_front_porch; + data[9] = h_sync_pulse; + data[10] = ((v_front_porch & 0xf) << 4) | (v_sync_pulse & 0xf); + data[11] = (((h_front_porch >> 8) & 3) << 6) + | (((h_sync_pulse >> 8) & 3) << 4) + | (((v_front_porch >> 4) & 3) << 2) + | ((v_sync_pulse >> 4) & 3); + data[12] = mwidth; + data[13] = mheight; + data[14] = (((mwidth >> 8) & 0xf) << 4) | ((mheight >> 8) & 0xf); + data[17] = 0x1e; +} + +static CFDataRef generate_edid(CGDirectDisplayID display_id, uint32_t vendor_number, uint32_t model_number, + uint32_t serial_number) +{ + struct display_param { + size_t width; + size_t height; + double refresh; + } best_params[3]; + double mwidth, mheight, refresh; + CGDisplayModeRef mode = NULL; + size_t width = 0, height = 0; + uint8_t edid[128], sum, *p; + CFDataRef data = NULL; + int i, j, timings = 0; + CGSize screen_size; + CFArrayRef modes; + + screen_size = CGDisplayScreenSize(display_id); + mwidth = screen_size.width; + mheight = screen_size.height; + + memset(&edid, 0, sizeof(edid)); + *(uint64_t *)&edid[0] = 0x00ffffffffffff00; + + *(uint16_t *)&edid[8] = get_manufacturer_from_vendor(vendor_number); + *(uint16_t *)&edid[10] = (uint16_t)model_number; + *(uint32_t *)&edid[12] = serial_number; + edid[16] = 52; /* weeks */ + edid[17] = 30; /* year */ + edid[18] = 1; /* version */ + edid[19] = 4; /* revision */ + edid[20] = 0xb5; /* video input parameters: 10 bits DisplayPort */ + edid[21] = (uint8_t)(mwidth / 10.0); /* horizontal screen size in cm */ + edid[22] = (uint8_t)(mheight / 10.0); /* vertical screen size in cm */ + edid[23] = 0x78; /* gamma: 2.2 */ + edid[24] = 0x06; /* supported features: RGB 4:4:4, sRGB, native pixel format and refresh rate */ + + /* color characteristics: sRGB */ + edid[25] = 0xee; + edid[26] = 0x91; + edid[27] = 0xa3; + edid[28] = 0x54; + edid[29] = 0x4c; + edid[30] = 0x99; + edid[31] = 0x26; + edid[32] = 0x0f; + edid[33] = 0x50; + edid[34] = 0x54; + + for (i = 0; i < 16; ++i) edid[38 + i] = 1; + + /* detailed timing descriptors */ + p = edid + 54; + modes = CGDisplayCopyAllDisplayModes(display_id, NULL); + if (modes) + i = CFArrayGetCount(modes); + else + i = 0; + for (i--; i >= 0; i--) + { + CGDisplayModeRef candidate_mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(modes, i); + int flags = CGDisplayModeGetIOFlags(candidate_mode); + if (flags & kDisplayModeNativeFlag || flags & kDisplayModeDefaultFlag) + { + width = CGDisplayModeGetPixelWidth(candidate_mode); + height = CGDisplayModeGetPixelHeight(candidate_mode); + refresh = CGDisplayModeGetRefreshRate(candidate_mode); + if (!timings) + { + best_params[0].width = width; + best_params[0].height = height; + best_params[0].refresh = refresh; + timings++; + } + else + { + for (j = 0; j < timings; j++) + { + if (best_params[j].width < width || + (best_params[j].width == width && best_params[j].refresh < refresh)) + { + struct display_param swap_display_param = best_params[j]; + + best_params[j].width = width; + best_params[j].height = height; + best_params[j].refresh = refresh; + width = swap_display_param.width; + height = swap_display_param.height; + refresh = swap_display_param.refresh; + } + } + if (timings != 3) + { + best_params[timings].width = width; + best_params[timings].height = height; + best_params[timings].refresh = refresh; + timings++; + } + } + } + } + if (modes) + CFRelease(modes); + if (!timings) + { + mode = CGDisplayCopyDisplayMode(display_id); + width = CGDisplayModeGetPixelWidth(mode); + height = CGDisplayModeGetPixelHeight(mode); + refresh = CGDisplayModeGetRefreshRate(mode); + fill_detailed_timing_desc(p, width, height, refresh, (size_t)mwidth, (size_t)mheight); + timings++; + CGDisplayModeRelease(mode); + } + else + { + for (i = 0; i < timings; i++) + { + fill_detailed_timing_desc(p, best_params[i].width, best_params[i].height, + best_params[i].refresh, (size_t)mwidth, (size_t)mheight); + p += 18; + p[3] = 0x10; + } + } + while (timings != 3) + { + p += 18; + p[3] = 0x10; + timings++; + } + + /* display product name */ + p[3] = 0xfc; + strcpy((char *)p + 5, "Wine Monitor"); + p[17] = 0x0a; + + /* checksum */ + sum = 0; + for (i = 0; i < 127; ++i) + sum += edid[i]; + edid[127] = 256 - sum; + + data = CFDataCreate(NULL, edid, 128); + return data; +} + /*********************************************************************** * macdrv_get_monitors * @@ -771,6 +964,8 @@ int macdrv_get_monitors(CGDirectDisplayID adapter_id, struct macdrv_monitor** ne edid_data = get_edid_from_dcpav_service_proxy(vendor_number, model_number, serial_number); if (!edid_data) edid_data = get_edid_from_io_display_edid(vendor_number, model_number, serial_number); + if (!edid_data) + edid_data = generate_edid(monitors[monitor_count].id, vendor_number, model_number, serial_number); if (edid_data && (length = CFDataGetLength(edid_data))) { const unsigned char *edid_ptr = CFDataGetBytePtr(edid_data);
On Thu Nov 20 12:17:54 2025 +0000, Jactry Zeng wrote:
changed this line in [version 3 of the diff](/wine/wine/-/merge_requests/8925/diffs?diff_id=225951&start_sha=5a2c35be908fba2d608f0a50188780ad9fc37fc6#6b4081e7cf08b46f58903606af163cc58cfde8f9_984_963)
Sure, this is reasonable. I push a v3. Thanks!
v3: - Fix a leak in get_edid_from_dcpav_service_proxy() when CFDataGetLength(edid) < 13. - Return CFDataRef from helpers instead.