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