darwin_ios.go (5134B)
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 ios 7 8 package app 9 10 /* 11 #cgo CFLAGS: -x objective-c -DGL_SILENCE_DEPRECATION 12 #cgo LDFLAGS: -framework Foundation -framework UIKit -framework GLKit -framework OpenGLES -framework QuartzCore 13 #include <sys/utsname.h> 14 #include <stdint.h> 15 #include <pthread.h> 16 #include <UIKit/UIDevice.h> 17 #import <GLKit/GLKit.h> 18 19 extern struct utsname sysInfo; 20 21 void runApp(void); 22 void makeCurrentContext(GLintptr ctx); 23 void swapBuffers(GLintptr ctx); 24 uint64_t threadID(); 25 */ 26 import "C" 27 import ( 28 "log" 29 "runtime" 30 "strings" 31 "sync" 32 33 "golang.org/x/mobile/event/lifecycle" 34 "golang.org/x/mobile/event/paint" 35 "golang.org/x/mobile/event/size" 36 "golang.org/x/mobile/event/touch" 37 "golang.org/x/mobile/geom" 38 ) 39 40 var initThreadID uint64 41 42 func init() { 43 // Lock the goroutine responsible for initialization to an OS thread. 44 // This means the goroutine running main (and calling the run function 45 // below) is locked to the OS thread that started the program. This is 46 // necessary for the correct delivery of UIKit events to the process. 47 // 48 // A discussion on this topic: 49 // https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ 50 runtime.LockOSThread() 51 initThreadID = uint64(C.threadID()) 52 } 53 54 func main(f func(App)) { 55 if tid := uint64(C.threadID()); tid != initThreadID { 56 log.Fatalf("app.Run called on thread %d, but app.init ran on %d", tid, initThreadID) 57 } 58 59 go func() { 60 f(theApp) 61 // TODO(crawshaw): trigger runApp to return 62 }() 63 C.runApp() 64 panic("unexpected return from app.runApp") 65 } 66 67 var pixelsPerPt float32 68 var screenScale int // [UIScreen mainScreen].scale, either 1, 2, or 3. 69 70 //export setScreen 71 func setScreen(scale int) { 72 C.uname(&C.sysInfo) 73 name := C.GoString(&C.sysInfo.machine[0]) 74 75 var v float32 76 77 switch { 78 case strings.HasPrefix(name, "iPhone"): 79 v = 163 80 case strings.HasPrefix(name, "iPad"): 81 // TODO: is there a better way to distinguish the iPad Mini? 82 switch name { 83 case "iPad2,5", "iPad2,6", "iPad2,7", "iPad4,4", "iPad4,5", "iPad4,6", "iPad4,7": 84 v = 163 // iPad Mini 85 default: 86 v = 132 87 } 88 default: 89 v = 163 // names like i386 and x86_64 are the simulator 90 } 91 92 if v == 0 { 93 log.Printf("unknown machine: %s", name) 94 v = 163 // emergency fallback 95 } 96 97 pixelsPerPt = v * float32(scale) / 72 98 screenScale = scale 99 } 100 101 //export updateConfig 102 func updateConfig(width, height, orientation int32) { 103 o := size.OrientationUnknown 104 switch orientation { 105 case C.UIDeviceOrientationPortrait, C.UIDeviceOrientationPortraitUpsideDown: 106 o = size.OrientationPortrait 107 case C.UIDeviceOrientationLandscapeLeft, C.UIDeviceOrientationLandscapeRight: 108 o = size.OrientationLandscape 109 } 110 widthPx := screenScale * int(width) 111 heightPx := screenScale * int(height) 112 theApp.eventsIn <- size.Event{ 113 WidthPx: widthPx, 114 HeightPx: heightPx, 115 WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), 116 HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), 117 PixelsPerPt: pixelsPerPt, 118 Orientation: o, 119 } 120 theApp.eventsIn <- paint.Event{External: true} 121 } 122 123 // touchIDs is the current active touches. The position in the array 124 // is the ID, the value is the UITouch* pointer value. 125 // 126 // It is widely reported that the iPhone can handle up to 5 simultaneous 127 // touch events, while the iPad can handle 11. 128 var touchIDs [11]uintptr 129 130 var touchEvents struct { 131 sync.Mutex 132 pending []touch.Event 133 } 134 135 //export sendTouch 136 func sendTouch(cTouch, cTouchType uintptr, x, y float32) { 137 id := -1 138 for i, val := range touchIDs { 139 if val == cTouch { 140 id = i 141 break 142 } 143 } 144 if id == -1 { 145 for i, val := range touchIDs { 146 if val == 0 { 147 touchIDs[i] = cTouch 148 id = i 149 break 150 } 151 } 152 if id == -1 { 153 panic("out of touchIDs") 154 } 155 } 156 157 t := touch.Type(cTouchType) 158 if t == touch.TypeEnd { 159 touchIDs[id] = 0 160 } 161 162 theApp.eventsIn <- touch.Event{ 163 X: x, 164 Y: y, 165 Sequence: touch.Sequence(id), 166 Type: t, 167 } 168 } 169 170 //export lifecycleDead 171 func lifecycleDead() { theApp.sendLifecycle(lifecycle.StageDead) } 172 173 //export lifecycleAlive 174 func lifecycleAlive() { theApp.sendLifecycle(lifecycle.StageAlive) } 175 176 //export lifecycleVisible 177 func lifecycleVisible() { theApp.sendLifecycle(lifecycle.StageVisible) } 178 179 //export lifecycleFocused 180 func lifecycleFocused() { theApp.sendLifecycle(lifecycle.StageFocused) } 181 182 //export startloop 183 func startloop(ctx C.GLintptr) { 184 go theApp.loop(ctx) 185 } 186 187 // loop is the primary drawing loop. 188 // 189 // After UIKit has captured the initial OS thread for processing UIKit 190 // events in runApp, it starts loop on another goroutine. It is locked 191 // to an OS thread for its OpenGL context. 192 func (a *app) loop(ctx C.GLintptr) { 193 runtime.LockOSThread() 194 C.makeCurrentContext(ctx) 195 196 workAvailable := a.worker.WorkAvailable() 197 198 for { 199 select { 200 case <-workAvailable: 201 a.worker.DoWork() 202 case <-theApp.publish: 203 loop1: 204 for { 205 select { 206 case <-workAvailable: 207 a.worker.DoWork() 208 default: 209 break loop1 210 } 211 } 212 C.swapBuffers(ctx) 213 theApp.publishResult <- PublishResult{} 214 } 215 } 216 }