volviewer

Volumetric Data Toy Viewer
git clone anongit@rnpnr.xyz:volviewer.git
Log | Files | Refs | Feed | LICENSE

cocoa_window.m (60320B)


      1 //========================================================================
      2 // GLFW 3.4 macOS - www.glfw.org
      3 //------------------------------------------------------------------------
      4 // Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org>
      5 //
      6 // This software is provided 'as-is', without any express or implied
      7 // warranty. In no event will the authors be held liable for any damages
      8 // arising from the use of this software.
      9 //
     10 // Permission is granted to anyone to use this software for any purpose,
     11 // including commercial applications, and to alter it and redistribute it
     12 // freely, subject to the following restrictions:
     13 //
     14 // 1. The origin of this software must not be misrepresented; you must not
     15 //    claim that you wrote the original software. If you use this software
     16 //    in a product, an acknowledgment in the product documentation would
     17 //    be appreciated but is not required.
     18 //
     19 // 2. Altered source versions must be plainly marked as such, and must not
     20 //    be misrepresented as being the original software.
     21 //
     22 // 3. This notice may not be removed or altered from any source
     23 //    distribution.
     24 //
     25 //========================================================================
     26 
     27 #include "internal.h"
     28 
     29 #if defined(_GLFW_COCOA)
     30 
     31 #include <float.h>
     32 #include <string.h>
     33 
     34 // HACK: This enum value is missing from framework headers on OS X 10.11 despite
     35 //       having been (according to documentation) added in Mac OS X 10.7
     36 #define NSWindowCollectionBehaviorFullScreenNone (1 << 9)
     37 
     38 // Returns whether the cursor is in the content area of the specified window
     39 //
     40 static GLFWbool cursorInContentArea(_GLFWwindow* window)
     41 {
     42     const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream];
     43     return [window->ns.view mouse:pos inRect:[window->ns.view frame]];
     44 }
     45 
     46 // Hides the cursor if not already hidden
     47 //
     48 static void hideCursor(_GLFWwindow* window)
     49 {
     50     if (!_glfw.ns.cursorHidden)
     51     {
     52         [NSCursor hide];
     53         _glfw.ns.cursorHidden = GLFW_TRUE;
     54     }
     55 }
     56 
     57 // Shows the cursor if not already shown
     58 //
     59 static void showCursor(_GLFWwindow* window)
     60 {
     61     if (_glfw.ns.cursorHidden)
     62     {
     63         [NSCursor unhide];
     64         _glfw.ns.cursorHidden = GLFW_FALSE;
     65     }
     66 }
     67 
     68 // Updates the cursor image according to its cursor mode
     69 //
     70 static void updateCursorImage(_GLFWwindow* window)
     71 {
     72     if (window->cursorMode == GLFW_CURSOR_NORMAL)
     73     {
     74         showCursor(window);
     75 
     76         if (window->cursor)
     77             [(NSCursor*) window->cursor->ns.object set];
     78         else
     79             [[NSCursor arrowCursor] set];
     80     }
     81     else
     82         hideCursor(window);
     83 }
     84 
     85 // Apply chosen cursor mode to a focused window
     86 //
     87 static void updateCursorMode(_GLFWwindow* window)
     88 {
     89     if (window->cursorMode == GLFW_CURSOR_DISABLED)
     90     {
     91         _glfw.ns.disabledCursorWindow = window;
     92         _glfwGetCursorPosCocoa(window,
     93                                &_glfw.ns.restoreCursorPosX,
     94                                &_glfw.ns.restoreCursorPosY);
     95         _glfwCenterCursorInContentArea(window);
     96         CGAssociateMouseAndMouseCursorPosition(false);
     97     }
     98     else if (_glfw.ns.disabledCursorWindow == window)
     99     {
    100         _glfw.ns.disabledCursorWindow = NULL;
    101         _glfwSetCursorPosCocoa(window,
    102                                _glfw.ns.restoreCursorPosX,
    103                                _glfw.ns.restoreCursorPosY);
    104         // NOTE: The matching CGAssociateMouseAndMouseCursorPosition call is
    105         //       made in _glfwSetCursorPosCocoa as part of a workaround
    106     }
    107 
    108     if (cursorInContentArea(window))
    109         updateCursorImage(window);
    110 }
    111 
    112 // Make the specified window and its video mode active on its monitor
    113 //
    114 static void acquireMonitorCocoa(_GLFWwindow* window)
    115 {
    116     _glfwSetVideoModeCocoa(window->monitor, &window->videoMode);
    117     const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID);
    118     const NSRect frame = NSMakeRect(bounds.origin.x,
    119                                     _glfwTransformYCocoa(bounds.origin.y + bounds.size.height - 1),
    120                                     bounds.size.width,
    121                                     bounds.size.height);
    122 
    123     [window->ns.object setFrame:frame display:YES];
    124 
    125     _glfwInputMonitorWindow(window->monitor, window);
    126 }
    127 
    128 // Remove the window and restore the original video mode
    129 //
    130 static void releaseMonitorCocoa(_GLFWwindow* window)
    131 {
    132     if (window->monitor->window != window)
    133         return;
    134 
    135     _glfwInputMonitorWindow(window->monitor, NULL);
    136     _glfwRestoreVideoModeCocoa(window->monitor);
    137 }
    138 
    139 // Translates macOS key modifiers into GLFW ones
    140 //
    141 static int translateFlags(NSUInteger flags)
    142 {
    143     int mods = 0;
    144 
    145     if (flags & NSEventModifierFlagShift)
    146         mods |= GLFW_MOD_SHIFT;
    147     if (flags & NSEventModifierFlagControl)
    148         mods |= GLFW_MOD_CONTROL;
    149     if (flags & NSEventModifierFlagOption)
    150         mods |= GLFW_MOD_ALT;
    151     if (flags & NSEventModifierFlagCommand)
    152         mods |= GLFW_MOD_SUPER;
    153     if (flags & NSEventModifierFlagCapsLock)
    154         mods |= GLFW_MOD_CAPS_LOCK;
    155 
    156     return mods;
    157 }
    158 
    159 // Translates a macOS keycode to a GLFW keycode
    160 //
    161 static int translateKeyCocoa(unsigned int key)
    162 {
    163     if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0]))
    164         return GLFW_KEY_UNKNOWN;
    165 
    166     return _glfw.ns.keycodes[key];
    167 }
    168 
    169 // Translate a GLFW keycode to a Cocoa modifier flag
    170 //
    171 static NSUInteger translateKeyToModifierFlag(int key)
    172 {
    173     switch (key)
    174     {
    175         case GLFW_KEY_LEFT_SHIFT:
    176         case GLFW_KEY_RIGHT_SHIFT:
    177             return NSEventModifierFlagShift;
    178         case GLFW_KEY_LEFT_CONTROL:
    179         case GLFW_KEY_RIGHT_CONTROL:
    180             return NSEventModifierFlagControl;
    181         case GLFW_KEY_LEFT_ALT:
    182         case GLFW_KEY_RIGHT_ALT:
    183             return NSEventModifierFlagOption;
    184         case GLFW_KEY_LEFT_SUPER:
    185         case GLFW_KEY_RIGHT_SUPER:
    186             return NSEventModifierFlagCommand;
    187         case GLFW_KEY_CAPS_LOCK:
    188             return NSEventModifierFlagCapsLock;
    189     }
    190 
    191     return 0;
    192 }
    193 
    194 // Defines a constant for empty ranges in NSTextInputClient
    195 //
    196 static const NSRange kEmptyRange = { NSNotFound, 0 };
    197 
    198 
    199 //------------------------------------------------------------------------
    200 // Delegate for window related notifications
    201 //------------------------------------------------------------------------
    202 
    203 @interface GLFWWindowDelegate : NSObject
    204 {
    205     _GLFWwindow* window;
    206 }
    207 
    208 - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
    209 
    210 @end
    211 
    212 @implementation GLFWWindowDelegate
    213 
    214 - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
    215 {
    216     self = [super init];
    217     if (self != nil)
    218         window = initWindow;
    219 
    220     return self;
    221 }
    222 
    223 - (BOOL)windowShouldClose:(id)sender
    224 {
    225     _glfwInputWindowCloseRequest(window);
    226     return NO;
    227 }
    228 
    229 - (void)windowDidResize:(NSNotification *)notification
    230 {
    231     if (window->context.source == GLFW_NATIVE_CONTEXT_API)
    232         [window->context.nsgl.object update];
    233 
    234     if (_glfw.ns.disabledCursorWindow == window)
    235         _glfwCenterCursorInContentArea(window);
    236 
    237     const int maximized = [window->ns.object isZoomed];
    238     if (window->ns.maximized != maximized)
    239     {
    240         window->ns.maximized = maximized;
    241         _glfwInputWindowMaximize(window, maximized);
    242     }
    243 
    244     const NSRect contentRect = [window->ns.view frame];
    245     const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
    246 
    247     if (fbRect.size.width != window->ns.fbWidth ||
    248         fbRect.size.height != window->ns.fbHeight)
    249     {
    250         window->ns.fbWidth  = fbRect.size.width;
    251         window->ns.fbHeight = fbRect.size.height;
    252         _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
    253     }
    254 
    255     if (contentRect.size.width != window->ns.width ||
    256         contentRect.size.height != window->ns.height)
    257     {
    258         window->ns.width  = contentRect.size.width;
    259         window->ns.height = contentRect.size.height;
    260         _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height);
    261     }
    262 }
    263 
    264 - (void)windowDidMove:(NSNotification *)notification
    265 {
    266     if (window->context.source == GLFW_NATIVE_CONTEXT_API)
    267         [window->context.nsgl.object update];
    268 
    269     if (_glfw.ns.disabledCursorWindow == window)
    270         _glfwCenterCursorInContentArea(window);
    271 
    272     int x, y;
    273     _glfwGetWindowPosCocoa(window, &x, &y);
    274     _glfwInputWindowPos(window, x, y);
    275 }
    276 
    277 - (void)windowDidMiniaturize:(NSNotification *)notification
    278 {
    279     if (window->monitor)
    280         releaseMonitorCocoa(window);
    281 
    282     _glfwInputWindowIconify(window, GLFW_TRUE);
    283 }
    284 
    285 - (void)windowDidDeminiaturize:(NSNotification *)notification
    286 {
    287     if (window->monitor)
    288         acquireMonitorCocoa(window);
    289 
    290     _glfwInputWindowIconify(window, GLFW_FALSE);
    291 }
    292 
    293 - (void)windowDidBecomeKey:(NSNotification *)notification
    294 {
    295     if (_glfw.ns.disabledCursorWindow == window)
    296         _glfwCenterCursorInContentArea(window);
    297 
    298     _glfwInputWindowFocus(window, GLFW_TRUE);
    299     updateCursorMode(window);
    300 }
    301 
    302 - (void)windowDidResignKey:(NSNotification *)notification
    303 {
    304     if (window->monitor && window->autoIconify)
    305         _glfwIconifyWindowCocoa(window);
    306 
    307     _glfwInputWindowFocus(window, GLFW_FALSE);
    308 }
    309 
    310 - (void)windowDidChangeOcclusionState:(NSNotification* )notification
    311 {
    312 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
    313     if ([window->ns.object respondsToSelector:@selector(occlusionState)])
    314     {
    315         if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible)
    316             window->ns.occluded = GLFW_FALSE;
    317         else
    318             window->ns.occluded = GLFW_TRUE;
    319     }
    320 #endif
    321 }
    322 
    323 @end
    324 
    325 
    326 //------------------------------------------------------------------------
    327 // Content view class for the GLFW window
    328 //------------------------------------------------------------------------
    329 
    330 @interface GLFWContentView : NSView <NSTextInputClient>
    331 {
    332     _GLFWwindow* window;
    333     NSTrackingArea* trackingArea;
    334     NSMutableAttributedString* markedText;
    335 }
    336 
    337 - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
    338 
    339 @end
    340 
    341 @implementation GLFWContentView
    342 
    343 - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
    344 {
    345     self = [super init];
    346     if (self != nil)
    347     {
    348         window = initWindow;
    349         trackingArea = nil;
    350         markedText = [[NSMutableAttributedString alloc] init];
    351 
    352         [self updateTrackingAreas];
    353         [self registerForDraggedTypes:@[NSPasteboardTypeURL]];
    354     }
    355 
    356     return self;
    357 }
    358 
    359 - (void)dealloc
    360 {
    361     [trackingArea release];
    362     [markedText release];
    363     [super dealloc];
    364 }
    365 
    366 - (BOOL)isOpaque
    367 {
    368     return [window->ns.object isOpaque];
    369 }
    370 
    371 - (BOOL)canBecomeKeyView
    372 {
    373     return YES;
    374 }
    375 
    376 - (BOOL)acceptsFirstResponder
    377 {
    378     return YES;
    379 }
    380 
    381 - (BOOL)wantsUpdateLayer
    382 {
    383     return YES;
    384 }
    385 
    386 - (void)updateLayer
    387 {
    388     if (window->context.source == GLFW_NATIVE_CONTEXT_API)
    389         [window->context.nsgl.object update];
    390 
    391     _glfwInputWindowDamage(window);
    392 }
    393 
    394 - (void)cursorUpdate:(NSEvent *)event
    395 {
    396     updateCursorImage(window);
    397 }
    398 
    399 - (BOOL)acceptsFirstMouse:(NSEvent *)event
    400 {
    401     return YES;
    402 }
    403 
    404 - (void)mouseDown:(NSEvent *)event
    405 {
    406     _glfwInputMouseClick(window,
    407                          GLFW_MOUSE_BUTTON_LEFT,
    408                          GLFW_PRESS,
    409                          translateFlags([event modifierFlags]));
    410 }
    411 
    412 - (void)mouseDragged:(NSEvent *)event
    413 {
    414     [self mouseMoved:event];
    415 }
    416 
    417 - (void)mouseUp:(NSEvent *)event
    418 {
    419     _glfwInputMouseClick(window,
    420                          GLFW_MOUSE_BUTTON_LEFT,
    421                          GLFW_RELEASE,
    422                          translateFlags([event modifierFlags]));
    423 }
    424 
    425 - (void)mouseMoved:(NSEvent *)event
    426 {
    427     if (window->cursorMode == GLFW_CURSOR_DISABLED)
    428     {
    429         const double dx = [event deltaX] - window->ns.cursorWarpDeltaX;
    430         const double dy = [event deltaY] - window->ns.cursorWarpDeltaY;
    431 
    432         _glfwInputCursorPos(window,
    433                             window->virtualCursorPosX + dx,
    434                             window->virtualCursorPosY + dy);
    435     }
    436     else
    437     {
    438         const NSRect contentRect = [window->ns.view frame];
    439         // NOTE: The returned location uses base 0,1 not 0,0
    440         const NSPoint pos = [event locationInWindow];
    441 
    442         _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y);
    443     }
    444 
    445     window->ns.cursorWarpDeltaX = 0;
    446     window->ns.cursorWarpDeltaY = 0;
    447 }
    448 
    449 - (void)rightMouseDown:(NSEvent *)event
    450 {
    451     _glfwInputMouseClick(window,
    452                          GLFW_MOUSE_BUTTON_RIGHT,
    453                          GLFW_PRESS,
    454                          translateFlags([event modifierFlags]));
    455 }
    456 
    457 - (void)rightMouseDragged:(NSEvent *)event
    458 {
    459     [self mouseMoved:event];
    460 }
    461 
    462 - (void)rightMouseUp:(NSEvent *)event
    463 {
    464     _glfwInputMouseClick(window,
    465                          GLFW_MOUSE_BUTTON_RIGHT,
    466                          GLFW_RELEASE,
    467                          translateFlags([event modifierFlags]));
    468 }
    469 
    470 - (void)otherMouseDown:(NSEvent *)event
    471 {
    472     _glfwInputMouseClick(window,
    473                          (int) [event buttonNumber],
    474                          GLFW_PRESS,
    475                          translateFlags([event modifierFlags]));
    476 }
    477 
    478 - (void)otherMouseDragged:(NSEvent *)event
    479 {
    480     [self mouseMoved:event];
    481 }
    482 
    483 - (void)otherMouseUp:(NSEvent *)event
    484 {
    485     _glfwInputMouseClick(window,
    486                          (int) [event buttonNumber],
    487                          GLFW_RELEASE,
    488                          translateFlags([event modifierFlags]));
    489 }
    490 
    491 - (void)mouseExited:(NSEvent *)event
    492 {
    493     if (window->cursorMode == GLFW_CURSOR_HIDDEN)
    494         showCursor(window);
    495 
    496     _glfwInputCursorEnter(window, GLFW_FALSE);
    497 }
    498 
    499 - (void)mouseEntered:(NSEvent *)event
    500 {
    501     if (window->cursorMode == GLFW_CURSOR_HIDDEN)
    502         hideCursor(window);
    503 
    504     _glfwInputCursorEnter(window, GLFW_TRUE);
    505 }
    506 
    507 - (void)viewDidChangeBackingProperties
    508 {
    509     const NSRect contentRect = [window->ns.view frame];
    510     const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
    511     const float xscale = fbRect.size.width / contentRect.size.width;
    512     const float yscale = fbRect.size.height / contentRect.size.height;
    513 
    514     if (xscale != window->ns.xscale || yscale != window->ns.yscale)
    515     {
    516         if (window->ns.scaleFramebuffer && window->ns.layer)
    517             [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
    518 
    519         window->ns.xscale = xscale;
    520         window->ns.yscale = yscale;
    521         _glfwInputWindowContentScale(window, xscale, yscale);
    522     }
    523 
    524     if (fbRect.size.width != window->ns.fbWidth ||
    525         fbRect.size.height != window->ns.fbHeight)
    526     {
    527         window->ns.fbWidth  = fbRect.size.width;
    528         window->ns.fbHeight = fbRect.size.height;
    529         _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
    530     }
    531 }
    532 
    533 - (void)drawRect:(NSRect)rect
    534 {
    535     _glfwInputWindowDamage(window);
    536 }
    537 
    538 - (void)updateTrackingAreas
    539 {
    540     if (trackingArea != nil)
    541     {
    542         [self removeTrackingArea:trackingArea];
    543         [trackingArea release];
    544     }
    545 
    546     const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
    547                                           NSTrackingActiveInKeyWindow |
    548                                           NSTrackingEnabledDuringMouseDrag |
    549                                           NSTrackingCursorUpdate |
    550                                           NSTrackingInVisibleRect |
    551                                           NSTrackingAssumeInside;
    552 
    553     trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
    554                                                 options:options
    555                                                   owner:self
    556                                                userInfo:nil];
    557 
    558     [self addTrackingArea:trackingArea];
    559     [super updateTrackingAreas];
    560 }
    561 
    562 - (void)keyDown:(NSEvent *)event
    563 {
    564     const int key = translateKeyCocoa([event keyCode]);
    565     const int mods = translateFlags([event modifierFlags]);
    566 
    567     _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
    568 
    569     [self interpretKeyEvents:@[event]];
    570 }
    571 
    572 - (void)flagsChanged:(NSEvent *)event
    573 {
    574     int action;
    575     const unsigned int modifierFlags =
    576         [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
    577     const int key = translateKeyCocoa([event keyCode]);
    578     const int mods = translateFlags(modifierFlags);
    579     const NSUInteger keyFlag = translateKeyToModifierFlag(key);
    580 
    581     if (keyFlag & modifierFlags)
    582     {
    583         if (window->keys[key] == GLFW_PRESS)
    584             action = GLFW_RELEASE;
    585         else
    586             action = GLFW_PRESS;
    587     }
    588     else
    589         action = GLFW_RELEASE;
    590 
    591     _glfwInputKey(window, key, [event keyCode], action, mods);
    592 }
    593 
    594 - (void)keyUp:(NSEvent *)event
    595 {
    596     const int key = translateKeyCocoa([event keyCode]);
    597     const int mods = translateFlags([event modifierFlags]);
    598     _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods);
    599 }
    600 
    601 - (void)scrollWheel:(NSEvent *)event
    602 {
    603     double deltaX = [event scrollingDeltaX];
    604     double deltaY = [event scrollingDeltaY];
    605 
    606     if ([event hasPreciseScrollingDeltas])
    607     {
    608         deltaX *= 0.1;
    609         deltaY *= 0.1;
    610     }
    611 
    612     if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
    613         _glfwInputScroll(window, deltaX, deltaY);
    614 }
    615 
    616 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
    617 {
    618     // HACK: We don't know what to say here because we don't know what the
    619     //       application wants to do with the paths
    620     return NSDragOperationGeneric;
    621 }
    622 
    623 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
    624 {
    625     const NSRect contentRect = [window->ns.view frame];
    626     // NOTE: The returned location uses base 0,1 not 0,0
    627     const NSPoint pos = [sender draggingLocation];
    628     _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y);
    629 
    630     NSPasteboard* pasteboard = [sender draggingPasteboard];
    631     NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
    632     NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
    633                                               options:options];
    634     const NSUInteger count = [urls count];
    635     if (count)
    636     {
    637         char** paths = _glfw_calloc(count, sizeof(char*));
    638 
    639         for (NSUInteger i = 0;  i < count;  i++)
    640             paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]);
    641 
    642         _glfwInputDrop(window, (int) count, (const char**) paths);
    643 
    644         for (NSUInteger i = 0;  i < count;  i++)
    645             _glfw_free(paths[i]);
    646         _glfw_free(paths);
    647     }
    648 
    649     return YES;
    650 }
    651 
    652 - (BOOL)hasMarkedText
    653 {
    654     return [markedText length] > 0;
    655 }
    656 
    657 - (NSRange)markedRange
    658 {
    659     if ([markedText length] > 0)
    660         return NSMakeRange(0, [markedText length] - 1);
    661     else
    662         return kEmptyRange;
    663 }
    664 
    665 - (NSRange)selectedRange
    666 {
    667     return kEmptyRange;
    668 }
    669 
    670 - (void)setMarkedText:(id)string
    671         selectedRange:(NSRange)selectedRange
    672      replacementRange:(NSRange)replacementRange
    673 {
    674     [markedText release];
    675     if ([string isKindOfClass:[NSAttributedString class]])
    676         markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
    677     else
    678         markedText = [[NSMutableAttributedString alloc] initWithString:string];
    679 }
    680 
    681 - (void)unmarkText
    682 {
    683     [[markedText mutableString] setString:@""];
    684 }
    685 
    686 - (NSArray*)validAttributesForMarkedText
    687 {
    688     return [NSArray array];
    689 }
    690 
    691 - (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
    692                                                actualRange:(NSRangePointer)actualRange
    693 {
    694     return nil;
    695 }
    696 
    697 - (NSUInteger)characterIndexForPoint:(NSPoint)point
    698 {
    699     return 0;
    700 }
    701 
    702 - (NSRect)firstRectForCharacterRange:(NSRange)range
    703                          actualRange:(NSRangePointer)actualRange
    704 {
    705     const NSRect frame = [window->ns.view frame];
    706     return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
    707 }
    708 
    709 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange
    710 {
    711     NSString* characters;
    712     NSEvent* event = [NSApp currentEvent];
    713     const int mods = translateFlags([event modifierFlags]);
    714     const int plain = !(mods & GLFW_MOD_SUPER);
    715 
    716     if ([string isKindOfClass:[NSAttributedString class]])
    717         characters = [string string];
    718     else
    719         characters = (NSString*) string;
    720 
    721     NSRange range = NSMakeRange(0, [characters length]);
    722     while (range.length)
    723     {
    724         uint32_t codepoint = 0;
    725 
    726         if ([characters getBytes:&codepoint
    727                        maxLength:sizeof(codepoint)
    728                       usedLength:NULL
    729                         encoding:NSUTF32StringEncoding
    730                          options:0
    731                            range:range
    732                   remainingRange:&range])
    733         {
    734             if (codepoint >= 0xf700 && codepoint <= 0xf7ff)
    735                 continue;
    736 
    737             _glfwInputChar(window, codepoint, mods, plain);
    738         }
    739     }
    740 }
    741 
    742 - (void)doCommandBySelector:(SEL)selector
    743 {
    744 }
    745 
    746 @end
    747 
    748 
    749 //------------------------------------------------------------------------
    750 // GLFW window class
    751 //------------------------------------------------------------------------
    752 
    753 @interface GLFWWindow : NSWindow {}
    754 @end
    755 
    756 @implementation GLFWWindow
    757 
    758 - (BOOL)canBecomeKeyWindow
    759 {
    760     // Required for NSWindowStyleMaskBorderless windows
    761     return YES;
    762 }
    763 
    764 - (BOOL)canBecomeMainWindow
    765 {
    766     return YES;
    767 }
    768 
    769 @end
    770 
    771 
    772 // Create the Cocoa window
    773 //
    774 static GLFWbool createNativeWindow(_GLFWwindow* window,
    775                                    const _GLFWwndconfig* wndconfig,
    776                                    const _GLFWfbconfig* fbconfig)
    777 {
    778     window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window];
    779     if (window->ns.delegate == nil)
    780     {
    781         _glfwInputError(GLFW_PLATFORM_ERROR,
    782                         "Cocoa: Failed to create window delegate");
    783         return GLFW_FALSE;
    784     }
    785 
    786     NSRect contentRect;
    787 
    788     if (window->monitor)
    789     {
    790         GLFWvidmode mode;
    791         int xpos, ypos;
    792 
    793         _glfwGetVideoModeCocoa(window->monitor, &mode);
    794         _glfwGetMonitorPosCocoa(window->monitor, &xpos, &ypos);
    795 
    796         contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height);
    797     }
    798     else
    799     {
    800         if (wndconfig->xpos == GLFW_ANY_POSITION ||
    801             wndconfig->ypos == GLFW_ANY_POSITION)
    802         {
    803             contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height);
    804         }
    805         else
    806         {
    807             const int xpos = wndconfig->xpos;
    808             const int ypos = _glfwTransformYCocoa(wndconfig->ypos + wndconfig->height - 1);
    809             contentRect = NSMakeRect(xpos, ypos, wndconfig->width, wndconfig->height);
    810         }
    811     }
    812 
    813     NSUInteger styleMask = NSWindowStyleMaskMiniaturizable;
    814 
    815     if (window->monitor || !window->decorated)
    816         styleMask |= NSWindowStyleMaskBorderless;
    817     else
    818     {
    819         styleMask |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable);
    820 
    821         if (window->resizable)
    822             styleMask |= NSWindowStyleMaskResizable;
    823     }
    824 
    825     window->ns.object = [[GLFWWindow alloc]
    826         initWithContentRect:contentRect
    827                   styleMask:styleMask
    828                     backing:NSBackingStoreBuffered
    829                       defer:NO];
    830 
    831     if (window->ns.object == nil)
    832     {
    833         _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window");
    834         return GLFW_FALSE;
    835     }
    836 
    837     if (window->monitor)
    838         [window->ns.object setLevel:NSMainMenuWindowLevel + 1];
    839     else
    840     {
    841         if (wndconfig->xpos == GLFW_ANY_POSITION ||
    842             wndconfig->ypos == GLFW_ANY_POSITION)
    843         {
    844             [(NSWindow*) window->ns.object center];
    845             _glfw.ns.cascadePoint =
    846                 NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
    847                                 NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
    848         }
    849 
    850         if (wndconfig->resizable)
    851         {
    852             const NSWindowCollectionBehavior behavior =
    853                 NSWindowCollectionBehaviorFullScreenPrimary |
    854                 NSWindowCollectionBehaviorManaged;
    855             [window->ns.object setCollectionBehavior:behavior];
    856         }
    857         else
    858         {
    859             const NSWindowCollectionBehavior behavior =
    860                 NSWindowCollectionBehaviorFullScreenNone;
    861             [window->ns.object setCollectionBehavior:behavior];
    862         }
    863 
    864         if (wndconfig->floating)
    865             [window->ns.object setLevel:NSFloatingWindowLevel];
    866 
    867         if (wndconfig->maximized)
    868             [window->ns.object zoom:nil];
    869     }
    870 
    871     if (strlen(wndconfig->ns.frameName))
    872         [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)];
    873 
    874     window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
    875     window->ns.scaleFramebuffer = wndconfig->scaleFramebuffer;
    876 
    877     if (fbconfig->transparent)
    878     {
    879         [window->ns.object setOpaque:NO];
    880         [window->ns.object setHasShadow:NO];
    881         [window->ns.object setBackgroundColor:[NSColor clearColor]];
    882     }
    883 
    884     [window->ns.object setContentView:window->ns.view];
    885     [window->ns.object makeFirstResponder:window->ns.view];
    886     [window->ns.object setTitle:@(wndconfig->title)];
    887     [window->ns.object setDelegate:window->ns.delegate];
    888     [window->ns.object setAcceptsMouseMovedEvents:YES];
    889     [window->ns.object setRestorable:NO];
    890 
    891 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
    892     if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)])
    893         [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed];
    894 #endif
    895 
    896     _glfwGetWindowSizeCocoa(window, &window->ns.width, &window->ns.height);
    897     _glfwGetFramebufferSizeCocoa(window, &window->ns.fbWidth, &window->ns.fbHeight);
    898 
    899     return GLFW_TRUE;
    900 }
    901 
    902 
    903 //////////////////////////////////////////////////////////////////////////
    904 //////                       GLFW internal API                      //////
    905 //////////////////////////////////////////////////////////////////////////
    906 
    907 // Transforms a y-coordinate between the CG display and NS screen spaces
    908 //
    909 float _glfwTransformYCocoa(float y)
    910 {
    911     return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1;
    912 }
    913 
    914 
    915 //////////////////////////////////////////////////////////////////////////
    916 //////                       GLFW platform API                      //////
    917 //////////////////////////////////////////////////////////////////////////
    918 
    919 GLFWbool _glfwCreateWindowCocoa(_GLFWwindow* window,
    920                                 const _GLFWwndconfig* wndconfig,
    921                                 const _GLFWctxconfig* ctxconfig,
    922                                 const _GLFWfbconfig* fbconfig)
    923 {
    924     @autoreleasepool {
    925 
    926     if (!createNativeWindow(window, wndconfig, fbconfig))
    927         return GLFW_FALSE;
    928 
    929     if (ctxconfig->client != GLFW_NO_API)
    930     {
    931         if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API)
    932         {
    933             if (!_glfwInitNSGL())
    934                 return GLFW_FALSE;
    935             if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig))
    936                 return GLFW_FALSE;
    937         }
    938         else if (ctxconfig->source == GLFW_EGL_CONTEXT_API)
    939         {
    940             // EGL implementation on macOS use CALayer* EGLNativeWindowType so we
    941             // need to get the layer for EGL window surface creation.
    942             [window->ns.view setWantsLayer:YES];
    943             window->ns.layer = [window->ns.view layer];
    944 
    945             if (!_glfwInitEGL())
    946                 return GLFW_FALSE;
    947             if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
    948                 return GLFW_FALSE;
    949         }
    950         else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
    951         {
    952             if (!_glfwInitOSMesa())
    953                 return GLFW_FALSE;
    954             if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
    955                 return GLFW_FALSE;
    956         }
    957 
    958         if (!_glfwRefreshContextAttribs(window, ctxconfig))
    959             return GLFW_FALSE;
    960     }
    961 
    962     if (wndconfig->mousePassthrough)
    963         _glfwSetWindowMousePassthroughCocoa(window, GLFW_TRUE);
    964 
    965     if (window->monitor)
    966     {
    967         _glfwShowWindowCocoa(window);
    968         _glfwFocusWindowCocoa(window);
    969         acquireMonitorCocoa(window);
    970 
    971         if (wndconfig->centerCursor)
    972             _glfwCenterCursorInContentArea(window);
    973     }
    974     else
    975     {
    976         if (wndconfig->visible)
    977         {
    978             _glfwShowWindowCocoa(window);
    979             if (wndconfig->focused)
    980                 _glfwFocusWindowCocoa(window);
    981         }
    982     }
    983 
    984     return GLFW_TRUE;
    985 
    986     } // autoreleasepool
    987 }
    988 
    989 void _glfwDestroyWindowCocoa(_GLFWwindow* window)
    990 {
    991     @autoreleasepool {
    992 
    993     if (_glfw.ns.disabledCursorWindow == window)
    994         _glfw.ns.disabledCursorWindow = NULL;
    995 
    996     [window->ns.object orderOut:nil];
    997 
    998     if (window->monitor)
    999         releaseMonitorCocoa(window);
   1000 
   1001     if (window->context.destroy)
   1002         window->context.destroy(window);
   1003 
   1004     [window->ns.object setDelegate:nil];
   1005     [window->ns.delegate release];
   1006     window->ns.delegate = nil;
   1007 
   1008     [window->ns.view release];
   1009     window->ns.view = nil;
   1010 
   1011     [window->ns.object close];
   1012     window->ns.object = nil;
   1013 
   1014     // HACK: Allow Cocoa to catch up before returning
   1015     _glfwPollEventsCocoa();
   1016 
   1017     } // autoreleasepool
   1018 }
   1019 
   1020 void _glfwSetWindowTitleCocoa(_GLFWwindow* window, const char* title)
   1021 {
   1022     @autoreleasepool {
   1023     NSString* string = @(title);
   1024     [window->ns.object setTitle:string];
   1025     // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it
   1026     //       if the window lacks NSWindowStyleMaskTitled
   1027     [window->ns.object setMiniwindowTitle:string];
   1028     } // autoreleasepool
   1029 }
   1030 
   1031 void _glfwSetWindowIconCocoa(_GLFWwindow* window,
   1032                              int count, const GLFWimage* images)
   1033 {
   1034     _glfwInputError(GLFW_FEATURE_UNAVAILABLE,
   1035                     "Cocoa: Regular windows do not have icons on macOS");
   1036 }
   1037 
   1038 void _glfwGetWindowPosCocoa(_GLFWwindow* window, int* xpos, int* ypos)
   1039 {
   1040     @autoreleasepool {
   1041 
   1042     const NSRect contentRect =
   1043         [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
   1044 
   1045     if (xpos)
   1046         *xpos = contentRect.origin.x;
   1047     if (ypos)
   1048         *ypos = _glfwTransformYCocoa(contentRect.origin.y + contentRect.size.height - 1);
   1049 
   1050     } // autoreleasepool
   1051 }
   1052 
   1053 void _glfwSetWindowPosCocoa(_GLFWwindow* window, int x, int y)
   1054 {
   1055     @autoreleasepool {
   1056 
   1057     const NSRect contentRect = [window->ns.view frame];
   1058     const NSRect dummyRect = NSMakeRect(x, _glfwTransformYCocoa(y + contentRect.size.height - 1), 0, 0);
   1059     const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect];
   1060     [window->ns.object setFrameOrigin:frameRect.origin];
   1061 
   1062     } // autoreleasepool
   1063 }
   1064 
   1065 void _glfwGetWindowSizeCocoa(_GLFWwindow* window, int* width, int* height)
   1066 {
   1067     @autoreleasepool {
   1068 
   1069     const NSRect contentRect = [window->ns.view frame];
   1070 
   1071     if (width)
   1072         *width = contentRect.size.width;
   1073     if (height)
   1074         *height = contentRect.size.height;
   1075 
   1076     } // autoreleasepool
   1077 }
   1078 
   1079 void _glfwSetWindowSizeCocoa(_GLFWwindow* window, int width, int height)
   1080 {
   1081     @autoreleasepool {
   1082 
   1083     if (window->monitor)
   1084     {
   1085         if (window->monitor->window == window)
   1086             acquireMonitorCocoa(window);
   1087     }
   1088     else
   1089     {
   1090         NSRect contentRect =
   1091             [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
   1092         contentRect.origin.y += contentRect.size.height - height;
   1093         contentRect.size = NSMakeSize(width, height);
   1094         [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect]
   1095                             display:YES];
   1096     }
   1097 
   1098     } // autoreleasepool
   1099 }
   1100 
   1101 void _glfwSetWindowSizeLimitsCocoa(_GLFWwindow* window,
   1102                                    int minwidth, int minheight,
   1103                                    int maxwidth, int maxheight)
   1104 {
   1105     @autoreleasepool {
   1106 
   1107     if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE)
   1108         [window->ns.object setContentMinSize:NSMakeSize(0, 0)];
   1109     else
   1110         [window->ns.object setContentMinSize:NSMakeSize(minwidth, minheight)];
   1111 
   1112     if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE)
   1113         [window->ns.object setContentMaxSize:NSMakeSize(DBL_MAX, DBL_MAX)];
   1114     else
   1115         [window->ns.object setContentMaxSize:NSMakeSize(maxwidth, maxheight)];
   1116 
   1117     } // autoreleasepool
   1118 }
   1119 
   1120 void _glfwSetWindowAspectRatioCocoa(_GLFWwindow* window, int numer, int denom)
   1121 {
   1122     @autoreleasepool {
   1123     if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
   1124         [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
   1125     else
   1126         [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
   1127     } // autoreleasepool
   1128 }
   1129 
   1130 void _glfwGetFramebufferSizeCocoa(_GLFWwindow* window, int* width, int* height)
   1131 {
   1132     @autoreleasepool {
   1133 
   1134     const NSRect contentRect = [window->ns.view frame];
   1135     const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
   1136 
   1137     if (width)
   1138         *width = (int) fbRect.size.width;
   1139     if (height)
   1140         *height = (int) fbRect.size.height;
   1141 
   1142     } // autoreleasepool
   1143 }
   1144 
   1145 void _glfwGetWindowFrameSizeCocoa(_GLFWwindow* window,
   1146                                   int* left, int* top,
   1147                                   int* right, int* bottom)
   1148 {
   1149     @autoreleasepool {
   1150 
   1151     const NSRect contentRect = [window->ns.view frame];
   1152     const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect];
   1153 
   1154     if (left)
   1155         *left = contentRect.origin.x - frameRect.origin.x;
   1156     if (top)
   1157         *top = frameRect.origin.y + frameRect.size.height -
   1158                contentRect.origin.y - contentRect.size.height;
   1159     if (right)
   1160         *right = frameRect.origin.x + frameRect.size.width -
   1161                  contentRect.origin.x - contentRect.size.width;
   1162     if (bottom)
   1163         *bottom = contentRect.origin.y - frameRect.origin.y;
   1164 
   1165     } // autoreleasepool
   1166 }
   1167 
   1168 void _glfwGetWindowContentScaleCocoa(_GLFWwindow* window,
   1169                                      float* xscale, float* yscale)
   1170 {
   1171     @autoreleasepool {
   1172 
   1173     const NSRect points = [window->ns.view frame];
   1174     const NSRect pixels = [window->ns.view convertRectToBacking:points];
   1175 
   1176     if (xscale)
   1177         *xscale = (float) (pixels.size.width / points.size.width);
   1178     if (yscale)
   1179         *yscale = (float) (pixels.size.height / points.size.height);
   1180 
   1181     } // autoreleasepool
   1182 }
   1183 
   1184 void _glfwIconifyWindowCocoa(_GLFWwindow* window)
   1185 {
   1186     @autoreleasepool {
   1187     [window->ns.object miniaturize:nil];
   1188     } // autoreleasepool
   1189 }
   1190 
   1191 void _glfwRestoreWindowCocoa(_GLFWwindow* window)
   1192 {
   1193     @autoreleasepool {
   1194     if ([window->ns.object isMiniaturized])
   1195         [window->ns.object deminiaturize:nil];
   1196     else if ([window->ns.object isZoomed])
   1197         [window->ns.object zoom:nil];
   1198     } // autoreleasepool
   1199 }
   1200 
   1201 void _glfwMaximizeWindowCocoa(_GLFWwindow* window)
   1202 {
   1203     @autoreleasepool {
   1204     if (![window->ns.object isZoomed])
   1205         [window->ns.object zoom:nil];
   1206     } // autoreleasepool
   1207 }
   1208 
   1209 void _glfwShowWindowCocoa(_GLFWwindow* window)
   1210 {
   1211     @autoreleasepool {
   1212     [window->ns.object orderFront:nil];
   1213     } // autoreleasepool
   1214 }
   1215 
   1216 void _glfwHideWindowCocoa(_GLFWwindow* window)
   1217 {
   1218     @autoreleasepool {
   1219     [window->ns.object orderOut:nil];
   1220     } // autoreleasepool
   1221 }
   1222 
   1223 void _glfwRequestWindowAttentionCocoa(_GLFWwindow* window)
   1224 {
   1225     @autoreleasepool {
   1226     [NSApp requestUserAttention:NSInformationalRequest];
   1227     } // autoreleasepool
   1228 }
   1229 
   1230 void _glfwFocusWindowCocoa(_GLFWwindow* window)
   1231 {
   1232     @autoreleasepool {
   1233     // Make us the active application
   1234     // HACK: This is here to prevent applications using only hidden windows from
   1235     //       being activated, but should probably not be done every time any
   1236     //       window is shown
   1237     [NSApp activateIgnoringOtherApps:YES];
   1238     [window->ns.object makeKeyAndOrderFront:nil];
   1239     } // autoreleasepool
   1240 }
   1241 
   1242 void _glfwSetWindowMonitorCocoa(_GLFWwindow* window,
   1243                                 _GLFWmonitor* monitor,
   1244                                 int xpos, int ypos,
   1245                                 int width, int height,
   1246                                 int refreshRate)
   1247 {
   1248     @autoreleasepool {
   1249 
   1250     if (window->monitor == monitor)
   1251     {
   1252         if (monitor)
   1253         {
   1254             if (monitor->window == window)
   1255                 acquireMonitorCocoa(window);
   1256         }
   1257         else
   1258         {
   1259             const NSRect contentRect =
   1260                 NSMakeRect(xpos, _glfwTransformYCocoa(ypos + height - 1), width, height);
   1261             const NSUInteger styleMask = [window->ns.object styleMask];
   1262             const NSRect frameRect =
   1263                 [window->ns.object frameRectForContentRect:contentRect
   1264                                                  styleMask:styleMask];
   1265 
   1266             [window->ns.object setFrame:frameRect display:YES];
   1267         }
   1268 
   1269         return;
   1270     }
   1271 
   1272     if (window->monitor)
   1273         releaseMonitorCocoa(window);
   1274 
   1275     _glfwInputWindowMonitor(window, monitor);
   1276 
   1277     // HACK: Allow the state cached in Cocoa to catch up to reality
   1278     // TODO: Solve this in a less terrible way
   1279     _glfwPollEventsCocoa();
   1280 
   1281     NSUInteger styleMask = [window->ns.object styleMask];
   1282 
   1283     if (window->monitor)
   1284     {
   1285         styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable);
   1286         styleMask |= NSWindowStyleMaskBorderless;
   1287     }
   1288     else
   1289     {
   1290         if (window->decorated)
   1291         {
   1292             styleMask &= ~NSWindowStyleMaskBorderless;
   1293             styleMask |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable);
   1294         }
   1295 
   1296         if (window->resizable)
   1297             styleMask |= NSWindowStyleMaskResizable;
   1298         else
   1299             styleMask &= ~NSWindowStyleMaskResizable;
   1300     }
   1301 
   1302     [window->ns.object setStyleMask:styleMask];
   1303     // HACK: Changing the style mask can cause the first responder to be cleared
   1304     [window->ns.object makeFirstResponder:window->ns.view];
   1305 
   1306     if (window->monitor)
   1307     {
   1308         [window->ns.object setLevel:NSMainMenuWindowLevel + 1];
   1309         [window->ns.object setHasShadow:NO];
   1310 
   1311         acquireMonitorCocoa(window);
   1312     }
   1313     else
   1314     {
   1315         NSRect contentRect = NSMakeRect(xpos, _glfwTransformYCocoa(ypos + height - 1),
   1316                                         width, height);
   1317         NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect
   1318                                                             styleMask:styleMask];
   1319         [window->ns.object setFrame:frameRect display:YES];
   1320 
   1321         if (window->numer != GLFW_DONT_CARE &&
   1322             window->denom != GLFW_DONT_CARE)
   1323         {
   1324             [window->ns.object setContentAspectRatio:NSMakeSize(window->numer,
   1325                                                                 window->denom)];
   1326         }
   1327 
   1328         if (window->minwidth != GLFW_DONT_CARE &&
   1329             window->minheight != GLFW_DONT_CARE)
   1330         {
   1331             [window->ns.object setContentMinSize:NSMakeSize(window->minwidth,
   1332                                                             window->minheight)];
   1333         }
   1334 
   1335         if (window->maxwidth != GLFW_DONT_CARE &&
   1336             window->maxheight != GLFW_DONT_CARE)
   1337         {
   1338             [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth,
   1339                                                             window->maxheight)];
   1340         }
   1341 
   1342         if (window->floating)
   1343             [window->ns.object setLevel:NSFloatingWindowLevel];
   1344         else
   1345             [window->ns.object setLevel:NSNormalWindowLevel];
   1346 
   1347         if (window->resizable)
   1348         {
   1349             const NSWindowCollectionBehavior behavior =
   1350                 NSWindowCollectionBehaviorFullScreenPrimary |
   1351                 NSWindowCollectionBehaviorManaged;
   1352             [window->ns.object setCollectionBehavior:behavior];
   1353         }
   1354         else
   1355         {
   1356             const NSWindowCollectionBehavior behavior =
   1357                 NSWindowCollectionBehaviorFullScreenNone;
   1358             [window->ns.object setCollectionBehavior:behavior];
   1359         }
   1360 
   1361         [window->ns.object setHasShadow:YES];
   1362         // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window
   1363         //       title property but the miniwindow title property is unaffected
   1364         [window->ns.object setTitle:[window->ns.object miniwindowTitle]];
   1365     }
   1366 
   1367     } // autoreleasepool
   1368 }
   1369 
   1370 GLFWbool _glfwWindowFocusedCocoa(_GLFWwindow* window)
   1371 {
   1372     @autoreleasepool {
   1373     return [window->ns.object isKeyWindow];
   1374     } // autoreleasepool
   1375 }
   1376 
   1377 GLFWbool _glfwWindowIconifiedCocoa(_GLFWwindow* window)
   1378 {
   1379     @autoreleasepool {
   1380     return [window->ns.object isMiniaturized];
   1381     } // autoreleasepool
   1382 }
   1383 
   1384 GLFWbool _glfwWindowVisibleCocoa(_GLFWwindow* window)
   1385 {
   1386     @autoreleasepool {
   1387     return [window->ns.object isVisible];
   1388     } // autoreleasepool
   1389 }
   1390 
   1391 GLFWbool _glfwWindowMaximizedCocoa(_GLFWwindow* window)
   1392 {
   1393     @autoreleasepool {
   1394 
   1395     if (window->resizable)
   1396         return [window->ns.object isZoomed];
   1397     else
   1398         return GLFW_FALSE;
   1399 
   1400     } // autoreleasepool
   1401 }
   1402 
   1403 GLFWbool _glfwWindowHoveredCocoa(_GLFWwindow* window)
   1404 {
   1405     @autoreleasepool {
   1406 
   1407     const NSPoint point = [NSEvent mouseLocation];
   1408 
   1409     if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] !=
   1410         [window->ns.object windowNumber])
   1411     {
   1412         return GLFW_FALSE;
   1413     }
   1414 
   1415     return NSMouseInRect(point,
   1416         [window->ns.object convertRectToScreen:[window->ns.view frame]], NO);
   1417 
   1418     } // autoreleasepool
   1419 }
   1420 
   1421 GLFWbool _glfwFramebufferTransparentCocoa(_GLFWwindow* window)
   1422 {
   1423     @autoreleasepool {
   1424     return ![window->ns.object isOpaque] && ![window->ns.view isOpaque];
   1425     } // autoreleasepool
   1426 }
   1427 
   1428 void _glfwSetWindowResizableCocoa(_GLFWwindow* window, GLFWbool enabled)
   1429 {
   1430     @autoreleasepool {
   1431 
   1432     const NSUInteger styleMask = [window->ns.object styleMask];
   1433     if (enabled)
   1434     {
   1435         [window->ns.object setStyleMask:(styleMask | NSWindowStyleMaskResizable)];
   1436         const NSWindowCollectionBehavior behavior =
   1437             NSWindowCollectionBehaviorFullScreenPrimary |
   1438             NSWindowCollectionBehaviorManaged;
   1439         [window->ns.object setCollectionBehavior:behavior];
   1440     }
   1441     else
   1442     {
   1443         [window->ns.object setStyleMask:(styleMask & ~NSWindowStyleMaskResizable)];
   1444         const NSWindowCollectionBehavior behavior =
   1445             NSWindowCollectionBehaviorFullScreenNone;
   1446         [window->ns.object setCollectionBehavior:behavior];
   1447     }
   1448 
   1449     } // autoreleasepool
   1450 }
   1451 
   1452 void _glfwSetWindowDecoratedCocoa(_GLFWwindow* window, GLFWbool enabled)
   1453 {
   1454     @autoreleasepool {
   1455 
   1456     NSUInteger styleMask = [window->ns.object styleMask];
   1457     if (enabled)
   1458     {
   1459         styleMask |= (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable);
   1460         styleMask &= ~NSWindowStyleMaskBorderless;
   1461     }
   1462     else
   1463     {
   1464         styleMask |= NSWindowStyleMaskBorderless;
   1465         styleMask &= ~(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable);
   1466     }
   1467 
   1468     [window->ns.object setStyleMask:styleMask];
   1469     [window->ns.object makeFirstResponder:window->ns.view];
   1470 
   1471     } // autoreleasepool
   1472 }
   1473 
   1474 void _glfwSetWindowFloatingCocoa(_GLFWwindow* window, GLFWbool enabled)
   1475 {
   1476     @autoreleasepool {
   1477     if (enabled)
   1478         [window->ns.object setLevel:NSFloatingWindowLevel];
   1479     else
   1480         [window->ns.object setLevel:NSNormalWindowLevel];
   1481     } // autoreleasepool
   1482 }
   1483 
   1484 void _glfwSetWindowMousePassthroughCocoa(_GLFWwindow* window, GLFWbool enabled)
   1485 {
   1486     @autoreleasepool {
   1487     [window->ns.object setIgnoresMouseEvents:enabled];
   1488     }
   1489 }
   1490 
   1491 float _glfwGetWindowOpacityCocoa(_GLFWwindow* window)
   1492 {
   1493     @autoreleasepool {
   1494     return (float) [window->ns.object alphaValue];
   1495     } // autoreleasepool
   1496 }
   1497 
   1498 void _glfwSetWindowOpacityCocoa(_GLFWwindow* window, float opacity)
   1499 {
   1500     @autoreleasepool {
   1501     [window->ns.object setAlphaValue:opacity];
   1502     } // autoreleasepool
   1503 }
   1504 
   1505 void _glfwSetRawMouseMotionCocoa(_GLFWwindow *window, GLFWbool enabled)
   1506 {
   1507     _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
   1508                     "Cocoa: Raw mouse motion not yet implemented");
   1509 }
   1510 
   1511 GLFWbool _glfwRawMouseMotionSupportedCocoa(void)
   1512 {
   1513     return GLFW_FALSE;
   1514 }
   1515 
   1516 void _glfwPollEventsCocoa(void)
   1517 {
   1518     @autoreleasepool {
   1519 
   1520     for (;;)
   1521     {
   1522         NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
   1523                                             untilDate:[NSDate distantPast]
   1524                                                inMode:NSDefaultRunLoopMode
   1525                                               dequeue:YES];
   1526         if (event == nil)
   1527             break;
   1528 
   1529         [NSApp sendEvent:event];
   1530     }
   1531 
   1532     } // autoreleasepool
   1533 }
   1534 
   1535 void _glfwWaitEventsCocoa(void)
   1536 {
   1537     @autoreleasepool {
   1538 
   1539     // I wanted to pass NO to dequeue:, and rely on PollEvents to
   1540     // dequeue and send.  For reasons not at all clear to me, passing
   1541     // NO to dequeue: causes this method never to return.
   1542     NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
   1543                                         untilDate:[NSDate distantFuture]
   1544                                            inMode:NSDefaultRunLoopMode
   1545                                           dequeue:YES];
   1546     [NSApp sendEvent:event];
   1547 
   1548     _glfwPollEventsCocoa();
   1549 
   1550     } // autoreleasepool
   1551 }
   1552 
   1553 void _glfwWaitEventsTimeoutCocoa(double timeout)
   1554 {
   1555     @autoreleasepool {
   1556 
   1557     NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout];
   1558     NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
   1559                                         untilDate:date
   1560                                            inMode:NSDefaultRunLoopMode
   1561                                           dequeue:YES];
   1562     if (event)
   1563         [NSApp sendEvent:event];
   1564 
   1565     _glfwPollEventsCocoa();
   1566 
   1567     } // autoreleasepool
   1568 }
   1569 
   1570 void _glfwPostEmptyEventCocoa(void)
   1571 {
   1572     @autoreleasepool {
   1573 
   1574     NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
   1575                                         location:NSMakePoint(0, 0)
   1576                                    modifierFlags:0
   1577                                        timestamp:0
   1578                                     windowNumber:0
   1579                                          context:nil
   1580                                          subtype:0
   1581                                            data1:0
   1582                                            data2:0];
   1583     [NSApp postEvent:event atStart:YES];
   1584 
   1585     } // autoreleasepool
   1586 }
   1587 
   1588 void _glfwGetCursorPosCocoa(_GLFWwindow* window, double* xpos, double* ypos)
   1589 {
   1590     @autoreleasepool {
   1591 
   1592     const NSRect contentRect = [window->ns.view frame];
   1593     // NOTE: The returned location uses base 0,1 not 0,0
   1594     const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream];
   1595 
   1596     if (xpos)
   1597         *xpos = pos.x;
   1598     if (ypos)
   1599         *ypos = contentRect.size.height - pos.y;
   1600 
   1601     } // autoreleasepool
   1602 }
   1603 
   1604 void _glfwSetCursorPosCocoa(_GLFWwindow* window, double x, double y)
   1605 {
   1606     @autoreleasepool {
   1607 
   1608     updateCursorImage(window);
   1609 
   1610     const NSRect contentRect = [window->ns.view frame];
   1611     // NOTE: The returned location uses base 0,1 not 0,0
   1612     const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream];
   1613 
   1614     window->ns.cursorWarpDeltaX += x - pos.x;
   1615     window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y;
   1616 
   1617     if (window->monitor)
   1618     {
   1619         CGDisplayMoveCursorToPoint(window->monitor->ns.displayID,
   1620                                    CGPointMake(x, y));
   1621     }
   1622     else
   1623     {
   1624         const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0);
   1625         const NSRect globalRect = [window->ns.object convertRectToScreen:localRect];
   1626         const NSPoint globalPoint = globalRect.origin;
   1627 
   1628         CGWarpMouseCursorPosition(CGPointMake(globalPoint.x,
   1629                                               _glfwTransformYCocoa(globalPoint.y)));
   1630     }
   1631 
   1632     // HACK: Calling this right after setting the cursor position prevents macOS
   1633     //       from freezing the cursor for a fraction of a second afterwards
   1634     if (window->cursorMode != GLFW_CURSOR_DISABLED)
   1635         CGAssociateMouseAndMouseCursorPosition(true);
   1636 
   1637     } // autoreleasepool
   1638 }
   1639 
   1640 void _glfwSetCursorModeCocoa(_GLFWwindow* window, int mode)
   1641 {
   1642     @autoreleasepool {
   1643 
   1644     if (mode == GLFW_CURSOR_CAPTURED)
   1645     {
   1646         _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
   1647                         "Cocoa: Captured cursor mode not yet implemented");
   1648     }
   1649 
   1650     if (_glfwWindowFocusedCocoa(window))
   1651         updateCursorMode(window);
   1652 
   1653     } // autoreleasepool
   1654 }
   1655 
   1656 const char* _glfwGetScancodeNameCocoa(int scancode)
   1657 {
   1658     @autoreleasepool {
   1659 
   1660     if (scancode < 0 || scancode > 0xff)
   1661     {
   1662         _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode %i", scancode);
   1663         return NULL;
   1664     }
   1665 
   1666     const int key = _glfw.ns.keycodes[scancode];
   1667     if (key == GLFW_KEY_UNKNOWN)
   1668         return NULL;
   1669 
   1670     UInt32 deadKeyState = 0;
   1671     UniChar characters[4];
   1672     UniCharCount characterCount = 0;
   1673 
   1674     if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes],
   1675                        scancode,
   1676                        kUCKeyActionDisplay,
   1677                        0,
   1678                        LMGetKbdType(),
   1679                        kUCKeyTranslateNoDeadKeysBit,
   1680                        &deadKeyState,
   1681                        sizeof(characters) / sizeof(characters[0]),
   1682                        &characterCount,
   1683                        characters) != noErr)
   1684     {
   1685         return NULL;
   1686     }
   1687 
   1688     if (!characterCount)
   1689         return NULL;
   1690 
   1691     CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
   1692                                                             characters,
   1693                                                             characterCount,
   1694                                                             kCFAllocatorNull);
   1695     CFStringGetCString(string,
   1696                        _glfw.ns.keynames[key],
   1697                        sizeof(_glfw.ns.keynames[key]),
   1698                        kCFStringEncodingUTF8);
   1699     CFRelease(string);
   1700 
   1701     return _glfw.ns.keynames[key];
   1702 
   1703     } // autoreleasepool
   1704 }
   1705 
   1706 int _glfwGetKeyScancodeCocoa(int key)
   1707 {
   1708     return _glfw.ns.scancodes[key];
   1709 }
   1710 
   1711 GLFWbool _glfwCreateCursorCocoa(_GLFWcursor* cursor,
   1712                                 const GLFWimage* image,
   1713                                 int xhot, int yhot)
   1714 {
   1715     @autoreleasepool {
   1716 
   1717     NSImage* native;
   1718     NSBitmapImageRep* rep;
   1719 
   1720     rep = [[NSBitmapImageRep alloc]
   1721         initWithBitmapDataPlanes:NULL
   1722                       pixelsWide:image->width
   1723                       pixelsHigh:image->height
   1724                    bitsPerSample:8
   1725                  samplesPerPixel:4
   1726                         hasAlpha:YES
   1727                         isPlanar:NO
   1728                   colorSpaceName:NSCalibratedRGBColorSpace
   1729                     bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
   1730                      bytesPerRow:image->width * 4
   1731                     bitsPerPixel:32];
   1732 
   1733     if (rep == nil)
   1734         return GLFW_FALSE;
   1735 
   1736     memcpy([rep bitmapData], image->pixels, image->width * image->height * 4);
   1737 
   1738     native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
   1739     [native addRepresentation:rep];
   1740 
   1741     cursor->ns.object = [[NSCursor alloc] initWithImage:native
   1742                                                 hotSpot:NSMakePoint(xhot, yhot)];
   1743 
   1744     [native release];
   1745     [rep release];
   1746 
   1747     if (cursor->ns.object == nil)
   1748         return GLFW_FALSE;
   1749 
   1750     return GLFW_TRUE;
   1751 
   1752     } // autoreleasepool
   1753 }
   1754 
   1755 GLFWbool _glfwCreateStandardCursorCocoa(_GLFWcursor* cursor, int shape)
   1756 {
   1757     @autoreleasepool {
   1758 
   1759     SEL cursorSelector = NULL;
   1760 
   1761     // HACK: Try to use a private message
   1762     switch (shape)
   1763     {
   1764         case GLFW_RESIZE_EW_CURSOR:
   1765             cursorSelector = NSSelectorFromString(@"_windowResizeEastWestCursor");
   1766             break;
   1767         case GLFW_RESIZE_NS_CURSOR:
   1768             cursorSelector = NSSelectorFromString(@"_windowResizeNorthSouthCursor");
   1769             break;
   1770         case GLFW_RESIZE_NWSE_CURSOR:
   1771             cursorSelector = NSSelectorFromString(@"_windowResizeNorthWestSouthEastCursor");
   1772             break;
   1773         case GLFW_RESIZE_NESW_CURSOR:
   1774             cursorSelector = NSSelectorFromString(@"_windowResizeNorthEastSouthWestCursor");
   1775             break;
   1776     }
   1777 
   1778     if (cursorSelector && [NSCursor respondsToSelector:cursorSelector])
   1779     {
   1780         id object = [NSCursor performSelector:cursorSelector];
   1781         if ([object isKindOfClass:[NSCursor class]])
   1782             cursor->ns.object = object;
   1783     }
   1784 
   1785     if (!cursor->ns.object)
   1786     {
   1787         switch (shape)
   1788         {
   1789             case GLFW_ARROW_CURSOR:
   1790                 cursor->ns.object = [NSCursor arrowCursor];
   1791                 break;
   1792             case GLFW_IBEAM_CURSOR:
   1793                 cursor->ns.object = [NSCursor IBeamCursor];
   1794                 break;
   1795             case GLFW_CROSSHAIR_CURSOR:
   1796                 cursor->ns.object = [NSCursor crosshairCursor];
   1797                 break;
   1798             case GLFW_POINTING_HAND_CURSOR:
   1799                 cursor->ns.object = [NSCursor pointingHandCursor];
   1800                 break;
   1801             case GLFW_RESIZE_EW_CURSOR:
   1802                 cursor->ns.object = [NSCursor resizeLeftRightCursor];
   1803                 break;
   1804             case GLFW_RESIZE_NS_CURSOR:
   1805                 cursor->ns.object = [NSCursor resizeUpDownCursor];
   1806                 break;
   1807             case GLFW_RESIZE_ALL_CURSOR:
   1808                 cursor->ns.object = [NSCursor closedHandCursor];
   1809                 break;
   1810             case GLFW_NOT_ALLOWED_CURSOR:
   1811                 cursor->ns.object = [NSCursor operationNotAllowedCursor];
   1812                 break;
   1813         }
   1814     }
   1815 
   1816     if (!cursor->ns.object)
   1817     {
   1818         _glfwInputError(GLFW_CURSOR_UNAVAILABLE,
   1819                         "Cocoa: Standard cursor shape unavailable");
   1820         return GLFW_FALSE;
   1821     }
   1822 
   1823     [cursor->ns.object retain];
   1824     return GLFW_TRUE;
   1825 
   1826     } // autoreleasepool
   1827 }
   1828 
   1829 void _glfwDestroyCursorCocoa(_GLFWcursor* cursor)
   1830 {
   1831     @autoreleasepool {
   1832     if (cursor->ns.object)
   1833         [(NSCursor*) cursor->ns.object release];
   1834     } // autoreleasepool
   1835 }
   1836 
   1837 void _glfwSetCursorCocoa(_GLFWwindow* window, _GLFWcursor* cursor)
   1838 {
   1839     @autoreleasepool {
   1840     if (cursorInContentArea(window))
   1841         updateCursorImage(window);
   1842     } // autoreleasepool
   1843 }
   1844 
   1845 void _glfwSetClipboardStringCocoa(const char* string)
   1846 {
   1847     @autoreleasepool {
   1848     NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
   1849     [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
   1850     [pasteboard setString:@(string) forType:NSPasteboardTypeString];
   1851     } // autoreleasepool
   1852 }
   1853 
   1854 const char* _glfwGetClipboardStringCocoa(void)
   1855 {
   1856     @autoreleasepool {
   1857 
   1858     NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
   1859 
   1860     if (![[pasteboard types] containsObject:NSPasteboardTypeString])
   1861     {
   1862         _glfwInputError(GLFW_FORMAT_UNAVAILABLE,
   1863                         "Cocoa: Failed to retrieve string from pasteboard");
   1864         return NULL;
   1865     }
   1866 
   1867     NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
   1868     if (!object)
   1869     {
   1870         _glfwInputError(GLFW_PLATFORM_ERROR,
   1871                         "Cocoa: Failed to retrieve object from pasteboard");
   1872         return NULL;
   1873     }
   1874 
   1875     _glfw_free(_glfw.ns.clipboardString);
   1876     _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]);
   1877 
   1878     return _glfw.ns.clipboardString;
   1879 
   1880     } // autoreleasepool
   1881 }
   1882 
   1883 EGLenum _glfwGetEGLPlatformCocoa(EGLint** attribs)
   1884 {
   1885     if (_glfw.egl.ANGLE_platform_angle)
   1886     {
   1887         int type = 0;
   1888 
   1889         if (_glfw.egl.ANGLE_platform_angle_opengl)
   1890         {
   1891             if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
   1892                 type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
   1893         }
   1894 
   1895         if (_glfw.egl.ANGLE_platform_angle_metal)
   1896         {
   1897             if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL)
   1898                 type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE;
   1899         }
   1900 
   1901         if (type)
   1902         {
   1903             *attribs = _glfw_calloc(3, sizeof(EGLint));
   1904             (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
   1905             (*attribs)[1] = type;
   1906             (*attribs)[2] = EGL_NONE;
   1907             return EGL_PLATFORM_ANGLE_ANGLE;
   1908         }
   1909     }
   1910 
   1911     return 0;
   1912 }
   1913 
   1914 EGLNativeDisplayType _glfwGetEGLNativeDisplayCocoa(void)
   1915 {
   1916     return EGL_DEFAULT_DISPLAY;
   1917 }
   1918 
   1919 EGLNativeWindowType _glfwGetEGLNativeWindowCocoa(_GLFWwindow* window)
   1920 {
   1921     return window->ns.layer;
   1922 }
   1923 
   1924 void _glfwGetRequiredInstanceExtensionsCocoa(char** extensions)
   1925 {
   1926     if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
   1927     {
   1928         extensions[0] = "VK_KHR_surface";
   1929         extensions[1] = "VK_EXT_metal_surface";
   1930     }
   1931     else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface)
   1932     {
   1933         extensions[0] = "VK_KHR_surface";
   1934         extensions[1] = "VK_MVK_macos_surface";
   1935     }
   1936 }
   1937 
   1938 GLFWbool _glfwGetPhysicalDevicePresentationSupportCocoa(VkInstance instance,
   1939                                                         VkPhysicalDevice device,
   1940                                                         uint32_t queuefamily)
   1941 {
   1942     return GLFW_TRUE;
   1943 }
   1944 
   1945 VkResult _glfwCreateWindowSurfaceCocoa(VkInstance instance,
   1946                                        _GLFWwindow* window,
   1947                                        const VkAllocationCallbacks* allocator,
   1948                                        VkSurfaceKHR* surface)
   1949 {
   1950     @autoreleasepool {
   1951 
   1952 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
   1953     // HACK: Dynamically load Core Animation to avoid adding an extra
   1954     //       dependency for the majority who don't use MoltenVK
   1955     NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"];
   1956     if (!bundle)
   1957     {
   1958         _glfwInputError(GLFW_PLATFORM_ERROR,
   1959                         "Cocoa: Failed to find QuartzCore.framework");
   1960         return VK_ERROR_EXTENSION_NOT_PRESENT;
   1961     }
   1962 
   1963     // NOTE: Create the layer here as makeBackingLayer should not return nil
   1964     window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer];
   1965     if (!window->ns.layer)
   1966     {
   1967         _glfwInputError(GLFW_PLATFORM_ERROR,
   1968                         "Cocoa: Failed to create layer for view");
   1969         return VK_ERROR_EXTENSION_NOT_PRESENT;
   1970     }
   1971 
   1972     if (window->ns.scaleFramebuffer)
   1973         [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
   1974 
   1975     [window->ns.view setLayer:window->ns.layer];
   1976     [window->ns.view setWantsLayer:YES];
   1977 
   1978     VkResult err;
   1979 
   1980     if (_glfw.vk.EXT_metal_surface)
   1981     {
   1982         VkMetalSurfaceCreateInfoEXT sci;
   1983 
   1984         PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT;
   1985         vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT)
   1986             vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT");
   1987         if (!vkCreateMetalSurfaceEXT)
   1988         {
   1989             _glfwInputError(GLFW_API_UNAVAILABLE,
   1990                             "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension");
   1991             return VK_ERROR_EXTENSION_NOT_PRESENT;
   1992         }
   1993 
   1994         memset(&sci, 0, sizeof(sci));
   1995         sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
   1996         sci.pLayer = window->ns.layer;
   1997 
   1998         err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface);
   1999     }
   2000     else
   2001     {
   2002         VkMacOSSurfaceCreateInfoMVK sci;
   2003 
   2004         PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
   2005         vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
   2006             vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
   2007         if (!vkCreateMacOSSurfaceMVK)
   2008         {
   2009             _glfwInputError(GLFW_API_UNAVAILABLE,
   2010                             "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
   2011             return VK_ERROR_EXTENSION_NOT_PRESENT;
   2012         }
   2013 
   2014         memset(&sci, 0, sizeof(sci));
   2015         sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
   2016         sci.pView = window->ns.view;
   2017 
   2018         err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
   2019     }
   2020 
   2021     if (err)
   2022     {
   2023         _glfwInputError(GLFW_PLATFORM_ERROR,
   2024                         "Cocoa: Failed to create Vulkan surface: %s",
   2025                         _glfwGetVulkanResultString(err));
   2026     }
   2027 
   2028     return err;
   2029 #else
   2030     return VK_ERROR_EXTENSION_NOT_PRESENT;
   2031 #endif
   2032 
   2033     } // autoreleasepool
   2034 }
   2035 
   2036 
   2037 //////////////////////////////////////////////////////////////////////////
   2038 //////                        GLFW native API                       //////
   2039 //////////////////////////////////////////////////////////////////////////
   2040 
   2041 GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
   2042 {
   2043     _GLFWwindow* window = (_GLFWwindow*) handle;
   2044     _GLFW_REQUIRE_INIT_OR_RETURN(nil);
   2045 
   2046     if (_glfw.platform.platformID != GLFW_PLATFORM_COCOA)
   2047     {
   2048         _glfwInputError(GLFW_PLATFORM_UNAVAILABLE,
   2049                         "Cocoa: Platform not initialized");
   2050         return nil;
   2051     }
   2052 
   2053     return window->ns.object;
   2054 }
   2055 
   2056 GLFWAPI id glfwGetCocoaView(GLFWwindow* handle)
   2057 {
   2058     _GLFWwindow* window = (_GLFWwindow*) handle;
   2059     _GLFW_REQUIRE_INIT_OR_RETURN(nil);
   2060 
   2061     if (_glfw.platform.platformID != GLFW_PLATFORM_COCOA)
   2062     {
   2063         _glfwInputError(GLFW_PLATFORM_UNAVAILABLE,
   2064                         "Cocoa: Platform not initialized");
   2065         return nil;
   2066     }
   2067 
   2068     return window->ns.view;
   2069 }
   2070 
   2071 #endif // _GLFW_COCOA
   2072