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