aboutsummaryrefslogtreecommitdiff
path: root/vendor/gioui.org/app/os_darwin.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gioui.org/app/os_darwin.go')
-rw-r--r--vendor/gioui.org/app/os_darwin.go237
1 files changed, 237 insertions, 0 deletions
diff --git a/vendor/gioui.org/app/os_darwin.go b/vendor/gioui.org/app/os_darwin.go
new file mode 100644
index 0000000..c0554db
--- /dev/null
+++ b/vendor/gioui.org/app/os_darwin.go
@@ -0,0 +1,237 @@
+// 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)];
+}
+*/
+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)
+
+// runOnMain runs the function on the main thread.
+func runOnMain(f func()) {
+ if C.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, and
+// releases the original string.
+func nsstringToString(str C.CFTypeRef) string {
+ if str == 0 {
+ return ""
+ }
+ defer C.CFRelease(str)
+ 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)
+}
+
+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(dl C.CFTypeRef) {
+ if d, exists := displayLinks.Load(dl); exists {
+ d := d.(*displayLink)
+ if atomic.LoadUint32(&d.running) != 0 {
+ d.callback()
+ }
+ }
+}
+
+// windowSetCursor updates the cursor from the current one to a new one
+// and returns the new one.
+func windowSetCursor(from, to pointer.CursorName) pointer.CursorName {
+ if from == to {
+ return to
+ }
+ var curID int
+ switch to {
+ default:
+ to = pointer.CursorDefault
+ fallthrough
+ case pointer.CursorDefault:
+ curID = 1
+ case pointer.CursorText:
+ curID = 2
+ case pointer.CursorPointer:
+ curID = 3
+ case pointer.CursorCrossHair:
+ curID = 4
+ case pointer.CursorColResize:
+ curID = 5
+ case pointer.CursorRowResize:
+ curID = 6
+ case pointer.CursorGrab:
+ curID = 7
+ case pointer.CursorNone:
+ C.gio_hideCursor()
+ return to
+ }
+ if from == pointer.CursorNone {
+ C.gio_showCursor()
+ }
+ C.gio_setCursor(C.NSUInteger(curID))
+ return to
+}
+
+func (w *window) Wakeup() {
+ runOnMain(func() {
+ w.w.Event(wakeupEvent{})
+ })
+}