os_darwin.go raw

   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/p9c/p9/pkg/gel/gio/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