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