master
ブランチ master
cpputils / deps / glfw / src / cocoa_window.m
53.1 KB、1820行 生 Blame 履歴
1//========================================================================
2// GLFW 3.3 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//
36static 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//
56static 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//
64static 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//
75static 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//
86static 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//
103static 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//
129static 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//
145static 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//
156static 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//
176static 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//
186static 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//
211static 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@end
326
327
328//------------------------------------------------------------------------
329// Content view class for the GLFW window
330//------------------------------------------------------------------------
331
332@interface GLFWContentView : NSView <NSTextInputClient>
333{
334 _GLFWwindow* window;
335 NSTrackingArea* trackingArea;
336 NSMutableAttributedString* markedText;
337}
338
339- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
340
341@end
342
343@implementation GLFWContentView
344
345- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow
346{
347 self = [super init];
348 if (self != nil)
349 {
350 window = initWindow;
351 trackingArea = nil;
352 markedText = [[NSMutableAttributedString alloc] init];
353
354 [self updateTrackingAreas];
355 // NOTE: kUTTypeURL corresponds to NSPasteboardTypeURL but is available
356 // on 10.7 without having been deprecated yet
357 [self registerForDraggedTypes:@[(__bridge NSString*) kUTTypeURL]];
358 }
359
360 return self;
361}
362
363- (void)dealloc
364{
365 [trackingArea release];
366 [markedText release];
367 [super dealloc];
368}
369
370- (BOOL)isOpaque
371{
372 return [window->ns.object isOpaque];
373}
374
375- (BOOL)canBecomeKeyView
376{
377 return YES;
378}
379
380- (BOOL)acceptsFirstResponder
381{
382 return YES;
383}
384
385- (BOOL)wantsUpdateLayer
386{
387 return YES;
388}
389
390- (void)updateLayer
391{
392 if (window->context.client != GLFW_NO_API)
393 [window->context.nsgl.object update];
394
395 _glfwInputWindowDamage(window);
396}
397
398- (void)cursorUpdate:(NSEvent *)event
399{
400 updateCursorImage(window);
401}
402
403- (BOOL)acceptsFirstMouse:(NSEvent *)event
404{
405 return YES;
406}
407
408- (void)mouseDown:(NSEvent *)event
409{
410 _glfwInputMouseClick(window,
411 GLFW_MOUSE_BUTTON_LEFT,
412 GLFW_PRESS,
413 translateFlags([event modifierFlags]));
414}
415
416- (void)mouseDragged:(NSEvent *)event
417{
418 [self mouseMoved:event];
419}
420
421- (void)mouseUp:(NSEvent *)event
422{
423 _glfwInputMouseClick(window,
424 GLFW_MOUSE_BUTTON_LEFT,
425 GLFW_RELEASE,
426 translateFlags([event modifierFlags]));
427}
428
429- (void)mouseMoved:(NSEvent *)event
430{
431 if (window->cursorMode == GLFW_CURSOR_DISABLED)
432 {
433 const double dx = [event deltaX] - window->ns.cursorWarpDeltaX;
434 const double dy = [event deltaY] - window->ns.cursorWarpDeltaY;
435
436 _glfwInputCursorPos(window,
437 window->virtualCursorPosX + dx,
438 window->virtualCursorPosY + dy);
439 }
440 else
441 {
442 const NSRect contentRect = [window->ns.view frame];
443 // NOTE: The returned location uses base 0,1 not 0,0
444 const NSPoint pos = [event locationInWindow];
445
446 _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y);
447 }
448
449 window->ns.cursorWarpDeltaX = 0;
450 window->ns.cursorWarpDeltaY = 0;
451}
452
453- (void)rightMouseDown:(NSEvent *)event
454{
455 _glfwInputMouseClick(window,
456 GLFW_MOUSE_BUTTON_RIGHT,
457 GLFW_PRESS,
458 translateFlags([event modifierFlags]));
459}
460
461- (void)rightMouseDragged:(NSEvent *)event
462{
463 [self mouseMoved:event];
464}
465
466- (void)rightMouseUp:(NSEvent *)event
467{
468 _glfwInputMouseClick(window,
469 GLFW_MOUSE_BUTTON_RIGHT,
470 GLFW_RELEASE,
471 translateFlags([event modifierFlags]));
472}
473
474- (void)otherMouseDown:(NSEvent *)event
475{
476 _glfwInputMouseClick(window,
477 (int) [event buttonNumber],
478 GLFW_PRESS,
479 translateFlags([event modifierFlags]));
480}
481
482- (void)otherMouseDragged:(NSEvent *)event
483{
484 [self mouseMoved:event];
485}
486
487- (void)otherMouseUp:(NSEvent *)event
488{
489 _glfwInputMouseClick(window,
490 (int) [event buttonNumber],
491 GLFW_RELEASE,
492 translateFlags([event modifierFlags]));
493}
494
495- (void)mouseExited:(NSEvent *)event
496{
497 if (window->cursorMode == GLFW_CURSOR_HIDDEN)
498 showCursor(window);
499
500 _glfwInputCursorEnter(window, GLFW_FALSE);
501}
502
503- (void)mouseEntered:(NSEvent *)event
504{
505 if (window->cursorMode == GLFW_CURSOR_HIDDEN)
506 hideCursor(window);
507
508 _glfwInputCursorEnter(window, GLFW_TRUE);
509}
510
511- (void)viewDidChangeBackingProperties
512{
513 const NSRect contentRect = [window->ns.view frame];
514 const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
515
516 if (fbRect.size.width != window->ns.fbWidth ||
517 fbRect.size.height != window->ns.fbHeight)
518 {
519 window->ns.fbWidth = fbRect.size.width;
520 window->ns.fbHeight = fbRect.size.height;
521 _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
522 }
523
524 const float xscale = fbRect.size.width / contentRect.size.width;
525 const float yscale = fbRect.size.height / contentRect.size.height;
526
527 if (xscale != window->ns.xscale || yscale != window->ns.yscale)
528 {
529 window->ns.xscale = xscale;
530 window->ns.yscale = yscale;
531 _glfwInputWindowContentScale(window, xscale, yscale);
532
533 if (window->ns.retina && window->ns.layer)
534 [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
535 }
536}
537
538- (void)drawRect:(NSRect)rect
539{
540 _glfwInputWindowDamage(window);
541}
542
543- (void)updateTrackingAreas
544{
545 if (trackingArea != nil)
546 {
547 [self removeTrackingArea:trackingArea];
548 [trackingArea release];
549 }
550
551 const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
552 NSTrackingActiveInKeyWindow |
553 NSTrackingEnabledDuringMouseDrag |
554 NSTrackingCursorUpdate |
555 NSTrackingInVisibleRect |
556 NSTrackingAssumeInside;
557
558 trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
559 options:options
560 owner:self
561 userInfo:nil];
562
563 [self addTrackingArea:trackingArea];
564 [super updateTrackingAreas];
565}
566
567- (void)keyDown:(NSEvent *)event
568{
569 const int key = translateKey([event keyCode]);
570 const int mods = translateFlags([event modifierFlags]);
571
572 _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
573
574 [self interpretKeyEvents:@[event]];
575}
576
577- (void)flagsChanged:(NSEvent *)event
578{
579 int action;
580 const unsigned int modifierFlags =
581 [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
582 const int key = translateKey([event keyCode]);
583 const int mods = translateFlags(modifierFlags);
584 const NSUInteger keyFlag = translateKeyToModifierFlag(key);
585
586 if (keyFlag & modifierFlags)
587 {
588 if (window->keys[key] == GLFW_PRESS)
589 action = GLFW_RELEASE;
590 else
591 action = GLFW_PRESS;
592 }
593 else
594 action = GLFW_RELEASE;
595
596 _glfwInputKey(window, key, [event keyCode], action, mods);
597}
598
599- (void)keyUp:(NSEvent *)event
600{
601 const int key = translateKey([event keyCode]);
602 const int mods = translateFlags([event modifierFlags]);
603 _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods);
604}
605
606- (void)scrollWheel:(NSEvent *)event
607{
608 double deltaX = [event scrollingDeltaX];
609 double deltaY = [event scrollingDeltaY];
610
611 if ([event hasPreciseScrollingDeltas])
612 {
613 deltaX *= 0.1;
614 deltaY *= 0.1;
615 }
616
617 if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
618 _glfwInputScroll(window, deltaX, deltaY);
619}
620
621- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
622{
623 // HACK: We don't know what to say here because we don't know what the
624 // application wants to do with the paths
625 return NSDragOperationGeneric;
626}
627
628- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
629{
630 const NSRect contentRect = [window->ns.view frame];
631 // NOTE: The returned location uses base 0,1 not 0,0
632 const NSPoint pos = [sender draggingLocation];
633 _glfwInputCursorPos(window, pos.x, contentRect.size.height - pos.y);
634
635 NSPasteboard* pasteboard = [sender draggingPasteboard];
636 NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
637 NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
638 options:options];
639 const NSUInteger count = [urls count];
640 if (count)
641 {
642 char** paths = calloc(count, sizeof(char*));
643
644 for (NSUInteger i = 0; i < count; i++)
645 paths[i] = _glfw_strdup([urls[i] fileSystemRepresentation]);
646
647 _glfwInputDrop(window, (int) count, (const char**) paths);
648
649 for (NSUInteger i = 0; i < count; i++)
650 free(paths[i]);
651 free(paths);
652 }
653
654 return YES;
655}
656
657- (BOOL)hasMarkedText
658{
659 return [markedText length] > 0;
660}
661
662- (NSRange)markedRange
663{
664 if ([markedText length] > 0)
665 return NSMakeRange(0, [markedText length] - 1);
666 else
667 return kEmptyRange;
668}
669
670- (NSRange)selectedRange
671{
672 return kEmptyRange;
673}
674
675- (void)setMarkedText:(id)string
676 selectedRange:(NSRange)selectedRange
677 replacementRange:(NSRange)replacementRange
678{
679 [markedText release];
680 if ([string isKindOfClass:[NSAttributedString class]])
681 markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
682 else
683 markedText = [[NSMutableAttributedString alloc] initWithString:string];
684}
685
686- (void)unmarkText
687{
688 [[markedText mutableString] setString:@""];
689}
690
691- (NSArray*)validAttributesForMarkedText
692{
693 return [NSArray array];
694}
695
696- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)range
697 actualRange:(NSRangePointer)actualRange
698{
699 return nil;
700}
701
702- (NSUInteger)characterIndexForPoint:(NSPoint)point
703{
704 return 0;
705}
706
707- (NSRect)firstRectForCharacterRange:(NSRange)range
708 actualRange:(NSRangePointer)actualRange
709{
710 const NSRect frame = [window->ns.view frame];
711 return NSMakeRect(frame.origin.x, frame.origin.y, 0.0, 0.0);
712}
713
714- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
715{
716 NSString* characters;
717 NSEvent* event = [NSApp currentEvent];
718 const int mods = translateFlags([event modifierFlags]);
719 const int plain = !(mods & GLFW_MOD_SUPER);
720
721 if ([string isKindOfClass:[NSAttributedString class]])
722 characters = [string string];
723 else
724 characters = (NSString*) string;
725
726 const NSUInteger length = [characters length];
727 for (NSUInteger i = 0; i < length; i++)
728 {
729 const unichar codepoint = [characters characterAtIndex:i];
730 if ((codepoint & 0xff00) == 0xf700)
731 continue;
732
733 _glfwInputChar(window, codepoint, mods, plain);
734 }
735}
736
737- (void)doCommandBySelector:(SEL)selector
738{
739}
740
741@end
742
743
744//------------------------------------------------------------------------
745// GLFW window class
746//------------------------------------------------------------------------
747
748@interface GLFWWindow : NSWindow {}
749@end
750
751@implementation GLFWWindow
752
753- (BOOL)canBecomeKeyWindow
754{
755 // Required for NSWindowStyleMaskBorderless windows
756 return YES;
757}
758
759- (BOOL)canBecomeMainWindow
760{
761 return YES;
762}
763
764@end
765
766
767// Create the Cocoa window
768//
769static GLFWbool createNativeWindow(_GLFWwindow* window,
770 const _GLFWwndconfig* wndconfig,
771 const _GLFWfbconfig* fbconfig)
772{
773 window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window];
774 if (window->ns.delegate == nil)
775 {
776 _glfwInputError(GLFW_PLATFORM_ERROR,
777 "Cocoa: Failed to create window delegate");
778 return GLFW_FALSE;
779 }
780
781 NSRect contentRect;
782
783 if (window->monitor)
784 {
785 GLFWvidmode mode;
786 int xpos, ypos;
787
788 _glfwPlatformGetVideoMode(window->monitor, &mode);
789 _glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos);
790
791 contentRect = NSMakeRect(xpos, ypos, mode.width, mode.height);
792 }
793 else
794 contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height);
795
796 window->ns.object = [[GLFWWindow alloc]
797 initWithContentRect:contentRect
798 styleMask:getStyleMask(window)
799 backing:NSBackingStoreBuffered
800 defer:NO];
801
802 if (window->ns.object == nil)
803 {
804 _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window");
805 return GLFW_FALSE;
806 }
807
808 if (window->monitor)
809 [window->ns.object setLevel:NSMainMenuWindowLevel + 1];
810 else
811 {
812 [(NSWindow*) window->ns.object center];
813 _glfw.ns.cascadePoint =
814 NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
815 NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
816
817 if (wndconfig->resizable)
818 {
819 const NSWindowCollectionBehavior behavior =
820 NSWindowCollectionBehaviorFullScreenPrimary |
821 NSWindowCollectionBehaviorManaged;
822 [window->ns.object setCollectionBehavior:behavior];
823 }
824
825 if (wndconfig->floating)
826 [window->ns.object setLevel:NSFloatingWindowLevel];
827
828 if (wndconfig->maximized)
829 [window->ns.object zoom:nil];
830 }
831
832 if (strlen(wndconfig->ns.frameName))
833 [window->ns.object setFrameAutosaveName:@(wndconfig->ns.frameName)];
834
835 window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
836 window->ns.retina = wndconfig->ns.retina;
837
838 if (fbconfig->transparent)
839 {
840 [window->ns.object setOpaque:NO];
841 [window->ns.object setHasShadow:NO];
842 [window->ns.object setBackgroundColor:[NSColor clearColor]];
843 }
844
845 [window->ns.object setContentView:window->ns.view];
846 [window->ns.object makeFirstResponder:window->ns.view];
847 [window->ns.object setTitle:@(wndconfig->title)];
848 [window->ns.object setDelegate:window->ns.delegate];
849 [window->ns.object setAcceptsMouseMovedEvents:YES];
850 [window->ns.object setRestorable:NO];
851
852#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200
853 if ([window->ns.object respondsToSelector:@selector(setTabbingMode:)])
854 [window->ns.object setTabbingMode:NSWindowTabbingModeDisallowed];
855#endif
856
857 _glfwPlatformGetWindowSize(window, &window->ns.width, &window->ns.height);
858 _glfwPlatformGetFramebufferSize(window, &window->ns.fbWidth, &window->ns.fbHeight);
859
860 return GLFW_TRUE;
861}
862
863
864//////////////////////////////////////////////////////////////////////////
865////// GLFW internal API //////
866//////////////////////////////////////////////////////////////////////////
867
868// Transforms a y-coordinate between the CG display and NS screen spaces
869//
870float _glfwTransformYNS(float y)
871{
872 return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1;
873}
874
875
876//////////////////////////////////////////////////////////////////////////
877////// GLFW platform API //////
878//////////////////////////////////////////////////////////////////////////
879
880int _glfwPlatformCreateWindow(_GLFWwindow* window,
881 const _GLFWwndconfig* wndconfig,
882 const _GLFWctxconfig* ctxconfig,
883 const _GLFWfbconfig* fbconfig)
884{
885 @autoreleasepool {
886
887 if (!_glfw.ns.finishedLaunching)
888 [NSApp run];
889
890 if (!createNativeWindow(window, wndconfig, fbconfig))
891 return GLFW_FALSE;
892
893 if (ctxconfig->client != GLFW_NO_API)
894 {
895 if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API)
896 {
897 if (!_glfwInitNSGL())
898 return GLFW_FALSE;
899 if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig))
900 return GLFW_FALSE;
901 }
902 else if (ctxconfig->source == GLFW_EGL_CONTEXT_API)
903 {
904 if (!_glfwInitEGL())
905 return GLFW_FALSE;
906 if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
907 return GLFW_FALSE;
908 }
909 else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
910 {
911 if (!_glfwInitOSMesa())
912 return GLFW_FALSE;
913 if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig))
914 return GLFW_FALSE;
915 }
916 }
917
918 if (window->monitor)
919 {
920 _glfwPlatformShowWindow(window);
921 _glfwPlatformFocusWindow(window);
922 acquireMonitor(window);
923 }
924
925 return GLFW_TRUE;
926
927 } // autoreleasepool
928}
929
930void _glfwPlatformDestroyWindow(_GLFWwindow* window)
931{
932 @autoreleasepool {
933
934 if (_glfw.ns.disabledCursorWindow == window)
935 _glfw.ns.disabledCursorWindow = NULL;
936
937 [window->ns.object orderOut:nil];
938
939 if (window->monitor)
940 releaseMonitor(window);
941
942 if (window->context.destroy)
943 window->context.destroy(window);
944
945 [window->ns.object setDelegate:nil];
946 [window->ns.delegate release];
947 window->ns.delegate = nil;
948
949 [window->ns.view release];
950 window->ns.view = nil;
951
952 [window->ns.object close];
953 window->ns.object = nil;
954
955 // HACK: Allow Cocoa to catch up before returning
956 _glfwPlatformPollEvents();
957
958 } // autoreleasepool
959}
960
961void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
962{
963 @autoreleasepool {
964 NSString* string = @(title);
965 [window->ns.object setTitle:string];
966 // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it
967 // if the window lacks NSWindowStyleMaskTitled
968 [window->ns.object setMiniwindowTitle:string];
969 } // autoreleasepool
970}
971
972void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
973 int count, const GLFWimage* images)
974{
975 // Regular windows do not have icons
976}
977
978void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
979{
980 @autoreleasepool {
981
982 const NSRect contentRect =
983 [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
984
985 if (xpos)
986 *xpos = contentRect.origin.x;
987 if (ypos)
988 *ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1);
989
990 } // autoreleasepool
991}
992
993void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y)
994{
995 @autoreleasepool {
996
997 const NSRect contentRect = [window->ns.view frame];
998 const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0);
999 const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect];
1000 [window->ns.object setFrameOrigin:frameRect.origin];
1001
1002 } // autoreleasepool
1003}
1004
1005void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
1006{
1007 @autoreleasepool {
1008
1009 const NSRect contentRect = [window->ns.view frame];
1010
1011 if (width)
1012 *width = contentRect.size.width;
1013 if (height)
1014 *height = contentRect.size.height;
1015
1016 } // autoreleasepool
1017}
1018
1019void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
1020{
1021 @autoreleasepool {
1022
1023 if (window->monitor)
1024 {
1025 if (window->monitor->window == window)
1026 acquireMonitor(window);
1027 }
1028 else
1029 {
1030 NSRect contentRect =
1031 [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
1032 contentRect.origin.y += contentRect.size.height - height;
1033 contentRect.size = NSMakeSize(width, height);
1034 [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect]
1035 display:YES];
1036 }
1037
1038 } // autoreleasepool
1039}
1040
1041void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
1042 int minwidth, int minheight,
1043 int maxwidth, int maxheight)
1044{
1045 @autoreleasepool {
1046
1047 if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE)
1048 [window->ns.object setContentMinSize:NSMakeSize(0, 0)];
1049 else
1050 [window->ns.object setContentMinSize:NSMakeSize(minwidth, minheight)];
1051
1052 if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE)
1053 [window->ns.object setContentMaxSize:NSMakeSize(DBL_MAX, DBL_MAX)];
1054 else
1055 [window->ns.object setContentMaxSize:NSMakeSize(maxwidth, maxheight)];
1056
1057 } // autoreleasepool
1058}
1059
1060void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
1061{
1062 @autoreleasepool {
1063 if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
1064 [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
1065 else
1066 [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
1067 } // autoreleasepool
1068}
1069
1070void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
1071{
1072 @autoreleasepool {
1073
1074 const NSRect contentRect = [window->ns.view frame];
1075 const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect];
1076
1077 if (width)
1078 *width = (int) fbRect.size.width;
1079 if (height)
1080 *height = (int) fbRect.size.height;
1081
1082 } // autoreleasepool
1083}
1084
1085void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
1086 int* left, int* top,
1087 int* right, int* bottom)
1088{
1089 @autoreleasepool {
1090
1091 const NSRect contentRect = [window->ns.view frame];
1092 const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect];
1093
1094 if (left)
1095 *left = contentRect.origin.x - frameRect.origin.x;
1096 if (top)
1097 *top = frameRect.origin.y + frameRect.size.height -
1098 contentRect.origin.y - contentRect.size.height;
1099 if (right)
1100 *right = frameRect.origin.x + frameRect.size.width -
1101 contentRect.origin.x - contentRect.size.width;
1102 if (bottom)
1103 *bottom = contentRect.origin.y - frameRect.origin.y;
1104
1105 } // autoreleasepool
1106}
1107
1108void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
1109 float* xscale, float* yscale)
1110{
1111 @autoreleasepool {
1112
1113 const NSRect points = [window->ns.view frame];
1114 const NSRect pixels = [window->ns.view convertRectToBacking:points];
1115
1116 if (xscale)
1117 *xscale = (float) (pixels.size.width / points.size.width);
1118 if (yscale)
1119 *yscale = (float) (pixels.size.height / points.size.height);
1120
1121 } // autoreleasepool
1122}
1123
1124void _glfwPlatformIconifyWindow(_GLFWwindow* window)
1125{
1126 @autoreleasepool {
1127 [window->ns.object miniaturize:nil];
1128 } // autoreleasepool
1129}
1130
1131void _glfwPlatformRestoreWindow(_GLFWwindow* window)
1132{
1133 @autoreleasepool {
1134 if ([window->ns.object isMiniaturized])
1135 [window->ns.object deminiaturize:nil];
1136 else if ([window->ns.object isZoomed])
1137 [window->ns.object zoom:nil];
1138 } // autoreleasepool
1139}
1140
1141void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
1142{
1143 @autoreleasepool {
1144 if (![window->ns.object isZoomed])
1145 [window->ns.object zoom:nil];
1146 } // autoreleasepool
1147}
1148
1149void _glfwPlatformShowWindow(_GLFWwindow* window)
1150{
1151 @autoreleasepool {
1152 [window->ns.object orderFront:nil];
1153 } // autoreleasepool
1154}
1155
1156void _glfwPlatformHideWindow(_GLFWwindow* window)
1157{
1158 @autoreleasepool {
1159 [window->ns.object orderOut:nil];
1160 } // autoreleasepool
1161}
1162
1163void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
1164{
1165 @autoreleasepool {
1166 [NSApp requestUserAttention:NSInformationalRequest];
1167 } // autoreleasepool
1168}
1169
1170void _glfwPlatformFocusWindow(_GLFWwindow* window)
1171{
1172 @autoreleasepool {
1173 // Make us the active application
1174 // HACK: This is here to prevent applications using only hidden windows from
1175 // being activated, but should probably not be done every time any
1176 // window is shown
1177 [NSApp activateIgnoringOtherApps:YES];
1178 [window->ns.object makeKeyAndOrderFront:nil];
1179 } // autoreleasepool
1180}
1181
1182void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
1183 _GLFWmonitor* monitor,
1184 int xpos, int ypos,
1185 int width, int height,
1186 int refreshRate)
1187{
1188 @autoreleasepool {
1189
1190 if (window->monitor == monitor)
1191 {
1192 if (monitor)
1193 {
1194 if (monitor->window == window)
1195 acquireMonitor(window);
1196 }
1197 else
1198 {
1199 const NSRect contentRect =
1200 NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height);
1201 const NSRect frameRect =
1202 [window->ns.object frameRectForContentRect:contentRect
1203 styleMask:getStyleMask(window)];
1204
1205 [window->ns.object setFrame:frameRect display:YES];
1206 }
1207
1208 return;
1209 }
1210
1211 if (window->monitor)
1212 releaseMonitor(window);
1213
1214 _glfwInputWindowMonitor(window, monitor);
1215
1216 // HACK: Allow the state cached in Cocoa to catch up to reality
1217 // TODO: Solve this in a less terrible way
1218 _glfwPlatformPollEvents();
1219
1220 const NSUInteger styleMask = getStyleMask(window);
1221 [window->ns.object setStyleMask:styleMask];
1222 // HACK: Changing the style mask can cause the first responder to be cleared
1223 [window->ns.object makeFirstResponder:window->ns.view];
1224
1225 if (window->monitor)
1226 {
1227 [window->ns.object setLevel:NSMainMenuWindowLevel + 1];
1228 [window->ns.object setHasShadow:NO];
1229
1230 acquireMonitor(window);
1231 }
1232 else
1233 {
1234 NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1),
1235 width, height);
1236 NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect
1237 styleMask:styleMask];
1238 [window->ns.object setFrame:frameRect display:YES];
1239
1240 if (window->numer != GLFW_DONT_CARE &&
1241 window->denom != GLFW_DONT_CARE)
1242 {
1243 [window->ns.object setContentAspectRatio:NSMakeSize(window->numer,
1244 window->denom)];
1245 }
1246
1247 if (window->minwidth != GLFW_DONT_CARE &&
1248 window->minheight != GLFW_DONT_CARE)
1249 {
1250 [window->ns.object setContentMinSize:NSMakeSize(window->minwidth,
1251 window->minheight)];
1252 }
1253
1254 if (window->maxwidth != GLFW_DONT_CARE &&
1255 window->maxheight != GLFW_DONT_CARE)
1256 {
1257 [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth,
1258 window->maxheight)];
1259 }
1260
1261 if (window->floating)
1262 [window->ns.object setLevel:NSFloatingWindowLevel];
1263 else
1264 [window->ns.object setLevel:NSNormalWindowLevel];
1265
1266 [window->ns.object setHasShadow:YES];
1267 // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window
1268 // title property but the miniwindow title property is unaffected
1269 [window->ns.object setTitle:[window->ns.object miniwindowTitle]];
1270 }
1271
1272 } // autoreleasepool
1273}
1274
1275int _glfwPlatformWindowFocused(_GLFWwindow* window)
1276{
1277 @autoreleasepool {
1278 return [window->ns.object isKeyWindow];
1279 } // autoreleasepool
1280}
1281
1282int _glfwPlatformWindowIconified(_GLFWwindow* window)
1283{
1284 @autoreleasepool {
1285 return [window->ns.object isMiniaturized];
1286 } // autoreleasepool
1287}
1288
1289int _glfwPlatformWindowVisible(_GLFWwindow* window)
1290{
1291 @autoreleasepool {
1292 return [window->ns.object isVisible];
1293 } // autoreleasepool
1294}
1295
1296int _glfwPlatformWindowMaximized(_GLFWwindow* window)
1297{
1298 @autoreleasepool {
1299 return [window->ns.object isZoomed];
1300 } // autoreleasepool
1301}
1302
1303int _glfwPlatformWindowHovered(_GLFWwindow* window)
1304{
1305 @autoreleasepool {
1306
1307 const NSPoint point = [NSEvent mouseLocation];
1308
1309 if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] !=
1310 [window->ns.object windowNumber])
1311 {
1312 return GLFW_FALSE;
1313 }
1314
1315 return NSMouseInRect(point,
1316 [window->ns.object convertRectToScreen:[window->ns.view frame]], NO);
1317
1318 } // autoreleasepool
1319}
1320
1321int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
1322{
1323 @autoreleasepool {
1324 return ![window->ns.object isOpaque] && ![window->ns.view isOpaque];
1325 } // autoreleasepool
1326}
1327
1328void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
1329{
1330 @autoreleasepool {
1331 [window->ns.object setStyleMask:getStyleMask(window)];
1332 } // autoreleasepool
1333}
1334
1335void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled)
1336{
1337 @autoreleasepool {
1338 [window->ns.object setStyleMask:getStyleMask(window)];
1339 [window->ns.object makeFirstResponder:window->ns.view];
1340 } // autoreleasepool
1341}
1342
1343void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled)
1344{
1345 @autoreleasepool {
1346 if (enabled)
1347 [window->ns.object setLevel:NSFloatingWindowLevel];
1348 else
1349 [window->ns.object setLevel:NSNormalWindowLevel];
1350 } // autoreleasepool
1351}
1352
1353float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
1354{
1355 @autoreleasepool {
1356 return (float) [window->ns.object alphaValue];
1357 } // autoreleasepool
1358}
1359
1360void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
1361{
1362 @autoreleasepool {
1363 [window->ns.object setAlphaValue:opacity];
1364 } // autoreleasepool
1365}
1366
1367void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled)
1368{
1369}
1370
1371GLFWbool _glfwPlatformRawMouseMotionSupported(void)
1372{
1373 return GLFW_FALSE;
1374}
1375
1376void _glfwPlatformPollEvents(void)
1377{
1378 @autoreleasepool {
1379
1380 if (!_glfw.ns.finishedLaunching)
1381 [NSApp run];
1382
1383 for (;;)
1384 {
1385 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
1386 untilDate:[NSDate distantPast]
1387 inMode:NSDefaultRunLoopMode
1388 dequeue:YES];
1389 if (event == nil)
1390 break;
1391
1392 [NSApp sendEvent:event];
1393 }
1394
1395 } // autoreleasepool
1396}
1397
1398void _glfwPlatformWaitEvents(void)
1399{
1400 @autoreleasepool {
1401
1402 if (!_glfw.ns.finishedLaunching)
1403 [NSApp run];
1404
1405 // I wanted to pass NO to dequeue:, and rely on PollEvents to
1406 // dequeue and send. For reasons not at all clear to me, passing
1407 // NO to dequeue: causes this method never to return.
1408 NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
1409 untilDate:[NSDate distantFuture]
1410 inMode:NSDefaultRunLoopMode
1411 dequeue:YES];
1412 [NSApp sendEvent:event];
1413
1414 _glfwPlatformPollEvents();
1415
1416 } // autoreleasepool
1417}
1418
1419void _glfwPlatformWaitEventsTimeout(double timeout)
1420{
1421 @autoreleasepool {
1422
1423 if (!_glfw.ns.finishedLaunching)
1424 [NSApp run];
1425
1426 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout];
1427 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
1428 untilDate:date
1429 inMode:NSDefaultRunLoopMode
1430 dequeue:YES];
1431 if (event)
1432 [NSApp sendEvent:event];
1433
1434 _glfwPlatformPollEvents();
1435
1436 } // autoreleasepool
1437}
1438
1439void _glfwPlatformPostEmptyEvent(void)
1440{
1441 @autoreleasepool {
1442
1443 if (!_glfw.ns.finishedLaunching)
1444 [NSApp run];
1445
1446 NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
1447 location:NSMakePoint(0, 0)
1448 modifierFlags:0
1449 timestamp:0
1450 windowNumber:0
1451 context:nil
1452 subtype:0
1453 data1:0
1454 data2:0];
1455 [NSApp postEvent:event atStart:YES];
1456
1457 } // autoreleasepool
1458}
1459
1460void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
1461{
1462 @autoreleasepool {
1463
1464 const NSRect contentRect = [window->ns.view frame];
1465 // NOTE: The returned location uses base 0,1 not 0,0
1466 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream];
1467
1468 if (xpos)
1469 *xpos = pos.x;
1470 if (ypos)
1471 *ypos = contentRect.size.height - pos.y;
1472
1473 } // autoreleasepool
1474}
1475
1476void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
1477{
1478 @autoreleasepool {
1479
1480 updateCursorImage(window);
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 window->ns.cursorWarpDeltaX += x - pos.x;
1487 window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y;
1488
1489 if (window->monitor)
1490 {
1491 CGDisplayMoveCursorToPoint(window->monitor->ns.displayID,
1492 CGPointMake(x, y));
1493 }
1494 else
1495 {
1496 const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0);
1497 const NSRect globalRect = [window->ns.object convertRectToScreen:localRect];
1498 const NSPoint globalPoint = globalRect.origin;
1499
1500 CGWarpMouseCursorPosition(CGPointMake(globalPoint.x,
1501 _glfwTransformYNS(globalPoint.y)));
1502 }
1503
1504 } // autoreleasepool
1505}
1506
1507void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
1508{
1509 @autoreleasepool {
1510 if (_glfwPlatformWindowFocused(window))
1511 updateCursorMode(window);
1512 } // autoreleasepool
1513}
1514
1515const char* _glfwPlatformGetScancodeName(int scancode)
1516{
1517 @autoreleasepool {
1518
1519 if (scancode < 0 || scancode > 0xff ||
1520 _glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN)
1521 {
1522 _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode");
1523 return NULL;
1524 }
1525
1526 const int key = _glfw.ns.keycodes[scancode];
1527
1528 UInt32 deadKeyState = 0;
1529 UniChar characters[4];
1530 UniCharCount characterCount = 0;
1531
1532 if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes],
1533 scancode,
1534 kUCKeyActionDisplay,
1535 0,
1536 LMGetKbdType(),
1537 kUCKeyTranslateNoDeadKeysBit,
1538 &deadKeyState,
1539 sizeof(characters) / sizeof(characters[0]),
1540 &characterCount,
1541 characters) != noErr)
1542 {
1543 return NULL;
1544 }
1545
1546 if (!characterCount)
1547 return NULL;
1548
1549 CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
1550 characters,
1551 characterCount,
1552 kCFAllocatorNull);
1553 CFStringGetCString(string,
1554 _glfw.ns.keynames[key],
1555 sizeof(_glfw.ns.keynames[key]),
1556 kCFStringEncodingUTF8);
1557 CFRelease(string);
1558
1559 return _glfw.ns.keynames[key];
1560
1561 } // autoreleasepool
1562}
1563
1564int _glfwPlatformGetKeyScancode(int key)
1565{
1566 return _glfw.ns.scancodes[key];
1567}
1568
1569int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
1570 const GLFWimage* image,
1571 int xhot, int yhot)
1572{
1573 @autoreleasepool {
1574
1575 NSImage* native;
1576 NSBitmapImageRep* rep;
1577
1578 rep = [[NSBitmapImageRep alloc]
1579 initWithBitmapDataPlanes:NULL
1580 pixelsWide:image->width
1581 pixelsHigh:image->height
1582 bitsPerSample:8
1583 samplesPerPixel:4
1584 hasAlpha:YES
1585 isPlanar:NO
1586 colorSpaceName:NSCalibratedRGBColorSpace
1587 bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
1588 bytesPerRow:image->width * 4
1589 bitsPerPixel:32];
1590
1591 if (rep == nil)
1592 return GLFW_FALSE;
1593
1594 memcpy([rep bitmapData], image->pixels, image->width * image->height * 4);
1595
1596 native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
1597 [native addRepresentation:rep];
1598
1599 cursor->ns.object = [[NSCursor alloc] initWithImage:native
1600 hotSpot:NSMakePoint(xhot, yhot)];
1601
1602 [native release];
1603 [rep release];
1604
1605 if (cursor->ns.object == nil)
1606 return GLFW_FALSE;
1607
1608 return GLFW_TRUE;
1609
1610 } // autoreleasepool
1611}
1612
1613int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
1614{
1615 @autoreleasepool {
1616
1617 if (shape == GLFW_ARROW_CURSOR)
1618 cursor->ns.object = [NSCursor arrowCursor];
1619 else if (shape == GLFW_IBEAM_CURSOR)
1620 cursor->ns.object = [NSCursor IBeamCursor];
1621 else if (shape == GLFW_CROSSHAIR_CURSOR)
1622 cursor->ns.object = [NSCursor crosshairCursor];
1623 else if (shape == GLFW_HAND_CURSOR)
1624 cursor->ns.object = [NSCursor pointingHandCursor];
1625 else if (shape == GLFW_HRESIZE_CURSOR)
1626 cursor->ns.object = [NSCursor resizeLeftRightCursor];
1627 else if (shape == GLFW_VRESIZE_CURSOR)
1628 cursor->ns.object = [NSCursor resizeUpDownCursor];
1629
1630 if (!cursor->ns.object)
1631 {
1632 _glfwInputError(GLFW_PLATFORM_ERROR,
1633 "Cocoa: Failed to retrieve standard cursor");
1634 return GLFW_FALSE;
1635 }
1636
1637 [cursor->ns.object retain];
1638 return GLFW_TRUE;
1639
1640 } // autoreleasepool
1641}
1642
1643void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
1644{
1645 @autoreleasepool {
1646 if (cursor->ns.object)
1647 [(NSCursor*) cursor->ns.object release];
1648 } // autoreleasepool
1649}
1650
1651void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
1652{
1653 @autoreleasepool {
1654 if (cursorInContentArea(window))
1655 updateCursorImage(window);
1656 } // autoreleasepool
1657}
1658
1659void _glfwPlatformSetClipboardString(const char* string)
1660{
1661 @autoreleasepool {
1662 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
1663 [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
1664 [pasteboard setString:@(string) forType:NSPasteboardTypeString];
1665 } // autoreleasepool
1666}
1667
1668const char* _glfwPlatformGetClipboardString(void)
1669{
1670 @autoreleasepool {
1671
1672 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
1673
1674 if (![[pasteboard types] containsObject:NSPasteboardTypeString])
1675 {
1676 _glfwInputError(GLFW_FORMAT_UNAVAILABLE,
1677 "Cocoa: Failed to retrieve string from pasteboard");
1678 return NULL;
1679 }
1680
1681 NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
1682 if (!object)
1683 {
1684 _glfwInputError(GLFW_PLATFORM_ERROR,
1685 "Cocoa: Failed to retrieve object from pasteboard");
1686 return NULL;
1687 }
1688
1689 free(_glfw.ns.clipboardString);
1690 _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]);
1691
1692 return _glfw.ns.clipboardString;
1693
1694 } // autoreleasepool
1695}
1696
1697void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
1698{
1699 if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
1700 {
1701 extensions[0] = "VK_KHR_surface";
1702 extensions[1] = "VK_EXT_metal_surface";
1703 }
1704 else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface)
1705 {
1706 extensions[0] = "VK_KHR_surface";
1707 extensions[1] = "VK_MVK_macos_surface";
1708 }
1709}
1710
1711int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
1712 VkPhysicalDevice device,
1713 uint32_t queuefamily)
1714{
1715 return GLFW_TRUE;
1716}
1717
1718VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
1719 _GLFWwindow* window,
1720 const VkAllocationCallbacks* allocator,
1721 VkSurfaceKHR* surface)
1722{
1723 @autoreleasepool {
1724
1725#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
1726 // HACK: Dynamically load Core Animation to avoid adding an extra
1727 // dependency for the majority who don't use MoltenVK
1728 NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"];
1729 if (!bundle)
1730 {
1731 _glfwInputError(GLFW_PLATFORM_ERROR,
1732 "Cocoa: Failed to find QuartzCore.framework");
1733 return VK_ERROR_EXTENSION_NOT_PRESENT;
1734 }
1735
1736 // NOTE: Create the layer here as makeBackingLayer should not return nil
1737 window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer];
1738 if (!window->ns.layer)
1739 {
1740 _glfwInputError(GLFW_PLATFORM_ERROR,
1741 "Cocoa: Failed to create layer for view");
1742 return VK_ERROR_EXTENSION_NOT_PRESENT;
1743 }
1744
1745 if (window->ns.retina)
1746 [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
1747
1748 [window->ns.view setLayer:window->ns.layer];
1749 [window->ns.view setWantsLayer:YES];
1750
1751 VkResult err;
1752
1753 if (_glfw.vk.EXT_metal_surface)
1754 {
1755 VkMetalSurfaceCreateInfoEXT sci;
1756
1757 PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT;
1758 vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT)
1759 vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT");
1760 if (!vkCreateMetalSurfaceEXT)
1761 {
1762 _glfwInputError(GLFW_API_UNAVAILABLE,
1763 "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension");
1764 return VK_ERROR_EXTENSION_NOT_PRESENT;
1765 }
1766
1767 memset(&sci, 0, sizeof(sci));
1768 sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
1769 sci.pLayer = window->ns.layer;
1770
1771 err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface);
1772 }
1773 else
1774 {
1775 VkMacOSSurfaceCreateInfoMVK sci;
1776
1777 PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
1778 vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
1779 vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
1780 if (!vkCreateMacOSSurfaceMVK)
1781 {
1782 _glfwInputError(GLFW_API_UNAVAILABLE,
1783 "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
1784 return VK_ERROR_EXTENSION_NOT_PRESENT;
1785 }
1786
1787 memset(&sci, 0, sizeof(sci));
1788 sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
1789 sci.pView = window->ns.view;
1790
1791 err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
1792 }
1793
1794 if (err)
1795 {
1796 _glfwInputError(GLFW_PLATFORM_ERROR,
1797 "Cocoa: Failed to create Vulkan surface: %s",
1798 _glfwGetVulkanResultString(err));
1799 }
1800
1801 return err;
1802#else
1803 return VK_ERROR_EXTENSION_NOT_PRESENT;
1804#endif
1805
1806 } // autoreleasepool
1807}
1808
1809
1810//////////////////////////////////////////////////////////////////////////
1811////// GLFW native API //////
1812//////////////////////////////////////////////////////////////////////////
1813
1814GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
1815{
1816 _GLFWwindow* window = (_GLFWwindow*) handle;
1817 _GLFW_REQUIRE_INIT_OR_RETURN(nil);
1818 return window->ns.object;
1819}
1820