github.com/as/shiny@v0.8.2/driver/gldriver/cocoa.go (about) 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 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 "github.com/as/shiny/driver/internal/lifecycler" 40 "github.com/as/shiny/event/key" 41 "github.com/as/shiny/event/lifecycle" 42 "github.com/as/shiny/event/mouse" 43 "github.com/as/shiny/event/paint" 44 "github.com/as/shiny/event/size" 45 "github.com/as/shiny/geom" 46 "github.com/as/shiny/gl" 47 "github.com/as/shiny/screen" 48 ) 49 50 var initThreadID C.uint64_t 51 52 func newWindow(opts *screen.NewWindowOptions) (uintptr, error) { 53 width, height := optsSize(opts) 54 55 title := C.CString(opts.GetTitle()) 56 defer C.free(unsafe.Pointer(title)) 57 58 return uintptr(C.doNewWindow(C.int(width), C.int(height), title)), nil 59 } 60 61 func initWindow(w *windowImpl) { 62 w.glctx, w.worker = gl.NewContext() 63 } 64 65 func showWindow(w *windowImpl) { 66 C.doShowWindow(C.uintptr_t(w.id)) 67 } 68 69 //export preparedOpenGL 70 func preparedOpenGL(id, ctx, vba uintptr) { 71 w := theScreen.window 72 w.ctx = ctx 73 go drawLoop(w, vba) 74 } 75 76 func closeWindow(id uintptr) { 77 C.doCloseWindow(C.uintptr_t(id)) 78 } 79 80 var mainCallback func(screen.Screen) 81 82 func main(f func(screen.Screen)) error { 83 initThreadID = C.threadID() 84 mainCallback = f 85 runtime.LockOSThread() 86 C.startDriver() 87 return nil 88 } 89 90 //export driverStarted 91 func driverStarted() { 92 go func() { 93 mainCallback(theScreen) 94 C.stopDriver() 95 }() 96 } 97 98 //export drawgl 99 func drawgl(id uintptr) { 100 w := theScreen.window 101 if w == nil { 102 panic("dead") 103 return // closing window 104 } 105 // TODO: is this necessary? 106 screen.SendPaint(paint.Event{External: true}) 107 <-w.drawDone 108 } 109 110 // drawLoop is the primary drawing loop. 111 // 112 // After Cocoa has created an NSWindow and called prepareOpenGL, 113 // it starts drawLoop on a locked goroutine to handle OpenGL calls. 114 // 115 // The screen is drawn every time a paint.Event is received, which can be 116 // triggered either by the user or by Cocoa via drawgl (for example, when 117 // the window is resized). 118 func drawLoop(w *windowImpl, vba uintptr) { 119 runtime.LockOSThread() 120 C.makeCurrentContext(C.uintptr_t(w.ctx.(uintptr))) 121 122 // Starting in OS X 10.11 (El Capitan), the vertex array is 123 // occasionally getting unbound when the context changes threads. 124 // 125 // Avoid this by binding it again. 126 C.glBindVertexArray(C.GLuint(vba)) 127 if errno := C.glGetError(); errno != 0 { 128 panic(fmt.Sprintf("gldriver: glBindVertexArray failed: %d", errno)) 129 } 130 131 workAvailable := w.worker.WorkAvailable() 132 133 // TODO(crawshaw): exit this goroutine on Release. 134 for { 135 select { 136 case <-workAvailable: 137 w.worker.DoWork() 138 case <-w.publish: 139 loop: 140 for { 141 select { 142 case <-workAvailable: 143 w.worker.DoWork() 144 default: 145 break loop 146 } 147 } 148 C.flushContext(C.uintptr_t(w.ctx.(uintptr))) 149 C.flushContext(C.uintptr_t(w.ctx.(uintptr))) 150 w.publishDone <- screen.PublishResult{} 151 } 152 } 153 } 154 155 //export setGeom 156 func setGeom(id uintptr, ppp float32, widthPx, heightPx int) { 157 w := theScreen.window 158 if w == nil { 159 panic("closing window") 160 return // closing window 161 } 162 163 w.sz = size.Event{ 164 WidthPx: widthPx, 165 HeightPx: heightPx, 166 WidthPt: geom.Pt(float32(widthPx) / ppp), 167 HeightPt: geom.Pt(float32(heightPx) / ppp), 168 PixelsPerPt: ppp, 169 } 170 screen.SendSize(w.sz) 171 } 172 173 //export windowClosing 174 func windowClosing(id uintptr) { 175 screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageDead}) 176 //sendLifecycle(id, (*lifecycler.State).SetDead, true) 177 } 178 179 func sendWindowEvent(id uintptr, e interface{}) { 180 log.Printf("sendWindowEvent: %#v, %#T\n", id, e) 181 //w.Send(e) 182 } 183 184 var mods = [...]struct { 185 flags uint32 186 code uint16 187 mod key.Modifiers 188 }{ 189 // Left and right variants of modifier keys have their own masks, 190 // but they are not documented. These were determined empirically. 191 {1<<17 | 0x102, C.kVK_Shift, key.ModShift}, 192 {1<<17 | 0x104, C.kVK_RightShift, key.ModShift}, 193 {1<<18 | 0x101, C.kVK_Control, key.ModControl}, 194 // TODO key.ControlRight 195 {1<<19 | 0x120, C.kVK_Option, key.ModAlt}, 196 {1<<19 | 0x140, C.kVK_RightOption, key.ModAlt}, 197 {1<<20 | 0x108, C.kVK_Command, key.ModMeta}, 198 {1<<20 | 0x110, C.kVK_Command, key.ModMeta}, // TODO: missing kVK_RightCommand 199 } 200 201 func cocoaMods(flags uint32) (m key.Modifiers) { 202 for _, mod := range mods { 203 if flags&mod.flags == mod.flags { 204 m |= mod.mod 205 } 206 } 207 return m 208 } 209 210 func cocoaMouseDir(ty int32) mouse.Direction { 211 switch ty { 212 case C.NSLeftMouseDown, C.NSRightMouseDown, C.NSOtherMouseDown: 213 return mouse.DirPress 214 case C.NSLeftMouseUp, C.NSRightMouseUp, C.NSOtherMouseUp: 215 return mouse.DirRelease 216 default: // dragged 217 return mouse.DirNone 218 } 219 } 220 221 func cocoaMouseButton(button int32) mouse.Button { 222 switch button { 223 case 0: 224 return mouse.ButtonLeft 225 case 1: 226 return mouse.ButtonRight 227 case 2: 228 return mouse.ButtonMiddle 229 default: 230 return mouse.ButtonNone 231 } 232 } 233 234 func scrollEvent(id uintptr, x, y, dx, dy float32, ty, buttonDeleteMe int32, flags uint32) { 235 button := mouse.ButtonWheelUp 236 if dy < 0 { 237 dy = -dy 238 button = mouse.ButtonWheelDown 239 } 240 screen.SendScroll(mouse.Event{ 241 X: x, 242 Y: y, 243 Button: button, 244 Direction: mouse.DirStep, 245 Modifiers: cocoaMods(flags), 246 }) 247 return 248 } 249 250 //export mouseEvent 251 func mouseEvent(id uintptr, x, y, dx, dy float32, ty, button int32, flags uint32) { 252 if ty == C.NSScrollWheel { 253 scrollEvent(id, x, y, dx, dy, ty, button, flags) 254 return 255 } 256 257 cmButton := mouse.ButtonNone 258 switch ty { 259 default: 260 cmButton = cocoaMouseButton(button) 261 case C.NSMouseMoved, 262 C.NSLeftMouseDragged, 263 C.NSRightMouseDragged, 264 C.NSOtherMouseDragged: 265 // No-op. 266 } 267 screen.SendMouse(mouse.Event{ 268 X: x, 269 Y: y, 270 Button: cmButton, 271 Direction: cocoaMouseDir(ty), 272 Modifiers: cocoaMods(flags), 273 }) 274 } 275 276 //export keyEvent 277 func keyEvent(id uintptr, runeVal rune, dir uint8, code uint16, flags uint32) { 278 screen.SendKey(key.Event{ 279 Rune: cocoaRune(runeVal), 280 Direction: key.Direction(dir), 281 Code: cocoaKeyCode(code), 282 Modifiers: cocoaMods(flags), 283 }) 284 } 285 286 //export flagEvent 287 func flagEvent(id uintptr, flags uint32) { 288 for _, mod := range mods { 289 if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags { 290 keyEvent(id, -1, C.NSKeyDown, mod.code, flags) 291 } 292 if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags { 293 keyEvent(id, -1, C.NSKeyUp, mod.code, flags) 294 } 295 } 296 lastFlags = flags 297 } 298 299 var lastFlags uint32 300 301 func sendLifecycle(id uintptr, setter func(*lifecycler.State, bool), val bool) { 302 303 } 304 305 func sendLifecycleAll(dead bool) { 306 screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageFocused}) 307 screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageVisible}) 308 } 309 310 //export lifecycleDeadAll 311 func lifecycleDeadAll() { sendLifecycleAll(true) } 312 313 //export lifecycleHideAll 314 func lifecycleHideAll() { sendLifecycleAll(false) } 315 316 //export lifecycleVisible 317 func lifecycleVisible(id uintptr, val bool) { 318 screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageVisible}) 319 } 320 321 //export lifecycleFocused 322 func lifecycleFocused(id uintptr, val bool) { 323 screen.SendLifecycle(lifecycle.Event{To: lifecycle.StageFocused}) 324 } 325 326 // cocoaRune marks the Carbon/Cocoa private-range unicode rune representing 327 // a non-unicode key event to -1, used for Rune in the key package. 328 // 329 // http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT 330 func cocoaRune(r rune) rune { 331 if '\uE000' <= r && r <= '\uF8FF' { 332 return -1 333 } 334 return r 335 } 336 337 // cocoaKeyCode converts a Carbon/Cocoa virtual key code number 338 // into the standard keycodes used by the key package. 339 // 340 // To get a sense of the key map, see the diagram on 341 // http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes 342 func cocoaKeyCode(vkcode uint16) key.Code { 343 switch vkcode { 344 case C.kVK_ANSI_A: 345 return key.CodeA 346 case C.kVK_ANSI_B: 347 return key.CodeB 348 case C.kVK_ANSI_C: 349 return key.CodeC 350 case C.kVK_ANSI_D: 351 return key.CodeD 352 case C.kVK_ANSI_E: 353 return key.CodeE 354 case C.kVK_ANSI_F: 355 return key.CodeF 356 case C.kVK_ANSI_G: 357 return key.CodeG 358 case C.kVK_ANSI_H: 359 return key.CodeH 360 case C.kVK_ANSI_I: 361 return key.CodeI 362 case C.kVK_ANSI_J: 363 return key.CodeJ 364 case C.kVK_ANSI_K: 365 return key.CodeK 366 case C.kVK_ANSI_L: 367 return key.CodeL 368 case C.kVK_ANSI_M: 369 return key.CodeM 370 case C.kVK_ANSI_N: 371 return key.CodeN 372 case C.kVK_ANSI_O: 373 return key.CodeO 374 case C.kVK_ANSI_P: 375 return key.CodeP 376 case C.kVK_ANSI_Q: 377 return key.CodeQ 378 case C.kVK_ANSI_R: 379 return key.CodeR 380 case C.kVK_ANSI_S: 381 return key.CodeS 382 case C.kVK_ANSI_T: 383 return key.CodeT 384 case C.kVK_ANSI_U: 385 return key.CodeU 386 case C.kVK_ANSI_V: 387 return key.CodeV 388 case C.kVK_ANSI_W: 389 return key.CodeW 390 case C.kVK_ANSI_X: 391 return key.CodeX 392 case C.kVK_ANSI_Y: 393 return key.CodeY 394 case C.kVK_ANSI_Z: 395 return key.CodeZ 396 case C.kVK_ANSI_1: 397 return key.Code1 398 case C.kVK_ANSI_2: 399 return key.Code2 400 case C.kVK_ANSI_3: 401 return key.Code3 402 case C.kVK_ANSI_4: 403 return key.Code4 404 case C.kVK_ANSI_5: 405 return key.Code5 406 case C.kVK_ANSI_6: 407 return key.Code6 408 case C.kVK_ANSI_7: 409 return key.Code7 410 case C.kVK_ANSI_8: 411 return key.Code8 412 case C.kVK_ANSI_9: 413 return key.Code9 414 case C.kVK_ANSI_0: 415 return key.Code0 416 // TODO: move the rest of these codes to constants in key.go 417 // if we are happy with them. 418 case C.kVK_Return: 419 return key.CodeReturnEnter 420 case C.kVK_Escape: 421 return key.CodeEscape 422 case C.kVK_Delete: 423 return key.CodeDeleteBackspace 424 case C.kVK_Tab: 425 return key.CodeTab 426 case C.kVK_Space: 427 return key.CodeSpacebar 428 case C.kVK_ANSI_Minus: 429 return key.CodeHyphenMinus 430 case C.kVK_ANSI_Equal: 431 return key.CodeEqualSign 432 case C.kVK_ANSI_LeftBracket: 433 return key.CodeLeftSquareBracket 434 case C.kVK_ANSI_RightBracket: 435 return key.CodeRightSquareBracket 436 case C.kVK_ANSI_Backslash: 437 return key.CodeBackslash 438 // 50: Keyboard Non-US "#" and ~ 439 case C.kVK_ANSI_Semicolon: 440 return key.CodeSemicolon 441 case C.kVK_ANSI_Quote: 442 return key.CodeApostrophe 443 case C.kVK_ANSI_Grave: 444 return key.CodeGraveAccent 445 case C.kVK_ANSI_Comma: 446 return key.CodeComma 447 case C.kVK_ANSI_Period: 448 return key.CodeFullStop 449 case C.kVK_ANSI_Slash: 450 return key.CodeSlash 451 case C.kVK_CapsLock: 452 return key.CodeCapsLock 453 case C.kVK_F1: 454 return key.CodeF1 455 case C.kVK_F2: 456 return key.CodeF2 457 case C.kVK_F3: 458 return key.CodeF3 459 case C.kVK_F4: 460 return key.CodeF4 461 case C.kVK_F5: 462 return key.CodeF5 463 case C.kVK_F6: 464 return key.CodeF6 465 case C.kVK_F7: 466 return key.CodeF7 467 case C.kVK_F8: 468 return key.CodeF8 469 case C.kVK_F9: 470 return key.CodeF9 471 case C.kVK_F10: 472 return key.CodeF10 473 case C.kVK_F11: 474 return key.CodeF11 475 case C.kVK_F12: 476 return key.CodeF12 477 // 70: PrintScreen 478 // 71: Scroll Lock 479 // 72: Pause 480 // 73: Insert 481 case C.kVK_Home: 482 return key.CodeHome 483 case C.kVK_PageUp: 484 return key.CodePageUp 485 case C.kVK_ForwardDelete: 486 return key.CodeDeleteForward 487 case C.kVK_End: 488 return key.CodeEnd 489 case C.kVK_PageDown: 490 return key.CodePageDown 491 case C.kVK_RightArrow: 492 return key.CodeRightArrow 493 case C.kVK_LeftArrow: 494 return key.CodeLeftArrow 495 case C.kVK_DownArrow: 496 return key.CodeDownArrow 497 case C.kVK_UpArrow: 498 return key.CodeUpArrow 499 case C.kVK_ANSI_KeypadClear: 500 return key.CodeKeypadNumLock 501 case C.kVK_ANSI_KeypadDivide: 502 return key.CodeKeypadSlash 503 case C.kVK_ANSI_KeypadMultiply: 504 return key.CodeKeypadAsterisk 505 case C.kVK_ANSI_KeypadMinus: 506 return key.CodeKeypadHyphenMinus 507 case C.kVK_ANSI_KeypadPlus: 508 return key.CodeKeypadPlusSign 509 case C.kVK_ANSI_KeypadEnter: 510 return key.CodeKeypadEnter 511 case C.kVK_ANSI_Keypad1: 512 return key.CodeKeypad1 513 case C.kVK_ANSI_Keypad2: 514 return key.CodeKeypad2 515 case C.kVK_ANSI_Keypad3: 516 return key.CodeKeypad3 517 case C.kVK_ANSI_Keypad4: 518 return key.CodeKeypad4 519 case C.kVK_ANSI_Keypad5: 520 return key.CodeKeypad5 521 case C.kVK_ANSI_Keypad6: 522 return key.CodeKeypad6 523 case C.kVK_ANSI_Keypad7: 524 return key.CodeKeypad7 525 case C.kVK_ANSI_Keypad8: 526 return key.CodeKeypad8 527 case C.kVK_ANSI_Keypad9: 528 return key.CodeKeypad9 529 case C.kVK_ANSI_Keypad0: 530 return key.CodeKeypad0 531 case C.kVK_ANSI_KeypadDecimal: 532 return key.CodeKeypadFullStop 533 case C.kVK_ANSI_KeypadEquals: 534 return key.CodeKeypadEqualSign 535 case C.kVK_F13: 536 return key.CodeF13 537 case C.kVK_F14: 538 return key.CodeF14 539 case C.kVK_F15: 540 return key.CodeF15 541 case C.kVK_F16: 542 return key.CodeF16 543 case C.kVK_F17: 544 return key.CodeF17 545 case C.kVK_F18: 546 return key.CodeF18 547 case C.kVK_F19: 548 return key.CodeF19 549 case C.kVK_F20: 550 return key.CodeF20 551 // 116: Keyboard Execute 552 case C.kVK_Help: 553 return key.CodeHelp 554 // 118: Keyboard Menu 555 // 119: Keyboard Select 556 // 120: Keyboard Stop 557 // 121: Keyboard Again 558 // 122: Keyboard Undo 559 // 123: Keyboard Cut 560 // 124: Keyboard Copy 561 // 125: Keyboard Paste 562 // 126: Keyboard Find 563 case C.kVK_Mute: 564 return key.CodeMute 565 case C.kVK_VolumeUp: 566 return key.CodeVolumeUp 567 case C.kVK_VolumeDown: 568 return key.CodeVolumeDown 569 // 130: Keyboard Locking Caps Lock 570 // 131: Keyboard Locking Num Lock 571 // 132: Keyboard Locking Scroll Lock 572 // 133: Keyboard Comma 573 // 134: Keyboard Equal Sign 574 // ...: Bunch of stuff 575 case C.kVK_Control: 576 return key.CodeLeftControl 577 case C.kVK_Shift: 578 return key.CodeLeftShift 579 case C.kVK_Option: 580 return key.CodeLeftAlt 581 case C.kVK_Command: 582 return key.CodeLeftGUI 583 case C.kVK_RightControl: 584 return key.CodeRightControl 585 case C.kVK_RightShift: 586 return key.CodeRightShift 587 case C.kVK_RightOption: 588 return key.CodeRightAlt 589 // TODO key.CodeRightGUI 590 default: 591 return key.CodeUnknown 592 } 593 } 594 595 func surfaceCreate() error { 596 return errors.New("gldriver: surface creation not implemented on darwin") 597 }