darwin_ios.go 5.0 KB

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