github.com/goki/mobile@v0.0.0-20230707090321-193544ec5700/app/darwin_desktop.go (about)

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