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