1//========================================================================
2// GLFW 3.3 macOS - www.glfw.org
3//------------------------------------------------------------------------
4// Copyright (c) 2002-2006 Marcus Geelnard
5// Copyright (c) 2006-2019 Camilla Löwy <elmindreda@glfw.org>
6//
7// This software is provided 'as-is', without any express or implied
8// warranty. In no event will the authors be held liable for any damages
9// arising from the use of this software.
10//
11// Permission is granted to anyone to use this software for any purpose,
12// including commercial applications, and to alter it and redistribute it
13// freely, subject to the following restrictions:
14//
15// 1. The origin of this software must not be misrepresented; you must not
16//    claim that you wrote the original software. If you use this software
17//    in a product, an acknowledgment in the product documentation would
18//    be appreciated but is not required.
19//
20// 2. Altered source versions must be plainly marked as such, and must not
21//    be misrepresented as being the original software.
22//
23// 3. This notice may not be removed or altered from any source
24//    distribution.
25//
26//========================================================================
27// It is fine to use C99 in this file because it will not be built with VS
28//========================================================================
29
30#include "internal.h"
31
32#include <stdlib.h>
33#include <limits.h>
34#include <math.h>
35
36#include <IOKit/graphics/IOGraphicsLib.h>
37#include <ApplicationServices/ApplicationServices.h>
38
39
40// Get the name of the specified display, or NULL
41//
42static char* getDisplayName(CGDirectDisplayID displayID)
43{
44    io_iterator_t it;
45    io_service_t service;
46    CFDictionaryRef info;
47
48    if (IOServiceGetMatchingServices(kIOMasterPortDefault,
49                                     IOServiceMatching("IODisplayConnect"),
50                                     &it) != 0)
51    {
52        // This may happen if a desktop Mac is running headless
53        return NULL;
54    }
55
56    while ((service = IOIteratorNext(it)) != 0)
57    {
58        info = IODisplayCreateInfoDictionary(service,
59                                             kIODisplayOnlyPreferredName);
60
61        CFNumberRef vendorIDRef =
62            CFDictionaryGetValue(info, CFSTR(kDisplayVendorID));
63        CFNumberRef productIDRef =
64            CFDictionaryGetValue(info, CFSTR(kDisplayProductID));
65        if (!vendorIDRef || !productIDRef)
66        {
67            CFRelease(info);
68            continue;
69        }
70
71        unsigned int vendorID, productID;
72        CFNumberGetValue(vendorIDRef, kCFNumberIntType, &vendorID);
73        CFNumberGetValue(productIDRef, kCFNumberIntType, &productID);
74
75        if (CGDisplayVendorNumber(displayID) == vendorID &&
76            CGDisplayModelNumber(displayID) == productID)
77        {
78            // Info dictionary is used and freed below
79            break;
80        }
81
82        CFRelease(info);
83    }
84
85    IOObjectRelease(it);
86
87    if (!service)
88    {
89        _glfwInputError(GLFW_PLATFORM_ERROR,
90                        "Cocoa: Failed to find service port for display");
91        return NULL;
92    }
93
94    CFDictionaryRef names =
95        CFDictionaryGetValue(info, CFSTR(kDisplayProductName));
96
97    CFStringRef nameRef;
98
99    if (!names || !CFDictionaryGetValueIfPresent(names, CFSTR("en_US"),
100                                                 (const void**) &nameRef))
101    {
102        // This may happen if a desktop Mac is running headless
103        CFRelease(info);
104        return NULL;
105    }
106
107    const CFIndex size =
108        CFStringGetMaximumSizeForEncoding(CFStringGetLength(nameRef),
109                                          kCFStringEncodingUTF8);
110    char* name = calloc(size + 1, 1);
111    CFStringGetCString(nameRef, name, size, kCFStringEncodingUTF8);
112
113    CFRelease(info);
114    return name;
115}
116
117// Check whether the display mode should be included in enumeration
118//
119static GLFWbool modeIsGood(CGDisplayModeRef mode)
120{
121    uint32_t flags = CGDisplayModeGetIOFlags(mode);
122
123    if (!(flags & kDisplayModeValidFlag) || !(flags & kDisplayModeSafeFlag))
124        return GLFW_FALSE;
125    if (flags & kDisplayModeInterlacedFlag)
126        return GLFW_FALSE;
127    if (flags & kDisplayModeStretchedFlag)
128        return GLFW_FALSE;
129
130#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
131    CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
132    if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) &&
133        CFStringCompare(format, CFSTR(IO32BitDirectPixels), 0))
134    {
135        CFRelease(format);
136        return GLFW_FALSE;
137    }
138
139    CFRelease(format);
140#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
141    return GLFW_TRUE;
142}
143
144// Convert Core Graphics display mode to GLFW video mode
145//
146static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
147                                            double fallbackRefreshRate)
148{
149    GLFWvidmode result;
150    result.width = (int) CGDisplayModeGetWidth(mode);
151    result.height = (int) CGDisplayModeGetHeight(mode);
152    result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode));
153
154    if (result.refreshRate == 0)
155        result.refreshRate = (int) round(fallbackRefreshRate);
156
157#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
158    CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
159    if (CFStringCompare(format, CFSTR(IO16BitDirectPixels), 0) == 0)
160    {
161        result.redBits = 5;
162        result.greenBits = 5;
163        result.blueBits = 5;
164    }
165    else
166#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
167    {
168        result.redBits = 8;
169        result.greenBits = 8;
170        result.blueBits = 8;
171    }
172
173#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
174    CFRelease(format);
175#endif /* MAC_OS_X_VERSION_MAX_ALLOWED */
176    return result;
177}
178
179// Starts reservation for display fading
180//
181static CGDisplayFadeReservationToken beginFadeReservation(void)
182{
183    CGDisplayFadeReservationToken token = kCGDisplayFadeReservationInvalidToken;
184
185    if (CGAcquireDisplayFadeReservation(5, &token) == kCGErrorSuccess)
186    {
187        CGDisplayFade(token, 0.3,
188                      kCGDisplayBlendNormal,
189                      kCGDisplayBlendSolidColor,
190                      0.0, 0.0, 0.0,
191                      TRUE);
192    }
193
194    return token;
195}
196
197// Ends reservation for display fading
198//
199static void endFadeReservation(CGDisplayFadeReservationToken token)
200{
201    if (token != kCGDisplayFadeReservationInvalidToken)
202    {
203        CGDisplayFade(token, 0.5,
204                      kCGDisplayBlendSolidColor,
205                      kCGDisplayBlendNormal,
206                      0.0, 0.0, 0.0,
207                      FALSE);
208        CGReleaseDisplayFadeReservation(token);
209    }
210}
211
212// Finds and caches the NSScreen corresponding to the specified monitor
213//
214static GLFWbool refreshMonitorScreen(_GLFWmonitor* monitor)
215{
216    if (monitor->ns.screen)
217        return GLFW_TRUE;
218
219    for (NSScreen* screen in [NSScreen screens])
220    {
221        NSNumber* displayID = [screen deviceDescription][@"NSScreenNumber"];
222
223        // HACK: Compare unit numbers instead of display IDs to work around
224        //       display replacement on machines with automatic graphics
225        //       switching
226        if (monitor->ns.unitNumber == CGDisplayUnitNumber([displayID unsignedIntValue]))
227        {
228            monitor->ns.screen = screen;
229            return GLFW_TRUE;
230        }
231    }
232
233    _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find a screen for monitor");
234    return GLFW_FALSE;
235}
236
237// Returns the display refresh rate queried from the I/O registry
238//
239static double getFallbackRefreshRate(CGDirectDisplayID displayID)
240{
241    double refreshRate = 60.0;
242
243    io_iterator_t it;
244    io_service_t service;
245
246    if (IOServiceGetMatchingServices(kIOMasterPortDefault,
247                                     IOServiceMatching("IOFramebuffer"),
248                                     &it) != 0)
249    {
250        return refreshRate;
251    }
252
253    while ((service = IOIteratorNext(it)) != 0)
254    {
255        const CFNumberRef indexRef =
256            IORegistryEntryCreateCFProperty(service,
257                                            CFSTR("IOFramebufferOpenGLIndex"),
258                                            kCFAllocatorDefault,
259                                            kNilOptions);
260        if (!indexRef)
261            continue;
262
263        uint32_t index = 0;
264        CFNumberGetValue(indexRef, kCFNumberIntType, &index);
265        CFRelease(indexRef);
266
267        if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID)
268            continue;
269
270        const CFNumberRef clockRef =
271            IORegistryEntryCreateCFProperty(service,
272                                            CFSTR("IOFBCurrentPixelClock"),
273                                            kCFAllocatorDefault,
274                                            kNilOptions);
275        const CFNumberRef countRef =
276            IORegistryEntryCreateCFProperty(service,
277                                            CFSTR("IOFBCurrentPixelCount"),
278                                            kCFAllocatorDefault,
279                                            kNilOptions);
280        if (!clockRef || !countRef)
281            break;
282
283        uint32_t clock = 0, count = 0;
284        CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
285        CFNumberGetValue(countRef, kCFNumberIntType, &count);
286        CFRelease(clockRef);
287        CFRelease(countRef);
288
289        if (clock > 0 && count > 0)
290            refreshRate = clock / (double) count;
291
292        break;
293    }
294
295    IOObjectRelease(it);
296    return refreshRate;
297}
298
299
300//////////////////////////////////////////////////////////////////////////
301//////                       GLFW internal API                      //////
302//////////////////////////////////////////////////////////////////////////
303
304// Poll for changes in the set of connected monitors
305//
306void _glfwPollMonitorsNS(void)
307{
308    uint32_t displayCount;
309    CGGetOnlineDisplayList(0, NULL, &displayCount);
310    CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID));
311    CGGetOnlineDisplayList(displayCount, displays, &displayCount);
312
313    for (int i = 0;  i < _glfw.monitorCount;  i++)
314        _glfw.monitors[i]->ns.screen = nil;
315
316    _GLFWmonitor** disconnected = NULL;
317    uint32_t disconnectedCount = _glfw.monitorCount;
318    if (disconnectedCount)
319    {
320        disconnected = calloc(_glfw.monitorCount, sizeof(_GLFWmonitor*));
321        memcpy(disconnected,
322               _glfw.monitors,
323               _glfw.monitorCount * sizeof(_GLFWmonitor*));
324    }
325
326    for (uint32_t i = 0;  i < displayCount;  i++)
327    {
328        if (CGDisplayIsAsleep(displays[i]))
329            continue;
330
331        // HACK: Compare unit numbers instead of display IDs to work around
332        //       display replacement on machines with automatic graphics
333        //       switching
334        const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
335        for (uint32_t j = 0;  j < disconnectedCount;  j++)
336        {
337            if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber)
338            {
339                disconnected[j] = NULL;
340                break;
341            }
342        }
343
344        const CGSize size = CGDisplayScreenSize(displays[i]);
345        char* name = getDisplayName(displays[i]);
346        if (!name)
347            name = _glfw_strdup("Unknown");
348
349        _GLFWmonitor* monitor = _glfwAllocMonitor(name, size.width, size.height);
350        monitor->ns.displayID  = displays[i];
351        monitor->ns.unitNumber = unitNumber;
352
353        free(name);
354
355        CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
356        if (CGDisplayModeGetRefreshRate(mode) == 0.0)
357            monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]);
358        CGDisplayModeRelease(mode);
359
360        _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
361    }
362
363    for (uint32_t i = 0;  i < disconnectedCount;  i++)
364    {
365        if (disconnected[i])
366            _glfwInputMonitor(disconnected[i], GLFW_DISCONNECTED, 0);
367    }
368
369    free(disconnected);
370    free(displays);
371}
372
373// Change the current video mode
374//
375void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
376{
377    GLFWvidmode current;
378    _glfwPlatformGetVideoMode(monitor, &current);
379
380    const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
381    if (_glfwCompareVideoModes(&current, best) == 0)
382        return;
383
384    CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
385    const CFIndex count = CFArrayGetCount(modes);
386    CGDisplayModeRef native = NULL;
387
388    for (CFIndex i = 0;  i < count;  i++)
389    {
390        CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
391        if (!modeIsGood(dm))
392            continue;
393
394        const GLFWvidmode mode =
395            vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
396        if (_glfwCompareVideoModes(best, &mode) == 0)
397        {
398            native = dm;
399            break;
400        }
401    }
402
403    if (native)
404    {
405        if (monitor->ns.previousMode == NULL)
406            monitor->ns.previousMode = CGDisplayCopyDisplayMode(monitor->ns.displayID);
407
408        CGDisplayFadeReservationToken token = beginFadeReservation();
409        CGDisplaySetDisplayMode(monitor->ns.displayID, native, NULL);
410        endFadeReservation(token);
411    }
412
413    CFRelease(modes);
414}
415
416// Restore the previously saved (original) video mode
417//
418void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor)
419{
420    if (monitor->ns.previousMode)
421    {
422        CGDisplayFadeReservationToken token = beginFadeReservation();
423        CGDisplaySetDisplayMode(monitor->ns.displayID,
424                                monitor->ns.previousMode, NULL);
425        endFadeReservation(token);
426
427        CGDisplayModeRelease(monitor->ns.previousMode);
428        monitor->ns.previousMode = NULL;
429    }
430}
431
432
433//////////////////////////////////////////////////////////////////////////
434//////                       GLFW platform API                      //////
435//////////////////////////////////////////////////////////////////////////
436
437void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
438{
439}
440
441void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
442{
443    @autoreleasepool {
444
445    const CGRect bounds = CGDisplayBounds(monitor->ns.displayID);
446
447    if (xpos)
448        *xpos = (int) bounds.origin.x;
449    if (ypos)
450        *ypos = (int) bounds.origin.y;
451
452    } // autoreleasepool
453}
454
455void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
456                                         float* xscale, float* yscale)
457{
458    @autoreleasepool {
459
460    if (!refreshMonitorScreen(monitor))
461        return;
462
463    const NSRect points = [monitor->ns.screen frame];
464    const NSRect pixels = [monitor->ns.screen convertRectToBacking:points];
465
466    if (xscale)
467        *xscale = (float) (pixels.size.width / points.size.width);
468    if (yscale)
469        *yscale = (float) (pixels.size.height / points.size.height);
470
471    } // autoreleasepool
472}
473
474void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
475                                     int* xpos, int* ypos,
476                                     int* width, int* height)
477{
478    @autoreleasepool {
479
480    if (!refreshMonitorScreen(monitor))
481        return;
482
483    const NSRect frameRect = [monitor->ns.screen visibleFrame];
484
485    if (xpos)
486        *xpos = frameRect.origin.x;
487    if (ypos)
488        *ypos = _glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1);
489    if (width)
490        *width = frameRect.size.width;
491    if (height)
492        *height = frameRect.size.height;
493
494    } // autoreleasepool
495}
496
497GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
498{
499    @autoreleasepool {
500
501    *count = 0;
502
503    CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
504    const CFIndex found = CFArrayGetCount(modes);
505    GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode));
506
507    for (CFIndex i = 0;  i < found;  i++)
508    {
509        CGDisplayModeRef dm = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
510        if (!modeIsGood(dm))
511            continue;
512
513        const GLFWvidmode mode =
514            vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
515        CFIndex j;
516
517        for (j = 0;  j < *count;  j++)
518        {
519            if (_glfwCompareVideoModes(result + j, &mode) == 0)
520                break;
521        }
522
523        // Skip duplicate modes
524        if (i < *count)
525            continue;
526
527        (*count)++;
528        result[*count - 1] = mode;
529    }
530
531    CFRelease(modes);
532    return result;
533
534    } // autoreleasepool
535}
536
537void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
538{
539    @autoreleasepool {
540
541    CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);
542    *mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
543    CGDisplayModeRelease(native);
544
545    } // autoreleasepool
546}
547
548GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
549{
550    @autoreleasepool {
551
552    uint32_t size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
553    CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
554
555    CGGetDisplayTransferByTable(monitor->ns.displayID,
556                                size,
557                                values,
558                                values + size,
559                                values + size * 2,
560                                &size);
561
562    _glfwAllocGammaArrays(ramp, size);
563
564    for (uint32_t i = 0; i < size; i++)
565    {
566        ramp->red[i]   = (unsigned short) (values[i] * 65535);
567        ramp->green[i] = (unsigned short) (values[i + size] * 65535);
568        ramp->blue[i]  = (unsigned short) (values[i + size * 2] * 65535);
569    }
570
571    free(values);
572    return GLFW_TRUE;
573
574    } // autoreleasepool
575}
576
577void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
578{
579    @autoreleasepool {
580
581    CGGammaValue* values = calloc(ramp->size * 3, sizeof(CGGammaValue));
582
583    for (unsigned int i = 0;  i < ramp->size;  i++)
584    {
585        values[i]                  = ramp->red[i] / 65535.f;
586        values[i + ramp->size]     = ramp->green[i] / 65535.f;
587        values[i + ramp->size * 2] = ramp->blue[i] / 65535.f;
588    }
589
590    CGSetDisplayTransferByTable(monitor->ns.displayID,
591                                ramp->size,
592                                values,
593                                values + ramp->size,
594                                values + ramp->size * 2);
595
596    free(values);
597
598    } // autoreleasepool
599}
600
601
602//////////////////////////////////////////////////////////////////////////
603//////                        GLFW native API                       //////
604//////////////////////////////////////////////////////////////////////////
605
606GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
607{
608    _GLFWmonitor* monitor = (_GLFWmonitor*) handle;
609    _GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
610    return monitor->ns.displayID;
611}
612
613