| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build darwin |
| // +build 386 amd64 |
| // +build !ios |
| |
| #include "_cgo_export.h" |
| #include <pthread.h> |
| #include <stdio.h> |
| |
| #import <Cocoa/Cocoa.h> |
| #import <Foundation/Foundation.h> |
| #import <OpenGL/gl3.h> |
| |
| #define IS_MAC_SIERRA_OR_LATER (NSAppKitVersionNumber - NSAppKitVersionNumber10_11) |
| |
| // The variables did not exist on older OS X releases, |
| // we use the old variables deprecated on macOS to define them. |
| #if !IS_MAC_SIERRA_OR_LATER |
| enum |
| { |
| NSEventTypeScrollWheel = NSScrollWheel, |
| NSEventTypeKeyDown = NSKeyDown |
| }; |
| enum { |
| NSWindowStyleMaskTitled = NSTitledWindowMask, |
| NSWindowStyleMaskResizable = NSResizableWindowMask, |
| NSWindowStyleMaskMiniaturizable = NSMiniaturizableWindowMask, |
| NSWindowStyleMaskClosable = NSClosableWindowMask |
| }; |
| #endif |
| |
| void makeCurrentContext(uintptr_t context) { |
| NSOpenGLContext* ctx = (NSOpenGLContext*)context; |
| [ctx makeCurrentContext]; |
| } |
| |
| void flushContext(uintptr_t context) { |
| NSOpenGLContext* ctx = (NSOpenGLContext*)context; |
| [ctx flushBuffer]; |
| } |
| |
| uint64 threadID() { |
| uint64 id; |
| if (pthread_threadid_np(pthread_self(), &id)) { |
| abort(); |
| } |
| return id; |
| } |
| |
| @interface ScreenGLView : NSOpenGLView<NSWindowDelegate> |
| { |
| } |
| @end |
| |
| @implementation ScreenGLView |
| - (void)prepareOpenGL { |
| [self setWantsBestResolutionOpenGLSurface:YES]; |
| GLint swapInt = 1; |
| NSOpenGLContext *ctx = [self openGLContext]; |
| [ctx setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; |
| |
| // Using attribute arrays in OpenGL 3.3 requires the use of a VBA. |
| // But VBAs don't exist in ES 2. So we bind a default one. |
| GLuint vba; |
| glGenVertexArrays(1, &vba); |
| glBindVertexArray(vba); |
| |
| preparedOpenGL((GoUintptr)self, (GoUintptr)ctx, (GoUintptr)vba); |
| } |
| |
| - (void)callSetGeom { |
| // Calculate screen PPI. |
| // |
| // Note that the backingScaleFactor converts from logical |
| // pixels to actual pixels, but both of these units vary |
| // independently from real world size. E.g. |
| // |
| // 13" Retina Macbook Pro, 2560x1600, 227ppi, backingScaleFactor=2, scale=3.15 |
| // 15" Retina Macbook Pro, 2880x1800, 220ppi, backingScaleFactor=2, scale=3.06 |
| // 27" iMac, 2560x1440, 109ppi, backingScaleFactor=1, scale=1.51 |
| // 27" Retina iMac, 5120x2880, 218ppi, backingScaleFactor=2, scale=3.03 |
| NSScreen *screen = self.window.screen; |
| double screenPixW = [screen frame].size.width * [screen backingScaleFactor]; |
| |
| CGDirectDisplayID display = (CGDirectDisplayID)[[[screen deviceDescription] valueForKey:@"NSScreenNumber"] intValue]; |
| CGSize screenSizeMM = CGDisplayScreenSize(display); // in millimeters |
| float ppi = 25.4 * screenPixW / screenSizeMM.width; |
| float pixelsPerPt = ppi/72.0; |
| |
| // The width and height reported to the geom package are the |
| // bounds of the OpenGL view. Several steps are necessary. |
| // First, [self bounds] gives us the number of logical pixels |
| // in the view. Multiplying this by the backingScaleFactor |
| // gives us the number of actual pixels. |
| NSRect r = [self bounds]; |
| int w = r.size.width * [screen backingScaleFactor]; |
| int h = r.size.height * [screen backingScaleFactor]; |
| |
| setGeom((GoUintptr)self, pixelsPerPt, w, h); |
| } |
| |
| - (void)reshape { |
| [super reshape]; |
| [self callSetGeom]; |
| } |
| |
| - (void)drawRect:(NSRect)theRect { |
| // Called during resize. Do an extra draw if we are visible. |
| // This gets rid of flicker when resizing. |
| drawgl((GoUintptr)self); |
| } |
| |
| - (void)mouseEventNS:(NSEvent *)theEvent { |
| NSPoint p = [theEvent locationInWindow]; |
| double h = self.frame.size.height; |
| |
| // Both h and p are measured in Cocoa pixels, which are a fraction of |
| // physical pixels, so we multiply by backingScaleFactor. |
| double scale = [self.window.screen backingScaleFactor]; |
| |
| double x = p.x * scale; |
| double y = (h - p.y) * scale - 1; // flip origin from bottom-left to top-left. |
| |
| double dx, dy; |
| if (theEvent.type == NSEventTypeScrollWheel) { |
| dx = theEvent.scrollingDeltaX; |
| dy = theEvent.scrollingDeltaY; |
| } |
| |
| mouseEvent((GoUintptr)self, x, y, dx, dy, theEvent.type, theEvent.buttonNumber, theEvent.modifierFlags); |
| } |
| |
| - (void)mouseMoved:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)mouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)mouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)mouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)rightMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)rightMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)rightMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)otherMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)otherMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)otherMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| - (void)scrollWheel:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } |
| |
| // raw modifier key presses |
| - (void)flagsChanged:(NSEvent *)theEvent { |
| flagEvent((GoUintptr)self, theEvent.modifierFlags); |
| } |
| |
| // overrides special handling of escape and tab |
| - (BOOL)performKeyEquivalent:(NSEvent *)theEvent { |
| [self key:theEvent]; |
| return YES; |
| } |
| |
| - (void)keyDown:(NSEvent *)theEvent { [self key:theEvent]; } |
| - (void)keyUp:(NSEvent *)theEvent { [self key:theEvent]; } |
| |
| - (void)key:(NSEvent *)theEvent { |
| NSRange range = [theEvent.characters rangeOfComposedCharacterSequenceAtIndex:0]; |
| |
| uint8_t buf[4] = {0, 0, 0, 0}; |
| if (![theEvent.characters getBytes:buf |
| maxLength:4 |
| usedLength:nil |
| encoding:NSUTF32LittleEndianStringEncoding |
| options:NSStringEncodingConversionAllowLossy |
| range:range |
| remainingRange:nil]) { |
| NSLog(@"failed to read key event %@", theEvent); |
| return; |
| } |
| |
| uint32_t rune = (uint32_t)buf[0]<<0 | (uint32_t)buf[1]<<8 | (uint32_t)buf[2]<<16 | (uint32_t)buf[3]<<24; |
| |
| uint8_t direction; |
| if ([theEvent isARepeat]) { |
| direction = 0; |
| } else if (theEvent.type == NSEventTypeKeyDown) { |
| direction = 1; |
| } else { |
| direction = 2; |
| } |
| keyEvent((GoUintptr)self, (int32_t)rune, direction, theEvent.keyCode, theEvent.modifierFlags); |
| } |
| |
| - (void)windowDidChangeScreenProfile:(NSNotification *)notification { |
| [self callSetGeom]; |
| } |
| |
| // TODO: catch windowDidMiniaturize? |
| |
| - (void)windowDidExpose:(NSNotification *)notification { |
| lifecycleVisible((GoUintptr)self, true); |
| } |
| |
| - (void)windowDidBecomeKey:(NSNotification *)notification { |
| lifecycleFocused((GoUintptr)self, true); |
| } |
| |
| - (void)windowDidResignKey:(NSNotification *)notification { |
| lifecycleFocused((GoUintptr)self, false); |
| if ([NSApp isHidden]) { |
| lifecycleVisible((GoUintptr)self, false); |
| } |
| } |
| |
| - (void)windowWillClose:(NSNotification *)notification { |
| // TODO: is this right? Closing a window via the top-left red button |
| // seems to return early without ever calling windowClosing. |
| if (self.window.nextResponder == NULL) { |
| return; // already called close |
| } |
| |
| windowClosing((GoUintptr)self); |
| [self.window.nextResponder release]; |
| self.window.nextResponder = NULL; |
| } |
| @end |
| |
| @interface AppDelegate : NSObject<NSApplicationDelegate> |
| { |
| } |
| @end |
| |
| @implementation AppDelegate |
| - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { |
| driverStarted(); |
| [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; |
| } |
| |
| - (void)applicationWillTerminate:(NSNotification *)aNotification { |
| lifecycleDeadAll(); |
| } |
| |
| - (void)applicationWillHide:(NSNotification *)aNotification { |
| lifecycleHideAll(); |
| } |
| @end |
| |
| uintptr_t doNewWindow(int width, int height, char* title) { |
| NSScreen *screen = [NSScreen mainScreen]; |
| double w = (double)width / [screen backingScaleFactor]; |
| double h = (double)height / [screen backingScaleFactor]; |
| __block ScreenGLView* view = NULL; |
| |
| dispatch_sync(dispatch_get_main_queue(), ^{ |
| id menuBar = [NSMenu new]; |
| id menuItem = [NSMenuItem new]; |
| [menuBar addItem:menuItem]; |
| [NSApp setMainMenu:menuBar]; |
| |
| id menu = [NSMenu new]; |
| NSString* name = [[NSString alloc] initWithUTF8String:title]; |
| |
| id hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide" |
| action:@selector(hide:) keyEquivalent:@"h"]; |
| [menu addItem:hideMenuItem]; |
| |
| id quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit" |
| action:@selector(terminate:) keyEquivalent:@"q"]; |
| [menu addItem:quitMenuItem]; |
| [menuItem setSubmenu:menu]; |
| |
| NSRect rect = NSMakeRect(0, 0, w, h); |
| |
| NSWindow* window = [[NSWindow alloc] initWithContentRect:rect |
| styleMask:NSWindowStyleMaskTitled |
| backing:NSBackingStoreBuffered |
| defer:NO]; |
| window.styleMask |= NSWindowStyleMaskResizable; |
| window.styleMask |= NSWindowStyleMaskMiniaturizable; |
| window.styleMask |= NSWindowStyleMaskClosable; |
| window.title = name; |
| window.displaysWhenScreenProfileChanges = YES; |
| [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; |
| [window setAcceptsMouseMovedEvents:YES]; |
| |
| NSOpenGLPixelFormatAttribute attr[] = { |
| NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, |
| NSOpenGLPFAColorSize, 24, |
| NSOpenGLPFAAlphaSize, 8, |
| NSOpenGLPFADepthSize, 16, |
| NSOpenGLPFAAccelerated, |
| NSOpenGLPFADoubleBuffer, |
| NSOpenGLPFAAllowOfflineRenderers, |
| 0 |
| }; |
| id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; |
| view = [[ScreenGLView alloc] initWithFrame:rect pixelFormat:pixFormat]; |
| [window setContentView:view]; |
| [window setDelegate:view]; |
| [window makeFirstResponder:view]; |
| }); |
| |
| return (uintptr_t)view; |
| } |
| |
| void doShowWindow(uintptr_t viewID) { |
| ScreenGLView* view = (ScreenGLView*)viewID; |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| [view.window makeKeyAndOrderFront:view.window]; |
| }); |
| } |
| |
| void doCloseWindow(uintptr_t viewID) { |
| ScreenGLView* view = (ScreenGLView*)viewID; |
| dispatch_sync(dispatch_get_main_queue(), ^{ |
| [view.window performClose:view]; |
| }); |
| } |
| |
| void startDriver() { |
| [NSAutoreleasePool new]; |
| [NSApplication sharedApplication]; |
| [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; |
| AppDelegate* delegate = [[AppDelegate alloc] init]; |
| [NSApp setDelegate:delegate]; |
| [NSApp run]; |
| } |
| |
| void stopDriver() { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| [NSApp terminate:nil]; |
| }); |
| } |