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