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  }