zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

cocoa_monitor.m (19442B)


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