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 }