123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package app
- /*
- #include <Foundation/Foundation.h>
- __attribute__ ((visibility ("hidden"))) void gio_wakeupMainThread(void);
- __attribute__ ((visibility ("hidden"))) CFTypeRef gio_createDisplayLink(void);
- __attribute__ ((visibility ("hidden"))) void gio_releaseDisplayLink(CFTypeRef dl);
- __attribute__ ((visibility ("hidden"))) int gio_startDisplayLink(CFTypeRef dl);
- __attribute__ ((visibility ("hidden"))) int gio_stopDisplayLink(CFTypeRef dl);
- __attribute__ ((visibility ("hidden"))) void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did);
- __attribute__ ((visibility ("hidden"))) void gio_hideCursor();
- __attribute__ ((visibility ("hidden"))) void gio_showCursor();
- __attribute__ ((visibility ("hidden"))) void gio_setCursor(NSUInteger curID);
- static bool isMainThread() {
- return [NSThread isMainThread];
- }
- static NSUInteger nsstringLength(CFTypeRef cstr) {
- NSString *str = (__bridge NSString *)cstr;
- return [str length];
- }
- static void nsstringGetCharacters(CFTypeRef cstr, unichar *chars, NSUInteger loc, NSUInteger length) {
- NSString *str = (__bridge NSString *)cstr;
- [str getCharacters:chars range:NSMakeRange(loc, length)];
- }
- static CFTypeRef newNSString(unichar *chars, NSUInteger length) {
- @autoreleasepool {
- NSString *s = [NSString string];
- if (length > 0) {
- s = [NSString stringWithCharacters:chars length:length];
- }
- return CFBridgingRetain(s);
- }
- }
- */
- import "C"
- import (
- "errors"
- "sync"
- "sync/atomic"
- "time"
- "unicode/utf16"
- "unsafe"
- "gioui.org/io/pointer"
- )
- // displayLink is the state for a display link (CVDisplayLinkRef on macOS,
- // CADisplayLink on iOS). It runs a state-machine goroutine that keeps the
- // display link running for a while after being stopped to avoid the thread
- // start/stop overhead and because the CVDisplayLink sometimes fails to
- // start, stop and start again within a short duration.
- type displayLink struct {
- callback func()
- // states is for starting or stopping the display link.
- states chan bool
- // done is closed when the display link is destroyed.
- done chan struct{}
- // dids receives the display id when the callback owner is moved
- // to a different screen.
- dids chan uint64
- // running tracks the desired state of the link. running is accessed
- // with atomic.
- running uint32
- }
- // displayLinks maps CFTypeRefs to *displayLinks.
- var displayLinks sync.Map
- var mainFuncs = make(chan func(), 1)
- func isMainThread() bool {
- return bool(C.isMainThread())
- }
- // runOnMain runs the function on the main thread.
- func runOnMain(f func()) {
- if isMainThread() {
- f()
- return
- }
- go func() {
- mainFuncs <- f
- C.gio_wakeupMainThread()
- }()
- }
- //export gio_dispatchMainFuncs
- func gio_dispatchMainFuncs() {
- for {
- select {
- case f := <-mainFuncs:
- f()
- default:
- return
- }
- }
- }
- // nsstringToString converts a NSString to a Go string.
- func nsstringToString(str C.CFTypeRef) string {
- if str == 0 {
- return ""
- }
- n := C.nsstringLength(str)
- if n == 0 {
- return ""
- }
- chars := make([]uint16, n)
- C.nsstringGetCharacters(str, (*C.unichar)(unsafe.Pointer(&chars[0])), 0, n)
- utf8 := utf16.Decode(chars)
- return string(utf8)
- }
- // stringToNSString converts a Go string to a retained NSString.
- func stringToNSString(str string) C.CFTypeRef {
- u16 := utf16.Encode([]rune(str))
- var chars *C.unichar
- if len(u16) > 0 {
- chars = (*C.unichar)(unsafe.Pointer(&u16[0]))
- }
- return C.newNSString(chars, C.NSUInteger(len(u16)))
- }
- func newDisplayLink(callback func()) (*displayLink, error) {
- d := &displayLink{
- callback: callback,
- done: make(chan struct{}),
- states: make(chan bool),
- dids: make(chan uint64),
- }
- dl := C.gio_createDisplayLink()
- if dl == 0 {
- return nil, errors.New("app: failed to create display link")
- }
- go d.run(dl)
- return d, nil
- }
- func (d *displayLink) run(dl C.CFTypeRef) {
- defer C.gio_releaseDisplayLink(dl)
- displayLinks.Store(dl, d)
- defer displayLinks.Delete(dl)
- var stopTimer *time.Timer
- var tchan <-chan time.Time
- started := false
- for {
- select {
- case <-tchan:
- tchan = nil
- started = false
- C.gio_stopDisplayLink(dl)
- case start := <-d.states:
- switch {
- case !start && tchan == nil:
- // stopTimeout is the delay before stopping the display link to
- // avoid the overhead of frequently starting and stopping the
- // link thread.
- const stopTimeout = 500 * time.Millisecond
- if stopTimer == nil {
- stopTimer = time.NewTimer(stopTimeout)
- } else {
- // stopTimer is always drained when tchan == nil.
- stopTimer.Reset(stopTimeout)
- }
- tchan = stopTimer.C
- atomic.StoreUint32(&d.running, 0)
- case start:
- if tchan != nil && !stopTimer.Stop() {
- <-tchan
- }
- tchan = nil
- atomic.StoreUint32(&d.running, 1)
- if !started {
- started = true
- C.gio_startDisplayLink(dl)
- }
- }
- case did := <-d.dids:
- C.gio_setDisplayLinkDisplay(dl, C.uint64_t(did))
- case <-d.done:
- return
- }
- }
- }
- func (d *displayLink) Start() {
- d.states <- true
- }
- func (d *displayLink) Stop() {
- d.states <- false
- }
- func (d *displayLink) Close() {
- close(d.done)
- }
- func (d *displayLink) SetDisplayID(did uint64) {
- d.dids <- did
- }
- //export gio_onFrameCallback
- func gio_onFrameCallback(ref C.CFTypeRef) {
- d, exists := displayLinks.Load(ref)
- if !exists {
- return
- }
- dl := d.(*displayLink)
- if atomic.LoadUint32(&dl.running) != 0 {
- dl.callback()
- }
- }
- var macosCursorID = [...]byte{
- pointer.CursorDefault: 0,
- pointer.CursorNone: 1,
- pointer.CursorText: 2,
- pointer.CursorVerticalText: 3,
- pointer.CursorPointer: 4,
- pointer.CursorCrosshair: 5,
- pointer.CursorAllScroll: 6,
- pointer.CursorColResize: 7,
- pointer.CursorRowResize: 8,
- pointer.CursorGrab: 9,
- pointer.CursorGrabbing: 10,
- pointer.CursorNotAllowed: 11,
- pointer.CursorWait: 12,
- pointer.CursorProgress: 13,
- pointer.CursorNorthWestResize: 14,
- pointer.CursorNorthEastResize: 15,
- pointer.CursorSouthWestResize: 16,
- pointer.CursorSouthEastResize: 17,
- pointer.CursorNorthSouthResize: 18,
- pointer.CursorEastWestResize: 19,
- pointer.CursorWestResize: 20,
- pointer.CursorEastResize: 21,
- pointer.CursorNorthResize: 22,
- pointer.CursorSouthResize: 23,
- pointer.CursorNorthEastSouthWestResize: 24,
- pointer.CursorNorthWestSouthEastResize: 25,
- }
- // windowSetCursor updates the cursor from the current one to a new one
- // and returns the new one.
- func windowSetCursor(from, to pointer.Cursor) pointer.Cursor {
- if from == to {
- return to
- }
- if to == pointer.CursorNone {
- C.gio_hideCursor()
- return to
- }
- if from == pointer.CursorNone {
- C.gio_showCursor()
- }
- C.gio_setCursor(C.NSUInteger(macosCursorID[to]))
- return to
- }
- func (w *window) wakeup() {
- runOnMain(func() {
- w.loop.Wakeup()
- w.loop.FlushEvents()
- })
- }
|