// 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 #import // for HIToolbox/Events.h #import #include #include #include 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") }