// 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 #include #import #import #import // The variables did not exist on older OS X releases, // we use the old variables deprecated on macOS to define them. #if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200 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 { } @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 { } @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, 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]; }); }