cocoa_window.m (52532B)
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 // 36 static NSUInteger getStyleMask(_GLFWwindow* window) 37 { 38 NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; 39 40 if (window->monitor || !window->decorated) 41 styleMask |= NSWindowStyleMaskBorderless; 42 else 43 { 44 styleMask |= NSWindowStyleMaskTitled | 45 NSWindowStyleMaskClosable; 46 47 if (window->resizable) 48 styleMask |= NSWindowStyleMaskResizable; 49 } 50 51 return styleMask; 52 } 53 54 // Returns whether the cursor is in the content area of the specified window 55 // 56 static GLFWbool cursorInContentArea(_GLFWwindow* window) 57 { 58 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; 59 return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; 60 } 61 62 // Hides the cursor if not already hidden 63 // 64 static void hideCursor(_GLFWwindow* window) 65 { 66 if (!_glfw.ns.cursorHidden) 67 { 68 [NSCursor hide]; 69 _glfw.ns.cursorHidden = GLFW_TRUE; 70 } 71 } 72 73 // Shows the cursor if not already shown 74 // 75 static void showCursor(_GLFWwindow* window) 76 { 77 if (_glfw.ns.cursorHidden) 78 { 79 [NSCursor unhide]; 80 _glfw.ns.cursorHidden = GLFW_FALSE; 81 } 82 } 83 84 // Updates the cursor image according to its cursor mode 85 // 86 static void updateCursorImage(_GLFWwindow* window) 87 { 88 if (window->cursorMode == GLFW_CURSOR_NORMAL) 89 { 90 showCursor(window); 91 92 if (window->cursor) 93 [(NSCursor*) window->cursor->ns.object set]; 94 else 95 [[NSCursor arrowCursor] set]; 96 } 97 else 98 hideCursor(window); 99 } 100 101 // Apply chosen cursor mode to a focused window 102 // 103 static void updateCursorMode(_GLFWwindow* window) 104 { 105 if (window->cursorMode == GLFW_CURSOR_DISABLED) 106 { 107 _glfw.ns.disabledCursorWindow = window; 108 _glfwPlatformGetCursorPos(window, 109 &_glfw.ns.restoreCursorPosX, 110 &_glfw.ns.restoreCursorPosY); 111 _glfwCenterCursorInContentArea(window); 112 CGAssociateMouseAndMouseCursorPosition(false); 113 } 114 else if (_glfw.ns.disabledCursorWindow == window) 115 { 116 _glfw.ns.disabledCursorWindow = NULL; 117 CGAssociateMouseAndMouseCursorPosition(true); 118 _glfwPlatformSetCursorPos(window, 119 _glfw.ns.restoreCursorPosX, 120 _glfw.ns.restoreCursorPosY); 121 } 122 123 if (cursorInContentArea(window)) 124 updateCursorImage(window); 125 } 126 127 // Make the specified window and its video mode active on its monitor 128 // 129 static void acquireMonitor(_GLFWwindow* window) 130 { 131 _glfwSetVideoModeNS(window->monitor, &window->videoMode); 132 const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); 133 const NSRect frame = NSMakeRect(bounds.origin.x, 134 _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), 135 bounds.size.width, 136 bounds.size.height); 137 138 [window->ns.object setFrame:frame display:YES]; 139 140 _glfwInputMonitorWindow(window->monitor, window); 141 } 142 143 // Remove the window and restore the original video mode 144 // 145 static void releaseMonitor(_GLFWwindow* window) 146 { 147 if (window->monitor->window != window) 148 return; 149 150 _glfwInputMonitorWindow(window->monitor, NULL); 151 _glfwRestoreVideoModeNS(window->monitor); 152 } 153 154 // Translates macOS key modifiers into GLFW ones 155 // 156 static int translateFlags(NSUInteger flags) 157 { 158 int mods = 0; 159 160 if (flags & NSEventModifierFlagShift) 161 mods |= GLFW_MOD_SHIFT; 162 if (flags & NSEventModifierFlagControl) 163 mods |= GLFW_MOD_CONTROL; 164 if (flags & NSEventModifierFlagOption) 165 mods |= GLFW_MOD_ALT; 166 if (flags & NSEventModifierFlagCommand) 167 mods |= GLFW_MOD_SUPER; 168 if (flags & NSEventModifierFlagCapsLock) 169 mods |= GLFW_MOD_CAPS_LOCK; 170 171 return mods; 172 } 173 174 // Translates a macOS keycode to a GLFW keycode 175 // 176 static int translateKey(unsigned int key) 177 { 178 if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0])) 179 return GLFW_KEY_UNKNOWN; 180 181 return _glfw.ns.keycodes[key]; 182 } 183 184 // Translate a GLFW keycode to a Cocoa modifier flag 185 // 186 static NSUInteger translateKeyToModifierFlag(int key) 187 { 188 switch (key) 189 { 190 case GLFW_KEY_LEFT_SHIFT: 191 case GLFW_KEY_RIGHT_SHIFT: 192 return NSEventModifierFlagShift; 193 case GLFW_KEY_LEFT_CONTROL: 194 case GLFW_KEY_RIGHT_CONTROL: 195 return NSEventModifierFlagControl; 196 case GLFW_KEY_LEFT_ALT: 197 case GLFW_KEY_RIGHT_ALT: 198 return NSEventModifierFlagOption; 199 case GLFW_KEY_LEFT_SUPER: 200 case GLFW_KEY_RIGHT_SUPER: 201 return NSEventModifierFlagCommand; 202 case GLFW_KEY_CAPS_LOCK: 203 return NSEventModifierFlagCapsLock; 204 } 205 206 return 0; 207 } 208 209 // Defines a constant for empty ranges in NSTextInputClient 210 // 211 static const NSRange kEmptyRange = { NSNotFound, 0 }; 212 213 214 //------------------------------------------------------------------------ 215 // Delegate for window related notifications 216 //------------------------------------------------------------------------ 217 218 @interface GLFWWindowDelegate : NSObject 219 { 220 _GLFWwindow* window; 221 } 222 223 - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; 224 225 @end 226 227 @implementation GLFWWindowDelegate 228 229 - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow 230 { 231 self = [super init]; 232 if (self != nil) 233 window = initWindow; 234 235 return self; 236 } 237 238 - (BOOL)windowShouldClose:(id)sender 239 { 240 _glfwInputWindowCloseRequest(window); 241 return NO; 242 } 243 244 - (void)windowDidResize:(NSNotification *)notification 245 { 246 if (window->context.client != GLFW_NO_API) 247 [window->context.nsgl.object update]; 248 249 if (_glfw.ns.disabledCursorWindow == window) 250 _glfwCenterCursorInContentArea(window); 251 252 const int maximized = [window->ns.object isZoomed]; 253 if (window->ns.maximized != maximized) 254 { 255 window->ns.maximized = maximized; 256 _glfwInputWindowMaximize(window, maximized); 257 } 258 259 const NSRect contentRect = [window->ns.view frame]; 260 const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; 261 262 if (fbRect.size.width != window->ns.fbWidth || 263 fbRect.size.height != window->ns.fbHeight) 264 { 265 window->ns.fbWidth = fbRect.size.width; 266 window->ns.fbHeight = fbRect.size.height; 267 _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); 268 } 269 270 if (contentRect.size.width != window->ns.width || 271 contentRect.size.height != window->ns.height) 272 { 273 window->ns.width = contentRect.size.width; 274 window->ns.height = contentRect.size.height; 275 _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height); 276 } 277 } 278 279 - (void)windowDidMove:(NSNotification *)notification 280 { 281 if (window->context.client != GLFW_NO_API) 282 [window->context.nsgl.object update]; 283 284 if (_glfw.ns.disabledCursorWindow == window) 285 _glfwCenterCursorInContentArea(window); 286 287 int x, y; 288 _glfwPlatformGetWindowPos(window, &x, &y); 289 _glfwInputWindowPos(window, x, y); 290 } 291 292 - (void)windowDidMiniaturize:(NSNotification *)notification 293 { 294 if (window->monitor) 295 releaseMonitor(window); 296 297 _glfwInputWindowIconify(window, GLFW_TRUE); 298 } 299 300 - (void)windowDidDeminiaturize:(NSNotification *)notification 301 { 302 if (window->monitor) 303 acquireMonitor(window); 304 305 _glfwInputWindowIconify(window, GLFW_FALSE); 306 } 307 308 - (void)windowDidBecomeKey:(NSNotification *)notification 309 { 310 if (_glfw.ns.disabledCursorWindow == window) 311 _glfwCenterCursorInContentArea(window); 312 313 _glfwInputWindowFocus(window, GLFW_TRUE); 314 updateCursorMode(window); 315 } 316 317 - (void)windowDidResignKey:(NSNotification *)notification 318 { 319 if (window->monitor && window->autoIconify) 320 _glfwPlatformIconifyWindow(window); 321 322 _glfwInputWindowFocus(window, GLFW_FALSE); 323 } 324 325 @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 // 769 static 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 // 870 float _glfwTransformYNS(float y) 871 { 872 return CGDisplayBounds(CGMainDisplayID()).size.height - y - 1; 873 } 874 875 876 ////////////////////////////////////////////////////////////////////////// 877 ////// GLFW platform API ////// 878 ////////////////////////////////////////////////////////////////////////// 879 880 int _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 930 void _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 961 void _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 972 void _glfwPlatformSetWindowIcon(_GLFWwindow* window, 973 int count, const GLFWimage* images) 974 { 975 // Regular windows do not have icons 976 } 977 978 void _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 993 void _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 1005 void _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 1019 void _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 1041 void _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 1060 void _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 1070 void _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 1085 void _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 1108 void _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 1124 void _glfwPlatformIconifyWindow(_GLFWwindow* window) 1125 { 1126 @autoreleasepool { 1127 [window->ns.object miniaturize:nil]; 1128 } // autoreleasepool 1129 } 1130 1131 void _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 1141 void _glfwPlatformMaximizeWindow(_GLFWwindow* window) 1142 { 1143 @autoreleasepool { 1144 if (![window->ns.object isZoomed]) 1145 [window->ns.object zoom:nil]; 1146 } // autoreleasepool 1147 } 1148 1149 void _glfwPlatformShowWindow(_GLFWwindow* window) 1150 { 1151 @autoreleasepool { 1152 [window->ns.object orderFront:nil]; 1153 } // autoreleasepool 1154 } 1155 1156 void _glfwPlatformHideWindow(_GLFWwindow* window) 1157 { 1158 @autoreleasepool { 1159 [window->ns.object orderOut:nil]; 1160 } // autoreleasepool 1161 } 1162 1163 void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) 1164 { 1165 @autoreleasepool { 1166 [NSApp requestUserAttention:NSInformationalRequest]; 1167 } // autoreleasepool 1168 } 1169 1170 void _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 1182 void _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 1275 int _glfwPlatformWindowFocused(_GLFWwindow* window) 1276 { 1277 @autoreleasepool { 1278 return [window->ns.object isKeyWindow]; 1279 } // autoreleasepool 1280 } 1281 1282 int _glfwPlatformWindowIconified(_GLFWwindow* window) 1283 { 1284 @autoreleasepool { 1285 return [window->ns.object isMiniaturized]; 1286 } // autoreleasepool 1287 } 1288 1289 int _glfwPlatformWindowVisible(_GLFWwindow* window) 1290 { 1291 @autoreleasepool { 1292 return [window->ns.object isVisible]; 1293 } // autoreleasepool 1294 } 1295 1296 int _glfwPlatformWindowMaximized(_GLFWwindow* window) 1297 { 1298 @autoreleasepool { 1299 return [window->ns.object isZoomed]; 1300 } // autoreleasepool 1301 } 1302 1303 int _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 1321 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) 1322 { 1323 @autoreleasepool { 1324 return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; 1325 } // autoreleasepool 1326 } 1327 1328 void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) 1329 { 1330 @autoreleasepool { 1331 [window->ns.object setStyleMask:getStyleMask(window)]; 1332 } // autoreleasepool 1333 } 1334 1335 void _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 1343 void _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 1353 float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) 1354 { 1355 @autoreleasepool { 1356 return (float) [window->ns.object alphaValue]; 1357 } // autoreleasepool 1358 } 1359 1360 void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) 1361 { 1362 @autoreleasepool { 1363 [window->ns.object setAlphaValue:opacity]; 1364 } // autoreleasepool 1365 } 1366 1367 void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) 1368 { 1369 } 1370 1371 GLFWbool _glfwPlatformRawMouseMotionSupported(void) 1372 { 1373 return GLFW_FALSE; 1374 } 1375 1376 void _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 1398 void _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 1419 void _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 1439 void _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 1460 void _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 1476 void _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 1507 void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) 1508 { 1509 @autoreleasepool { 1510 if (_glfwPlatformWindowFocused(window)) 1511 updateCursorMode(window); 1512 } // autoreleasepool 1513 } 1514 1515 const 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 1564 int _glfwPlatformGetKeyScancode(int key) 1565 { 1566 return _glfw.ns.scancodes[key]; 1567 } 1568 1569 int _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 1613 int _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 1643 void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) 1644 { 1645 @autoreleasepool { 1646 if (cursor->ns.object) 1647 [(NSCursor*) cursor->ns.object release]; 1648 } // autoreleasepool 1649 } 1650 1651 void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) 1652 { 1653 @autoreleasepool { 1654 if (cursorInContentArea(window)) 1655 updateCursorImage(window); 1656 } // autoreleasepool 1657 } 1658 1659 void _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 1668 const 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 1697 void _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 1711 int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, 1712 VkPhysicalDevice device, 1713 uint32_t queuefamily) 1714 { 1715 return GLFW_TRUE; 1716 } 1717 1718 VkResult _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 1814 GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) 1815 { 1816 _GLFWwindow* window = (_GLFWwindow*) handle; 1817 _GLFW_REQUIRE_INIT_OR_RETURN(nil); 1818 return window->ns.object; 1819 } 1820