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