zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

cocoa.go (16052B)


      1 // Copyright 2015 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 package gldriver
     10 
     11 /*
     12 #cgo CFLAGS: -x objective-c -DGL_SILENCE_DEPRECATION
     13 #cgo LDFLAGS: -framework Cocoa -framework OpenGL
     14 #include <OpenGL/gl3.h>
     15 #import <Carbon/Carbon.h> // for HIToolbox/Events.h
     16 #import <Cocoa/Cocoa.h>
     17 #include <pthread.h>
     18 #include <stdint.h>
     19 #include <stdlib.h>
     20 
     21 void startDriver();
     22 void stopDriver();
     23 void makeCurrentContext(uintptr_t ctx);
     24 void flushContext(uintptr_t ctx);
     25 uintptr_t doNewWindow(int width, int height, char* title);
     26 void doShowWindow(uintptr_t id);
     27 void doCloseWindow(uintptr_t id);
     28 uint64_t threadID();
     29 */
     30 import "C"
     31 
     32 import (
     33 	"errors"
     34 	"fmt"
     35 	"log"
     36 	"runtime"
     37 	"unsafe"
     38 
     39 	"golang.org/x/exp/shiny/driver/internal/lifecycler"
     40 	"golang.org/x/exp/shiny/screen"
     41 	"golang.org/x/mobile/event/key"
     42 	"golang.org/x/mobile/event/mouse"
     43 	"golang.org/x/mobile/event/paint"
     44 	"golang.org/x/mobile/event/size"
     45 	"golang.org/x/mobile/geom"
     46 	"golang.org/x/mobile/gl"
     47 )
     48 
     49 const useLifecycler = true
     50 
     51 // TODO: change this to true, after manual testing on OS X.
     52 const handleSizeEventsAtChannelReceive = false
     53 
     54 var initThreadID C.uint64_t
     55 
     56 func init() {
     57 	// Lock the goroutine responsible for initialization to an OS thread.
     58 	// This means the goroutine running main (and calling startDriver below)
     59 	// is locked to the OS thread that started the program. This is
     60 	// necessary for the correct delivery of Cocoa events to the process.
     61 	//
     62 	// A discussion on this topic:
     63 	// https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ
     64 	runtime.LockOSThread()
     65 	initThreadID = C.threadID()
     66 }
     67 
     68 func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
     69 	width, height := optsSize(opts)
     70 
     71 	title := C.CString(opts.GetTitle())
     72 	defer C.free(unsafe.Pointer(title))
     73 
     74 	return uintptr(C.doNewWindow(C.int(width), C.int(height), title)), nil
     75 }
     76 
     77 func initWindow(w *windowImpl) {
     78 	w.glctx, w.worker = gl.NewContext()
     79 }
     80 
     81 func showWindow(w *windowImpl) {
     82 	C.doShowWindow(C.uintptr_t(w.id))
     83 }
     84 
     85 //export preparedOpenGL
     86 func preparedOpenGL(id, ctx, vba uintptr) {
     87 	theScreen.mu.Lock()
     88 	w := theScreen.windows[id]
     89 	theScreen.mu.Unlock()
     90 
     91 	w.ctx = ctx
     92 	go drawLoop(w, vba)
     93 }
     94 
     95 func closeWindow(id uintptr) {
     96 	C.doCloseWindow(C.uintptr_t(id))
     97 }
     98 
     99 var mainCallback func(screen.Screen)
    100 
    101 func main(f func(screen.Screen)) error {
    102 	if tid := C.threadID(); tid != initThreadID {
    103 		log.Fatalf("gldriver.Main called on thread %d, but gldriver.init ran on %d", tid, initThreadID)
    104 	}
    105 
    106 	mainCallback = f
    107 	C.startDriver()
    108 	return nil
    109 }
    110 
    111 //export driverStarted
    112 func driverStarted() {
    113 	go func() {
    114 		mainCallback(theScreen)
    115 		C.stopDriver()
    116 	}()
    117 }
    118 
    119 //export drawgl
    120 func drawgl(id uintptr) {
    121 	theScreen.mu.Lock()
    122 	w := theScreen.windows[id]
    123 	theScreen.mu.Unlock()
    124 
    125 	if w == nil {
    126 		return // closing window
    127 	}
    128 
    129 	// TODO: is this necessary?
    130 	w.lifecycler.SetVisible(true)
    131 	w.lifecycler.SendEvent(w, w.glctx)
    132 
    133 	w.Send(paint.Event{External: true})
    134 	<-w.drawDone
    135 }
    136 
    137 // drawLoop is the primary drawing loop.
    138 //
    139 // After Cocoa has created an NSWindow and called prepareOpenGL,
    140 // it starts drawLoop on a locked goroutine to handle OpenGL calls.
    141 //
    142 // The screen is drawn every time a paint.Event is received, which can be
    143 // triggered either by the user or by Cocoa via drawgl (for example, when
    144 // the window is resized).
    145 func drawLoop(w *windowImpl, vba uintptr) {
    146 	runtime.LockOSThread()
    147 	C.makeCurrentContext(C.uintptr_t(w.ctx.(uintptr)))
    148 
    149 	// Starting in OS X 10.11 (El Capitan), the vertex array is
    150 	// occasionally getting unbound when the context changes threads.
    151 	//
    152 	// Avoid this by binding it again.
    153 	C.glBindVertexArray(C.GLuint(vba))
    154 	if errno := C.glGetError(); errno != 0 {
    155 		panic(fmt.Sprintf("gldriver: glBindVertexArray failed: %d", errno))
    156 	}
    157 
    158 	workAvailable := w.worker.WorkAvailable()
    159 
    160 	// TODO(crawshaw): exit this goroutine on Release.
    161 	for {
    162 		select {
    163 		case <-workAvailable:
    164 			w.worker.DoWork()
    165 		case <-w.publish:
    166 		loop:
    167 			for {
    168 				select {
    169 				case <-workAvailable:
    170 					w.worker.DoWork()
    171 				default:
    172 					break loop
    173 				}
    174 			}
    175 			C.flushContext(C.uintptr_t(w.ctx.(uintptr)))
    176 			w.publishDone <- screen.PublishResult{}
    177 		}
    178 	}
    179 }
    180 
    181 //export setGeom
    182 func setGeom(id uintptr, ppp float32, widthPx, heightPx int) {
    183 	theScreen.mu.Lock()
    184 	w := theScreen.windows[id]
    185 	theScreen.mu.Unlock()
    186 
    187 	if w == nil {
    188 		return // closing window
    189 	}
    190 
    191 	sz := size.Event{
    192 		WidthPx:     widthPx,
    193 		HeightPx:    heightPx,
    194 		WidthPt:     geom.Pt(float32(widthPx) / ppp),
    195 		HeightPt:    geom.Pt(float32(heightPx) / ppp),
    196 		PixelsPerPt: ppp,
    197 	}
    198 
    199 	if !handleSizeEventsAtChannelReceive {
    200 		w.szMu.Lock()
    201 		w.sz = sz
    202 		w.szMu.Unlock()
    203 	}
    204 
    205 	w.Send(sz)
    206 }
    207 
    208 //export windowClosing
    209 func windowClosing(id uintptr) {
    210 	sendLifecycle(id, (*lifecycler.State).SetDead, true)
    211 }
    212 
    213 func sendWindowEvent(id uintptr, e interface{}) {
    214 	theScreen.mu.Lock()
    215 	w := theScreen.windows[id]
    216 	theScreen.mu.Unlock()
    217 
    218 	if w == nil {
    219 		return // closing window
    220 	}
    221 	w.Send(e)
    222 }
    223 
    224 var mods = [...]struct {
    225 	flags uint32
    226 	code  uint16
    227 	mod   key.Modifiers
    228 }{
    229 	// Left and right variants of modifier keys have their own masks,
    230 	// but they are not documented. These were determined empirically.
    231 	{1<<17 | 0x102, C.kVK_Shift, key.ModShift},
    232 	{1<<17 | 0x104, C.kVK_RightShift, key.ModShift},
    233 	{1<<18 | 0x101, C.kVK_Control, key.ModControl},
    234 	{33<<13 | 0x100, C.kVK_RightControl, key.ModControl},
    235 	{1<<19 | 0x120, C.kVK_Option, key.ModAlt},
    236 	{1<<19 | 0x140, C.kVK_RightOption, key.ModAlt},
    237 	{1<<20 | 0x108, C.kVK_Command, key.ModMeta},
    238 	{1<<20 | 0x110, 0x36 /* kVK_RightCommand */, key.ModMeta},
    239 }
    240 
    241 func cocoaMods(flags uint32) (m key.Modifiers) {
    242 	for _, mod := range mods {
    243 		if flags&mod.flags == mod.flags {
    244 			m |= mod.mod
    245 		}
    246 	}
    247 	return m
    248 }
    249 
    250 func cocoaMouseDir(ty int32) mouse.Direction {
    251 	switch ty {
    252 	case C.NSLeftMouseDown, C.NSRightMouseDown, C.NSOtherMouseDown:
    253 		return mouse.DirPress
    254 	case C.NSLeftMouseUp, C.NSRightMouseUp, C.NSOtherMouseUp:
    255 		return mouse.DirRelease
    256 	default: // dragged
    257 		return mouse.DirNone
    258 	}
    259 }
    260 
    261 func cocoaMouseButton(button int32) mouse.Button {
    262 	switch button {
    263 	case 0:
    264 		return mouse.ButtonLeft
    265 	case 1:
    266 		return mouse.ButtonRight
    267 	case 2:
    268 		return mouse.ButtonMiddle
    269 	default:
    270 		return mouse.ButtonNone
    271 	}
    272 }
    273 
    274 //export mouseEvent
    275 func mouseEvent(id uintptr, x, y, dx, dy float32, ty, button int32, flags uint32) {
    276 	cmButton := mouse.ButtonNone
    277 	switch ty {
    278 	default:
    279 		cmButton = cocoaMouseButton(button)
    280 	case C.NSMouseMoved, C.NSLeftMouseDragged, C.NSRightMouseDragged, C.NSOtherMouseDragged:
    281 		// No-op.
    282 	case C.NSScrollWheel:
    283 		// Note that the direction of scrolling is inverted by default
    284 		// on OS X by the "natural scrolling" setting. At the Cocoa
    285 		// level this inversion is applied to trackpads and mice behind
    286 		// the scenes, and the value of dy goes in the direction the OS
    287 		// wants scrolling to go.
    288 		//
    289 		// This means the same trackpad/mouse motion on OS X and Linux
    290 		// can produce wheel events in opposite directions, but the
    291 		// direction matches what other programs on the OS do.
    292 		//
    293 		// If we wanted to expose the phsyical device motion in the
    294 		// event we could use [NSEvent isDirectionInvertedFromDevice]
    295 		// to know if "natural scrolling" is enabled.
    296 		//
    297 		// TODO: On a trackpad, a scroll can be a drawn-out affair with a
    298 		// distinct beginning and end. Should the intermediate events be
    299 		// DirNone?
    300 		//
    301 		// TODO: handle horizontal scrolling
    302 		button := mouse.ButtonWheelUp
    303 		if dy < 0 {
    304 			dy = -dy
    305 			button = mouse.ButtonWheelDown
    306 		}
    307 		e := mouse.Event{
    308 			X:         x,
    309 			Y:         y,
    310 			Button:    button,
    311 			Direction: mouse.DirStep,
    312 			Modifiers: cocoaMods(flags),
    313 		}
    314 		for delta := int(dy); delta != 0; delta-- {
    315 			sendWindowEvent(id, e)
    316 		}
    317 		return
    318 	}
    319 	sendWindowEvent(id, mouse.Event{
    320 		X:         x,
    321 		Y:         y,
    322 		Button:    cmButton,
    323 		Direction: cocoaMouseDir(ty),
    324 		Modifiers: cocoaMods(flags),
    325 	})
    326 }
    327 
    328 //export keyEvent
    329 func keyEvent(id uintptr, runeVal rune, dir uint8, code uint16, flags uint32) {
    330 	sendWindowEvent(id, key.Event{
    331 		Rune:      cocoaRune(runeVal),
    332 		Direction: key.Direction(dir),
    333 		Code:      cocoaKeyCode(code),
    334 		Modifiers: cocoaMods(flags),
    335 	})
    336 }
    337 
    338 //export flagEvent
    339 func flagEvent(id uintptr, flags uint32) {
    340 	for _, mod := range mods {
    341 		if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags {
    342 			keyEvent(id, -1, C.NSKeyDown, mod.code, flags)
    343 		}
    344 		if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags {
    345 			keyEvent(id, -1, C.NSKeyUp, mod.code, flags)
    346 		}
    347 	}
    348 	lastFlags = flags
    349 }
    350 
    351 var lastFlags uint32
    352 
    353 func sendLifecycle(id uintptr, setter func(*lifecycler.State, bool), val bool) {
    354 	theScreen.mu.Lock()
    355 	w := theScreen.windows[id]
    356 	theScreen.mu.Unlock()
    357 
    358 	if w == nil {
    359 		return
    360 	}
    361 	setter(&w.lifecycler, val)
    362 	w.lifecycler.SendEvent(w, w.glctx)
    363 }
    364 
    365 func sendLifecycleAll(dead bool) {
    366 	windows := []*windowImpl{}
    367 
    368 	theScreen.mu.Lock()
    369 	for _, w := range theScreen.windows {
    370 		windows = append(windows, w)
    371 	}
    372 	theScreen.mu.Unlock()
    373 
    374 	for _, w := range windows {
    375 		w.lifecycler.SetFocused(false)
    376 		w.lifecycler.SetVisible(false)
    377 		if dead {
    378 			w.lifecycler.SetDead(true)
    379 		}
    380 		w.lifecycler.SendEvent(w, w.glctx)
    381 	}
    382 }
    383 
    384 //export lifecycleDeadAll
    385 func lifecycleDeadAll() { sendLifecycleAll(true) }
    386 
    387 //export lifecycleHideAll
    388 func lifecycleHideAll() { sendLifecycleAll(false) }
    389 
    390 //export lifecycleVisible
    391 func lifecycleVisible(id uintptr, val bool) {
    392 	sendLifecycle(id, (*lifecycler.State).SetVisible, val)
    393 }
    394 
    395 //export lifecycleFocused
    396 func lifecycleFocused(id uintptr, val bool) {
    397 	sendLifecycle(id, (*lifecycler.State).SetFocused, val)
    398 }
    399 
    400 // cocoaRune marks the Carbon/Cocoa private-range unicode rune representing
    401 // a non-unicode key event to -1, used for Rune in the key package.
    402 //
    403 // http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
    404 func cocoaRune(r rune) rune {
    405 	if '\uE000' <= r && r <= '\uF8FF' {
    406 		return -1
    407 	}
    408 	return r
    409 }
    410 
    411 // cocoaKeyCode converts a Carbon/Cocoa virtual key code number
    412 // into the standard keycodes used by the key package.
    413 //
    414 // To get a sense of the key map, see the diagram on
    415 //	http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes
    416 func cocoaKeyCode(vkcode uint16) key.Code {
    417 	switch vkcode {
    418 	case C.kVK_ANSI_A:
    419 		return key.CodeA
    420 	case C.kVK_ANSI_B:
    421 		return key.CodeB
    422 	case C.kVK_ANSI_C:
    423 		return key.CodeC
    424 	case C.kVK_ANSI_D:
    425 		return key.CodeD
    426 	case C.kVK_ANSI_E:
    427 		return key.CodeE
    428 	case C.kVK_ANSI_F:
    429 		return key.CodeF
    430 	case C.kVK_ANSI_G:
    431 		return key.CodeG
    432 	case C.kVK_ANSI_H:
    433 		return key.CodeH
    434 	case C.kVK_ANSI_I:
    435 		return key.CodeI
    436 	case C.kVK_ANSI_J:
    437 		return key.CodeJ
    438 	case C.kVK_ANSI_K:
    439 		return key.CodeK
    440 	case C.kVK_ANSI_L:
    441 		return key.CodeL
    442 	case C.kVK_ANSI_M:
    443 		return key.CodeM
    444 	case C.kVK_ANSI_N:
    445 		return key.CodeN
    446 	case C.kVK_ANSI_O:
    447 		return key.CodeO
    448 	case C.kVK_ANSI_P:
    449 		return key.CodeP
    450 	case C.kVK_ANSI_Q:
    451 		return key.CodeQ
    452 	case C.kVK_ANSI_R:
    453 		return key.CodeR
    454 	case C.kVK_ANSI_S:
    455 		return key.CodeS
    456 	case C.kVK_ANSI_T:
    457 		return key.CodeT
    458 	case C.kVK_ANSI_U:
    459 		return key.CodeU
    460 	case C.kVK_ANSI_V:
    461 		return key.CodeV
    462 	case C.kVK_ANSI_W:
    463 		return key.CodeW
    464 	case C.kVK_ANSI_X:
    465 		return key.CodeX
    466 	case C.kVK_ANSI_Y:
    467 		return key.CodeY
    468 	case C.kVK_ANSI_Z:
    469 		return key.CodeZ
    470 	case C.kVK_ANSI_1:
    471 		return key.Code1
    472 	case C.kVK_ANSI_2:
    473 		return key.Code2
    474 	case C.kVK_ANSI_3:
    475 		return key.Code3
    476 	case C.kVK_ANSI_4:
    477 		return key.Code4
    478 	case C.kVK_ANSI_5:
    479 		return key.Code5
    480 	case C.kVK_ANSI_6:
    481 		return key.Code6
    482 	case C.kVK_ANSI_7:
    483 		return key.Code7
    484 	case C.kVK_ANSI_8:
    485 		return key.Code8
    486 	case C.kVK_ANSI_9:
    487 		return key.Code9
    488 	case C.kVK_ANSI_0:
    489 		return key.Code0
    490 	// TODO: move the rest of these codes to constants in key.go
    491 	// if we are happy with them.
    492 	case C.kVK_Return:
    493 		return key.CodeReturnEnter
    494 	case C.kVK_Escape:
    495 		return key.CodeEscape
    496 	case C.kVK_Delete:
    497 		return key.CodeDeleteBackspace
    498 	case C.kVK_Tab:
    499 		return key.CodeTab
    500 	case C.kVK_Space:
    501 		return key.CodeSpacebar
    502 	case C.kVK_ANSI_Minus:
    503 		return key.CodeHyphenMinus
    504 	case C.kVK_ANSI_Equal:
    505 		return key.CodeEqualSign
    506 	case C.kVK_ANSI_LeftBracket:
    507 		return key.CodeLeftSquareBracket
    508 	case C.kVK_ANSI_RightBracket:
    509 		return key.CodeRightSquareBracket
    510 	case C.kVK_ANSI_Backslash:
    511 		return key.CodeBackslash
    512 	// 50: Keyboard Non-US "#" and ~
    513 	case C.kVK_ANSI_Semicolon:
    514 		return key.CodeSemicolon
    515 	case C.kVK_ANSI_Quote:
    516 		return key.CodeApostrophe
    517 	case C.kVK_ANSI_Grave:
    518 		return key.CodeGraveAccent
    519 	case C.kVK_ANSI_Comma:
    520 		return key.CodeComma
    521 	case C.kVK_ANSI_Period:
    522 		return key.CodeFullStop
    523 	case C.kVK_ANSI_Slash:
    524 		return key.CodeSlash
    525 	case C.kVK_CapsLock:
    526 		return key.CodeCapsLock
    527 	case C.kVK_F1:
    528 		return key.CodeF1
    529 	case C.kVK_F2:
    530 		return key.CodeF2
    531 	case C.kVK_F3:
    532 		return key.CodeF3
    533 	case C.kVK_F4:
    534 		return key.CodeF4
    535 	case C.kVK_F5:
    536 		return key.CodeF5
    537 	case C.kVK_F6:
    538 		return key.CodeF6
    539 	case C.kVK_F7:
    540 		return key.CodeF7
    541 	case C.kVK_F8:
    542 		return key.CodeF8
    543 	case C.kVK_F9:
    544 		return key.CodeF9
    545 	case C.kVK_F10:
    546 		return key.CodeF10
    547 	case C.kVK_F11:
    548 		return key.CodeF11
    549 	case C.kVK_F12:
    550 		return key.CodeF12
    551 	// 70: PrintScreen
    552 	// 71: Scroll Lock
    553 	// 72: Pause
    554 	// 73: Insert
    555 	case C.kVK_Home:
    556 		return key.CodeHome
    557 	case C.kVK_PageUp:
    558 		return key.CodePageUp
    559 	case C.kVK_ForwardDelete:
    560 		return key.CodeDeleteForward
    561 	case C.kVK_End:
    562 		return key.CodeEnd
    563 	case C.kVK_PageDown:
    564 		return key.CodePageDown
    565 	case C.kVK_RightArrow:
    566 		return key.CodeRightArrow
    567 	case C.kVK_LeftArrow:
    568 		return key.CodeLeftArrow
    569 	case C.kVK_DownArrow:
    570 		return key.CodeDownArrow
    571 	case C.kVK_UpArrow:
    572 		return key.CodeUpArrow
    573 	case C.kVK_ANSI_KeypadClear:
    574 		return key.CodeKeypadNumLock
    575 	case C.kVK_ANSI_KeypadDivide:
    576 		return key.CodeKeypadSlash
    577 	case C.kVK_ANSI_KeypadMultiply:
    578 		return key.CodeKeypadAsterisk
    579 	case C.kVK_ANSI_KeypadMinus:
    580 		return key.CodeKeypadHyphenMinus
    581 	case C.kVK_ANSI_KeypadPlus:
    582 		return key.CodeKeypadPlusSign
    583 	case C.kVK_ANSI_KeypadEnter:
    584 		return key.CodeKeypadEnter
    585 	case C.kVK_ANSI_Keypad1:
    586 		return key.CodeKeypad1
    587 	case C.kVK_ANSI_Keypad2:
    588 		return key.CodeKeypad2
    589 	case C.kVK_ANSI_Keypad3:
    590 		return key.CodeKeypad3
    591 	case C.kVK_ANSI_Keypad4:
    592 		return key.CodeKeypad4
    593 	case C.kVK_ANSI_Keypad5:
    594 		return key.CodeKeypad5
    595 	case C.kVK_ANSI_Keypad6:
    596 		return key.CodeKeypad6
    597 	case C.kVK_ANSI_Keypad7:
    598 		return key.CodeKeypad7
    599 	case C.kVK_ANSI_Keypad8:
    600 		return key.CodeKeypad8
    601 	case C.kVK_ANSI_Keypad9:
    602 		return key.CodeKeypad9
    603 	case C.kVK_ANSI_Keypad0:
    604 		return key.CodeKeypad0
    605 	case C.kVK_ANSI_KeypadDecimal:
    606 		return key.CodeKeypadFullStop
    607 	case C.kVK_ANSI_KeypadEquals:
    608 		return key.CodeKeypadEqualSign
    609 	case C.kVK_F13:
    610 		return key.CodeF13
    611 	case C.kVK_F14:
    612 		return key.CodeF14
    613 	case C.kVK_F15:
    614 		return key.CodeF15
    615 	case C.kVK_F16:
    616 		return key.CodeF16
    617 	case C.kVK_F17:
    618 		return key.CodeF17
    619 	case C.kVK_F18:
    620 		return key.CodeF18
    621 	case C.kVK_F19:
    622 		return key.CodeF19
    623 	case C.kVK_F20:
    624 		return key.CodeF20
    625 	// 116: Keyboard Execute
    626 	case C.kVK_Help:
    627 		return key.CodeHelp
    628 	// 118: Keyboard Menu
    629 	// 119: Keyboard Select
    630 	// 120: Keyboard Stop
    631 	// 121: Keyboard Again
    632 	// 122: Keyboard Undo
    633 	// 123: Keyboard Cut
    634 	// 124: Keyboard Copy
    635 	// 125: Keyboard Paste
    636 	// 126: Keyboard Find
    637 	case C.kVK_Mute:
    638 		return key.CodeMute
    639 	case C.kVK_VolumeUp:
    640 		return key.CodeVolumeUp
    641 	case C.kVK_VolumeDown:
    642 		return key.CodeVolumeDown
    643 	// 130: Keyboard Locking Caps Lock
    644 	// 131: Keyboard Locking Num Lock
    645 	// 132: Keyboard Locking Scroll Lock
    646 	// 133: Keyboard Comma
    647 	// 134: Keyboard Equal Sign
    648 	// ...: Bunch of stuff
    649 	case C.kVK_Control:
    650 		return key.CodeLeftControl
    651 	case C.kVK_Shift:
    652 		return key.CodeLeftShift
    653 	case C.kVK_Option:
    654 		return key.CodeLeftAlt
    655 	case C.kVK_Command:
    656 		return key.CodeLeftGUI
    657 	case C.kVK_RightControl:
    658 		return key.CodeRightControl
    659 	case C.kVK_RightShift:
    660 		return key.CodeRightShift
    661 	case C.kVK_RightOption:
    662 		return key.CodeRightAlt
    663 	// TODO key.CodeRightGUI
    664 	default:
    665 		return key.CodeUnknown
    666 	}
    667 }
    668 
    669 func surfaceCreate() error {
    670 	return errors.New("gldriver: surface creation not implemented on darwin")
    671 }