//======================================================================== // GLFW 3.1 OS X - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2010 Camilla Berglund // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include "internal.h" #include // Needed for _NSGetProgname #include // Returns the specified standard cursor // static NSCursor* getStandardCursor(int shape) { switch (shape) { case GLFW_ARROW_CURSOR: return [NSCursor arrowCursor]; case GLFW_IBEAM_CURSOR: return [NSCursor IBeamCursor]; case GLFW_CROSSHAIR_CURSOR: return [NSCursor crosshairCursor]; case GLFW_HAND_CURSOR: return [NSCursor pointingHandCursor]; case GLFW_HRESIZE_CURSOR: return [NSCursor resizeLeftRightCursor]; case GLFW_VRESIZE_CURSOR: return [NSCursor resizeUpDownCursor]; } return nil; } // Center the cursor in the view of the window // static void centerCursor(_GLFWwindow *window) { int width, height; _glfwPlatformGetWindowSize(window, &width, &height); _glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0); } // Update the cursor to match the specified cursor mode // static void updateModeCursor(_GLFWwindow* window) { if (window->cursorMode == GLFW_CURSOR_NORMAL) { if (window->cursor) [(NSCursor*) window->cursor->ns.object set]; else [[NSCursor arrowCursor] set]; } else [(NSCursor*) _glfw.ns.cursor set]; } // Enter full screen mode // static GLboolean enterFullscreenMode(_GLFWwindow* window) { GLboolean status; status = _glfwSetVideoMode(window->monitor, &window->videoMode); // NOTE: The window is resized despite mode setting failure to make // glfwSetWindowSize more robust [window->ns.object setFrame:[window->monitor->ns.screen frame] display:YES]; return status; } // Leave full screen mode // static void leaveFullscreenMode(_GLFWwindow* window) { _glfwRestoreVideoMode(window->monitor); } // Transforms the specified y-coordinate between the CG display and NS screen // coordinate systems // static float transformY(float y) { const float height = CGDisplayBounds(CGMainDisplayID()).size.height; return height - y; } // Returns the backing rect of the specified window // static NSRect convertRectToBacking(_GLFWwindow* window, NSRect contentRect) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) return [window->ns.view convertRectToBacking:contentRect]; else #endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ return contentRect; } //------------------------------------------------------------------------ // Delegate for window related notifications //------------------------------------------------------------------------ @interface GLFWWindowDelegate : NSObject { _GLFWwindow* window; } - (id)initWithGlfwWindow:(_GLFWwindow *)initWindow; @end @implementation GLFWWindowDelegate - (id)initWithGlfwWindow:(_GLFWwindow *)initWindow { self = [super init]; if (self != nil) window = initWindow; return self; } - (BOOL)windowShouldClose:(id)sender { _glfwInputWindowCloseRequest(window); return NO; } - (void)windowDidResize:(NSNotification *)notification { if (_glfw.focusedWindow == window && window->cursorMode == GLFW_CURSOR_DISABLED) { centerCursor(window); } const NSRect contentRect = [window->ns.view frame]; const NSRect fbRect = convertRectToBacking(window, contentRect); _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); _glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height); _glfwInputWindowDamage(window); } - (void)windowDidMove:(NSNotification *)notification { if (_glfw.focusedWindow == window && window->cursorMode == GLFW_CURSOR_DISABLED) { centerCursor(window); } int x, y; _glfwPlatformGetWindowPos(window, &x, &y); _glfwInputWindowPos(window, x, y); } - (void)windowDidMiniaturize:(NSNotification *)notification { _glfwInputWindowIconify(window, GL_TRUE); } - (void)windowDidDeminiaturize:(NSNotification *)notification { _glfwInputWindowIconify(window, GL_FALSE); } - (void)windowDidBecomeKey:(NSNotification *)notification { if (window->monitor) enterFullscreenMode(window); if (_glfw.focusedWindow == window && window->cursorMode == GLFW_CURSOR_DISABLED) { centerCursor(window); } _glfwInputWindowFocus(window, GL_TRUE); _glfwPlatformApplyCursorMode(window); } - (void)windowDidResignKey:(NSNotification *)notification { if (window->monitor) leaveFullscreenMode(window); _glfwInputWindowFocus(window, GL_FALSE); } @end //------------------------------------------------------------------------ // Delegate for application related notifications //------------------------------------------------------------------------ @interface GLFWApplicationDelegate : NSObject @end @implementation GLFWApplicationDelegate - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { _GLFWwindow* window; for (window = _glfw.windowListHead; window; window = window->next) _glfwInputWindowCloseRequest(window); return NSTerminateCancel; } - (void)applicationDidChangeScreenParameters:(NSNotification *) notification { _glfwInputMonitorChange(); } - (void)applicationDidFinishLaunching:(NSNotification *)notification { [NSApp stop:nil]; _glfwPlatformPostEmptyEvent(); } @end // Translates OS X key modifiers into GLFW ones // static int translateFlags(NSUInteger flags) { int mods = 0; if (flags & NSShiftKeyMask) mods |= GLFW_MOD_SHIFT; if (flags & NSControlKeyMask) mods |= GLFW_MOD_CONTROL; if (flags & NSAlternateKeyMask) mods |= GLFW_MOD_ALT; if (flags & NSCommandKeyMask) mods |= GLFW_MOD_SUPER; return mods; } // Translates a OS X keycode to a GLFW keycode // static int translateKey(unsigned int key) { if (key >= sizeof(_glfw.ns.publicKeys) / sizeof(_glfw.ns.publicKeys[0])) return GLFW_KEY_UNKNOWN; return _glfw.ns.publicKeys[key]; } //------------------------------------------------------------------------ // Content view class for the GLFW window //------------------------------------------------------------------------ @interface GLFWContentView : NSOpenGLView { _GLFWwindow* window; NSTrackingArea* trackingArea; } - (id)initWithGlfwWindow:(_GLFWwindow *)initWindow; @end @implementation GLFWContentView + (void)initialize { if (self == [GLFWContentView class]) { if (_glfw.ns.cursor == nil) { NSImage* data = [[NSImage alloc] initWithSize:NSMakeSize(16, 16)]; _glfw.ns.cursor = [[NSCursor alloc] initWithImage:data hotSpot:NSZeroPoint]; [data release]; } } } - (id)initWithGlfwWindow:(_GLFWwindow *)initWindow { self = [super initWithFrame:NSMakeRect(0, 0, 1, 1) pixelFormat:nil]; if (self != nil) { window = initWindow; trackingArea = nil; [self updateTrackingAreas]; [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]]; } return self; } -(void)dealloc { [trackingArea release]; [super dealloc]; } - (BOOL)isOpaque { return YES; } - (BOOL)canBecomeKeyView { return YES; } - (BOOL)acceptsFirstResponder { return YES; } - (void)cursorUpdate:(NSEvent *)event { updateModeCursor(window); } - (void)mouseDown:(NSEvent *)event { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)mouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)mouseUp:(NSEvent *)event { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)mouseMoved:(NSEvent *)event { if (window->cursorMode == GLFW_CURSOR_DISABLED) { _glfwInputCursorMotion(window, [event deltaX] - window->ns.warpDeltaX, [event deltaY] - window->ns.warpDeltaY); } else { const NSRect contentRect = [window->ns.view frame]; const NSPoint pos = [event locationInWindow]; _glfwInputCursorMotion(window, pos.x, contentRect.size.height - pos.y); } window->ns.warpDeltaX = 0; window->ns.warpDeltaY = 0; } - (void)rightMouseDown:(NSEvent *)event { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)rightMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)rightMouseUp:(NSEvent *)event { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)otherMouseDown:(NSEvent *)event { _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_PRESS, translateFlags([event modifierFlags])); } - (void)otherMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)otherMouseUp:(NSEvent *)event { _glfwInputMouseClick(window, (int) [event buttonNumber], GLFW_RELEASE, translateFlags([event modifierFlags])); } - (void)mouseExited:(NSEvent *)event { _glfwInputCursorEnter(window, GL_FALSE); } - (void)mouseEntered:(NSEvent *)event { _glfwInputCursorEnter(window, GL_TRUE); } - (void)viewDidChangeBackingProperties { const NSRect contentRect = [window->ns.view frame]; const NSRect fbRect = convertRectToBacking(window, contentRect); _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); _glfwInputWindowDamage(window); } - (void)updateTrackingAreas { if (trackingArea != nil) { [self removeTrackingArea:trackingArea]; [trackingArea release]; } const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingEnabledDuringMouseDrag | NSTrackingCursorUpdate | NSTrackingInVisibleRect | NSTrackingAssumeInside; trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; [super updateTrackingAreas]; } - (void)keyDown:(NSEvent *)event { const int key = translateKey([event keyCode]); const int mods = translateFlags([event modifierFlags]); _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); NSString* characters = [event characters]; NSUInteger i, length = [characters length]; const int plain = !(mods & GLFW_MOD_SUPER); for (i = 0; i < length; i++) { const unichar codepoint = [characters characterAtIndex:i]; if ((codepoint & 0xff00) == 0xf700) continue; _glfwInputChar(window, codepoint, mods, plain); } } - (void)flagsChanged:(NSEvent *)event { int action; const unsigned int modifierFlags = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask; const int key = translateKey([event keyCode]); const int mods = translateFlags(modifierFlags); if (modifierFlags == window->ns.modifierFlags) { if (window->keys[key] == GLFW_PRESS) action = GLFW_RELEASE; else action = GLFW_PRESS; } else if (modifierFlags > window->ns.modifierFlags) action = GLFW_PRESS; else action = GLFW_RELEASE; window->ns.modifierFlags = modifierFlags; _glfwInputKey(window, key, [event keyCode], action, mods); } - (void)keyUp:(NSEvent *)event { const int key = translateKey([event keyCode]); const int mods = translateFlags([event modifierFlags]); _glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods); } - (void)scrollWheel:(NSEvent *)event { double deltaX, deltaY; #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) { deltaX = [event scrollingDeltaX]; deltaY = [event scrollingDeltaY]; if ([event hasPreciseScrollingDeltas]) { deltaX *= 0.1; deltaY *= 0.1; } } else #endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ { deltaX = [event deltaX]; deltaY = [event deltaY]; } if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0) _glfwInputScroll(window, deltaX, deltaY); } - (NSDragOperation)draggingEntered:(id )sender { if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric) { [self setNeedsDisplay:YES]; return NSDragOperationGeneric; } return NSDragOperationNone; } - (BOOL)prepareForDragOperation:(id )sender { [self setNeedsDisplay:YES]; return YES; } - (BOOL)performDragOperation:(id )sender { NSPasteboard* pasteboard = [sender draggingPasteboard]; NSArray* files = [pasteboard propertyListForType:NSFilenamesPboardType]; const NSRect contentRect = [window->ns.view frame]; _glfwInputCursorMotion(window, [sender draggingLocation].x, contentRect.size.height - [sender draggingLocation].y); const int count = [files count]; if (count) { NSEnumerator* e = [files objectEnumerator]; char** paths = calloc(count, sizeof(char*)); int i; for (i = 0; i < count; i++) paths[i] = strdup([[e nextObject] UTF8String]); _glfwInputDrop(window, count, (const char**) paths); for (i = 0; i < count; i++) free(paths[i]); free(paths); } return YES; } - (void)concludeDragOperation:(id )sender { [self setNeedsDisplay:YES]; } @end //------------------------------------------------------------------------ // GLFW window class //------------------------------------------------------------------------ @interface GLFWWindow : NSWindow {} @end @implementation GLFWWindow - (BOOL)canBecomeKeyWindow { // Required for NSBorderlessWindowMask windows return YES; } @end //------------------------------------------------------------------------ // GLFW application class //------------------------------------------------------------------------ @interface GLFWApplication : NSApplication @end @implementation GLFWApplication // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost // This works around an AppKit bug, where key up events while holding // down the command key don't get sent to the key window. - (void)sendEvent:(NSEvent *)event { if ([event type] == NSKeyUp && ([event modifierFlags] & NSCommandKeyMask)) [[self keyWindow] sendEvent:event]; else [super sendEvent:event]; } @end #if defined(_GLFW_USE_MENUBAR) // Try to figure out what the calling application is called // static NSString* findAppName(void) { size_t i; NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; // Keys to search for as potential application names NSString* GLFWNameKeys[] = { @"CFBundleDisplayName", @"CFBundleName", @"CFBundleExecutable", }; for (i = 0; i < sizeof(GLFWNameKeys) / sizeof(GLFWNameKeys[0]); i++) { id name = [infoDictionary objectForKey:GLFWNameKeys[i]]; if (name && [name isKindOfClass:[NSString class]] && ![name isEqualToString:@""]) { return name; } } char** progname = _NSGetProgname(); if (progname && *progname) return [NSString stringWithUTF8String:*progname]; // Really shouldn't get here return @"GLFW Application"; } // Set up the menu bar (manually) // This is nasty, nasty stuff -- calls to undocumented semi-private APIs that // could go away at any moment, lots of stuff that really should be // localize(d|able), etc. Loading a nib would save us this horror, but that // doesn't seem like a good thing to require of GLFW users. // static void createMenuBar(void) { NSString* appName = findAppName(); NSMenu* bar = [[NSMenu alloc] init]; [NSApp setMainMenu:bar]; NSMenuItem* appMenuItem = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; NSMenu* appMenu = [[NSMenu alloc] init]; [appMenuItem setSubmenu:appMenu]; [appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName] action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; [appMenu addItem:[NSMenuItem separatorItem]]; NSMenu* servicesMenu = [[NSMenu alloc] init]; [NSApp setServicesMenu:servicesMenu]; [[appMenu addItemWithTitle:@"Services" action:NULL keyEquivalent:@""] setSubmenu:servicesMenu]; [appMenu addItem:[NSMenuItem separatorItem]]; [appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName] action:@selector(hide:) keyEquivalent:@"h"]; [[appMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"] setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask]; [appMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; [appMenu addItem:[NSMenuItem separatorItem]]; [appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName] action:@selector(terminate:) keyEquivalent:@"q"]; NSMenuItem* windowMenuItem = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""]; NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; [NSApp setWindowsMenu:windowMenu]; [windowMenuItem setSubmenu:windowMenu]; [windowMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; [windowMenu addItemWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""]; [windowMenu addItem:[NSMenuItem separatorItem]]; [windowMenu addItemWithTitle:@"Bring All to Front" action:@selector(arrangeInFront:) keyEquivalent:@""]; #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) { // TODO: Make this appear at the bottom of the menu (for consistency) [windowMenu addItem:[NSMenuItem separatorItem]]; [[windowMenu addItemWithTitle:@"Enter Full Screen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"] setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask]; } #endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ // Prior to Snow Leopard, we need to use this oddly-named semi-private API // to get the application menu working properly. SEL setAppleMenuSelector = NSSelectorFromString(@"setAppleMenu:"); [NSApp performSelector:setAppleMenuSelector withObject:appMenu]; } #endif /* _GLFW_USE_MENUBAR */ // Initialize the Cocoa Application Kit // static GLboolean initializeAppKit(void) { if (NSApp) return GL_TRUE; // Implicitly create shared NSApplication instance [GLFWApplication sharedApplication]; // In case we are unbundled, make us a proper UI application [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; #if defined(_GLFW_USE_MENUBAR) // Menu bar setup must go between sharedApplication above and // finishLaunching below, in order to properly emulate the behavior // of NSApplicationMain createMenuBar(); #endif // There can only be one application delegate, but we allocate it the // first time a window is created to keep all window code in this file _glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init]; if (_glfw.ns.delegate == nil) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create application delegate"); return GL_FALSE; } [NSApp setDelegate:_glfw.ns.delegate]; [NSApp run]; return GL_TRUE; } // Create the Cocoa window // static GLboolean createWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig) { window->ns.delegate = [[GLFWWindowDelegate alloc] initWithGlfwWindow:window]; if (window->ns.delegate == nil) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window delegate"); return GL_FALSE; } unsigned int styleMask = 0; if (wndconfig->monitor || !wndconfig->decorated) styleMask = NSBorderlessWindowMask; else { styleMask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask; if (wndconfig->resizable) styleMask |= NSResizableWindowMask; } NSRect contentRect; if (wndconfig->monitor) contentRect = [wndconfig->monitor->ns.screen frame]; else contentRect = NSMakeRect(0, 0, wndconfig->width, wndconfig->height); window->ns.object = [[GLFWWindow alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; if (window->ns.object == nil) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create window"); return GL_FALSE; } #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) { if (wndconfig->resizable) [window->ns.object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; } #endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ if (wndconfig->monitor) { [window->ns.object setLevel:NSMainMenuWindowLevel + 1]; [window->ns.object setHidesOnDeactivate:YES]; } else { [window->ns.object center]; if (wndconfig->floating) [window->ns.object setLevel:NSFloatingWindowLevel]; } [window->ns.object setTitle:[NSString stringWithUTF8String:wndconfig->title]]; [window->ns.object setDelegate:window->ns.delegate]; [window->ns.object setAcceptsMouseMovedEvents:YES]; #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) [window->ns.object setRestorable:NO]; #endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ return GL_TRUE; } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { if (!initializeAppKit()) return GL_FALSE; if (!createWindow(window, wndconfig)) return GL_FALSE; if (!_glfwCreateContext(window, ctxconfig, fbconfig)) return GL_FALSE; window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; #if defined(_GLFW_USE_RETINA) #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6) [window->ns.view setWantsBestResolutionOpenGLSurface:YES]; #endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ #endif /*_GLFW_USE_RETINA*/ [window->ns.object setContentView:window->ns.view]; // NOTE: If you set the pixel format before the context it creates another // context, only to have it destroyed by the next line // We cannot use the view to create the context because that interface // does not support context resource sharing [window->ns.view setOpenGLContext:window->nsgl.context]; [window->ns.view setPixelFormat:window->nsgl.pixelFormat]; [window->nsgl.context setView:window->ns.view]; if (wndconfig->monitor) { _glfwPlatformShowWindow(window); if (!enterFullscreenMode(window)) return GL_FALSE; } return GL_TRUE; } void _glfwPlatformDestroyWindow(_GLFWwindow* window) { [window->ns.object orderOut:nil]; if (window->monitor) leaveFullscreenMode(window); _glfwDestroyContext(window); [window->ns.object setDelegate:nil]; [window->ns.delegate release]; window->ns.delegate = nil; [window->ns.view release]; window->ns.view = nil; [window->ns.object close]; window->ns.object = nil; } void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char *title) { [window->ns.object setTitle:[NSString stringWithUTF8String:title]]; } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { const NSRect contentRect = [window->ns.object contentRectForFrameRect:[window->ns.object frame]]; if (xpos) *xpos = contentRect.origin.x; if (ypos) *ypos = transformY(contentRect.origin.y + contentRect.size.height); } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y) { const NSRect contentRect = [window->ns.view frame]; const NSRect dummyRect = NSMakeRect(x, transformY(y + contentRect.size.height), 0, 0); const NSRect frameRect = [window->ns.object frameRectForContentRect:dummyRect]; [window->ns.object setFrameOrigin:frameRect.origin]; } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) { const NSRect contentRect = [window->ns.view frame]; if (width) *width = contentRect.size.width; if (height) *height = contentRect.size.height; } void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { if (window->monitor) enterFullscreenMode(window); else [window->ns.object setContentSize:NSMakeSize(width, height)]; } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { const NSRect contentRect = [window->ns.view frame]; const NSRect fbRect = convertRectToBacking(window, contentRect); if (width) *width = (int) fbRect.size.width; if (height) *height = (int) fbRect.size.height; } void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) { const NSRect contentRect = [window->ns.view frame]; const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect]; if (left) *left = contentRect.origin.x - frameRect.origin.x; if (top) *top = frameRect.origin.y + frameRect.size.height - contentRect.origin.y - contentRect.size.height; if (right) *right = frameRect.origin.x + frameRect.size.width - contentRect.origin.x - contentRect.size.width; if (bottom) *bottom = contentRect.origin.y - frameRect.origin.y; } void _glfwPlatformIconifyWindow(_GLFWwindow* window) { [window->ns.object miniaturize:nil]; } void _glfwPlatformRestoreWindow(_GLFWwindow* window) { [window->ns.object deminiaturize:nil]; } void _glfwPlatformShowWindow(_GLFWwindow* window) { // Make us the active application // HACK: This has been moved here from initializeAppKit to prevent // applications using only hidden windows from being activated, but // should probably not be done every time any window is shown [NSApp activateIgnoringOtherApps:YES]; [window->ns.object makeKeyAndOrderFront:nil]; } void _glfwPlatformUnhideWindow(_GLFWwindow* window) { [window->ns.object orderFront:nil]; } void _glfwPlatformHideWindow(_GLFWwindow* window) { [window->ns.object orderOut:nil]; } int _glfwPlatformWindowFocused(_GLFWwindow* window) { return [window->ns.object isKeyWindow]; } int _glfwPlatformWindowIconified(_GLFWwindow* window) { return [window->ns.object isMiniaturized]; } int _glfwPlatformWindowVisible(_GLFWwindow* window) { return [window->ns.object isVisible]; } void _glfwPlatformPollEvents(void) { for (;;) { NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; if (event == nil) break; [NSApp sendEvent:event]; } [_glfw.ns.autoreleasePool drain]; _glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init]; } void _glfwPlatformWaitEvents(void) { // I wanted to pass NO to dequeue:, and rely on PollEvents to // dequeue and send. For reasons not at all clear to me, passing // NO to dequeue: causes this method never to return. NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSDefaultRunLoopMode dequeue:YES]; [NSApp sendEvent:event]; _glfwPlatformPollEvents(); } void _glfwPlatformPostEmptyEvent(void) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; NSEvent* event = [NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 timestamp:0 windowNumber:0 context:nil subtype:0 data1:0 data2:0]; [NSApp postEvent:event atStart:YES]; [pool drain]; } void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { const NSRect contentRect = [window->ns.view frame]; const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; if (xpos) *xpos = pos.x; if (ypos) *ypos = contentRect.size.height - pos.y - 1; } void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) { updateModeCursor(window); const NSRect contentRect = [window->ns.view frame]; const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; window->ns.warpDeltaX += x - pos.x; window->ns.warpDeltaY += y - contentRect.size.height + pos.y; if (window->monitor) { CGDisplayMoveCursorToPoint(window->monitor->ns.displayID, CGPointMake(x, y)); } else { const NSPoint localPoint = NSMakePoint(x, contentRect.size.height - y - 1); const NSPoint globalPoint = [window->ns.object convertBaseToScreen:localPoint]; CGWarpMouseCursorPosition(CGPointMake(globalPoint.x, transformY(globalPoint.y))); } } void _glfwPlatformApplyCursorMode(_GLFWwindow* window) { updateModeCursor(window); if (window->cursorMode == GLFW_CURSOR_DISABLED) CGAssociateMouseAndMouseCursorPosition(false); else CGAssociateMouseAndMouseCursorPosition(true); } int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot) { NSImage* native; NSBitmapImageRep* rep; rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:image->width pixelsHigh:image->height bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO colorSpaceName:NSCalibratedRGBColorSpace bitmapFormat:NSAlphaNonpremultipliedBitmapFormat bytesPerRow:image->width * 4 bitsPerPixel:32]; if (rep == nil) return GL_FALSE; memcpy([rep bitmapData], image->pixels, image->width * image->height * 4); native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)]; [native addRepresentation: rep]; cursor->ns.object = [[NSCursor alloc] initWithImage:native hotSpot:NSMakePoint(xhot, yhot)]; [native release]; [rep release]; if (cursor->ns.object == nil) return GL_FALSE; return GL_TRUE; } int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape) { cursor->ns.object = getStandardCursor(shape); if (!cursor->ns.object) { _glfwInputError(GLFW_INVALID_ENUM, "Cocoa: Invalid standard cursor"); return GL_FALSE; } [cursor->ns.object retain]; return GL_TRUE; } void _glfwPlatformDestroyCursor(_GLFWcursor* cursor) { if (cursor->ns.object) [(NSCursor*) cursor->ns.object release]; } void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) { const NSPoint pos = [window->ns.object mouseLocationOutsideOfEventStream]; if (window->cursorMode == GLFW_CURSOR_NORMAL && [window->ns.view mouse:pos inRect:[window->ns.view frame]]) { if (cursor) [(NSCursor*) cursor->ns.object set]; else [[NSCursor arrowCursor] set]; } } void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string) { NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil]; NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; [pasteboard declareTypes:types owner:nil]; [pasteboard setString:[NSString stringWithUTF8String:string] forType:NSStringPboardType]; } const char* _glfwPlatformGetClipboardString(_GLFWwindow* window) { NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; if (![[pasteboard types] containsObject:NSStringPboardType]) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, NULL); return NULL; } NSString* object = [pasteboard stringForType:NSStringPboardType]; if (!object) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve object from pasteboard"); return NULL; } free(_glfw.ns.clipboardString); _glfw.ns.clipboardString = strdup([object UTF8String]); return _glfw.ns.clipboardString; } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); return window->ns.object; }