darwin_desktop.go (11989B)
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 !ios 7 8 package app 9 10 // Simple on-screen app debugging for OS X. Not an officially supported 11 // development target for apps, as screens with mice are very different 12 // than screens with touch panels. 13 14 /* 15 #cgo CFLAGS: -x objective-c -DGL_SILENCE_DEPRECATION 16 #cgo LDFLAGS: -framework Cocoa -framework OpenGL 17 #import <Carbon/Carbon.h> // for HIToolbox/Events.h 18 #import <Cocoa/Cocoa.h> 19 #include <pthread.h> 20 21 void runApp(void); 22 void stopApp(void); 23 void makeCurrentContext(GLintptr); 24 uint64 threadID(); 25 */ 26 import "C" 27 import ( 28 "log" 29 "runtime" 30 "sync" 31 32 "golang.org/x/mobile/event/key" 33 "golang.org/x/mobile/event/lifecycle" 34 "golang.org/x/mobile/event/paint" 35 "golang.org/x/mobile/event/size" 36 "golang.org/x/mobile/event/touch" 37 "golang.org/x/mobile/geom" 38 ) 39 40 var initThreadID uint64 41 42 func init() { 43 // Lock the goroutine responsible for initialization to an OS thread. 44 // This means the goroutine running main (and calling runApp below) 45 // is locked to the OS thread that started the program. This is 46 // necessary for the correct delivery of Cocoa events to the process. 47 // 48 // A discussion on this topic: 49 // https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ 50 runtime.LockOSThread() 51 initThreadID = uint64(C.threadID()) 52 } 53 54 func main(f func(App)) { 55 if tid := uint64(C.threadID()); tid != initThreadID { 56 log.Fatalf("app.Main called on thread %d, but app.init ran on %d", tid, initThreadID) 57 } 58 59 go func() { 60 f(theApp) 61 C.stopApp() 62 // TODO(crawshaw): trigger runApp to return 63 }() 64 65 C.runApp() 66 } 67 68 // loop is the primary drawing loop. 69 // 70 // After Cocoa has captured the initial OS thread for processing Cocoa 71 // events in runApp, it starts loop on another goroutine. It is locked 72 // to an OS thread for its OpenGL context. 73 // 74 // The loop processes GL calls until a publish event appears. 75 // Then it runs any remaining GL calls and flushes the screen. 76 // 77 // As NSOpenGLCPSwapInterval is set to 1, the call to CGLFlushDrawable 78 // blocks until the screen refresh. 79 func (a *app) loop(ctx C.GLintptr) { 80 runtime.LockOSThread() 81 C.makeCurrentContext(ctx) 82 83 workAvailable := a.worker.WorkAvailable() 84 85 for { 86 select { 87 case <-workAvailable: 88 a.worker.DoWork() 89 case <-theApp.publish: 90 loop1: 91 for { 92 select { 93 case <-workAvailable: 94 a.worker.DoWork() 95 default: 96 break loop1 97 } 98 } 99 C.CGLFlushDrawable(C.CGLGetCurrentContext()) 100 theApp.publishResult <- PublishResult{} 101 select { 102 case drawDone <- struct{}{}: 103 default: 104 } 105 } 106 } 107 } 108 109 var drawDone = make(chan struct{}) 110 111 // drawgl is used by Cocoa to occasionally request screen updates. 112 // 113 //export drawgl 114 func drawgl() { 115 switch theApp.lifecycleStage { 116 case lifecycle.StageFocused, lifecycle.StageVisible: 117 theApp.Send(paint.Event{ 118 External: true, 119 }) 120 <-drawDone 121 } 122 } 123 124 //export startloop 125 func startloop(ctx C.GLintptr) { 126 go theApp.loop(ctx) 127 } 128 129 var windowHeightPx float32 130 131 //export setGeom 132 func setGeom(pixelsPerPt float32, widthPx, heightPx int) { 133 windowHeightPx = float32(heightPx) 134 theApp.eventsIn <- size.Event{ 135 WidthPx: widthPx, 136 HeightPx: heightPx, 137 WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), 138 HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), 139 PixelsPerPt: pixelsPerPt, 140 } 141 } 142 143 var touchEvents struct { 144 sync.Mutex 145 pending []touch.Event 146 } 147 148 func sendTouch(t touch.Type, x, y float32) { 149 theApp.eventsIn <- touch.Event{ 150 X: x, 151 Y: windowHeightPx - y, 152 Sequence: 0, 153 Type: t, 154 } 155 } 156 157 //export eventMouseDown 158 func eventMouseDown(x, y float32) { sendTouch(touch.TypeBegin, x, y) } 159 160 //export eventMouseDragged 161 func eventMouseDragged(x, y float32) { sendTouch(touch.TypeMove, x, y) } 162 163 //export eventMouseEnd 164 func eventMouseEnd(x, y float32) { sendTouch(touch.TypeEnd, x, y) } 165 166 //export lifecycleDead 167 func lifecycleDead() { theApp.sendLifecycle(lifecycle.StageDead) } 168 169 //export eventKey 170 func eventKey(runeVal int32, direction uint8, code uint16, flags uint32) { 171 var modifiers key.Modifiers 172 for _, mod := range mods { 173 if flags&mod.flags == mod.flags { 174 modifiers |= mod.mod 175 } 176 } 177 178 theApp.eventsIn <- key.Event{ 179 Rune: convRune(rune(runeVal)), 180 Code: convVirtualKeyCode(code), 181 Modifiers: modifiers, 182 Direction: key.Direction(direction), 183 } 184 } 185 186 //export eventFlags 187 func eventFlags(flags uint32) { 188 for _, mod := range mods { 189 if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags { 190 eventKey(-1, uint8(key.DirPress), mod.code, flags) 191 } 192 if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags { 193 eventKey(-1, uint8(key.DirRelease), mod.code, flags) 194 } 195 } 196 lastFlags = flags 197 } 198 199 var lastFlags uint32 200 201 var mods = [...]struct { 202 flags uint32 203 code uint16 204 mod key.Modifiers 205 }{ 206 // Left and right variants of modifier keys have their own masks, 207 // but they are not documented. These were determined empirically. 208 {1<<17 | 0x102, C.kVK_Shift, key.ModShift}, 209 {1<<17 | 0x104, C.kVK_RightShift, key.ModShift}, 210 {1<<18 | 0x101, C.kVK_Control, key.ModControl}, 211 // TODO key.ControlRight 212 {1<<19 | 0x120, C.kVK_Option, key.ModAlt}, 213 {1<<19 | 0x140, C.kVK_RightOption, key.ModAlt}, 214 {1<<20 | 0x108, C.kVK_Command, key.ModMeta}, 215 {1<<20 | 0x110, C.kVK_Command, key.ModMeta}, // TODO: missing kVK_RightCommand 216 } 217 218 //export lifecycleAlive 219 func lifecycleAlive() { theApp.sendLifecycle(lifecycle.StageAlive) } 220 221 //export lifecycleVisible 222 func lifecycleVisible() { 223 theApp.sendLifecycle(lifecycle.StageVisible) 224 } 225 226 //export lifecycleFocused 227 func lifecycleFocused() { theApp.sendLifecycle(lifecycle.StageFocused) } 228 229 // convRune marks the Carbon/Cocoa private-range unicode rune representing 230 // a non-unicode key event to -1, used for Rune in the key package. 231 // 232 // http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT 233 func convRune(r rune) rune { 234 if '\uE000' <= r && r <= '\uF8FF' { 235 return -1 236 } 237 return r 238 } 239 240 // convVirtualKeyCode converts a Carbon/Cocoa virtual key code number 241 // into the standard keycodes used by the key package. 242 // 243 // To get a sense of the key map, see the diagram on 244 // http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes 245 func convVirtualKeyCode(vkcode uint16) key.Code { 246 switch vkcode { 247 case C.kVK_ANSI_A: 248 return key.CodeA 249 case C.kVK_ANSI_B: 250 return key.CodeB 251 case C.kVK_ANSI_C: 252 return key.CodeC 253 case C.kVK_ANSI_D: 254 return key.CodeD 255 case C.kVK_ANSI_E: 256 return key.CodeE 257 case C.kVK_ANSI_F: 258 return key.CodeF 259 case C.kVK_ANSI_G: 260 return key.CodeG 261 case C.kVK_ANSI_H: 262 return key.CodeH 263 case C.kVK_ANSI_I: 264 return key.CodeI 265 case C.kVK_ANSI_J: 266 return key.CodeJ 267 case C.kVK_ANSI_K: 268 return key.CodeK 269 case C.kVK_ANSI_L: 270 return key.CodeL 271 case C.kVK_ANSI_M: 272 return key.CodeM 273 case C.kVK_ANSI_N: 274 return key.CodeN 275 case C.kVK_ANSI_O: 276 return key.CodeO 277 case C.kVK_ANSI_P: 278 return key.CodeP 279 case C.kVK_ANSI_Q: 280 return key.CodeQ 281 case C.kVK_ANSI_R: 282 return key.CodeR 283 case C.kVK_ANSI_S: 284 return key.CodeS 285 case C.kVK_ANSI_T: 286 return key.CodeT 287 case C.kVK_ANSI_U: 288 return key.CodeU 289 case C.kVK_ANSI_V: 290 return key.CodeV 291 case C.kVK_ANSI_W: 292 return key.CodeW 293 case C.kVK_ANSI_X: 294 return key.CodeX 295 case C.kVK_ANSI_Y: 296 return key.CodeY 297 case C.kVK_ANSI_Z: 298 return key.CodeZ 299 case C.kVK_ANSI_1: 300 return key.Code1 301 case C.kVK_ANSI_2: 302 return key.Code2 303 case C.kVK_ANSI_3: 304 return key.Code3 305 case C.kVK_ANSI_4: 306 return key.Code4 307 case C.kVK_ANSI_5: 308 return key.Code5 309 case C.kVK_ANSI_6: 310 return key.Code6 311 case C.kVK_ANSI_7: 312 return key.Code7 313 case C.kVK_ANSI_8: 314 return key.Code8 315 case C.kVK_ANSI_9: 316 return key.Code9 317 case C.kVK_ANSI_0: 318 return key.Code0 319 // TODO: move the rest of these codes to constants in key.go 320 // if we are happy with them. 321 case C.kVK_Return: 322 return key.CodeReturnEnter 323 case C.kVK_Escape: 324 return key.CodeEscape 325 case C.kVK_Delete: 326 return key.CodeDeleteBackspace 327 case C.kVK_Tab: 328 return key.CodeTab 329 case C.kVK_Space: 330 return key.CodeSpacebar 331 case C.kVK_ANSI_Minus: 332 return key.CodeHyphenMinus 333 case C.kVK_ANSI_Equal: 334 return key.CodeEqualSign 335 case C.kVK_ANSI_LeftBracket: 336 return key.CodeLeftSquareBracket 337 case C.kVK_ANSI_RightBracket: 338 return key.CodeRightSquareBracket 339 case C.kVK_ANSI_Backslash: 340 return key.CodeBackslash 341 // 50: Keyboard Non-US "#" and ~ 342 case C.kVK_ANSI_Semicolon: 343 return key.CodeSemicolon 344 case C.kVK_ANSI_Quote: 345 return key.CodeApostrophe 346 case C.kVK_ANSI_Grave: 347 return key.CodeGraveAccent 348 case C.kVK_ANSI_Comma: 349 return key.CodeComma 350 case C.kVK_ANSI_Period: 351 return key.CodeFullStop 352 case C.kVK_ANSI_Slash: 353 return key.CodeSlash 354 case C.kVK_CapsLock: 355 return key.CodeCapsLock 356 case C.kVK_F1: 357 return key.CodeF1 358 case C.kVK_F2: 359 return key.CodeF2 360 case C.kVK_F3: 361 return key.CodeF3 362 case C.kVK_F4: 363 return key.CodeF4 364 case C.kVK_F5: 365 return key.CodeF5 366 case C.kVK_F6: 367 return key.CodeF6 368 case C.kVK_F7: 369 return key.CodeF7 370 case C.kVK_F8: 371 return key.CodeF8 372 case C.kVK_F9: 373 return key.CodeF9 374 case C.kVK_F10: 375 return key.CodeF10 376 case C.kVK_F11: 377 return key.CodeF11 378 case C.kVK_F12: 379 return key.CodeF12 380 // 70: PrintScreen 381 // 71: Scroll Lock 382 // 72: Pause 383 // 73: Insert 384 case C.kVK_Home: 385 return key.CodeHome 386 case C.kVK_PageUp: 387 return key.CodePageUp 388 case C.kVK_ForwardDelete: 389 return key.CodeDeleteForward 390 case C.kVK_End: 391 return key.CodeEnd 392 case C.kVK_PageDown: 393 return key.CodePageDown 394 case C.kVK_RightArrow: 395 return key.CodeRightArrow 396 case C.kVK_LeftArrow: 397 return key.CodeLeftArrow 398 case C.kVK_DownArrow: 399 return key.CodeDownArrow 400 case C.kVK_UpArrow: 401 return key.CodeUpArrow 402 case C.kVK_ANSI_KeypadClear: 403 return key.CodeKeypadNumLock 404 case C.kVK_ANSI_KeypadDivide: 405 return key.CodeKeypadSlash 406 case C.kVK_ANSI_KeypadMultiply: 407 return key.CodeKeypadAsterisk 408 case C.kVK_ANSI_KeypadMinus: 409 return key.CodeKeypadHyphenMinus 410 case C.kVK_ANSI_KeypadPlus: 411 return key.CodeKeypadPlusSign 412 case C.kVK_ANSI_KeypadEnter: 413 return key.CodeKeypadEnter 414 case C.kVK_ANSI_Keypad1: 415 return key.CodeKeypad1 416 case C.kVK_ANSI_Keypad2: 417 return key.CodeKeypad2 418 case C.kVK_ANSI_Keypad3: 419 return key.CodeKeypad3 420 case C.kVK_ANSI_Keypad4: 421 return key.CodeKeypad4 422 case C.kVK_ANSI_Keypad5: 423 return key.CodeKeypad5 424 case C.kVK_ANSI_Keypad6: 425 return key.CodeKeypad6 426 case C.kVK_ANSI_Keypad7: 427 return key.CodeKeypad7 428 case C.kVK_ANSI_Keypad8: 429 return key.CodeKeypad8 430 case C.kVK_ANSI_Keypad9: 431 return key.CodeKeypad9 432 case C.kVK_ANSI_Keypad0: 433 return key.CodeKeypad0 434 case C.kVK_ANSI_KeypadDecimal: 435 return key.CodeKeypadFullStop 436 case C.kVK_ANSI_KeypadEquals: 437 return key.CodeKeypadEqualSign 438 case C.kVK_F13: 439 return key.CodeF13 440 case C.kVK_F14: 441 return key.CodeF14 442 case C.kVK_F15: 443 return key.CodeF15 444 case C.kVK_F16: 445 return key.CodeF16 446 case C.kVK_F17: 447 return key.CodeF17 448 case C.kVK_F18: 449 return key.CodeF18 450 case C.kVK_F19: 451 return key.CodeF19 452 case C.kVK_F20: 453 return key.CodeF20 454 // 116: Keyboard Execute 455 case C.kVK_Help: 456 return key.CodeHelp 457 // 118: Keyboard Menu 458 // 119: Keyboard Select 459 // 120: Keyboard Stop 460 // 121: Keyboard Again 461 // 122: Keyboard Undo 462 // 123: Keyboard Cut 463 // 124: Keyboard Copy 464 // 125: Keyboard Paste 465 // 126: Keyboard Find 466 case C.kVK_Mute: 467 return key.CodeMute 468 case C.kVK_VolumeUp: 469 return key.CodeVolumeUp 470 case C.kVK_VolumeDown: 471 return key.CodeVolumeDown 472 // 130: Keyboard Locking Caps Lock 473 // 131: Keyboard Locking Num Lock 474 // 132: Keyboard Locking Scroll Lock 475 // 133: Keyboard Comma 476 // 134: Keyboard Equal Sign 477 // ...: Bunch of stuff 478 case C.kVK_Control: 479 return key.CodeLeftControl 480 case C.kVK_Shift: 481 return key.CodeLeftShift 482 case C.kVK_Option: 483 return key.CodeLeftAlt 484 case C.kVK_Command: 485 return key.CodeLeftGUI 486 case C.kVK_RightControl: 487 return key.CodeRightControl 488 case C.kVK_RightShift: 489 return key.CodeRightShift 490 case C.kVK_RightOption: 491 return key.CodeRightAlt 492 // TODO key.CodeRightGUI 493 default: 494 return key.CodeUnknown 495 } 496 }