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 }