cocoa.m (10306B)
1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build darwin 6 // +build 386 amd64 7 // +build !ios 8 9 #include "_cgo_export.h" 10 #include <pthread.h> 11 #include <stdio.h> 12 13 #import <Cocoa/Cocoa.h> 14 #import <Foundation/Foundation.h> 15 #import <OpenGL/gl3.h> 16 17 // The variables did not exist on older OS X releases, 18 // we use the old variables deprecated on macOS to define them. 19 #if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200 20 enum 21 { 22 NSEventTypeScrollWheel = NSScrollWheel, 23 NSEventTypeKeyDown = NSKeyDown 24 }; 25 enum 26 { 27 NSWindowStyleMaskTitled = NSTitledWindowMask, 28 NSWindowStyleMaskResizable = NSResizableWindowMask, 29 NSWindowStyleMaskMiniaturizable = NSMiniaturizableWindowMask, 30 NSWindowStyleMaskClosable = NSClosableWindowMask 31 }; 32 #endif 33 34 void makeCurrentContext(uintptr_t context) { 35 NSOpenGLContext* ctx = (NSOpenGLContext*)context; 36 [ctx makeCurrentContext]; 37 [ctx update]; 38 } 39 40 void flushContext(uintptr_t context) { 41 NSOpenGLContext* ctx = (NSOpenGLContext*)context; 42 [ctx flushBuffer]; 43 } 44 45 uint64 threadID() { 46 uint64 id; 47 if (pthread_threadid_np(pthread_self(), &id)) { 48 abort(); 49 } 50 return id; 51 } 52 53 @interface ScreenGLView : NSOpenGLView<NSWindowDelegate> 54 { 55 } 56 @end 57 58 @implementation ScreenGLView 59 - (void)prepareOpenGL { 60 [super prepareOpenGL]; 61 62 [self setWantsBestResolutionOpenGLSurface:YES]; 63 GLint swapInt = 1; 64 NSOpenGLContext *ctx = [self openGLContext]; 65 66 #pragma clang diagnostic push 67 #pragma clang diagnostic ignored "-Wdeprecated-declarations" 68 [ctx setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; 69 #pragma clang diagnostic pop 70 71 // Using attribute arrays in OpenGL 3.3 requires the use of a VBA. 72 // But VBAs don't exist in ES 2. So we bind a default one. 73 GLuint vba; 74 glGenVertexArrays(1, &vba); 75 glBindVertexArray(vba); 76 77 preparedOpenGL((GoUintptr)self, (GoUintptr)ctx, (GoUintptr)vba); 78 } 79 80 - (void)callSetGeom { 81 // Calculate screen PPI. 82 // 83 // Note that the backingScaleFactor converts from logical 84 // pixels to actual pixels, but both of these units vary 85 // independently from real world size. E.g. 86 // 87 // 13" Retina Macbook Pro, 2560x1600, 227ppi, backingScaleFactor=2, scale=3.15 88 // 15" Retina Macbook Pro, 2880x1800, 220ppi, backingScaleFactor=2, scale=3.06 89 // 27" iMac, 2560x1440, 109ppi, backingScaleFactor=1, scale=1.51 90 // 27" Retina iMac, 5120x2880, 218ppi, backingScaleFactor=2, scale=3.03 91 NSScreen *screen = self.window.screen; 92 double screenPixW = [screen frame].size.width * [screen backingScaleFactor]; 93 94 CGDirectDisplayID display = (CGDirectDisplayID)[[[screen deviceDescription] valueForKey:@"NSScreenNumber"] intValue]; 95 CGSize screenSizeMM = CGDisplayScreenSize(display); // in millimeters 96 float ppi = 25.4 * screenPixW / screenSizeMM.width; 97 float pixelsPerPt = ppi/72.0; 98 99 // The width and height reported to the geom package are the 100 // bounds of the OpenGL view. Several steps are necessary. 101 // First, [self bounds] gives us the number of logical pixels 102 // in the view. Multiplying this by the backingScaleFactor 103 // gives us the number of actual pixels. 104 NSRect r = [self bounds]; 105 int w = r.size.width * [screen backingScaleFactor]; 106 int h = r.size.height * [screen backingScaleFactor]; 107 108 setGeom((GoUintptr)self, pixelsPerPt, w, h); 109 } 110 111 - (void)reshape { 112 [super reshape]; 113 [self callSetGeom]; 114 } 115 116 - (void)drawRect:(NSRect)theRect { 117 // Called during resize. Do an extra draw if we are visible. 118 // This gets rid of flicker when resizing. 119 drawgl((GoUintptr)self); 120 } 121 122 - (void)mouseEventNS:(NSEvent *)theEvent { 123 NSPoint p = [theEvent locationInWindow]; 124 double h = self.frame.size.height; 125 126 // Both h and p are measured in Cocoa pixels, which are a fraction of 127 // physical pixels, so we multiply by backingScaleFactor. 128 double scale = [self.window.screen backingScaleFactor]; 129 130 double x = p.x * scale; 131 double y = (h - p.y) * scale - 1; // flip origin from bottom-left to top-left. 132 133 double dx, dy; 134 if (theEvent.type == NSEventTypeScrollWheel) { 135 dx = theEvent.scrollingDeltaX; 136 dy = theEvent.scrollingDeltaY; 137 } 138 139 mouseEvent((GoUintptr)self, x, y, dx, dy, theEvent.type, theEvent.buttonNumber, theEvent.modifierFlags); 140 } 141 142 - (void)mouseMoved:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 143 - (void)mouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 144 - (void)mouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 145 - (void)mouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 146 - (void)rightMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 147 - (void)rightMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 148 - (void)rightMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 149 - (void)otherMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 150 - (void)otherMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 151 - (void)otherMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 152 - (void)scrollWheel:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } 153 154 // raw modifier key presses 155 - (void)flagsChanged:(NSEvent *)theEvent { 156 flagEvent((GoUintptr)self, theEvent.modifierFlags); 157 } 158 159 // overrides special handling of escape and tab 160 - (BOOL)performKeyEquivalent:(NSEvent *)theEvent { 161 [self key:theEvent]; 162 return YES; 163 } 164 165 - (void)keyDown:(NSEvent *)theEvent { [self key:theEvent]; } 166 - (void)keyUp:(NSEvent *)theEvent { [self key:theEvent]; } 167 168 - (void)key:(NSEvent *)theEvent { 169 NSRange range = [theEvent.characters rangeOfComposedCharacterSequenceAtIndex:0]; 170 171 uint8_t buf[4] = {0, 0, 0, 0}; 172 if (![theEvent.characters getBytes:buf 173 maxLength:4 174 usedLength:nil 175 encoding:NSUTF32LittleEndianStringEncoding 176 options:NSStringEncodingConversionAllowLossy 177 range:range 178 remainingRange:nil]) { 179 NSLog(@"failed to read key event %@", theEvent); 180 return; 181 } 182 183 uint32_t rune = (uint32_t)buf[0]<<0 | (uint32_t)buf[1]<<8 | (uint32_t)buf[2]<<16 | (uint32_t)buf[3]<<24; 184 185 uint8_t direction; 186 if ([theEvent isARepeat]) { 187 direction = 0; 188 } else if (theEvent.type == NSEventTypeKeyDown) { 189 direction = 1; 190 } else { 191 direction = 2; 192 } 193 keyEvent((GoUintptr)self, (int32_t)rune, direction, theEvent.keyCode, theEvent.modifierFlags); 194 } 195 196 - (void)windowDidChangeScreenProfile:(NSNotification *)notification { 197 [self callSetGeom]; 198 } 199 200 // TODO: catch windowDidMiniaturize? 201 202 - (void)windowDidExpose:(NSNotification *)notification { 203 lifecycleVisible((GoUintptr)self, true); 204 } 205 206 - (void)windowDidBecomeKey:(NSNotification *)notification { 207 lifecycleFocused((GoUintptr)self, true); 208 } 209 210 - (void)windowDidResignKey:(NSNotification *)notification { 211 lifecycleFocused((GoUintptr)self, false); 212 if ([NSApp isHidden]) { 213 lifecycleVisible((GoUintptr)self, false); 214 } 215 } 216 217 - (void)windowWillClose:(NSNotification *)notification { 218 // TODO: is this right? Closing a window via the top-left red button 219 // seems to return early without ever calling windowClosing. 220 if (self.window.nextResponder == NULL) { 221 return; // already called close 222 } 223 224 windowClosing((GoUintptr)self); 225 [self.window.nextResponder release]; 226 self.window.nextResponder = NULL; 227 } 228 @end 229 230 @interface AppDelegate : NSObject<NSApplicationDelegate> 231 { 232 } 233 @end 234 235 @implementation AppDelegate 236 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 237 driverStarted(); 238 [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; 239 } 240 241 - (void)applicationWillTerminate:(NSNotification *)aNotification { 242 lifecycleDeadAll(); 243 } 244 245 - (void)applicationWillHide:(NSNotification *)aNotification { 246 lifecycleHideAll(); 247 } 248 @end 249 250 uintptr_t doNewWindow(int width, int height, char* title) { 251 NSScreen *screen = [NSScreen mainScreen]; 252 double w = (double)width / [screen backingScaleFactor]; 253 double h = (double)height / [screen backingScaleFactor]; 254 __block ScreenGLView* view = NULL; 255 256 dispatch_sync(dispatch_get_main_queue(), ^{ 257 id menuBar = [NSMenu new]; 258 id menuItem = [NSMenuItem new]; 259 [menuBar addItem:menuItem]; 260 [NSApp setMainMenu:menuBar]; 261 262 id menu = [NSMenu new]; 263 NSString* name = [[NSString alloc] initWithUTF8String:title]; 264 265 id hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide" 266 action:@selector(hide:) keyEquivalent:@"h"]; 267 [menu addItem:hideMenuItem]; 268 269 id quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit" 270 action:@selector(terminate:) keyEquivalent:@"q"]; 271 [menu addItem:quitMenuItem]; 272 [menuItem setSubmenu:menu]; 273 274 NSRect rect = NSMakeRect(0, 0, w, h); 275 276 NSWindow* window = [[NSWindow alloc] initWithContentRect:rect 277 styleMask:NSWindowStyleMaskTitled 278 backing:NSBackingStoreBuffered 279 defer:NO]; 280 window.styleMask |= NSWindowStyleMaskResizable; 281 window.styleMask |= NSWindowStyleMaskMiniaturizable; 282 window.styleMask |= NSWindowStyleMaskClosable; 283 window.title = name; 284 window.displaysWhenScreenProfileChanges = YES; 285 [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; 286 [window setAcceptsMouseMovedEvents:YES]; 287 288 NSOpenGLPixelFormatAttribute attr[] = { 289 NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, 290 NSOpenGLPFAColorSize, 24, 291 NSOpenGLPFAAlphaSize, 8, 292 NSOpenGLPFADepthSize, 16, 293 NSOpenGLPFADoubleBuffer, 294 NSOpenGLPFAAllowOfflineRenderers, 295 0 296 }; 297 id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; 298 view = [[ScreenGLView alloc] initWithFrame:rect pixelFormat:pixFormat]; 299 [window setContentView:view]; 300 [window setDelegate:view]; 301 [window makeFirstResponder:view]; 302 }); 303 304 return (uintptr_t)view; 305 } 306 307 void doShowWindow(uintptr_t viewID) { 308 ScreenGLView* view = (ScreenGLView*)viewID; 309 dispatch_async(dispatch_get_main_queue(), ^{ 310 [view.window makeKeyAndOrderFront:view.window]; 311 }); 312 } 313 314 void doCloseWindow(uintptr_t viewID) { 315 ScreenGLView* view = (ScreenGLView*)viewID; 316 dispatch_sync(dispatch_get_main_queue(), ^{ 317 [view.window performClose:view]; 318 }); 319 } 320 321 void startDriver() { 322 [NSAutoreleasePool new]; 323 [NSApplication sharedApplication]; 324 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 325 AppDelegate* delegate = [[AppDelegate alloc] init]; 326 [NSApp setDelegate:delegate]; 327 [NSApp run]; 328 } 329 330 void stopDriver() { 331 dispatch_async(dispatch_get_main_queue(), ^{ 332 [NSApp terminate:nil]; 333 }); 334 }