From: Jactry Zeng jzeng@codeweavers.com
--- dlls/winemac.drv/cocoa_display.m | 3 +++ dlls/winemac.drv/macdrv_cocoa.h | 3 +++ 2 files changed, 6 insertions(+)
diff --git a/dlls/winemac.drv/cocoa_display.m b/dlls/winemac.drv/cocoa_display.m index 3f6246e7e1b..bb91d921b7c 100644 --- a/dlls/winemac.drv/cocoa_display.m +++ b/dlls/winemac.drv/cocoa_display.m @@ -88,6 +88,9 @@ int macdrv_get_displays(struct macdrv_display** displays, int* count) primary_frame); disps[i].frame = cgrect_win_from_mac(disps[i].frame); disps[i].work_frame = cgrect_win_from_mac(disps[i].work_frame); + disps[i].vendor_number = CGDisplayVendorNumber(disps[i].displayID); + disps[i].model_number = CGDisplayModelNumber(disps[i].displayID); + disps[i].serial_number = CGDisplaySerialNumber(disps[i].displayID); }
*displays = disps; diff --git a/dlls/winemac.drv/macdrv_cocoa.h b/dlls/winemac.drv/macdrv_cocoa.h index 82a577fed37..4da4c14d446 100644 --- a/dlls/winemac.drv/macdrv_cocoa.h +++ b/dlls/winemac.drv/macdrv_cocoa.h @@ -94,6 +94,9 @@ CGDirectDisplayID displayID; CGRect frame; CGRect work_frame; + uint32_t vendor_number; + uint32_t model_number; + uint32_t serial_number; };
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 */
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 | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+)
diff --git a/dlls/winemac.drv/cocoa_display.m b/dlls/winemac.drv/cocoa_display.m index 04b75227f52..8a17ca72f50 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 @@ -730,6 +731,59 @@ static unsigned int get_edid_from_dcpav_service_proxy(const struct macdrv_displa return len; }
+static unsigned int get_edid_from_io_display_edid(const struct macdrv_display *display, unsigned char **prop) +{ + io_iterator_t iterator; + kern_return_t result; + io_service_t service; + int length = 0; + + *prop = NULL; + result = IOServiceGetMatchingServices(0, IOServiceMatching("IODisplayConnect"), &iterator); + if (result != KERN_SUCCESS) + return length; + + while((service = IOIteratorNext(iterator))) + { + uint32_t vendor_number, model_number, serial_number; + 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) + { + 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 && + /* CGDisplaySerialNumber() isn't reliable on Intel machines; it returns 0 sometimes. */ + (!display->serial_number || display->serial_number == serial_number)) + { + length = CFDataGetLength(edid); + if (length && (*prop = malloc(length))) + memcpy(*prop, edid_ptr, length); + else + length = 0; + } + } + CFRelease(display_dict); + } + + IOObjectRelease(service); + if (length) + break; + } + + IOObjectRelease(iterator); + return length; +} + /*********************************************************************** * macdrv_get_monitors * @@ -793,6 +847,9 @@ int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** new_monitor 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); + if (!monitors[monitor_count].edid_len) + monitors[monitor_count].edid_len = get_edid_from_io_display_edid(&displays[j], + &monitors[monitor_count].edid); monitor_count++; break; }
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 8a17ca72f50..b38563a05eb 100644 --- a/dlls/winemac.drv/cocoa_display.m +++ b/dlls/winemac.drv/cocoa_display.m @@ -784,6 +784,199 @@ static unsigned int get_edid_from_io_display_edid(const struct macdrv_display *d return length; }
+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 unsigned int generate_edid(const struct macdrv_display *display, unsigned char **prop) +{ + 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; + int i, j, timings = 0; + CGSize screen_size; + CFArrayRef modes; + + *prop = malloc(128); + if (!*prop) + return 0; + + screen_size = CGDisplayScreenSize(display->displayID); + 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(display->vendor_number); + *(uint16_t *)&edid[10] = (uint16_t)display->model_number; + *(uint32_t *)&edid[12] = display->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->displayID, NULL); + if (modes) + i = CFArrayGetCount(modes); + 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_para = best_params[j]; + + best_params[j].width = width; + best_params[j].height = height; + best_params[j].refresh = refresh; + width = swap_display_para.width; + height = swap_display_para.height; + refresh = swap_display_para.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->displayID); + 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; + + memcpy(*prop, edid, 128); + return 128; +} + /*********************************************************************** * macdrv_get_monitors * @@ -850,6 +1043,8 @@ int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** new_monitor if (!monitors[monitor_count].edid_len) monitors[monitor_count].edid_len = get_edid_from_io_display_edid(&displays[j], &monitors[monitor_count].edid); + if (!monitors[monitor_count].edid_len) + monitors[monitor_count].edid_len = generate_edid(&displays[j], &monitors[monitor_count].edid); monitor_count++; break; }