2 * MACDRV Cocoa display settings
4 * Copyright 2011, 2012 Ken Thomases for CodeWeavers Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 #import <AppKit/AppKit.h>
24 #ifdef HAVE_MTLDEVICE_REGISTRYID
25 #import <Metal/Metal.h>
27 #include "macdrv_cocoa.h"
29 static uint64_t dedicated_gpu_id;
30 static uint64_t integrated_gpu_id;
32 /***********************************************************************
33 * convert_display_rect
35 * Converts an NSRect in Cocoa's y-goes-up-from-bottom coordinate system
36 * to a CGRect in y-goes-down-from-top coordinates.
38 static inline void convert_display_rect(CGRect* out_rect, NSRect in_rect,
41 *out_rect = NSRectToCGRect(in_rect);
42 out_rect->origin.y = NSMaxY(primary_frame) - NSMaxY(in_rect);
46 /***********************************************************************
49 * Returns information about the displays.
51 * Returns 0 on success and *displays contains a newly-allocated array
52 * of macdrv_display structures and *count contains the number of
53 * elements in that array. The first element of the array is the
54 * primary display. When the caller is done with the array, it should
55 * use macdrv_free_displays() to deallocate it.
57 * Returns non-zero on failure and *displays and *count are unchanged.
59 int macdrv_get_displays(struct macdrv_display** displays, int* count)
62 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
64 NSArray* screens = [NSScreen screens];
67 NSUInteger num_screens = [screens count];
68 struct macdrv_display* disps = malloc(num_screens * sizeof(disps[0]));
75 for (i = 0; i < num_screens; i++)
77 NSScreen* screen = [screens objectAtIndex:i];
78 NSRect frame = [screen frame];
79 NSRect visible_frame = [screen visibleFrame];
82 primary_frame = frame;
84 disps[i].displayID = [[[screen deviceDescription] objectForKey:@"NSScreenNumber"] unsignedIntValue];
85 convert_display_rect(&disps[i].frame, frame, primary_frame);
86 convert_display_rect(&disps[i].work_frame, visible_frame,
88 disps[i].frame = cgrect_win_from_mac(disps[i].frame);
89 disps[i].work_frame = cgrect_win_from_mac(disps[i].work_frame);
103 /***********************************************************************
104 * macdrv_free_displays
106 * Deallocates an array of macdrv_display structures previously returned
107 * from macdrv_get_displays().
109 void macdrv_free_displays(struct macdrv_display* displays)
114 /***********************************************************************
115 * get_entry_property_uint32
117 * Get an io registry entry property of type uint32 and store it in value parameter.
119 * Returns non-zero value on failure.
121 static int get_entry_property_uint32(io_registry_entry_t entry, CFStringRef property_name, uint32_t* value)
123 CFDataRef data = IORegistryEntrySearchCFProperty(entry, kIOServicePlane, property_name, kCFAllocatorDefault, 0);
127 if (CFGetTypeID(data) != CFDataGetTypeID() || CFDataGetLength(data) != sizeof(uint32_t))
133 CFDataGetBytes(data, CFRangeMake(0, sizeof(uint32_t)), (UInt8*)value);
138 /***********************************************************************
139 * get_entry_property_string
141 * Get an io registry entry property of type string and write it in buffer parameter.
143 * Returns non-zero value on failure.
145 static int get_entry_property_string(io_registry_entry_t entry, CFStringRef property_name, char* buffer,
150 CFStringRef string_ref;
154 type_ref = IORegistryEntrySearchCFProperty(entry, kIOServicePlane, property_name, kCFAllocatorDefault, 0);
158 if (CFGetTypeID(type_ref) == CFDataGetTypeID())
161 length = CFDataGetLength(data_ref);
162 if (length + 1 > buffer_size)
164 CFDataGetBytes(data_ref, CFRangeMake(0, length), (UInt8*)buffer);
167 else if (CFGetTypeID(type_ref) == CFStringGetTypeID())
169 string_ref = type_ref;
170 if (!CFStringGetCString(string_ref, buffer, buffer_size, kCFStringEncodingUTF8))
183 /***********************************************************************
184 * macdrv_get_gpu_info_from_entry
186 * Starting from entry, search upwards to find the PCI GPU. And get GPU information from the PCI GPU entry.
188 * Returns non-zero value on failure.
190 static int macdrv_get_gpu_info_from_entry(struct macdrv_gpu* gpu, io_registry_entry_t entry)
192 io_registry_entry_t parent_entry;
193 io_registry_entry_t gpu_entry;
194 kern_return_t result;
197 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
200 while (![@"IOPCIDevice" isEqualToString:[(NSString*)IOObjectCopyClass(gpu_entry) autorelease]]
201 || get_entry_property_string(gpu_entry, CFSTR("IOName"), buffer, sizeof(buffer))
202 || strcmp(buffer, "display"))
204 result = IORegistryEntryGetParentEntry(gpu_entry, kIOServicePlane, &parent_entry);
205 if (gpu_entry != entry)
206 IOObjectRelease(gpu_entry);
207 if (result != kIOReturnSuccess)
213 gpu_entry = parent_entry;
216 if (IORegistryEntryGetRegistryEntryID(gpu_entry, &gpu->id) != kIOReturnSuccess)
221 get_entry_property_uint32(gpu_entry, CFSTR("vendor-id"), &gpu->vendor_id);
222 get_entry_property_uint32(gpu_entry, CFSTR("device-id"), &gpu->device_id);
223 get_entry_property_uint32(gpu_entry, CFSTR("subsystem-id"), &gpu->subsys_id);
224 get_entry_property_uint32(gpu_entry, CFSTR("revision-id"), &gpu->revision_id);
225 get_entry_property_string(gpu_entry, CFSTR("model"), gpu->name, sizeof(gpu->name));
228 if (gpu_entry != entry)
229 IOObjectRelease(gpu_entry);
234 #ifdef HAVE_MTLDEVICE_REGISTRYID
236 /***********************************************************************
237 * macdrv_get_gpu_info_from_registry_id
239 * Get GPU information from a Metal device registry id.
241 * Returns non-zero value on failure.
243 static int macdrv_get_gpu_info_from_registry_id(struct macdrv_gpu* gpu, uint64_t registry_id)
246 io_registry_entry_t entry;
248 entry = IOServiceGetMatchingService(kIOMasterPortDefault, IORegistryEntryIDMatching(registry_id));
249 ret = macdrv_get_gpu_info_from_entry(gpu, entry);
250 IOObjectRelease(entry);
254 /***********************************************************************
255 * macdrv_get_gpus_from_metal
257 * Get a list of GPUs from Metal.
259 * Returns non-zero value on failure with parameters unchanged and zero on success.
261 static int macdrv_get_gpus_from_metal(struct macdrv_gpu** new_gpus, int* count)
263 struct macdrv_gpu* gpus = NULL;
264 struct macdrv_gpu primary_gpu;
265 id<MTLDevice> primary_device;
266 BOOL hide_integrated = FALSE;
267 int primary_index = 0, i;
270 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
272 /* Test if Metal is available */
273 if (&MTLCopyAllDevices == NULL)
275 NSArray<id<MTLDevice>>* devices = [MTLCopyAllDevices() autorelease];
276 if (!devices.count || ![devices[0] respondsToSelector:@selector(registryID)])
279 gpus = calloc(devices.count, sizeof(*gpus));
283 /* Use MTLCreateSystemDefaultDevice instead of CGDirectDisplayCopyCurrentMetalDevice(CGMainDisplayID()) to get
284 * the primary GPU because we need to hide the integrated GPU for an automatic graphic switching pair to avoid apps
285 * using the integrated GPU. This is the behavior of Windows on a Mac. */
286 primary_device = [MTLCreateSystemDefaultDevice() autorelease];
287 if (macdrv_get_gpu_info_from_registry_id(&primary_gpu, primary_device.registryID))
290 /* Hide the integrated GPU if the system default device is a dedicated GPU */
291 if (!primary_device.isLowPower)
293 dedicated_gpu_id = primary_gpu.id;
294 hide_integrated = TRUE;
297 for (i = 0; i < devices.count; i++)
299 if (macdrv_get_gpu_info_from_registry_id(&gpus[gpu_count], devices[i].registryID))
302 if (hide_integrated && devices[i].isLowPower)
304 integrated_gpu_id = gpus[gpu_count].id;
308 if (gpus[gpu_count].id == primary_gpu.id)
309 primary_index = gpu_count;
314 /* Make sure the first GPU is primary */
317 struct macdrv_gpu tmp;
319 gpus[0] = gpus[primary_index];
320 gpus[primary_index] = tmp;
328 macdrv_free_gpus(gpus);
335 static int macdrv_get_gpus_from_metal(struct macdrv_gpu** new_gpus, int* count)
342 /***********************************************************************
343 * macdrv_get_gpu_info_from_display_id
345 * Get GPU information from a display id.
346 * This is a fallback for 32bit build or older Mac OS version where Metal is unavailable.
348 * Returns non-zero value on failure.
350 static int macdrv_get_gpu_info_from_display_id(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
352 io_registry_entry_t entry = CGDisplayIOServicePort(display_id);
353 return macdrv_get_gpu_info_from_entry(gpu, entry);
356 /***********************************************************************
357 * macdrv_get_gpus_from_iokit
359 * Get a list of GPUs from IOKit.
360 * This is a fallback for 32bit build or older Mac OS version where Metal is unavailable.
362 * Returns non-zero value on failure with parameters unchanged and zero on success.
364 static int macdrv_get_gpus_from_iokit(struct macdrv_gpu** new_gpus, int* count)
366 static const int MAX_GPUS = 4;
367 struct macdrv_gpu primary_gpu = {0};
368 io_registry_entry_t entry;
369 io_iterator_t iterator;
370 struct macdrv_gpu* gpus;
371 int integrated_index = -1;
372 int primary_index = 0;
377 gpus = calloc(MAX_GPUS, sizeof(*gpus));
381 if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOPCIDevice"), &iterator)
385 while ((entry = IOIteratorNext(iterator)))
387 if (!macdrv_get_gpu_info_from_entry(&gpus[gpu_count], entry))
390 assert(gpu_count < MAX_GPUS);
392 IOObjectRelease(entry);
394 IOObjectRelease(iterator);
396 macdrv_get_gpu_info_from_display_id(&primary_gpu, CGMainDisplayID());
398 /* If there are more than two GPUs and an Intel card exists,
399 * assume an automatic graphics pair exists and hide the integrated GPU */
402 for (i = 0; i < gpu_count; i++)
405 * Find integrated GPU without Metal support.
406 * Assuming integrated GPU vendor is Intel for now */
407 if (gpus[i].vendor_id == 0x8086)
409 integrated_gpu_id = gpus[i].id;
410 integrated_index = i;
413 if (gpus[i].id == primary_gpu.id)
419 if (integrated_index != -1)
421 if (integrated_index != gpu_count - 1)
422 gpus[integrated_index] = gpus[gpu_count - 1];
425 * Find the dedicated GPU in an automatic graphics switching pair and use that as primary GPU.
426 * Choose the first dedicated GPU as primary */
427 if (primary_index == integrated_index)
429 else if (primary_index == gpu_count - 1)
430 primary_index = integrated_index;
432 dedicated_gpu_id = gpus[primary_index].id;
437 /* Make sure the first GPU is primary */
440 struct macdrv_gpu tmp;
442 gpus[0] = gpus[primary_index];
443 gpus[primary_index] = tmp;
451 macdrv_free_gpus(gpus);
455 /***********************************************************************
458 * Get a list of GPUs currently in the system. The first GPU is primary.
459 * Call macdrv_free_gpus() when you are done using the data.
461 * Returns non-zero value on failure with parameters unchanged and zero on success.
463 int macdrv_get_gpus(struct macdrv_gpu** new_gpus, int* count)
465 integrated_gpu_id = 0;
466 dedicated_gpu_id = 0;
468 if (!macdrv_get_gpus_from_metal(new_gpus, count))
471 return macdrv_get_gpus_from_iokit(new_gpus, count);
474 /***********************************************************************
477 * Frees a GPU list allocated from macdrv_get_gpus()
479 void macdrv_free_gpus(struct macdrv_gpu* gpus)
485 /***********************************************************************
486 * macdrv_get_adapters
488 * Get a list of adapters under gpu_id. The first adapter is primary if GPU is primary.
489 * Call macdrv_free_adapters() when you are done using the data.
491 * Returns non-zero value on failure with parameters unchanged and zero on success.
493 int macdrv_get_adapters(uint64_t gpu_id, struct macdrv_adapter** new_adapters, int* count)
495 CGDirectDisplayID display_ids[16];
496 uint32_t display_id_count;
497 struct macdrv_adapter* adapters;
498 struct macdrv_gpu gpu;
499 int primary_index = 0;
500 int adapter_count = 0;
504 if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count)
508 if (!display_id_count)
510 *new_adapters = NULL;
515 /* Actual adapter count may be less */
516 adapters = calloc(display_id_count, sizeof(*adapters));
520 for (i = 0; i < display_id_count; i++)
522 /* Mirrored displays are under the same adapter with primary display, so they doesn't increase adapter count */
523 if (CGDisplayMirrorsDisplay(display_ids[i]) != kCGNullDirectDisplay)
526 if (macdrv_get_gpu_info_from_display_id(&gpu, display_ids[i]))
529 if (gpu.id == gpu_id || (gpu_id == dedicated_gpu_id && gpu.id == integrated_gpu_id))
531 adapters[adapter_count].id = display_ids[i];
533 if (CGDisplayIsMain(display_ids[i]))
535 adapters[adapter_count].state_flags |= DISPLAY_DEVICE_PRIMARY_DEVICE;
536 primary_index = adapter_count;
539 if (CGDisplayIsActive(display_ids[i]))
540 adapters[adapter_count].state_flags |= DISPLAY_DEVICE_ATTACHED_TO_DESKTOP;
546 /* Make sure the first adapter is primary if the GPU is primary */
549 struct macdrv_adapter tmp;
551 adapters[0] = adapters[primary_index];
552 adapters[primary_index] = tmp;
555 *new_adapters = adapters;
556 *count = adapter_count;
560 macdrv_free_adapters(adapters);
564 /***********************************************************************
565 * macdrv_free_adapters
567 * Frees an adapter list allocated from macdrv_get_adapters()
569 void macdrv_free_adapters(struct macdrv_adapter* adapters)
575 /***********************************************************************
576 * macdrv_get_monitors
578 * Get a list of monitors under adapter_id. The first monitor is primary if adapter is primary.
579 * Call macdrv_free_monitors() when you are done using the data.
581 * Returns non-zero value on failure with parameters unchanged and zero on success.
583 int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** new_monitors, int* count)
585 struct macdrv_monitor* monitors = NULL;
586 struct macdrv_monitor* realloc_monitors;
587 struct macdrv_display* displays = NULL;
588 CGDirectDisplayID display_ids[16];
589 uint32_t display_id_count;
590 int primary_index = 0;
591 int monitor_count = 0;
597 /* 2 should be enough for most cases */
599 monitors = calloc(capacity, sizeof(*monitors));
603 /* Report an inactive monitor */
604 if (!CGDisplayIsActive(adapter_id) && !CGDisplayIsInMirrorSet(adapter_id))
606 strcpy(monitors[monitor_count].name, "Generic Non-PnP Monitor");
607 monitors[monitor_count].state_flags = DISPLAY_DEVICE_ATTACHED;
610 /* Report active and mirrored monitors in the same mirroring set */
613 if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count)
617 if (macdrv_get_displays(&displays, &display_count))
620 for (i = 0; i < display_id_count; i++)
622 if (display_ids[i] != adapter_id && CGDisplayMirrorsDisplay(display_ids[i]) != adapter_id)
625 /* Find and fill in monitor info */
626 for (j = 0; j < display_count; j++)
628 if (displays[j].displayID == display_ids[i]
629 || CGDisplayMirrorsDisplay(display_ids[i]) == displays[j].displayID)
631 /* Allocate more space if needed */
632 if (monitor_count >= capacity)
635 realloc_monitors = realloc(monitors, sizeof(*monitors) * capacity);
636 if (!realloc_monitors)
638 monitors = realloc_monitors;
642 primary_index = monitor_count;
644 strcpy(monitors[monitor_count].name, "Generic Non-PnP Monitor");
645 monitors[monitor_count].state_flags = DISPLAY_DEVICE_ATTACHED | DISPLAY_DEVICE_ACTIVE;
646 monitors[monitor_count].rc_monitor = displays[j].frame;
647 monitors[monitor_count].rc_work = displays[j].work_frame;
654 /* Make sure the first monitor on primary adapter is primary */
657 struct macdrv_monitor tmp;
659 monitors[0] = monitors[primary_index];
660 monitors[primary_index] = tmp;
664 *new_monitors = monitors;
665 *count = monitor_count;
669 macdrv_free_displays(displays);
671 macdrv_free_monitors(monitors);
675 /***********************************************************************
676 * macdrv_free_monitors
678 * Frees an monitor list allocated from macdrv_get_monitors()
680 void macdrv_free_monitors(struct macdrv_monitor* monitors)