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 }