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