File: cocoa.go

package info (click to toggle)
golang-golang-x-exp 0.0~git20150826.1.eb7c1fa-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 2,000 kB
  • ctags: 2,884
  • sloc: objc: 239; ansic: 195; sh: 124; asm: 24; makefile: 8
file content (274 lines) | stat: -rw-r--r-- 6,914 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// 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 -framework QuartzCore
#import <Carbon/Carbon.h> // for HIToolbox/Events.h
#import <Cocoa/Cocoa.h>
#include <pthread.h>
#include <stdint.h>

void startDriver();
void stopDriver();
void makeCurrentContext(uintptr_t ctx);
uintptr_t doNewWindow(int width, int height);
uintptr_t doShowWindow(uintptr_t id);
void doCloseWindow(uintptr_t id);
uint64_t threadID();
*/
import "C"

import (
	"log"
	"runtime"

	"golang.org/x/exp/shiny/screen"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"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"
)

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(width, height int32) uintptr {
	return uintptr(C.doNewWindow(C.int(width), C.int(height)))
}

func showWindow(id uintptr) uintptr {
	return uintptr(C.doShowWindow(C.uintptr_t(id)))
}

func closeWindow(id uintptr) {
	C.doCloseWindow(C.uintptr_t(id))
}

var (
	theScreen = &screenImpl{
		windows: make(map[uintptr]*windowImpl),
	}
	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
	}
	w.draw <- struct{}{}
	<-w.drawDone
}

// drawLoop is the primary drawing loop.
//
// After Cocoa has created an NSWindow on the initial OS thread for
// processing Cocoa events in doNewWindow, it starts drawLoop on another
// goroutine. It is locked to an OS thread for its OpenGL context.
//
// Two Cocoa threads deliver draw signals to drawLoop. The primary
// source of draw events is the CVDisplayLink timer, which is tied to
// the display vsync. Secondary draw events come from [NSView drawRect:]
// when the window is resized.
func drawLoop(w *windowImpl, ctx uintptr) {
	runtime.LockOSThread()
	// TODO(crawshaw): there are several problematic issues around having
	// a draw loop per window, but resolving them requires some thought.
	// Firstly, nothing should race on gl.DoWork, so only one person can
	// do that at a time. Secondly, which GL ctx we use matters. A ctx
	// carries window-specific state (for example, the current glViewport
	// value), so we only want to run GL commands on the right context
	// between a <-w.draw and a <-w.drawDone. Thirdly, some GL functions
	// can be legitimately called outside of a window draw cycle, for
	// example, gl.CreateTexture. It doesn't matter which GL ctx we use
	// for that, but we have to use a valid one. So if a window gets
	// closed, it's important we swap the default ctx. More work needed.
	C.makeCurrentContext(C.uintptr_t(ctx))

	// TODO(crawshaw): exit this goroutine on Release.
	for {
		select {
		case <-gl.WorkAvailable:
			gl.DoWork()
		case <-w.draw:
			w.Send(paint.Event{})
		loop:
			for {
				select {
				case <-gl.WorkAvailable:
					gl.DoWork()
				case <-w.endPaint:
					C.CGLFlushDrawable(C.CGLGetCurrentContext())
					break loop
				}
			}
			w.drawDone <- struct{}{}
		}
	}
}

//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,
	}

	w.mu.Lock()
	w.sz = sz
	w.mu.Unlock()

	w.Send(sz)
}

//export windowClosing
func windowClosing(id uintptr) {
	theScreen.mu.Lock()
	w := theScreen.windows[id]
	delete(theScreen.windows, id)
	theScreen.mu.Unlock()
	w.releaseCleanup()
}

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 float32, ty, button int32, flags uint32) {
	sendWindowEvent(id, mouse.Event{
		X:         x,
		Y:         y,
		Button:    cocoaMouseButton(button),
		Direction: cocoaMouseDir(ty),
		Modifiers: cocoaMods(flags),
	})
}

func sendLifecycle(to lifecycle.Stage) {
	log.Printf("sendLifecycle: %v", to) // TODO
}

//export lifecycleDead
func lifecycleDead() { sendLifecycle(lifecycle.StageDead) }

//export lifecycleAlive
func lifecycleAlive() { sendLifecycle(lifecycle.StageAlive) }

//export lifecycleVisible
func lifecycleVisible() { sendLifecycle(lifecycle.StageVisible) }

//export lifecycleFocused
func lifecycleFocused() { sendLifecycle(lifecycle.StageFocused) }