github.com/corfe83/mobile@v0.0.0-20220928034243-9edc37f43fac/app/darwin_ios.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build darwin && ios
     6  // +build darwin,ios
     7  
     8  package app
     9  
    10  /*
    11  #cgo CFLAGS: -x objective-c -DGL_SILENCE_DEPRECATION
    12  #cgo LDFLAGS: -framework Foundation -framework UIKit -framework GLKit -framework OpenGLES -framework QuartzCore
    13  #include <sys/utsname.h>
    14  #include <stdint.h>
    15  #include <pthread.h>
    16  #include <UIKit/UIDevice.h>
    17  #import <GLKit/GLKit.h>
    18  
    19  extern struct utsname sysInfo;
    20  
    21  void runApp(void);
    22  void makeCurrentContext(GLintptr ctx);
    23  void swapBuffers(GLintptr ctx);
    24  uint64_t threadID();
    25  */
    26  import "C"
    27  import (
    28  	"log"
    29  	"runtime"
    30  	"strings"
    31  	"sync"
    32  
    33  	"golang.org/x/mobile/event/lifecycle"
    34  	"golang.org/x/mobile/event/paint"
    35  	"golang.org/x/mobile/event/size"
    36  	"golang.org/x/mobile/event/touch"
    37  	"golang.org/x/mobile/geom"
    38  )
    39  
    40  var initThreadID uint64
    41  
    42  func init() {
    43  	// Lock the goroutine responsible for initialization to an OS thread.
    44  	// This means the goroutine running main (and calling the run function
    45  	// below) is locked to the OS thread that started the program. This is
    46  	// necessary for the correct delivery of UIKit events to the process.
    47  	//
    48  	// A discussion on this topic:
    49  	// https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ
    50  	runtime.LockOSThread()
    51  	initThreadID = uint64(C.threadID())
    52  }
    53  
    54  func main(f func(App)) {
    55  	if tid := uint64(C.threadID()); tid != initThreadID {
    56  		log.Fatalf("app.Run called on thread %d, but app.init ran on %d", tid, initThreadID)
    57  	}
    58  
    59  	go func() {
    60  		f(theApp)
    61  		// TODO(crawshaw): trigger runApp to return
    62  	}()
    63  	C.runApp()
    64  	panic("unexpected return from app.runApp")
    65  }
    66  
    67  var pixelsPerPt float32
    68  var screenScale int // [UIScreen mainScreen].scale, either 1, 2, or 3.
    69  
    70  //export setScreen
    71  func setScreen(scale int) {
    72  	C.uname(&C.sysInfo)
    73  	name := C.GoString(&C.sysInfo.machine[0])
    74  
    75  	var v float32
    76  
    77  	switch {
    78  	case strings.HasPrefix(name, "iPhone"):
    79  		v = 163
    80  	case strings.HasPrefix(name, "iPad"):
    81  		// TODO: is there a better way to distinguish the iPad Mini?
    82  		switch name {
    83  		case "iPad2,5", "iPad2,6", "iPad2,7", "iPad4,4", "iPad4,5", "iPad4,6", "iPad4,7":
    84  			v = 163 // iPad Mini
    85  		default:
    86  			v = 132
    87  		}
    88  	default:
    89  		v = 163 // names like i386 and x86_64 are the simulator
    90  	}
    91  
    92  	if v == 0 {
    93  		log.Printf("unknown machine: %s", name)
    94  		v = 163 // emergency fallback
    95  	}
    96  
    97  	pixelsPerPt = v * float32(scale) / 72
    98  	screenScale = scale
    99  }
   100  
   101  //export updateConfig
   102  func updateConfig(width, height, orientation int32) {
   103  	o := size.OrientationUnknown
   104  	switch orientation {
   105  	case C.UIDeviceOrientationPortrait, C.UIDeviceOrientationPortraitUpsideDown:
   106  		o = size.OrientationPortrait
   107  	case C.UIDeviceOrientationLandscapeLeft, C.UIDeviceOrientationLandscapeRight:
   108  		o = size.OrientationLandscape
   109  	}
   110  	widthPx := screenScale * int(width)
   111  	heightPx := screenScale * int(height)
   112  	theApp.eventsIn <- size.Event{
   113  		WidthPx:     widthPx,
   114  		HeightPx:    heightPx,
   115  		WidthPt:     geom.Pt(float32(widthPx) / pixelsPerPt),
   116  		HeightPt:    geom.Pt(float32(heightPx) / pixelsPerPt),
   117  		PixelsPerPt: pixelsPerPt,
   118  		Orientation: o,
   119  	}
   120  	theApp.eventsIn <- paint.Event{External: true}
   121  }
   122  
   123  // touchIDs is the current active touches. The position in the array
   124  // is the ID, the value is the UITouch* pointer value.
   125  //
   126  // It is widely reported that the iPhone can handle up to 5 simultaneous
   127  // touch events, while the iPad can handle 11.
   128  var touchIDs [11]uintptr
   129  
   130  var touchEvents struct {
   131  	sync.Mutex
   132  	pending []touch.Event
   133  }
   134  
   135  //export sendTouch
   136  func sendTouch(cTouch, cTouchType uintptr, x, y float32) {
   137  	id := -1
   138  	for i, val := range touchIDs {
   139  		if val == cTouch {
   140  			id = i
   141  			break
   142  		}
   143  	}
   144  	if id == -1 {
   145  		for i, val := range touchIDs {
   146  			if val == 0 {
   147  				touchIDs[i] = cTouch
   148  				id = i
   149  				break
   150  			}
   151  		}
   152  		if id == -1 {
   153  			panic("out of touchIDs")
   154  		}
   155  	}
   156  
   157  	t := touch.Type(cTouchType)
   158  	if t == touch.TypeEnd {
   159  		touchIDs[id] = 0
   160  	}
   161  
   162  	theApp.eventsIn <- touch.Event{
   163  		X:        x,
   164  		Y:        y,
   165  		Sequence: touch.Sequence(id),
   166  		Type:     t,
   167  	}
   168  }
   169  
   170  //export lifecycleDead
   171  func lifecycleDead() { theApp.sendLifecycle(lifecycle.StageDead) }
   172  
   173  //export lifecycleAlive
   174  func lifecycleAlive() { theApp.sendLifecycle(lifecycle.StageAlive) }
   175  
   176  //export lifecycleVisible
   177  func lifecycleVisible() { theApp.sendLifecycle(lifecycle.StageVisible) }
   178  
   179  //export lifecycleFocused
   180  func lifecycleFocused() { theApp.sendLifecycle(lifecycle.StageFocused) }
   181  
   182  //export startloop
   183  func startloop(ctx C.GLintptr) {
   184  	go theApp.loop(ctx)
   185  }
   186  
   187  // loop is the primary drawing loop.
   188  //
   189  // After UIKit has captured the initial OS thread for processing UIKit
   190  // events in runApp, it starts loop on another goroutine. It is locked
   191  // to an OS thread for its OpenGL context.
   192  func (a *app) loop(ctx C.GLintptr) {
   193  	runtime.LockOSThread()
   194  	C.makeCurrentContext(ctx)
   195  
   196  	workAvailable := a.worker.WorkAvailable()
   197  
   198  	for {
   199  		select {
   200  		case <-workAvailable:
   201  			a.worker.DoWork()
   202  		case <-theApp.publish:
   203  		loop1:
   204  			for {
   205  				select {
   206  				case <-workAvailable:
   207  					a.worker.DoWork()
   208  				default:
   209  					break loop1
   210  				}
   211  			}
   212  			C.swapBuffers(ctx)
   213  			theApp.publishResult <- PublishResult{}
   214  		}
   215  	}
   216  }