os_darwin.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. package app
  3. /*
  4. #include <Foundation/Foundation.h>
  5. __attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
  6. __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
  7. __attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
  8. __attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
  9. __attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
  10. __attribute__ ((visibility ("hidden"))) void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did);
  11. __attribute__ ((visibility ("hidden"))) void gio_hideCursor();
  12. __attribute__ ((visibility ("hidden"))) void gio_showCursor();
  13. __attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
  14. static bool isMainThread() {
  15. return [NSThread isMainThread];
  16. }
  17. static NSUInteger nsstringLength(CFTypeRef cstr) {
  18. NSString *str = (__bridge NSString *)cstr;
  19. return [str length];
  20. }
  21. static void nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
  22. NSString *str = (__bridge NSString *)cstr;
  23. [str getCharacters:chars range:NSMakeRange(loc, length)];
  24. }
  25. static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
  26. @autoreleasepool {
  27. NSString *s = [NSString string];
  28. if (length > 0) {
  29. s = [NSString stringWithCharacters:chars length:length];
  30. }
  31. return CFBridgingRetain(s);
  32. }
  33. }
  34. */
  35. import "C"
  36. import (
  37. "errors"
  38. "sync"
  39. "sync/atomic"
  40. "time"
  41. "unicode/utf16"
  42. "unsafe"
  43. "gioui.org/io/pointer"
  44. )
  45. // displayLink is the state for a display link (CVDisplayLinkRef on macOS,
  46. // CADisplayLink on iOS). It runs a state-machine goroutine that keeps the
  47. // display link running for a while after being stopped to avoid the thread
  48. // start/stop overhead and because the CVDisplayLink sometimes fails to
  49. // start, stop and start again within a short duration.
  50. type displayLink struct {
  51. callback func()
  52. // states is for starting or stopping the display link.
  53. states chan bool
  54. // done is closed when the display link is destroyed.
  55. done chan struct{}
  56. // dids receives the display id when the callback owner is moved
  57. // to a different screen.
  58. dids chan uint64
  59. // running tracks the desired state of the link. running is accessed
  60. // with atomic.
  61. running uint32
  62. }
  63. // displayLinks maps CFTypeRefs to *displayLinks.
  64. var displayLinks sync.Map
  65. var mainFuncs = make(chan func(), 1)
  66. func isMainThread() bool {
  67. return bool(C.isMainThread())
  68. }
  69. // runOnMain runs the function on the main thread.
  70. func runOnMain(f func()) {
  71. if isMainThread() {
  72. f()
  73. return
  74. }
  75. go func() {
  76. mainFuncs <- f
  77. C.gio_wakeupMainThread()
  78. }()
  79. }
  80. //export gio_dispatchMainFuncs
  81. func gio_dispatchMainFuncs() {
  82. for {
  83. select {
  84. case f := <-mainFuncs:
  85. f()
  86. default:
  87. return
  88. }
  89. }
  90. }
  91. // nsstringToString converts a NSString to a Go string.
  92. func nsstringToString(str C.CFTypeRef) string {
  93. if str == 0 {
  94. return ""
  95. }
  96. n := C.nsstringLength(str)
  97. if n == 0 {
  98. return ""
  99. }
  100. chars := make([]uint16, n)
  101. C.nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
  102. utf8 := utf16.Decode(chars)
  103. return string(utf8)
  104. }
  105. // stringToNSString converts a Go string to a retained NSString.
  106. func stringToNSString(str string) C.CFTypeRef {
  107. u16 := utf16.Encode([]rune(str))
  108. var chars *C.unichar
  109. if len(u16) > 0 {
  110. chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
  111. }
  112. return C.newNSString(chars, C.NSUInteger(len(u16)))
  113. }
  114. func newDisplayLink(callback func()) (*displayLink, error) {
  115. d := &displayLink{
  116. callback: callback,
  117. done: make(chan struct{}),
  118. states: make(chan bool),
  119. dids: make(chan uint64),
  120. }
  121. dl := C.gio_createDisplayLink()
  122. if dl == 0 {
  123. return nil, errors.New("app: failed to create display link")
  124. }
  125. go d.run(dl)
  126. return d, nil
  127. }
  128. func (d *displayLink) run(dl C.CFTypeRef) {
  129. defer C.gio_releaseDisplayLink(dl)
  130. displayLinks.Store(dl, d)
  131. defer displayLinks.Delete(dl)
  132. var stopTimer *time.Timer
  133. var tchan <-chan time.Time
  134. started := false
  135. for {
  136. select {
  137. case <-tchan:
  138. tchan = nil
  139. started = false
  140. C.gio_stopDisplayLink(dl)
  141. case start := <-d.states:
  142. switch {
  143. case !start && tchan == nil:
  144. // stopTimeout is the delay before stopping the display link to
  145. // avoid the overhead of frequently starting and stopping the
  146. // link thread.
  147. const stopTimeout = 500 * time.Millisecond
  148. if stopTimer == nil {
  149. stopTimer = time.NewTimer(stopTimeout)
  150. } else {
  151. // stopTimer is always drained when tchan == nil.
  152. stopTimer.Reset(stopTimeout)
  153. }
  154. tchan = stopTimer.C
  155. atomic.StoreUint32(&d.running, 0)
  156. case start:
  157. if tchan != nil && !stopTimer.Stop() {
  158. <-tchan
  159. }
  160. tchan = nil
  161. atomic.StoreUint32(&d.running, 1)
  162. if !started {
  163. started = true
  164. C.gio_startDisplayLink(dl)
  165. }
  166. }
  167. case did := <-d.dids:
  168. C.gio_setDisplayLinkDisplay(dl, C.uint64_t(did))
  169. case <-d.done:
  170. return
  171. }
  172. }
  173. }
  174. func (d *displayLink) Start() {
  175. d.states <- true
  176. }
  177. func (d *displayLink) Stop() {
  178. d.states <- false
  179. }
  180. func (d *displayLink) Close() {
  181. close(d.done)
  182. }
  183. func (d *displayLink) SetDisplayID(did uint64) {
  184. d.dids <- did
  185. }
  186. //export gio_onFrameCallback
  187. func gio_onFrameCallback(ref C.CFTypeRef) {
  188. d, exists := displayLinks.Load(ref)
  189. if !exists {
  190. return
  191. }
  192. dl := d.(*displayLink)
  193. if atomic.LoadUint32(&dl.running) != 0 {
  194. dl.callback()
  195. }
  196. }
  197. var macosCursorID = [...]byte{
  198. pointer.CursorDefault: 0,
  199. pointer.CursorNone: 1,
  200. pointer.CursorText: 2,
  201. pointer.CursorVerticalText: 3,
  202. pointer.CursorPointer: 4,
  203. pointer.CursorCrosshair: 5,
  204. pointer.CursorAllScroll: 6,
  205. pointer.CursorColResize: 7,
  206. pointer.CursorRowResize: 8,
  207. pointer.CursorGrab: 9,
  208. pointer.CursorGrabbing: 10,
  209. pointer.CursorNotAllowed: 11,
  210. pointer.CursorWait: 12,
  211. pointer.CursorProgress: 13,
  212. pointer.CursorNorthWestResize: 14,
  213. pointer.CursorNorthEastResize: 15,
  214. pointer.CursorSouthWestResize: 16,
  215. pointer.CursorSouthEastResize: 17,
  216. pointer.CursorNorthSouthResize: 18,
  217. pointer.CursorEastWestResize: 19,
  218. pointer.CursorWestResize: 20,
  219. pointer.CursorEastResize: 21,
  220. pointer.CursorNorthResize: 22,
  221. pointer.CursorSouthResize: 23,
  222. pointer.CursorNorthEastSouthWestResize: 24,
  223. pointer.CursorNorthWestSouthEastResize: 25,
  224. }
  225. // windowSetCursor updates the cursor from the current one to a new one
  226. // and returns the new one.
  227. func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
  228. if from == to {
  229. return to
  230. }
  231. if to == pointer.CursorNone {
  232. C.gio_hideCursor()
  233. return to
  234. }
  235. if from == pointer.CursorNone {
  236. C.gio_showCursor()
  237. }
  238. C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
  239. return to
  240. }
  241. func (w *window) wakeup() {
  242. runOnMain(func() {
  243. w.loop.Wakeup()
  244. w.loop.FlushEvents()
  245. })
  246. }