ntdll: Make wine_build a hidden symbol.
[wine.git] / dlls / winemac.drv / cocoa_display.m
blobf64a6c0f6ad8d7804fe5f1930d556d4ddecc997d
1 /*
2  * MACDRV Cocoa display settings
3  *
4  * Copyright 2011, 2012 Ken Thomases for CodeWeavers Inc.
5  *
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.
10  *
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.
15  *
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
19  */
21 #include "config.h"
23 #import <AppKit/AppKit.h>
24 #ifdef HAVE_MTLDEVICE_REGISTRYID
25 #import <Metal/Metal.h>
26 #endif
27 #include "macdrv_cocoa.h"
29 static uint64_t dedicated_gpu_id;
30 static uint64_t integrated_gpu_id;
32 /***********************************************************************
33  *              convert_display_rect
34  *
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.
37  */
38 static inline void convert_display_rect(CGRect* out_rect, NSRect in_rect,
39                                         NSRect primary_frame)
41     *out_rect = NSRectToCGRect(in_rect);
42     out_rect->origin.y = NSMaxY(primary_frame) - NSMaxY(in_rect);
46 /***********************************************************************
47  *              macdrv_get_displays
48  *
49  * Returns information about the displays.
50  *
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.
56  *
57  * Returns non-zero on failure and *displays and *count are unchanged.
58  */
59 int macdrv_get_displays(struct macdrv_display** displays, int* count)
61     int ret = -1;
62     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
64     NSArray* screens = [NSScreen screens];
65     if (screens)
66     {
67         NSUInteger num_screens = [screens count];
68         struct macdrv_display* disps = malloc(num_screens * sizeof(disps[0]));
70         if (disps)
71         {
72             NSRect primary_frame;
74             NSUInteger i;
75             for (i = 0; i < num_screens; i++)
76             {
77                 NSScreen* screen = [screens objectAtIndex:i];
78                 NSRect frame = [screen frame];
79                 NSRect visible_frame = [screen visibleFrame];
81                 if (i == 0)
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,
87                                      primary_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);
90             }
92             *displays = disps;
93             *count = num_screens;
94             ret = 0;
95         }
96     }
98     [pool release];
99     return ret;
103 /***********************************************************************
104  *              macdrv_free_displays
106  * Deallocates an array of macdrv_display structures previously returned
107  * from macdrv_get_displays().
108  */
109 void macdrv_free_displays(struct macdrv_display* displays)
111     free(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.
120  */
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);
124     if (!data)
125         return -1;
127     if (CFGetTypeID(data) != CFDataGetTypeID() || CFDataGetLength(data) != sizeof(uint32_t))
128     {
129         CFRelease(data);
130         return -1;
131     }
133     CFDataGetBytes(data, CFRangeMake(0, sizeof(uint32_t)), (UInt8*)value);
134     CFRelease(data);
135     return 0;
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.
144  */
145 static int get_entry_property_string(io_registry_entry_t entry, CFStringRef property_name, char* buffer,
146                                      size_t buffer_size)
148     CFTypeRef type_ref;
149     CFDataRef data_ref;
150     CFStringRef string_ref;
151     size_t length;
152     int ret = -1;
154     type_ref = IORegistryEntrySearchCFProperty(entry, kIOServicePlane, property_name, kCFAllocatorDefault, 0);
155     if (!type_ref)
156         goto done;
158     if (CFGetTypeID(type_ref) == CFDataGetTypeID())
159     {
160         data_ref = type_ref;
161         length = CFDataGetLength(data_ref);
162         if (length + 1 > buffer_size)
163             goto done;
164         CFDataGetBytes(data_ref, CFRangeMake(0, length), (UInt8*)buffer);
165         buffer[length] = 0;
166     }
167     else if (CFGetTypeID(type_ref) == CFStringGetTypeID())
168     {
169         string_ref = type_ref;
170         if (!CFStringGetCString(string_ref, buffer, buffer_size, kCFStringEncodingUTF8))
171             goto done;
172     }
173     else
174         goto done;
176     ret = 0;
177 done:
178     if (type_ref)
179         CFRelease(type_ref);
180     return ret;
183 /***********************************************************************
184  *              macdrv_get_gpu_info_from_entry
186  * Starting from entry (which must be the GPU or a child below the GPU),
187  * search upwards to find the IOPCIDevice and get information from it.
188  * In case the GPU is not a PCI device, get properties from 'entry'.
190  * Returns non-zero value on failure.
191  */
192 static int macdrv_get_gpu_info_from_entry(struct macdrv_gpu* gpu, io_registry_entry_t entry)
194     io_registry_entry_t parent_entry;
195     io_registry_entry_t gpu_entry;
196     kern_return_t result;
197     int ret = -1;
198     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
200     gpu_entry = entry;
201     while (![@"IOPCIDevice" isEqualToString:[(NSString*)IOObjectCopyClass(gpu_entry) autorelease]])
202     {
203         result = IORegistryEntryGetParentEntry(gpu_entry, kIOServicePlane, &parent_entry);
204         if (gpu_entry != entry)
205             IOObjectRelease(gpu_entry);
206         if (result == kIOReturnNoDevice)
207         {
208             /* If no IOPCIDevice node is found, get properties from the given entry. */
209             gpu_entry = entry;
210             break;
211         }
212         else if (result != kIOReturnSuccess)
213         {
214             [pool release];
215             return ret;
216         }
218         gpu_entry = parent_entry;
219     }
221     if (IORegistryEntryGetRegistryEntryID(gpu_entry, &gpu->id) != kIOReturnSuccess)
222         goto done;
224     ret = 0;
226     get_entry_property_uint32(gpu_entry, CFSTR("vendor-id"), &gpu->vendor_id);
227     get_entry_property_uint32(gpu_entry, CFSTR("device-id"), &gpu->device_id);
228     get_entry_property_uint32(gpu_entry, CFSTR("subsystem-id"), &gpu->subsys_id);
229     get_entry_property_uint32(gpu_entry, CFSTR("revision-id"), &gpu->revision_id);
230     get_entry_property_string(gpu_entry, CFSTR("model"), gpu->name, sizeof(gpu->name));
232 done:
233     if (gpu_entry != entry)
234         IOObjectRelease(gpu_entry);
235     [pool release];
236     return ret;
239 #ifdef HAVE_MTLDEVICE_REGISTRYID
241 /***********************************************************************
242  *              macdrv_get_gpu_info_from_registry_id
244  * Get GPU information from a Metal device registry id.
246  * Returns non-zero value on failure.
247  */
248 static int macdrv_get_gpu_info_from_registry_id(struct macdrv_gpu* gpu, uint64_t registry_id)
250     int ret;
251     io_registry_entry_t entry;
253     entry = IOServiceGetMatchingService(kIOMasterPortDefault, IORegistryEntryIDMatching(registry_id));
254     ret = macdrv_get_gpu_info_from_entry(gpu, entry);
255     IOObjectRelease(entry);
256     return ret;
259 /***********************************************************************
260  *              macdrv_get_gpus_from_metal
262  * Get a list of GPUs from Metal.
264  * Returns non-zero value on failure with parameters unchanged and zero on success.
265  */
266 static int macdrv_get_gpus_from_metal(struct macdrv_gpu** new_gpus, int* count)
268     struct macdrv_gpu* gpus = NULL;
269     struct macdrv_gpu primary_gpu;
270     id<MTLDevice> primary_device;
271     BOOL hide_integrated = FALSE;
272     int primary_index = 0, i;
273     int gpu_count = 0;
274     int ret = -1;
275     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
277     /* Test if Metal is available */
278     if (&MTLCopyAllDevices == NULL)
279         goto done;
280     NSArray<id<MTLDevice>>* devices = [MTLCopyAllDevices() autorelease];
281     if (!devices.count || ![devices[0] respondsToSelector:@selector(registryID)])
282         goto done;
284     gpus = calloc(devices.count, sizeof(*gpus));
285     if (!gpus)
286         goto done;
288     /* Use MTLCreateSystemDefaultDevice instead of CGDirectDisplayCopyCurrentMetalDevice(CGMainDisplayID()) to get
289      * the primary GPU because we need to hide the integrated GPU for an automatic graphic switching pair to avoid apps
290      * using the integrated GPU. This is the behavior of Windows on a Mac. */
291     primary_device = [MTLCreateSystemDefaultDevice() autorelease];
292     if (macdrv_get_gpu_info_from_registry_id(&primary_gpu, primary_device.registryID))
293         goto done;
295     /* Hide the integrated GPU if the system default device is a dedicated GPU */
296     if (!primary_device.isLowPower)
297     {
298         dedicated_gpu_id = primary_gpu.id;
299         hide_integrated = TRUE;
300     }
302     for (i = 0; i < devices.count; i++)
303     {
304         if (macdrv_get_gpu_info_from_registry_id(&gpus[gpu_count], devices[i].registryID))
305             goto done;
307         if (hide_integrated && devices[i].isLowPower)
308         {
309             integrated_gpu_id = gpus[gpu_count].id;
310             continue;
311         }
313         if (gpus[gpu_count].id == primary_gpu.id)
314             primary_index = gpu_count;
316         gpu_count++;
317     }
319     /* Make sure the first GPU is primary */
320     if (primary_index)
321     {
322         struct macdrv_gpu tmp;
323         tmp = gpus[0];
324         gpus[0] = gpus[primary_index];
325         gpus[primary_index] = tmp;
326     }
328     *new_gpus = gpus;
329     *count = gpu_count;
330     ret = 0;
331 done:
332     if (ret)
333         macdrv_free_gpus(gpus);
334     [pool release];
335     return ret;
338 /***********************************************************************
339  *              macdrv_get_gpu_info_from_display_id_using_metal
341  * Get GPU information for a CG display id using Metal.
343  * Returns non-zero value on failure.
344  */
345 static int macdrv_get_gpu_info_from_display_id_using_metal(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
347     id<MTLDevice> device;
348     int ret = -1;
349     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
351     /* Test if Metal is available */
352     if (&CGDirectDisplayCopyCurrentMetalDevice == NULL)
353         goto done;
355     device = [CGDirectDisplayCopyCurrentMetalDevice(display_id) autorelease];
356     if (device && [device respondsToSelector:@selector(registryID)])
357         ret = macdrv_get_gpu_info_from_registry_id(gpu, device.registryID);
359 done:
360     [pool release];
361     return ret;
364 #else
366 static int macdrv_get_gpus_from_metal(struct macdrv_gpu** new_gpus, int* count)
368     return -1;
371 static int macdrv_get_gpu_info_from_display_id_using_metal(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
373     return -1;
376 #endif
378 /***********************************************************************
379  *              macdrv_get_gpu_info_from_display_id
381  * Get GPU information from a display id.
383  * Returns non-zero value on failure.
384  */
385 static int macdrv_get_gpu_info_from_display_id(struct macdrv_gpu* gpu, CGDirectDisplayID display_id)
387     int ret;
388     io_registry_entry_t entry;
390     ret = macdrv_get_gpu_info_from_display_id_using_metal(gpu, display_id);
391     if (ret)
392     {
393         entry = CGDisplayIOServicePort(display_id);
394         ret = macdrv_get_gpu_info_from_entry(gpu, entry);
395     }
396     return ret;
399 /***********************************************************************
400  *              macdrv_get_gpus_from_iokit
402  * Get a list of GPUs from IOKit.
403  * This is a fallback for 32bit build or older Mac OS version where Metal is unavailable.
405  * Returns non-zero value on failure with parameters unchanged and zero on success.
406  */
407 static int macdrv_get_gpus_from_iokit(struct macdrv_gpu** new_gpus, int* count)
409     static const int MAX_GPUS = 4;
410     struct macdrv_gpu primary_gpu = {0};
411     io_registry_entry_t entry;
412     io_iterator_t iterator;
413     struct macdrv_gpu* gpus;
414     int integrated_index = -1;
415     int primary_index = 0;
416     int gpu_count = 0;
417     char buffer[64];
418     int ret = -1;
419     int i;
421     gpus = calloc(MAX_GPUS, sizeof(*gpus));
422     if (!gpus)
423         goto done;
425     if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOPCIDevice"), &iterator)
426         != kIOReturnSuccess)
427         goto done;
429     while ((entry = IOIteratorNext(iterator)))
430     {
431         if (!get_entry_property_string(entry, CFSTR("IOName"), buffer, sizeof(buffer)) &&
432             !strcmp(buffer, "display") &&
433             !macdrv_get_gpu_info_from_entry(&gpus[gpu_count], entry))
434         {
435             gpu_count++;
436             assert(gpu_count < MAX_GPUS);
437         }
438         IOObjectRelease(entry);
439     }
440     IOObjectRelease(iterator);
442     macdrv_get_gpu_info_from_display_id(&primary_gpu, CGMainDisplayID());
444     /* If there are more than two GPUs and an Intel card exists,
445      * assume an automatic graphics pair exists and hide the integrated GPU */
446     if (gpu_count > 1)
447     {
448         for (i = 0; i < gpu_count; i++)
449         {
450             /* FIXME:
451              * Find integrated GPU without Metal support.
452              * Assuming integrated GPU vendor is Intel for now */
453             if (gpus[i].vendor_id == 0x8086)
454             {
455                 integrated_gpu_id = gpus[i].id;
456                 integrated_index = i;
457             }
459             if (gpus[i].id == primary_gpu.id)
460             {
461                 primary_index = i;
462             }
463         }
465         if (integrated_index != -1)
466         {
467             if (integrated_index != gpu_count - 1)
468                 gpus[integrated_index] = gpus[gpu_count - 1];
470             /* FIXME:
471              * Find the dedicated GPU in an automatic graphics switching pair and use that as primary GPU.
472              * Choose the first dedicated GPU as primary */
473             if (primary_index == integrated_index)
474                 primary_index = 0;
475             else if (primary_index == gpu_count - 1)
476                 primary_index = integrated_index;
478             dedicated_gpu_id = gpus[primary_index].id;
479             gpu_count--;
480         }
481     }
483     /* Make sure the first GPU is primary */
484     if (primary_index)
485     {
486         struct macdrv_gpu tmp;
487         tmp = gpus[0];
488         gpus[0] = gpus[primary_index];
489         gpus[primary_index] = tmp;
490     }
492     *new_gpus = gpus;
493     *count = gpu_count;
494     ret = 0;
495 done:
496     if (ret)
497         macdrv_free_gpus(gpus);
498     return ret;
501 /***********************************************************************
502  *              macdrv_get_gpus
504  * Get a list of GPUs currently in the system. The first GPU is primary.
505  * Call macdrv_free_gpus() when you are done using the data.
507  * Returns non-zero value on failure with parameters unchanged and zero on success.
508  */
509 int macdrv_get_gpus(struct macdrv_gpu** new_gpus, int* count)
511     integrated_gpu_id = 0;
512     dedicated_gpu_id = 0;
514     if (!macdrv_get_gpus_from_metal(new_gpus, count))
515         return 0;
516     else
517         return macdrv_get_gpus_from_iokit(new_gpus, count);
520 /***********************************************************************
521  *              macdrv_free_gpus
523  * Frees a GPU list allocated from macdrv_get_gpus()
524  */
525 void macdrv_free_gpus(struct macdrv_gpu* gpus)
527     if (gpus)
528         free(gpus);
531 /***********************************************************************
532  *              macdrv_get_adapters
534  * Get a list of adapters under gpu_id. The first adapter is primary if GPU is primary.
535  * Call macdrv_free_adapters() when you are done using the data.
537  * Returns non-zero value on failure with parameters unchanged and zero on success.
538  */
539 int macdrv_get_adapters(uint64_t gpu_id, struct macdrv_adapter** new_adapters, int* count)
541     CGDirectDisplayID display_ids[16];
542     uint32_t display_id_count;
543     struct macdrv_adapter* adapters;
544     struct macdrv_gpu gpu;
545     int primary_index = 0;
546     int adapter_count = 0;
547     int ret = -1;
548     uint32_t i;
550     if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count)
551         != kCGErrorSuccess)
552         return -1;
554     if (!display_id_count)
555     {
556         *new_adapters = NULL;
557         *count = 0;
558         return 0;
559     }
561     /* Actual adapter count may be less */
562     adapters = calloc(display_id_count, sizeof(*adapters));
563     if (!adapters)
564         return -1;
566     for (i = 0; i < display_id_count; i++)
567     {
568         /* Mirrored displays are under the same adapter with primary display, so they doesn't increase adapter count */
569         if (CGDisplayMirrorsDisplay(display_ids[i]) != kCGNullDirectDisplay)
570             continue;
572         if (macdrv_get_gpu_info_from_display_id(&gpu, display_ids[i]))
573             goto done;
575         if (gpu.id == gpu_id || (gpu_id == dedicated_gpu_id && gpu.id == integrated_gpu_id))
576         {
577             adapters[adapter_count].id = display_ids[i];
578             adapters[adapter_count].state_flags = DISPLAY_DEVICE_ATTACHED_TO_DESKTOP;
580             if (CGDisplayIsMain(display_ids[i]))
581             {
582                 adapters[adapter_count].state_flags |= DISPLAY_DEVICE_PRIMARY_DEVICE;
583                 primary_index = adapter_count;
584             }
586             adapter_count++;
587         }
588     }
590     /* Make sure the first adapter is primary if the GPU is primary */
591     if (primary_index)
592     {
593         struct macdrv_adapter tmp;
594         tmp = adapters[0];
595         adapters[0] = adapters[primary_index];
596         adapters[primary_index] = tmp;
597     }
599     *new_adapters = adapters;
600     *count = adapter_count;
601     ret = 0;
602 done:
603     if (ret)
604         macdrv_free_adapters(adapters);
605     return ret;
608 /***********************************************************************
609  *              macdrv_free_adapters
611  * Frees an adapter list allocated from macdrv_get_adapters()
612  */
613 void macdrv_free_adapters(struct macdrv_adapter* adapters)
615     if (adapters)
616         free(adapters);
619 /***********************************************************************
620  *              macdrv_get_monitors
622  * Get a list of monitors under adapter_id. The first monitor is primary if adapter is primary.
623  * Call macdrv_free_monitors() when you are done using the data.
625  * Returns non-zero value on failure with parameters unchanged and zero on success.
626  */
627 int macdrv_get_monitors(uint32_t adapter_id, struct macdrv_monitor** new_monitors, int* count)
629     struct macdrv_monitor* monitors = NULL;
630     struct macdrv_monitor* realloc_monitors;
631     struct macdrv_display* displays = NULL;
632     CGDirectDisplayID display_ids[16];
633     uint32_t display_id_count;
634     int primary_index = 0;
635     int monitor_count = 0;
636     int display_count;
637     int capacity;
638     int ret = -1;
639     int i, j;
641     /* 2 should be enough for most cases */
642     capacity = 2;
643     monitors = calloc(capacity, sizeof(*monitors));
644     if (!monitors)
645         return -1;
647     if (CGGetOnlineDisplayList(sizeof(display_ids) / sizeof(display_ids[0]), display_ids, &display_id_count)
648         != kCGErrorSuccess)
649         goto done;
651     if (macdrv_get_displays(&displays, &display_count))
652         goto done;
654     for (i = 0; i < display_id_count; i++)
655     {
656         if (display_ids[i] != adapter_id && CGDisplayMirrorsDisplay(display_ids[i]) != adapter_id)
657             continue;
659         /* Find and fill in monitor info */
660         for (j = 0; j < display_count; j++)
661         {
662             if (displays[j].displayID == display_ids[i]
663                 || CGDisplayMirrorsDisplay(display_ids[i]) == displays[j].displayID)
664             {
665                 /* Allocate more space if needed */
666                 if (monitor_count >= capacity)
667                 {
668                     capacity *= 2;
669                     realloc_monitors = realloc(monitors, sizeof(*monitors) * capacity);
670                     if (!realloc_monitors)
671                         goto done;
672                     monitors = realloc_monitors;
673                 }
675                 if (j == 0)
676                     primary_index = monitor_count;
678                 strcpy(monitors[monitor_count].name, "Generic Non-PnP Monitor");
679                 monitors[monitor_count].state_flags = DISPLAY_DEVICE_ATTACHED | DISPLAY_DEVICE_ACTIVE;
680                 monitors[monitor_count].rc_monitor = displays[j].frame;
681                 monitors[monitor_count].rc_work = displays[j].work_frame;
682                 monitor_count++;
683                 break;
684             }
685         }
686     }
688     /* Make sure the first monitor on primary adapter is primary */
689     if (primary_index)
690     {
691         struct macdrv_monitor tmp;
692         tmp = monitors[0];
693         monitors[0] = monitors[primary_index];
694         monitors[primary_index] = tmp;
695     }
697     *new_monitors = monitors;
698     *count = monitor_count;
699     ret = 0;
700 done:
701     if (displays)
702         macdrv_free_displays(displays);
703     if (ret)
704         macdrv_free_monitors(monitors);
705     return ret;
708 /***********************************************************************
709  *              macdrv_free_monitors
711  * Frees an monitor list allocated from macdrv_get_monitors()
712  */
713 void macdrv_free_monitors(struct macdrv_monitor* monitors)
715     if (monitors)
716         free(monitors);