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