mirror of https://github.com/miguelmota/cointop
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
672 lines
16 KiB
Go
672 lines
16 KiB
Go
// Copyright 2015 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
|
|
|
|
package gldriver
|
|
|
|
/*
|
|
#cgo CFLAGS: -x objective-c
|
|
#cgo LDFLAGS: -framework Cocoa -framework OpenGL
|
|
#include <OpenGL/gl3.h>
|
|
#import <Carbon/Carbon.h> // for HIToolbox/Events.h
|
|
#import <Cocoa/Cocoa.h>
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
void startDriver();
|
|
void stopDriver();
|
|
void makeCurrentContext(uintptr_t ctx);
|
|
void flushContext(uintptr_t ctx);
|
|
uintptr_t doNewWindow(int width, int height, char* title);
|
|
void doShowWindow(uintptr_t id);
|
|
void doCloseWindow(uintptr_t id);
|
|
uint64_t threadID();
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"runtime"
|
|
"unsafe"
|
|
|
|
"golang.org/x/exp/shiny/driver/internal/lifecycler"
|
|
"golang.org/x/exp/shiny/screen"
|
|
"golang.org/x/mobile/event/key"
|
|
"golang.org/x/mobile/event/mouse"
|
|
"golang.org/x/mobile/event/paint"
|
|
"golang.org/x/mobile/event/size"
|
|
"golang.org/x/mobile/geom"
|
|
"golang.org/x/mobile/gl"
|
|
)
|
|
|
|
const useLifecycler = true
|
|
|
|
// TODO: change this to true, after manual testing on OS X.
|
|
const handleSizeEventsAtChannelReceive = false
|
|
|
|
var initThreadID C.uint64_t
|
|
|
|
func init() {
|
|
// Lock the goroutine responsible for initialization to an OS thread.
|
|
// This means the goroutine running main (and calling startDriver below)
|
|
// is locked to the OS thread that started the program. This is
|
|
// necessary for the correct delivery of Cocoa events to the process.
|
|
//
|
|
// A discussion on this topic:
|
|
// https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ
|
|
runtime.LockOSThread()
|
|
initThreadID = C.threadID()
|
|
}
|
|
|
|
func newWindow(opts *screen.NewWindowOptions) (uintptr, error) {
|
|
width, height := optsSize(opts)
|
|
|
|
title := C.CString(opts.GetTitle())
|
|
defer C.free(unsafe.Pointer(title))
|
|
|
|
return uintptr(C.doNewWindow(C.int(width), C.int(height), title)), nil
|
|
}
|
|
|
|
func initWindow(w *windowImpl) {
|
|
w.glctx, w.worker = gl.NewContext()
|
|
}
|
|
|
|
func showWindow(w *windowImpl) {
|
|
C.doShowWindow(C.uintptr_t(w.id))
|
|
}
|
|
|
|
//export preparedOpenGL
|
|
func preparedOpenGL(id, ctx, vba uintptr) {
|
|
theScreen.mu.Lock()
|
|
w := theScreen.windows[id]
|
|
theScreen.mu.Unlock()
|
|
|
|
w.ctx = ctx
|
|
go drawLoop(w, vba)
|
|
}
|
|
|
|
func closeWindow(id uintptr) {
|
|
C.doCloseWindow(C.uintptr_t(id))
|
|
}
|
|
|
|
var mainCallback func(screen.Screen)
|
|
|
|
func main(f func(screen.Screen)) error {
|
|
if tid := C.threadID(); tid != initThreadID {
|
|
log.Fatalf("gldriver.Main called on thread %d, but gldriver.init ran on %d", tid, initThreadID)
|
|
}
|
|
|
|
mainCallback = f
|
|
C.startDriver()
|
|
return nil
|
|
}
|
|
|
|
//export driverStarted
|
|
func driverStarted() {
|
|
go func() {
|
|
mainCallback(theScreen)
|
|
C.stopDriver()
|
|
}()
|
|
}
|
|
|
|
//export drawgl
|
|
func drawgl(id uintptr) {
|
|
theScreen.mu.Lock()
|
|
w := theScreen.windows[id]
|
|
theScreen.mu.Unlock()
|
|
|
|
if w == nil {
|
|
return // closing window
|
|
}
|
|
|
|
// TODO: is this necessary?
|
|
w.lifecycler.SetVisible(true)
|
|
w.lifecycler.SendEvent(w, w.glctx)
|
|
|
|
w.Send(paint.Event{External: true})
|
|
<-w.drawDone
|
|
}
|
|
|
|
// drawLoop is the primary drawing loop.
|
|
//
|
|
// After Cocoa has created an NSWindow and called prepareOpenGL,
|
|
// it starts drawLoop on a locked goroutine to handle OpenGL calls.
|
|
//
|
|
// The screen is drawn every time a paint.Event is received, which can be
|
|
// triggered either by the user or by Cocoa via drawgl (for example, when
|
|
// the window is resized).
|
|
func drawLoop(w *windowImpl, vba uintptr) {
|
|
runtime.LockOSThread()
|
|
C.makeCurrentContext(C.uintptr_t(w.ctx.(uintptr)))
|
|
|
|
// Starting in OS X 10.11 (El Capitan), the vertex array is
|
|
// occasionally getting unbound when the context changes threads.
|
|
//
|
|
// Avoid this by binding it again.
|
|
C.glBindVertexArray(C.GLuint(vba))
|
|
if errno := C.glGetError(); errno != 0 {
|
|
panic(fmt.Sprintf("gldriver: glBindVertexArray failed: %d", errno))
|
|
}
|
|
|
|
workAvailable := w.worker.WorkAvailable()
|
|
|
|
// TODO(crawshaw): exit this goroutine on Release.
|
|
for {
|
|
select {
|
|
case <-workAvailable:
|
|
w.worker.DoWork()
|
|
case <-w.publish:
|
|
loop:
|
|
for {
|
|
select {
|
|
case <-workAvailable:
|
|
w.worker.DoWork()
|
|
default:
|
|
break loop
|
|
}
|
|
}
|
|
C.flushContext(C.uintptr_t(w.ctx.(uintptr)))
|
|
w.publishDone <- screen.PublishResult{}
|
|
}
|
|
}
|
|
}
|
|
|
|
//export setGeom
|
|
func setGeom(id uintptr, ppp float32, widthPx, heightPx int) {
|
|
theScreen.mu.Lock()
|
|
w := theScreen.windows[id]
|
|
theScreen.mu.Unlock()
|
|
|
|
if w == nil {
|
|
return // closing window
|
|
}
|
|
|
|
sz := size.Event{
|
|
WidthPx: widthPx,
|
|
HeightPx: heightPx,
|
|
WidthPt: geom.Pt(float32(widthPx) / ppp),
|
|
HeightPt: geom.Pt(float32(heightPx) / ppp),
|
|
PixelsPerPt: ppp,
|
|
}
|
|
|
|
if !handleSizeEventsAtChannelReceive {
|
|
w.szMu.Lock()
|
|
w.sz = sz
|
|
w.szMu.Unlock()
|
|
}
|
|
|
|
w.Send(sz)
|
|
}
|
|
|
|
//export windowClosing
|
|
func windowClosing(id uintptr) {
|
|
sendLifecycle(id, (*lifecycler.State).SetDead, true)
|
|
}
|
|
|
|
func sendWindowEvent(id uintptr, e interface{}) {
|
|
theScreen.mu.Lock()
|
|
w := theScreen.windows[id]
|
|
theScreen.mu.Unlock()
|
|
|
|
if w == nil {
|
|
return // closing window
|
|
}
|
|
w.Send(e)
|
|
}
|
|
|
|
var mods = [...]struct {
|
|
flags uint32
|
|
code uint16
|
|
mod key.Modifiers
|
|
}{
|
|
// Left and right variants of modifier keys have their own masks,
|
|
// but they are not documented. These were determined empirically.
|
|
{1<<17 | 0x102, C.kVK_Shift, key.ModShift},
|
|
{1<<17 | 0x104, C.kVK_RightShift, key.ModShift},
|
|
{1<<18 | 0x101, C.kVK_Control, key.ModControl},
|
|
// TODO key.ControlRight
|
|
{1<<19 | 0x120, C.kVK_Option, key.ModAlt},
|
|
{1<<19 | 0x140, C.kVK_RightOption, key.ModAlt},
|
|
{1<<20 | 0x108, C.kVK_Command, key.ModMeta},
|
|
{1<<20 | 0x110, C.kVK_Command, key.ModMeta}, // TODO: missing kVK_RightCommand
|
|
}
|
|
|
|
func cocoaMods(flags uint32) (m key.Modifiers) {
|
|
for _, mod := range mods {
|
|
if flags&mod.flags == mod.flags {
|
|
m |= mod.mod
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func cocoaMouseDir(ty int32) mouse.Direction {
|
|
switch ty {
|
|
case C.NSLeftMouseDown, C.NSRightMouseDown, C.NSOtherMouseDown:
|
|
return mouse.DirPress
|
|
case C.NSLeftMouseUp, C.NSRightMouseUp, C.NSOtherMouseUp:
|
|
return mouse.DirRelease
|
|
default: // dragged
|
|
return mouse.DirNone
|
|
}
|
|
}
|
|
|
|
func cocoaMouseButton(button int32) mouse.Button {
|
|
switch button {
|
|
case 0:
|
|
return mouse.ButtonLeft
|
|
case 1:
|
|
return mouse.ButtonRight
|
|
case 2:
|
|
return mouse.ButtonMiddle
|
|
default:
|
|
return mouse.ButtonNone
|
|
}
|
|
}
|
|
|
|
//export mouseEvent
|
|
func mouseEvent(id uintptr, x, y, dx, dy float32, ty, button int32, flags uint32) {
|
|
cmButton := mouse.ButtonNone
|
|
switch ty {
|
|
default:
|
|
cmButton = cocoaMouseButton(button)
|
|
case C.NSMouseMoved, C.NSLeftMouseDragged, C.NSRightMouseDragged, C.NSOtherMouseDragged:
|
|
// No-op.
|
|
case C.NSScrollWheel:
|
|
// Note that the direction of scrolling is inverted by default
|
|
// on OS X by the "natural scrolling" setting. At the Cocoa
|
|
// level this inversion is applied to trackpads and mice behind
|
|
// the scenes, and the value of dy goes in the direction the OS
|
|
// wants scrolling to go.
|
|
//
|
|
// This means the same trackpad/mouse motion on OS X and Linux
|
|
// can produce wheel events in opposite directions, but the
|
|
// direction matches what other programs on the OS do.
|
|
//
|
|
// If we wanted to expose the phsyical device motion in the
|
|
// event we could use [NSEvent isDirectionInvertedFromDevice]
|
|
// to know if "natural scrolling" is enabled.
|
|
//
|
|
// TODO: On a trackpad, a scroll can be a drawn-out affair with a
|
|
// distinct beginning and end. Should the intermediate events be
|
|
// DirNone?
|
|
//
|
|
// TODO: handle horizontal scrolling
|
|
button := mouse.ButtonWheelUp
|
|
if dy < 0 {
|
|
dy = -dy
|
|
button = mouse.ButtonWheelDown
|
|
}
|
|
e := mouse.Event{
|
|
X: x,
|
|
Y: y,
|
|
Button: button,
|
|
Direction: mouse.DirStep,
|
|
Modifiers: cocoaMods(flags),
|
|
}
|
|
for delta := int(dy); delta != 0; delta-- {
|
|
sendWindowEvent(id, e)
|
|
}
|
|
return
|
|
}
|
|
sendWindowEvent(id, mouse.Event{
|
|
X: x,
|
|
Y: y,
|
|
Button: cmButton,
|
|
Direction: cocoaMouseDir(ty),
|
|
Modifiers: cocoaMods(flags),
|
|
})
|
|
}
|
|
|
|
//export keyEvent
|
|
func keyEvent(id uintptr, runeVal rune, dir uint8, code uint16, flags uint32) {
|
|
sendWindowEvent(id, key.Event{
|
|
Rune: cocoaRune(runeVal),
|
|
Direction: key.Direction(dir),
|
|
Code: cocoaKeyCode(code),
|
|
Modifiers: cocoaMods(flags),
|
|
})
|
|
}
|
|
|
|
//export flagEvent
|
|
func flagEvent(id uintptr, flags uint32) {
|
|
for _, mod := range mods {
|
|
if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags {
|
|
keyEvent(id, -1, C.NSKeyDown, mod.code, flags)
|
|
}
|
|
if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags {
|
|
keyEvent(id, -1, C.NSKeyUp, mod.code, flags)
|
|
}
|
|
}
|
|
lastFlags = flags
|
|
}
|
|
|
|
var lastFlags uint32
|
|
|
|
func sendLifecycle(id uintptr, setter func(*lifecycler.State, bool), val bool) {
|
|
theScreen.mu.Lock()
|
|
w := theScreen.windows[id]
|
|
theScreen.mu.Unlock()
|
|
|
|
if w == nil {
|
|
return
|
|
}
|
|
setter(&w.lifecycler, val)
|
|
w.lifecycler.SendEvent(w, w.glctx)
|
|
}
|
|
|
|
func sendLifecycleAll(dead bool) {
|
|
windows := []*windowImpl{}
|
|
|
|
theScreen.mu.Lock()
|
|
for _, w := range theScreen.windows {
|
|
windows = append(windows, w)
|
|
}
|
|
theScreen.mu.Unlock()
|
|
|
|
for _, w := range windows {
|
|
w.lifecycler.SetFocused(false)
|
|
w.lifecycler.SetVisible(false)
|
|
if dead {
|
|
w.lifecycler.SetDead(true)
|
|
}
|
|
w.lifecycler.SendEvent(w, w.glctx)
|
|
}
|
|
}
|
|
|
|
//export lifecycleDeadAll
|
|
func lifecycleDeadAll() { sendLifecycleAll(true) }
|
|
|
|
//export lifecycleHideAll
|
|
func lifecycleHideAll() { sendLifecycleAll(false) }
|
|
|
|
//export lifecycleVisible
|
|
func lifecycleVisible(id uintptr, val bool) {
|
|
sendLifecycle(id, (*lifecycler.State).SetVisible, val)
|
|
}
|
|
|
|
//export lifecycleFocused
|
|
func lifecycleFocused(id uintptr, val bool) {
|
|
sendLifecycle(id, (*lifecycler.State).SetFocused, val)
|
|
}
|
|
|
|
// cocoaRune marks the Carbon/Cocoa private-range unicode rune representing
|
|
// a non-unicode key event to -1, used for Rune in the key package.
|
|
//
|
|
// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
|
|
func cocoaRune(r rune) rune {
|
|
if '\uE000' <= r && r <= '\uF8FF' {
|
|
return -1
|
|
}
|
|
return r
|
|
}
|
|
|
|
// cocoaKeyCode converts a Carbon/Cocoa virtual key code number
|
|
// into the standard keycodes used by the key package.
|
|
//
|
|
// To get a sense of the key map, see the diagram on
|
|
// http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes
|
|
func cocoaKeyCode(vkcode uint16) key.Code {
|
|
switch vkcode {
|
|
case C.kVK_ANSI_A:
|
|
return key.CodeA
|
|
case C.kVK_ANSI_B:
|
|
return key.CodeB
|
|
case C.kVK_ANSI_C:
|
|
return key.CodeC
|
|
case C.kVK_ANSI_D:
|
|
return key.CodeD
|
|
case C.kVK_ANSI_E:
|
|
return key.CodeE
|
|
case C.kVK_ANSI_F:
|
|
return key.CodeF
|
|
case C.kVK_ANSI_G:
|
|
return key.CodeG
|
|
case C.kVK_ANSI_H:
|
|
return key.CodeH
|
|
case C.kVK_ANSI_I:
|
|
return key.CodeI
|
|
case C.kVK_ANSI_J:
|
|
return key.CodeJ
|
|
case C.kVK_ANSI_K:
|
|
return key.CodeK
|
|
case C.kVK_ANSI_L:
|
|
return key.CodeL
|
|
case C.kVK_ANSI_M:
|
|
return key.CodeM
|
|
case C.kVK_ANSI_N:
|
|
return key.CodeN
|
|
case C.kVK_ANSI_O:
|
|
return key.CodeO
|
|
case C.kVK_ANSI_P:
|
|
return key.CodeP
|
|
case C.kVK_ANSI_Q:
|
|
return key.CodeQ
|
|
case C.kVK_ANSI_R:
|
|
return key.CodeR
|
|
case C.kVK_ANSI_S:
|
|
return key.CodeS
|
|
case C.kVK_ANSI_T:
|
|
return key.CodeT
|
|
case C.kVK_ANSI_U:
|
|
return key.CodeU
|
|
case C.kVK_ANSI_V:
|
|
return key.CodeV
|
|
case C.kVK_ANSI_W:
|
|
return key.CodeW
|
|
case C.kVK_ANSI_X:
|
|
return key.CodeX
|
|
case C.kVK_ANSI_Y:
|
|
return key.CodeY
|
|
case C.kVK_ANSI_Z:
|
|
return key.CodeZ
|
|
case C.kVK_ANSI_1:
|
|
return key.Code1
|
|
case C.kVK_ANSI_2:
|
|
return key.Code2
|
|
case C.kVK_ANSI_3:
|
|
return key.Code3
|
|
case C.kVK_ANSI_4:
|
|
return key.Code4
|
|
case C.kVK_ANSI_5:
|
|
return key.Code5
|
|
case C.kVK_ANSI_6:
|
|
return key.Code6
|
|
case C.kVK_ANSI_7:
|
|
return key.Code7
|
|
case C.kVK_ANSI_8:
|
|
return key.Code8
|
|
case C.kVK_ANSI_9:
|
|
return key.Code9
|
|
case C.kVK_ANSI_0:
|
|
return key.Code0
|
|
// TODO: move the rest of these codes to constants in key.go
|
|
// if we are happy with them.
|
|
case C.kVK_Return:
|
|
return key.CodeReturnEnter
|
|
case C.kVK_Escape:
|
|
return key.CodeEscape
|
|
case C.kVK_Delete:
|
|
return key.CodeDeleteBackspace
|
|
case C.kVK_Tab:
|
|
return key.CodeTab
|
|
case C.kVK_Space:
|
|
return key.CodeSpacebar
|
|
case C.kVK_ANSI_Minus:
|
|
return key.CodeHyphenMinus
|
|
case C.kVK_ANSI_Equal:
|
|
return key.CodeEqualSign
|
|
case C.kVK_ANSI_LeftBracket:
|
|
return key.CodeLeftSquareBracket
|
|
case C.kVK_ANSI_RightBracket:
|
|
return key.CodeRightSquareBracket
|
|
case C.kVK_ANSI_Backslash:
|
|
return key.CodeBackslash
|
|
// 50: Keyboard Non-US "#" and ~
|
|
case C.kVK_ANSI_Semicolon:
|
|
return key.CodeSemicolon
|
|
case C.kVK_ANSI_Quote:
|
|
return key.CodeApostrophe
|
|
case C.kVK_ANSI_Grave:
|
|
return key.CodeGraveAccent
|
|
case C.kVK_ANSI_Comma:
|
|
return key.CodeComma
|
|
case C.kVK_ANSI_Period:
|
|
return key.CodeFullStop
|
|
case C.kVK_ANSI_Slash:
|
|
return key.CodeSlash
|
|
case C.kVK_CapsLock:
|
|
return key.CodeCapsLock
|
|
case C.kVK_F1:
|
|
return key.CodeF1
|
|
case C.kVK_F2:
|
|
return key.CodeF2
|
|
case C.kVK_F3:
|
|
return key.CodeF3
|
|
case C.kVK_F4:
|
|
return key.CodeF4
|
|
case C.kVK_F5:
|
|
return key.CodeF5
|
|
case C.kVK_F6:
|
|
return key.CodeF6
|
|
case C.kVK_F7:
|
|
return key.CodeF7
|
|
case C.kVK_F8:
|
|
return key.CodeF8
|
|
case C.kVK_F9:
|
|
return key.CodeF9
|
|
case C.kVK_F10:
|
|
return key.CodeF10
|
|
case C.kVK_F11:
|
|
return key.CodeF11
|
|
case C.kVK_F12:
|
|
return key.CodeF12
|
|
// 70: PrintScreen
|
|
// 71: Scroll Lock
|
|
// 72: Pause
|
|
// 73: Insert
|
|
case C.kVK_Home:
|
|
return key.CodeHome
|
|
case C.kVK_PageUp:
|
|
return key.CodePageUp
|
|
case C.kVK_ForwardDelete:
|
|
return key.CodeDeleteForward
|
|
case C.kVK_End:
|
|
return key.CodeEnd
|
|
case C.kVK_PageDown:
|
|
return key.CodePageDown
|
|
case C.kVK_RightArrow:
|
|
return key.CodeRightArrow
|
|
case C.kVK_LeftArrow:
|
|
return key.CodeLeftArrow
|
|
case C.kVK_DownArrow:
|
|
return key.CodeDownArrow
|
|
case C.kVK_UpArrow:
|
|
return key.CodeUpArrow
|
|
case C.kVK_ANSI_KeypadClear:
|
|
return key.CodeKeypadNumLock
|
|
case C.kVK_ANSI_KeypadDivide:
|
|
return key.CodeKeypadSlash
|
|
case C.kVK_ANSI_KeypadMultiply:
|
|
return key.CodeKeypadAsterisk
|
|
case C.kVK_ANSI_KeypadMinus:
|
|
return key.CodeKeypadHyphenMinus
|
|
case C.kVK_ANSI_KeypadPlus:
|
|
return key.CodeKeypadPlusSign
|
|
case C.kVK_ANSI_KeypadEnter:
|
|
return key.CodeKeypadEnter
|
|
case C.kVK_ANSI_Keypad1:
|
|
return key.CodeKeypad1
|
|
case C.kVK_ANSI_Keypad2:
|
|
return key.CodeKeypad2
|
|
case C.kVK_ANSI_Keypad3:
|
|
return key.CodeKeypad3
|
|
case C.kVK_ANSI_Keypad4:
|
|
return key.CodeKeypad4
|
|
case C.kVK_ANSI_Keypad5:
|
|
return key.CodeKeypad5
|
|
case C.kVK_ANSI_Keypad6:
|
|
return key.CodeKeypad6
|
|
case C.kVK_ANSI_Keypad7:
|
|
return key.CodeKeypad7
|
|
case C.kVK_ANSI_Keypad8:
|
|
return key.CodeKeypad8
|
|
case C.kVK_ANSI_Keypad9:
|
|
return key.CodeKeypad9
|
|
case C.kVK_ANSI_Keypad0:
|
|
return key.CodeKeypad0
|
|
case C.kVK_ANSI_KeypadDecimal:
|
|
return key.CodeKeypadFullStop
|
|
case C.kVK_ANSI_KeypadEquals:
|
|
return key.CodeKeypadEqualSign
|
|
case C.kVK_F13:
|
|
return key.CodeF13
|
|
case C.kVK_F14:
|
|
return key.CodeF14
|
|
case C.kVK_F15:
|
|
return key.CodeF15
|
|
case C.kVK_F16:
|
|
return key.CodeF16
|
|
case C.kVK_F17:
|
|
return key.CodeF17
|
|
case C.kVK_F18:
|
|
return key.CodeF18
|
|
case C.kVK_F19:
|
|
return key.CodeF19
|
|
case C.kVK_F20:
|
|
return key.CodeF20
|
|
// 116: Keyboard Execute
|
|
case C.kVK_Help:
|
|
return key.CodeHelp
|
|
// 118: Keyboard Menu
|
|
// 119: Keyboard Select
|
|
// 120: Keyboard Stop
|
|
// 121: Keyboard Again
|
|
// 122: Keyboard Undo
|
|
// 123: Keyboard Cut
|
|
// 124: Keyboard Copy
|
|
// 125: Keyboard Paste
|
|
// 126: Keyboard Find
|
|
case C.kVK_Mute:
|
|
return key.CodeMute
|
|
case C.kVK_VolumeUp:
|
|
return key.CodeVolumeUp
|
|
case C.kVK_VolumeDown:
|
|
return key.CodeVolumeDown
|
|
// 130: Keyboard Locking Caps Lock
|
|
// 131: Keyboard Locking Num Lock
|
|
// 132: Keyboard Locking Scroll Lock
|
|
// 133: Keyboard Comma
|
|
// 134: Keyboard Equal Sign
|
|
// ...: Bunch of stuff
|
|
case C.kVK_Control:
|
|
return key.CodeLeftControl
|
|
case C.kVK_Shift:
|
|
return key.CodeLeftShift
|
|
case C.kVK_Option:
|
|
return key.CodeLeftAlt
|
|
case C.kVK_Command:
|
|
return key.CodeLeftGUI
|
|
case C.kVK_RightControl:
|
|
return key.CodeRightControl
|
|
case C.kVK_RightShift:
|
|
return key.CodeRightShift
|
|
case C.kVK_RightOption:
|
|
return key.CodeRightAlt
|
|
// TODO key.CodeRightGUI
|
|
default:
|
|
return key.CodeUnknown
|
|
}
|
|
}
|
|
|
|
func surfaceCreate() error {
|
|
return errors.New("gldriver: surface creation not implemented on darwin")
|
|
}
|