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  }