cocoa_window.m (53441B)
1 //======================================================================== 2 // GLFW 3.4 macOS - www.glfw.org 3 //------------------------------------------------------------------------ 4 // Copyright (c) 2009-2019 Camilla Löwy <elmindreda@glfw.org> 5 // 6 // This software is provided 'as-is', without any express or implied 7 // warranty. In no event will the authors be held liable for any damages 8 // arising from the use of this software. 9 // 10 // Permission is granted to anyone to use this software for any purpose, 11 // including commercial applications, and to alter it and redistribute it 12 // freely, subject to the following restrictions: 13 // 14 // 1. The origin of this software must not be misrepresented; you must not 15 // claim that you wrote the original software. If you use this software 16 // in a product, an acknowledgment in the product documentation would 17 // be appreciated but is not required. 18 // 19 // 2. Altered source versions must be plainly marked as such, and must not 20 // be misrepresented as being the original software. 21 // 22 // 3. This notice may not be removed or altered from any source 23 // distribution. 24 // 25 //======================================================================== 26 // It is fine to use C99 in this file because it will not be built with VS 27 //======================================================================== 28 29 #include "internal.h" 30 31 #include <float.h> 32 #include <string.h> 33 34 // Returns the style mask corresponding to the window settings 35 // 36 static NSUInteger getStyleMask(_GLFWwindow* window) 37 { 38 NSUInteger styleMask = NSWindowStyleMaskMiniaturizable; 39 40 if (window->monitor || !window->decorated) 41 styleMask |= NSWindowStyleMaskBorderless; 42 else 43 { 44 styleMask |= NSWindowStyleMaskTitled | 45 NSWindowStyleMaskClosable; 46 47 if (window->resizable) 48 styleMask |= NSWindowStyleMaskResizable; 49 } 50 51 return styleMask; 52 } 53 54 // Returns whether the cursor is in the content area of the specified window 55 // 56 static GLFWbool cursorInContentArea(_GLFWwindow* window) 57 { 58 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; 59 return [window->ns.view mouse:pos inRect:[window->ns.view frame]]; 60 } 61 62 // Hides the cursor if not already hidden 63 // 64 static void hideCursor(_GLFWwindow* window) 65 { 66 if (!_glfw.ns.cursorHidden) 67 { 68 [NSCursor hide]; 69 _glfw.ns.cursorHidden = GLFW_TRUE; 70 } 71 } 72 73 // Shows the cursor if not already shown 74 // 75 static void showCursor(_GLFWwindow* window) 76 { 77 if (_glfw.ns.cursorHidden) 78 { 79 [NSCursor unhide]; 80 _glfw.ns.cursorHidden = GLFW_FALSE; 81 } 82 } 83 84 // Updates the cursor image according to its cursor mode 85 // 86 static void updateCursorImage(_GLFWwindow* window) 87 { 88 if (window->cursorMode == GLFW_CURSOR_NORMAL) 89 { 90 showCursor(window); 91 92 if (window->cursor) 93 [(NSCursor*) window->cursor->ns.object set]; 94 else 95 [[NSCursor arrowCursor] set]; 96 } 97 else 98 hideCursor(window); 99 } 100 101 // Apply chosen cursor mode to a focused window 102 // 103 static void updateCursorMode(_GLFWwindow* window) 104 { 105 if (window->cursorMode == GLFW_CURSOR_DISABLED) 106 { 107 _glfw.ns.disabledCursorWindow = window; 108 _glfwPlatformGetCursorPos(window, 109 &_glfw.ns.restoreCursorPosX, 110 &_glfw.ns.restoreCursorPosY); 111 _glfwCenterCursorInContentArea(window); 112 CGAssociateMouseAndMouseCursorPosition(false); 113 } 114 else if (_glfw.ns.disabledCursorWindow == window) 115 { 116 _glfw.ns.disabledCursorWindow = NULL; 117 CGAssociateMouseAndMouseCursorPosition(true); 118 _glfwPlatformSetCursorPos(window, 119 _glfw.ns.restoreCursorPosX, 120 _glfw.ns.restoreCursorPosY); 121 } 122 123 if (cursorInContentArea(window)) 124 updateCursorImage(window); 125 } 126 127 // Make the specified window and its video mode active on its monitor 128 // 129 static void acquireMonitor(_GLFWwindow* window) 130 { 131 _glfwSetVideoModeNS(window->monitor, &window->videoMode); 132 const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); 133 const NSRect frame = NSMakeRect(bounds.origin.x, 134 _glfwTransformYNS(bounds.origin.y + bounds.size.height - 1), 135 bounds.size.width, 136 bounds.size.height); 137 138 [window->ns.object setFrame:frame display:YES]; 139 140 _glfwInputMonitorWindow(window->monitor, window); 141 } 142 143 // Remove the window and restore the original video mode 144 // 145 static void releaseMonitor(_GLFWwindow* window) 146 { 147 if (window->monitor->window != window) 148 return; 149 150 _glfwInputMonitorWindow(window->monitor, NULL); 151 _glfwRestoreVideoModeNS(window->monitor); 152 } 153 154 // Translates macOS key modifiers into GLFW ones 155 // 156 static int translateFlags(NSUInteger flags) 157 { 158 int mods = 0; 159 160 if (flags & NSEventModifierFlagShift) 161 mods |= GLFW_MOD_SHIFT; 162 if (flags & NSEventModifierFlagControl) 163 mods |= GLFW_MOD_CONTROL; 164 if (flags & NSEventModifierFlagOption) 165 mods |= GLFW_MOD_ALT; 166 if (flags & NSEventModifierFlagCommand) 167 mods |= GLFW_MOD_SUPER; 168 if (flags & NSEventModifierFlagCapsLock) 169 mods |= GLFW_MOD_CAPS_LOCK; 170 171 return mods; 172 } 173 174 // Translates a macOS keycode to a GLFW keycode 175 // 176 static int translateKey(unsigned int key) 177 { 178 if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0])) 179 return GLFW_KEY_UNKNOWN; 180 181 return _glfw.ns.keycodes[key]; 182 } 183 184 // Translate a GLFW keycode to a Cocoa modifier flag 185 // 186 static NSUInteger translateKeyToModifierFlag(int key) 187 { 188 switch (key) 189 { 190 case GLFW_KEY_LEFT_SHIFT: 191 case GLFW_KEY_RIGHT_SHIFT: 192 return NSEventModifierFlagShift; 193 case GLFW_KEY_LEFT_CONTROL: 194 case GLFW_KEY_RIGHT_CONTROL: 195 return NSEventModifierFlagControl; 196 case GLFW_KEY_LEFT_ALT: 197 case GLFW_KEY_RIGHT_ALT: 198 return NSEventModifierFlagOption; 199 case GLFW_KEY_LEFT_SUPER: 200 case GLFW_KEY_RIGHT_SUPER: 201 return NSEventModifierFlagCommand; 202 case GLFW_KEY_CAPS_LOCK: 203 return NSEventModifierFlagCapsLock; 204 } 205 206 return 0; 207 } 208 209 // Defines a constant for empty ranges in NSTextInputClient 210 // 211 static const NSRange kEmptyRange = { NSNotFound, 0 }; 212 213 214 //------------------------------------------------------------------------ 215 // Delegate for window related notifications 216 //------------------------------------------------------------------------ 217 218 @interface GLFWWindowDelegate : NSObject 219 { 220 _GLFWwindow* window; 221 } 222 223 - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow; 224 225 @end 226 227 @implementation GLFWWindowDelegate 228 229 - (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow 230 { 231 self = [super init]; 232 if (self != nil) 233 window = initWindow; 234 235 return self; 236 } 237 238 - (BOOL)windowShouldClose:(id)sender 239 { 240 _glfwInputWindowCloseRequest(window); 241 return NO; 242 } 243 244 - (void)windowDidResize:(NSNotification *)notification 245 { 246 if (window->context.client != GLFW_NO_API) 247 [window->context.nsgl.object update]; 248 249 if (_glfw.ns.disabledCursorWindow == window) 250 _glfwCenterCursorInContentArea(window); 251 252 const int maximized = [window->ns.object isZoomed]; 253 if (window->ns.maximized != maximized) 254 { 255 window->ns.maximized = maximized; 256 _glfwInputWindowMaximize(window, maximized); 257 } 258 259 const NSRect contentRect = [window->ns.view frame]; 260 const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; 261 262 if (fbRect.size.width != window->ns.fbWidth || 263 fbRect.size.height != window->ns.fbHeight) 264 { 265 window->ns.fbWidth = fbRect.size.width; 266 window->ns.fbHeight = fbRect.size.height; 267 _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); 268 } 269 270 if (contentRect.size.width != window->ns.width || 271 contentRect.size.height != window->ns.height) 272 { 273 window->ns.width = contentRect.size.width; 274 window->ns.height = contentRect.size.height; 275 _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height); 276 } 277 } 278 279 - (void)windowDidMove:(NSNotification *)notification 280 { 281 if (window->context.client != GLFW_NO_API) 282 [window->context.nsgl.object update]; 283 284 if (_glfw.ns.disabledCursorWindow == window) 285 _glfwCenterCursorInContentArea(window); 286 287 int x, y; 288 _glfwPlatformGetWindowPos(window, &x, &y); 289 _glfwInputWindowPos(window, x, y); 290 } 291 292 - (void)windowDidMiniaturize:(NSNotification *)notification 293 { 294 if (window->monitor) 295 releaseMonitor(window); 296 297 _glfwInputWindowIconify(window, GLFW_TRUE); 298 } 299 300 - (void)windowDidDeminiaturize:(NSNotification *)notification 301 { 302 if (window->monitor) 303 acquireMonitor(window); 304 305 _glfwInputWindowIconify(window, GLFW_FALSE); 306 } 307 308 - (void)windowDidBecomeKey:(NSNotification *)notification 309 { 310 if (_glfw.ns.disabledCursorWindow == window) 311 _glfwCenterCursorInContentArea(window); 312 313 _glfwInputWindowFocus(window, GLFW_TRUE); 314 updateCursorMode(window); 315 } 316 317 - (void)windowDidResignKey:(NSNotification *)notification 318 { 319 if (window->monitor && window->autoIconify) 320 _glfwPlatformIconifyWindow(window); 321 322 _glfwInputWindowFocus(window, GLFW_FALSE); 323 } 324 325 @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 = (char **)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 (!createNativeWindow(window, wndconfig, fbconfig)) 888 return GLFW_FALSE; 889 890 if (ctxconfig->client != GLFW_NO_API) 891 { 892 if (ctxconfig->source == GLFW_NATIVE_CONTEXT_API) 893 { 894 if (!_glfwInitNSGL()) 895 return GLFW_FALSE; 896 if (!_glfwCreateContextNSGL(window, ctxconfig, fbconfig)) 897 return GLFW_FALSE; 898 } 899 else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) 900 { 901 if (!_glfwInitEGL()) 902 return GLFW_FALSE; 903 if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) 904 return GLFW_FALSE; 905 } 906 else if (ctxconfig->source == GLFW_OSMESA_CONTEXT_API) 907 { 908 if (!_glfwInitOSMesa()) 909 return GLFW_FALSE; 910 if (!_glfwCreateContextOSMesa(window, ctxconfig, fbconfig)) 911 return GLFW_FALSE; 912 } 913 } 914 915 if (window->monitor) 916 { 917 _glfwPlatformShowWindow(window); 918 _glfwPlatformFocusWindow(window); 919 acquireMonitor(window); 920 } 921 922 return GLFW_TRUE; 923 924 } // autoreleasepool 925 } 926 927 void _glfwPlatformDestroyWindow(_GLFWwindow* window) 928 { 929 @autoreleasepool { 930 931 if (_glfw.ns.disabledCursorWindow == window) 932 _glfw.ns.disabledCursorWindow = NULL; 933 934 [window->ns.object orderOut:nil]; 935 936 if (window->monitor) 937 releaseMonitor(window); 938 939 if (window->context.destroy) 940 window->context.destroy(window); 941 942 [window->ns.object setDelegate:nil]; 943 [window->ns.delegate release]; 944 window->ns.delegate = nil; 945 946 [window->ns.view release]; 947 window->ns.view = nil; 948 949 [window->ns.object close]; 950 window->ns.object = nil; 951 952 // HACK: Allow Cocoa to catch up before returning 953 _glfwPlatformPollEvents(); 954 955 } // autoreleasepool 956 } 957 958 void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) 959 { 960 @autoreleasepool { 961 NSString* string = @(title); 962 [window->ns.object setTitle:string]; 963 // HACK: Set the miniwindow title explicitly as setTitle: doesn't update it 964 // if the window lacks NSWindowStyleMaskTitled 965 [window->ns.object setMiniwindowTitle:string]; 966 } // autoreleasepool 967 } 968 969 void _glfwPlatformSetWindowIcon(_GLFWwindow* window, 970 int count, const GLFWimage* images) 971 { 972 // Regular windows do not have icons 973 } 974 975 void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) 976 { 977 @autoreleasepool { 978 979 const NSRect contentRect = 980 [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; 981 982 if (xpos) 983 *xpos = contentRect.origin.x; 984 if (ypos) 985 *ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1); 986 987 } // autoreleasepool 988 } 989 990 void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) 991 { 992 @autoreleasepool { 993 994 const NSRect contentRect = [window->ns.view frame]; 995 const NSRect dummyRect = NSMakeRect(x, _glfwTransformYNS(y + contentRect.size.height - 1), 0, 0); 996 const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; 997 [window->ns.object setFrameOrigin:frameRect.origin]; 998 999 } // autoreleasepool 1000 } 1001 1002 void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) 1003 { 1004 @autoreleasepool { 1005 1006 const NSRect contentRect = [window->ns.view frame]; 1007 1008 if (width) 1009 *width = contentRect.size.width; 1010 if (height) 1011 *height = contentRect.size.height; 1012 1013 } // autoreleasepool 1014 } 1015 1016 void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) 1017 { 1018 @autoreleasepool { 1019 1020 if (window->monitor) 1021 { 1022 if (window->monitor->window == window) 1023 acquireMonitor(window); 1024 } 1025 else 1026 { 1027 NSRect contentRect = 1028 [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; 1029 contentRect.origin.y += contentRect.size.height - height; 1030 contentRect.size = NSMakeSize(width, height); 1031 [window->ns.object setFrame:[window->ns.object frameRectForContentRect:contentRect] 1032 display:YES]; 1033 } 1034 1035 } // autoreleasepool 1036 } 1037 1038 void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, 1039 int minwidth, int minheight, 1040 int maxwidth, int maxheight) 1041 { 1042 @autoreleasepool { 1043 1044 if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE) 1045 [window->ns.object setContentMinSize:NSMakeSize(0, 0)]; 1046 else 1047 [window->ns.object setContentMinSize:NSMakeSize(minwidth, minheight)]; 1048 1049 if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE) 1050 [window->ns.object setContentMaxSize:NSMakeSize(DBL_MAX, DBL_MAX)]; 1051 else 1052 [window->ns.object setContentMaxSize:NSMakeSize(maxwidth, maxheight)]; 1053 1054 } // autoreleasepool 1055 } 1056 1057 void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) 1058 { 1059 @autoreleasepool { 1060 if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE) 1061 [window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)]; 1062 else 1063 [window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)]; 1064 } // autoreleasepool 1065 } 1066 1067 void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) 1068 { 1069 @autoreleasepool { 1070 1071 const NSRect contentRect = [window->ns.view frame]; 1072 const NSRect fbRect = [window->ns.view convertRectToBacking:contentRect]; 1073 1074 if (width) 1075 *width = (int) fbRect.size.width; 1076 if (height) 1077 *height = (int) fbRect.size.height; 1078 1079 } // autoreleasepool 1080 } 1081 1082 void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, 1083 int* left, int* top, 1084 int* right, int* bottom) 1085 { 1086 @autoreleasepool { 1087 1088 const NSRect contentRect = [window->ns.view frame]; 1089 const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; 1090 1091 if (left) 1092 *left = contentRect.origin.x - frameRect.origin.x; 1093 if (top) 1094 *top = frameRect.origin.y + frameRect.size.height - 1095 contentRect.origin.y - contentRect.size.height; 1096 if (right) 1097 *right = frameRect.origin.x + frameRect.size.width - 1098 contentRect.origin.x - contentRect.size.width; 1099 if (bottom) 1100 *bottom = contentRect.origin.y - frameRect.origin.y; 1101 1102 } // autoreleasepool 1103 } 1104 1105 void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, 1106 float* xscale, float* yscale) 1107 { 1108 @autoreleasepool { 1109 1110 const NSRect points = [window->ns.view frame]; 1111 const NSRect pixels = [window->ns.view convertRectToBacking:points]; 1112 1113 if (xscale) 1114 *xscale = (float) (pixels.size.width / points.size.width); 1115 if (yscale) 1116 *yscale = (float) (pixels.size.height / points.size.height); 1117 1118 } // autoreleasepool 1119 } 1120 1121 void _glfwPlatformIconifyWindow(_GLFWwindow* window) 1122 { 1123 @autoreleasepool { 1124 [window->ns.object miniaturize:nil]; 1125 } // autoreleasepool 1126 } 1127 1128 void _glfwPlatformRestoreWindow(_GLFWwindow* window) 1129 { 1130 @autoreleasepool { 1131 if ([window->ns.object isMiniaturized]) 1132 [window->ns.object deminiaturize:nil]; 1133 else if ([window->ns.object isZoomed]) 1134 [window->ns.object zoom:nil]; 1135 } // autoreleasepool 1136 } 1137 1138 void _glfwPlatformMaximizeWindow(_GLFWwindow* window) 1139 { 1140 @autoreleasepool { 1141 if (![window->ns.object isZoomed]) 1142 [window->ns.object zoom:nil]; 1143 } // autoreleasepool 1144 } 1145 1146 void _glfwPlatformShowWindow(_GLFWwindow* window) 1147 { 1148 @autoreleasepool { 1149 [window->ns.object orderFront:nil]; 1150 } // autoreleasepool 1151 } 1152 1153 void _glfwPlatformHideWindow(_GLFWwindow* window) 1154 { 1155 @autoreleasepool { 1156 [window->ns.object orderOut:nil]; 1157 } // autoreleasepool 1158 } 1159 1160 void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) 1161 { 1162 @autoreleasepool { 1163 [NSApp requestUserAttention:NSInformationalRequest]; 1164 } // autoreleasepool 1165 } 1166 1167 void _glfwPlatformFocusWindow(_GLFWwindow* window) 1168 { 1169 @autoreleasepool { 1170 // Make us the active application 1171 // HACK: This is here to prevent applications using only hidden windows from 1172 // being activated, but should probably not be done every time any 1173 // window is shown 1174 [NSApp activateIgnoringOtherApps:YES]; 1175 [window->ns.object makeKeyAndOrderFront:nil]; 1176 } // autoreleasepool 1177 } 1178 1179 void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, 1180 _GLFWmonitor* monitor, 1181 int xpos, int ypos, 1182 int width, int height, 1183 int refreshRate) 1184 { 1185 @autoreleasepool { 1186 1187 if (window->monitor == monitor) 1188 { 1189 if (monitor) 1190 { 1191 if (monitor->window == window) 1192 acquireMonitor(window); 1193 } 1194 else 1195 { 1196 const NSRect contentRect = 1197 NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), width, height); 1198 const NSRect frameRect = 1199 [window->ns.object frameRectForContentRect:contentRect 1200 styleMask:getStyleMask(window)]; 1201 1202 [window->ns.object setFrame:frameRect display:YES]; 1203 } 1204 1205 return; 1206 } 1207 1208 if (window->monitor) 1209 releaseMonitor(window); 1210 1211 _glfwInputWindowMonitor(window, monitor); 1212 1213 // HACK: Allow the state cached in Cocoa to catch up to reality 1214 // TODO: Solve this in a less terrible way 1215 _glfwPlatformPollEvents(); 1216 1217 const NSUInteger styleMask = getStyleMask(window); 1218 [window->ns.object setStyleMask:styleMask]; 1219 // HACK: Changing the style mask can cause the first responder to be cleared 1220 [window->ns.object makeFirstResponder:window->ns.view]; 1221 1222 if (window->monitor) 1223 { 1224 [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; 1225 [window->ns.object setHasShadow:NO]; 1226 1227 acquireMonitor(window); 1228 } 1229 else 1230 { 1231 NSRect contentRect = NSMakeRect(xpos, _glfwTransformYNS(ypos + height - 1), 1232 width, height); 1233 NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect 1234 styleMask:styleMask]; 1235 [window->ns.object setFrame:frameRect display:YES]; 1236 1237 if (window->numer != GLFW_DONT_CARE && 1238 window->denom != GLFW_DONT_CARE) 1239 { 1240 [window->ns.object setContentAspectRatio:NSMakeSize(window->numer, 1241 window->denom)]; 1242 } 1243 1244 if (window->minwidth != GLFW_DONT_CARE && 1245 window->minheight != GLFW_DONT_CARE) 1246 { 1247 [window->ns.object setContentMinSize:NSMakeSize(window->minwidth, 1248 window->minheight)]; 1249 } 1250 1251 if (window->maxwidth != GLFW_DONT_CARE && 1252 window->maxheight != GLFW_DONT_CARE) 1253 { 1254 [window->ns.object setContentMaxSize:NSMakeSize(window->maxwidth, 1255 window->maxheight)]; 1256 } 1257 1258 if (window->floating) 1259 [window->ns.object setLevel:NSFloatingWindowLevel]; 1260 else 1261 [window->ns.object setLevel:NSNormalWindowLevel]; 1262 1263 [window->ns.object setHasShadow:YES]; 1264 // HACK: Clearing NSWindowStyleMaskTitled resets and disables the window 1265 // title property but the miniwindow title property is unaffected 1266 [window->ns.object setTitle:[window->ns.object miniwindowTitle]]; 1267 } 1268 1269 } // autoreleasepool 1270 } 1271 1272 int _glfwPlatformWindowFocused(_GLFWwindow* window) 1273 { 1274 @autoreleasepool { 1275 return [window->ns.object isKeyWindow]; 1276 } // autoreleasepool 1277 } 1278 1279 int _glfwPlatformWindowIconified(_GLFWwindow* window) 1280 { 1281 @autoreleasepool { 1282 return [window->ns.object isMiniaturized]; 1283 } // autoreleasepool 1284 } 1285 1286 int _glfwPlatformWindowVisible(_GLFWwindow* window) 1287 { 1288 @autoreleasepool { 1289 return [window->ns.object isVisible]; 1290 } // autoreleasepool 1291 } 1292 1293 int _glfwPlatformWindowMaximized(_GLFWwindow* window) 1294 { 1295 @autoreleasepool { 1296 return [window->ns.object isZoomed]; 1297 } // autoreleasepool 1298 } 1299 1300 int _glfwPlatformWindowHovered(_GLFWwindow* window) 1301 { 1302 @autoreleasepool { 1303 1304 const NSPoint point = [NSEvent mouseLocation]; 1305 1306 if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] != 1307 [window->ns.object windowNumber]) 1308 { 1309 return GLFW_FALSE; 1310 } 1311 1312 return NSMouseInRect(point, 1313 [window->ns.object convertRectToScreen:[window->ns.view frame]], NO); 1314 1315 } // autoreleasepool 1316 } 1317 1318 int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) 1319 { 1320 @autoreleasepool { 1321 return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; 1322 } // autoreleasepool 1323 } 1324 1325 void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) 1326 { 1327 @autoreleasepool { 1328 [window->ns.object setStyleMask:getStyleMask(window)]; 1329 } // autoreleasepool 1330 } 1331 1332 void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) 1333 { 1334 @autoreleasepool { 1335 [window->ns.object setStyleMask:getStyleMask(window)]; 1336 [window->ns.object makeFirstResponder:window->ns.view]; 1337 } // autoreleasepool 1338 } 1339 1340 void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) 1341 { 1342 @autoreleasepool { 1343 if (enabled) 1344 [window->ns.object setLevel:NSFloatingWindowLevel]; 1345 else 1346 [window->ns.object setLevel:NSNormalWindowLevel]; 1347 } // autoreleasepool 1348 } 1349 1350 float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) 1351 { 1352 @autoreleasepool { 1353 return (float) [window->ns.object alphaValue]; 1354 } // autoreleasepool 1355 } 1356 1357 void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) 1358 { 1359 @autoreleasepool { 1360 [window->ns.object setAlphaValue:opacity]; 1361 } // autoreleasepool 1362 } 1363 1364 void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) 1365 { 1366 } 1367 1368 GLFWbool _glfwPlatformRawMouseMotionSupported(void) 1369 { 1370 return GLFW_FALSE; 1371 } 1372 1373 void _glfwPlatformPollEvents(void) 1374 { 1375 @autoreleasepool { 1376 1377 for (;;) 1378 { 1379 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny 1380 untilDate:[NSDate distantPast] 1381 inMode:NSDefaultRunLoopMode 1382 dequeue:YES]; 1383 if (event == nil) 1384 break; 1385 1386 [NSApp sendEvent:event]; 1387 } 1388 1389 } // autoreleasepool 1390 } 1391 1392 void _glfwPlatformWaitEvents(void) 1393 { 1394 @autoreleasepool { 1395 1396 // I wanted to pass NO to dequeue:, and rely on PollEvents to 1397 // dequeue and send. For reasons not at all clear to me, passing 1398 // NO to dequeue: causes this method never to return. 1399 NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny 1400 untilDate:[NSDate distantFuture] 1401 inMode:NSDefaultRunLoopMode 1402 dequeue:YES]; 1403 [NSApp sendEvent:event]; 1404 1405 _glfwPlatformPollEvents(); 1406 1407 } // autoreleasepool 1408 } 1409 1410 void _glfwPlatformWaitEventsTimeout(double timeout) 1411 { 1412 @autoreleasepool { 1413 1414 NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; 1415 NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny 1416 untilDate:date 1417 inMode:NSDefaultRunLoopMode 1418 dequeue:YES]; 1419 if (event) 1420 [NSApp sendEvent:event]; 1421 1422 _glfwPlatformPollEvents(); 1423 1424 } // autoreleasepool 1425 } 1426 1427 void _glfwPlatformPostEmptyEvent(void) 1428 { 1429 @autoreleasepool { 1430 1431 NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined 1432 location:NSMakePoint(0, 0) 1433 modifierFlags:0 1434 timestamp:0 1435 windowNumber:0 1436 context:nil 1437 subtype:0 1438 data1:0 1439 data2:0]; 1440 [NSApp postEvent:event atStart:YES]; 1441 1442 } // autoreleasepool 1443 } 1444 1445 void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) 1446 { 1447 @autoreleasepool { 1448 1449 const NSRect contentRect = [window->ns.view frame]; 1450 // NOTE: The returned location uses base 0,1 not 0,0 1451 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; 1452 1453 if (xpos) 1454 *xpos = pos.x; 1455 if (ypos) 1456 *ypos = contentRect.size.height - pos.y; 1457 1458 } // autoreleasepool 1459 } 1460 1461 void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) 1462 { 1463 @autoreleasepool { 1464 1465 updateCursorImage(window); 1466 1467 const NSRect contentRect = [window->ns.view frame]; 1468 // NOTE: The returned location uses base 0,1 not 0,0 1469 const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; 1470 1471 window->ns.cursorWarpDeltaX += x - pos.x; 1472 window->ns.cursorWarpDeltaY += y - contentRect.size.height + pos.y; 1473 1474 if (window->monitor) 1475 { 1476 CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, 1477 CGPointMake(x, y)); 1478 } 1479 else 1480 { 1481 const NSRect localRect = NSMakeRect(x, contentRect.size.height - y - 1, 0, 0); 1482 const NSRect globalRect = [window->ns.object convertRectToScreen:localRect]; 1483 const NSPoint globalPoint = globalRect.origin; 1484 1485 CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, 1486 _glfwTransformYNS(globalPoint.y))); 1487 } 1488 1489 } // autoreleasepool 1490 } 1491 1492 void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) 1493 { 1494 @autoreleasepool { 1495 if (_glfwPlatformWindowFocused(window)) 1496 updateCursorMode(window); 1497 } // autoreleasepool 1498 } 1499 1500 const char* _glfwPlatformGetScancodeName(int scancode) 1501 { 1502 @autoreleasepool { 1503 1504 if (scancode < 0 || scancode > 0xff || 1505 _glfw.ns.keycodes[scancode] == GLFW_KEY_UNKNOWN) 1506 { 1507 _glfwInputError(GLFW_INVALID_VALUE, "Invalid scancode"); 1508 return NULL; 1509 } 1510 1511 const int key = _glfw.ns.keycodes[scancode]; 1512 1513 UInt32 deadKeyState = 0; 1514 UniChar characters[4]; 1515 UniCharCount characterCount = 0; 1516 1517 if (UCKeyTranslate((const UCKeyboardLayout *)[(NSData*) _glfw.ns.unicodeData bytes], 1518 scancode, 1519 kUCKeyActionDisplay, 1520 0, 1521 LMGetKbdType(), 1522 kUCKeyTranslateNoDeadKeysBit, 1523 &deadKeyState, 1524 sizeof(characters) / sizeof(characters[0]), 1525 &characterCount, 1526 characters) != noErr) 1527 { 1528 return NULL; 1529 } 1530 1531 if (!characterCount) 1532 return NULL; 1533 1534 CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, 1535 characters, 1536 characterCount, 1537 kCFAllocatorNull); 1538 CFStringGetCString(string, 1539 _glfw.ns.keynames[key], 1540 sizeof(_glfw.ns.keynames[key]), 1541 kCFStringEncodingUTF8); 1542 CFRelease(string); 1543 1544 return _glfw.ns.keynames[key]; 1545 1546 } // autoreleasepool 1547 } 1548 1549 int _glfwPlatformGetKeyScancode(int key) 1550 { 1551 return _glfw.ns.scancodes[key]; 1552 } 1553 1554 int _glfwPlatformCreateCursor(_GLFWcursor* cursor, 1555 const GLFWimage* image, 1556 int xhot, int yhot) 1557 { 1558 @autoreleasepool { 1559 1560 NSImage* native; 1561 NSBitmapImageRep* rep; 1562 1563 rep = [[NSBitmapImageRep alloc] 1564 initWithBitmapDataPlanes:NULL 1565 pixelsWide:image->width 1566 pixelsHigh:image->height 1567 bitsPerSample:8 1568 samplesPerPixel:4 1569 hasAlpha:YES 1570 isPlanar:NO 1571 colorSpaceName:NSCalibratedRGBColorSpace 1572 bitmapFormat:NSBitmapFormatAlphaNonpremultiplied 1573 bytesPerRow:image->width * 4 1574 bitsPerPixel:32]; 1575 1576 if (rep == nil) 1577 return GLFW_FALSE; 1578 1579 memcpy([rep bitmapData], image->pixels, image->width * image->height * 4); 1580 1581 native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; 1582 [native addRepresentation:rep]; 1583 1584 cursor->ns.object = [[NSCursor alloc] initWithImage:native 1585 hotSpot:NSMakePoint(xhot, yhot)]; 1586 1587 [native release]; 1588 [rep release]; 1589 1590 if (cursor->ns.object == nil) 1591 return GLFW_FALSE; 1592 1593 return GLFW_TRUE; 1594 1595 } // autoreleasepool 1596 } 1597 1598 int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) 1599 { 1600 @autoreleasepool { 1601 1602 SEL cursorSelector = NULL; 1603 1604 // HACK: Try to use a private message 1605 if (shape == GLFW_RESIZE_EW_CURSOR) 1606 cursorSelector = NSSelectorFromString(@"_windowResizeEastWestCursor"); 1607 else if (shape == GLFW_RESIZE_NS_CURSOR) 1608 cursorSelector = NSSelectorFromString(@"_windowResizeNorthSouthCursor"); 1609 else if (shape == GLFW_RESIZE_NWSE_CURSOR) 1610 cursorSelector = NSSelectorFromString(@"_windowResizeNorthWestSouthEastCursor"); 1611 else if (shape == GLFW_RESIZE_NESW_CURSOR) 1612 cursorSelector = NSSelectorFromString(@"_windowResizeNorthEastSouthWestCursor"); 1613 1614 if (cursorSelector && [NSCursor respondsToSelector:cursorSelector]) 1615 { 1616 id object = [NSCursor performSelector:cursorSelector]; 1617 if ([object isKindOfClass:[NSCursor class]]) 1618 cursor->ns.object = object; 1619 } 1620 1621 if (!cursor->ns.object) 1622 { 1623 if (shape == GLFW_ARROW_CURSOR) 1624 cursor->ns.object = [NSCursor arrowCursor]; 1625 else if (shape == GLFW_IBEAM_CURSOR) 1626 cursor->ns.object = [NSCursor IBeamCursor]; 1627 else if (shape == GLFW_CROSSHAIR_CURSOR) 1628 cursor->ns.object = [NSCursor crosshairCursor]; 1629 else if (shape == GLFW_POINTING_HAND_CURSOR) 1630 cursor->ns.object = [NSCursor pointingHandCursor]; 1631 else if (shape == GLFW_RESIZE_EW_CURSOR) 1632 cursor->ns.object = [NSCursor resizeLeftRightCursor]; 1633 else if (shape == GLFW_RESIZE_NS_CURSOR) 1634 cursor->ns.object = [NSCursor resizeUpDownCursor]; 1635 else if (shape == GLFW_RESIZE_ALL_CURSOR) 1636 cursor->ns.object = [NSCursor closedHandCursor]; 1637 else if (shape == GLFW_NOT_ALLOWED_CURSOR) 1638 cursor->ns.object = [NSCursor operationNotAllowedCursor]; 1639 } 1640 1641 if (!cursor->ns.object) 1642 { 1643 _glfwInputError(GLFW_CURSOR_UNAVAILABLE, 1644 "Cocoa: Standard cursor shape unavailable"); 1645 return GLFW_FALSE; 1646 } 1647 1648 [cursor->ns.object retain]; 1649 return GLFW_TRUE; 1650 1651 } // autoreleasepool 1652 } 1653 1654 void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) 1655 { 1656 @autoreleasepool { 1657 if (cursor->ns.object) 1658 [(NSCursor*) cursor->ns.object release]; 1659 } // autoreleasepool 1660 } 1661 1662 void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) 1663 { 1664 @autoreleasepool { 1665 if (cursorInContentArea(window)) 1666 updateCursorImage(window); 1667 } // autoreleasepool 1668 } 1669 1670 void _glfwPlatformSetClipboardString(const char* string) 1671 { 1672 @autoreleasepool { 1673 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; 1674 [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; 1675 [pasteboard setString:@(string) forType:NSPasteboardTypeString]; 1676 } // autoreleasepool 1677 } 1678 1679 const char* _glfwPlatformGetClipboardString(void) 1680 { 1681 @autoreleasepool { 1682 1683 NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; 1684 1685 if (![[pasteboard types] containsObject:NSPasteboardTypeString]) 1686 { 1687 _glfwInputError(GLFW_FORMAT_UNAVAILABLE, 1688 "Cocoa: Failed to retrieve string from pasteboard"); 1689 return NULL; 1690 } 1691 1692 NSString* object = [pasteboard stringForType:NSPasteboardTypeString]; 1693 if (!object) 1694 { 1695 _glfwInputError(GLFW_PLATFORM_ERROR, 1696 "Cocoa: Failed to retrieve object from pasteboard"); 1697 return NULL; 1698 } 1699 1700 free(_glfw.ns.clipboardString); 1701 _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]); 1702 1703 return _glfw.ns.clipboardString; 1704 1705 } // autoreleasepool 1706 } 1707 1708 void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) 1709 { 1710 if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) 1711 { 1712 extensions[0] = "VK_KHR_surface"; 1713 extensions[1] = "VK_EXT_metal_surface"; 1714 } 1715 else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface) 1716 { 1717 extensions[0] = "VK_KHR_surface"; 1718 extensions[1] = "VK_MVK_macos_surface"; 1719 } 1720 } 1721 1722 int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, 1723 VkPhysicalDevice device, 1724 uint32_t queuefamily) 1725 { 1726 return GLFW_TRUE; 1727 } 1728 1729 VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, 1730 _GLFWwindow* window, 1731 const VkAllocationCallbacks* allocator, 1732 VkSurfaceKHR* surface) 1733 { 1734 @autoreleasepool { 1735 1736 #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 1737 // HACK: Dynamically load Core Animation to avoid adding an extra 1738 // dependency for the majority who don't use MoltenVK 1739 NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"]; 1740 if (!bundle) 1741 { 1742 _glfwInputError(GLFW_PLATFORM_ERROR, 1743 "Cocoa: Failed to find QuartzCore.framework"); 1744 return VK_ERROR_EXTENSION_NOT_PRESENT; 1745 } 1746 1747 // NOTE: Create the layer here as makeBackingLayer should not return nil 1748 window->ns.layer = [[bundle classNamed:@"CAMetalLayer"] layer]; 1749 if (!window->ns.layer) 1750 { 1751 _glfwInputError(GLFW_PLATFORM_ERROR, 1752 "Cocoa: Failed to create layer for view"); 1753 return VK_ERROR_EXTENSION_NOT_PRESENT; 1754 } 1755 1756 if (window->ns.retina) 1757 [window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]]; 1758 1759 [window->ns.view setLayer:window->ns.layer]; 1760 [window->ns.view setWantsLayer:YES]; 1761 1762 VkResult err; 1763 1764 if (_glfw.vk.EXT_metal_surface) 1765 { 1766 VkMetalSurfaceCreateInfoEXT sci; 1767 1768 PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT; 1769 vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT) 1770 vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT"); 1771 if (!vkCreateMetalSurfaceEXT) 1772 { 1773 _glfwInputError(GLFW_API_UNAVAILABLE, 1774 "Cocoa: Vulkan instance missing VK_EXT_metal_surface extension"); 1775 return VK_ERROR_EXTENSION_NOT_PRESENT; 1776 } 1777 1778 memset(&sci, 0, sizeof(sci)); 1779 sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; 1780 sci.pLayer = window->ns.layer; 1781 1782 err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface); 1783 } 1784 else 1785 { 1786 VkMacOSSurfaceCreateInfoMVK sci; 1787 1788 PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK; 1789 vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK) 1790 vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK"); 1791 if (!vkCreateMacOSSurfaceMVK) 1792 { 1793 _glfwInputError(GLFW_API_UNAVAILABLE, 1794 "Cocoa: Vulkan instance missing VK_MVK_macos_surface extension"); 1795 return VK_ERROR_EXTENSION_NOT_PRESENT; 1796 } 1797 1798 memset(&sci, 0, sizeof(sci)); 1799 sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; 1800 sci.pView = window->ns.view; 1801 1802 err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface); 1803 } 1804 1805 if (err) 1806 { 1807 _glfwInputError(GLFW_PLATFORM_ERROR, 1808 "Cocoa: Failed to create Vulkan surface: %s", 1809 _glfwGetVulkanResultString(err)); 1810 } 1811 1812 return err; 1813 #else 1814 return VK_ERROR_EXTENSION_NOT_PRESENT; 1815 #endif 1816 1817 } // autoreleasepool 1818 } 1819 1820 1821 ////////////////////////////////////////////////////////////////////////// 1822 ////// GLFW native API ////// 1823 ////////////////////////////////////////////////////////////////////////// 1824 1825 GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) 1826 { 1827 _GLFWwindow* window = (_GLFWwindow*) handle; 1828 _GLFW_REQUIRE_INIT_OR_RETURN(nil); 1829 return window->ns.object; 1830 } 1831