darwin_ios.go 5.1 KB

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