// SPDX-License-Identifier: Unlicense OR MIT package app /* #cgo CFLAGS: -Werror #cgo LDFLAGS: -landroid #include #include #include #include #include static jint jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version) { return (*vm)->GetEnv(vm, (void **)env, version); } static jint jni_GetJavaVM(JNIEnv *env, JavaVM **jvm) { return (*env)->GetJavaVM(env, jvm); } static jint jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) { return (*vm)->AttachCurrentThread(vm, p_env, thr_args); } static jint jni_DetachCurrentThread(JavaVM *vm) { return (*vm)->DetachCurrentThread(vm); } static jobject jni_NewGlobalRef(JNIEnv *env, jobject obj) { return (*env)->NewGlobalRef(env, obj); } static void jni_DeleteGlobalRef(JNIEnv *env, jobject obj) { (*env)->DeleteGlobalRef(env, obj); } static jclass jni_GetObjectClass(JNIEnv *env, jobject obj) { return (*env)->GetObjectClass(env, obj); } static jmethodID jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) { return (*env)->GetMethodID(env, clazz, name, sig); } static jmethodID jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) { return (*env)->GetStaticMethodID(env, clazz, name, sig); } static jfloat jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID) { return (*env)->CallFloatMethod(env, obj, methodID); } static jint jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) { return (*env)->CallIntMethod(env, obj, methodID); } static void jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args) { (*env)->CallStaticVoidMethodA(env, cls, methodID, args); } static void jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) { (*env)->CallVoidMethodA(env, obj, methodID, args); } static jboolean jni_CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) { return (*env)->CallBooleanMethodA(env, obj, methodID, args); } static jbyte *jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) { return (*env)->GetByteArrayElements(env, arr, NULL); } static void jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) { (*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT); } static jsize jni_GetArrayLength(JNIEnv *env, jbyteArray arr) { return (*env)->GetArrayLength(env, arr); } static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) { return (*env)->NewString(env, unicodeChars, len); } static jsize jni_GetStringLength(JNIEnv *env, jstring str) { return (*env)->GetStringLength(env, str); } static const jchar *jni_GetStringChars(JNIEnv *env, jstring str) { return (*env)->GetStringChars(env, str, NULL); } static jthrowable jni_ExceptionOccurred(JNIEnv *env) { return (*env)->ExceptionOccurred(env); } static void jni_ExceptionClear(JNIEnv *env) { (*env)->ExceptionClear(env); } static jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { return (*env)->CallObjectMethodA(env, obj, method, args); } static jobject jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) { return (*env)->CallStaticObjectMethodA(env, cls, method, args); } static jclass jni_FindClass(JNIEnv *env, char *name) { return (*env)->FindClass(env, name); } static jobject jni_NewObjectA(JNIEnv *env, jclass cls, jmethodID cons, jvalue *args) { return (*env)->NewObjectA(env, cls, cons, args); } */ import "C" import ( "errors" "fmt" "image" "image/color" "io" "math" "os" "path/filepath" "runtime" "runtime/cgo" "runtime/debug" "strings" "sync" "time" "unicode/utf16" "unsafe" "gioui.org/internal/f32color" "gioui.org/op" "gioui.org/f32" "gioui.org/io/event" "gioui.org/io/input" "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/semantic" "gioui.org/io/system" "gioui.org/io/transfer" "gioui.org/unit" ) type window struct { callbacks *callbacks loop *eventLoop view C.jobject handle cgo.Handle dpi int fontScale float32 insets pixelInsets visible bool started bool animating bool win *C.ANativeWindow config Config inputHint key.InputHint semantic struct { hoverID input.SemanticID rootID input.SemanticID focusID input.SemanticID diffs []input.SemanticID } } // gioView hold cached JNI methods for GioView. var gioView struct { once sync.Once getDensity C.jmethodID getFontScale C.jmethodID showTextInput C.jmethodID hideTextInput C.jmethodID setInputHint C.jmethodID postFrameCallback C.jmethodID invalidate C.jmethodID // requests draw, called from UI thread setCursor C.jmethodID setOrientation C.jmethodID setNavigationColor C.jmethodID setStatusColor C.jmethodID setFullscreen C.jmethodID unregister C.jmethodID sendA11yEvent C.jmethodID sendA11yChange C.jmethodID isA11yActive C.jmethodID restartInput C.jmethodID updateSelection C.jmethodID updateCaret C.jmethodID } type pixelInsets struct { top, bottom, left, right int } // AndroidViewEvent is sent whenever the Window's underlying Android view // changes. type AndroidViewEvent struct { // View is a JNI global reference to the android.view.View // instance backing the Window. The reference is valid until // the next ViewEvent is received. // A zero View means that there is currently no view attached. View uintptr } type jvalue uint64 // The largest JNI type fits in 64 bits. var dataDirChan = make(chan string, 1) var android struct { // mu protects all fields of this structure. However, once a // non-nil jvm is returned from javaVM, all the other fields may // be accessed unlocked. mu sync.Mutex jvm *C.JavaVM // appCtx is the global Android App context. appCtx C.jobject // gioCls is the class of the Gio class. gioCls C.jclass mwriteClipboard C.jmethodID mreadClipboard C.jmethodID mwakeupMainThread C.jmethodID // android.view.accessibility.AccessibilityNodeInfo class. accessibilityNodeInfo struct { cls C.jclass // addChild(View, int) addChild C.jmethodID // setBoundsInScreen(Rect) setBoundsInScreen C.jmethodID // setText(CharSequence) setText C.jmethodID // setContentDescription(CharSequence) setContentDescription C.jmethodID // setParent(View, int) setParent C.jmethodID // addAction(int) addAction C.jmethodID // setClassName(CharSequence) setClassName C.jmethodID // setCheckable(boolean) setCheckable C.jmethodID // setSelected(boolean) setSelected C.jmethodID // setChecked(boolean) setChecked C.jmethodID // setEnabled(boolean) setEnabled C.jmethodID // setAccessibilityFocused(boolean) setAccessibilityFocused C.jmethodID } // android.graphics.Rect class. rect struct { cls C.jclass // (int, int, int, int) constructor. cons C.jmethodID } strings struct { // "android.view.View" androidViewView C.jstring // "android.widget.Button" androidWidgetButton C.jstring // "android.widget.CheckBox" androidWidgetCheckBox C.jstring // "android.widget.EditText" androidWidgetEditText C.jstring // "android.widget.RadioButton" androidWidgetRadioButton C.jstring // "android.widget.Switch" androidWidgetSwitch C.jstring } } var windows = make(map[*callbacks]*window) var mainWindow = newWindowRendezvous() var mainFuncs = make(chan func(env *C.JNIEnv), 1) var ( dataDirOnce sync.Once dataPath string ) var ( newAndroidVulkanContext func(w *window) (context, error) newAndroidGLESContext func(w *window) (context, error) ) // AccessibilityNodeProvider.HOST_VIEW_ID. const HOST_VIEW_ID = -1 const ( // AccessibilityEvent constants. TYPE_VIEW_HOVER_ENTER = 128 TYPE_VIEW_HOVER_EXIT = 256 ) const ( // AccessibilityNodeInfo constants. ACTION_ACCESSIBILITY_FOCUS = 64 ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128 ACTION_CLICK = 16 ) func (w *window) NewContext() (context, error) { funcs := []func(w *window) (context, error){newAndroidGLESContext, newAndroidVulkanContext} var firstErr error for _, f := range funcs { if f == nil { continue } c, err := f(w) if err != nil { if firstErr == nil { firstErr = err } continue } return c, nil } if firstErr != nil { return nil, firstErr } return nil, errors.New("x11: no available GPU backends") } func dataDir() (string, error) { dataDirOnce.Do(func() { dataPath = <-dataDirChan }) return dataPath, nil } func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID { m := C.CString(method) defer C.free(unsafe.Pointer(m)) s := C.CString(sig) defer C.free(unsafe.Pointer(s)) jm := C.jni_GetMethodID(env, class, m, s) if err := exception(env); err != nil { panic(err) } return jm } func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID { m := C.CString(method) defer C.free(unsafe.Pointer(m)) s := C.CString(sig) defer C.free(unsafe.Pointer(s)) jm := C.jni_GetStaticMethodID(env, class, m, s) if err := exception(env); err != nil { panic(err) } return jm } //export Java_org_gioui_Gio_runGoMain func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) { initJVM(env, class, context) dirBytes := C.jni_GetByteArrayElements(env, jdataDir) if dirBytes == nil { panic("runGoMain: GetByteArrayElements failed") } n := C.jni_GetArrayLength(env, jdataDir) dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n) // Set XDG_CACHE_HOME to make os.UserCacheDir work. if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists { cachePath := filepath.Join(dataDir, "cache") os.Setenv("XDG_CACHE_HOME", cachePath) } // Set XDG_CONFIG_HOME to make os.UserConfigDir work. if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists { cfgPath := filepath.Join(dataDir, "config") os.Setenv("XDG_CONFIG_HOME", cfgPath) } // Set HOME to make os.UserHomeDir work. if _, exists := os.LookupEnv("HOME"); !exists { os.Setenv("HOME", dataDir) } dataDirChan <- dataDir C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes) runMain() } func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) { android.mu.Lock() defer android.mu.Unlock() if res := C.jni_GetJavaVM(env, &android.jvm); res != 0 { panic("gio: GetJavaVM failed") } android.appCtx = C.jni_NewGlobalRef(env, ctx) android.gioCls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(gio))) cls := findClass(env, "android/view/accessibility/AccessibilityNodeInfo") android.accessibilityNodeInfo.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls))) android.accessibilityNodeInfo.addChild = getMethodID(env, cls, "addChild", "(Landroid/view/View;I)V") android.accessibilityNodeInfo.setBoundsInScreen = getMethodID(env, cls, "setBoundsInScreen", "(Landroid/graphics/Rect;)V") android.accessibilityNodeInfo.setText = getMethodID(env, cls, "setText", "(Ljava/lang/CharSequence;)V") android.accessibilityNodeInfo.setContentDescription = getMethodID(env, cls, "setContentDescription", "(Ljava/lang/CharSequence;)V") android.accessibilityNodeInfo.setParent = getMethodID(env, cls, "setParent", "(Landroid/view/View;I)V") android.accessibilityNodeInfo.addAction = getMethodID(env, cls, "addAction", "(I)V") android.accessibilityNodeInfo.setClassName = getMethodID(env, cls, "setClassName", "(Ljava/lang/CharSequence;)V") android.accessibilityNodeInfo.setCheckable = getMethodID(env, cls, "setCheckable", "(Z)V") android.accessibilityNodeInfo.setSelected = getMethodID(env, cls, "setSelected", "(Z)V") android.accessibilityNodeInfo.setChecked = getMethodID(env, cls, "setChecked", "(Z)V") android.accessibilityNodeInfo.setEnabled = getMethodID(env, cls, "setEnabled", "(Z)V") android.accessibilityNodeInfo.setAccessibilityFocused = getMethodID(env, cls, "setAccessibilityFocused", "(Z)V") cls = findClass(env, "android/graphics/Rect") android.rect.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls))) android.rect.cons = getMethodID(env, cls, "", "(IIII)V") android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V") android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;") android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V") intern := func(s string) C.jstring { ref := C.jni_NewGlobalRef(env, C.jobject(javaString(env, s))) return C.jstring(ref) } android.strings.androidViewView = intern("android.view.View") android.strings.androidWidgetButton = intern("android.widget.Button") android.strings.androidWidgetCheckBox = intern("android.widget.CheckBox") android.strings.androidWidgetEditText = intern("android.widget.EditText") android.strings.androidWidgetRadioButton = intern("android.widget.RadioButton") android.strings.androidWidgetSwitch = intern("android.widget.Switch") } // JavaVM returns the global JNI JavaVM. func JavaVM() uintptr { jvm := javaVM() return uintptr(unsafe.Pointer(jvm)) } func javaVM() *C.JavaVM { android.mu.Lock() defer android.mu.Unlock() return android.jvm } // AppContext returns the global Application context as a JNI jobject. func AppContext() uintptr { android.mu.Lock() defer android.mu.Unlock() return uintptr(android.appCtx) } //export Java_org_gioui_GioView_onCreateView func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong { gioView.once.Do(func() { m := &gioView m.getDensity = getMethodID(env, class, "getDensity", "()I") m.getFontScale = getMethodID(env, class, "getFontScale", "()F") m.showTextInput = getMethodID(env, class, "showTextInput", "()V") m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V") m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V") m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V") m.invalidate = getMethodID(env, class, "invalidate", "()V") m.setCursor = getMethodID(env, class, "setCursor", "(I)V") m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V") m.setNavigationColor = getMethodID(env, class, "setNavigationColor", "(II)V") m.setStatusColor = getMethodID(env, class, "setStatusColor", "(II)V") m.setFullscreen = getMethodID(env, class, "setFullscreen", "(Z)V") m.unregister = getMethodID(env, class, "unregister", "()V") m.sendA11yEvent = getMethodID(env, class, "sendA11yEvent", "(II)V") m.sendA11yChange = getMethodID(env, class, "sendA11yChange", "(I)V") m.isA11yActive = getMethodID(env, class, "isA11yActive", "()Z") m.restartInput = getMethodID(env, class, "restartInput", "()V") m.updateSelection = getMethodID(env, class, "updateSelection", "()V") m.updateCaret = getMethodID(env, class, "updateCaret", "(FFFFFFFFFF)V") }) view = C.jni_NewGlobalRef(env, view) wopts := <-mainWindow.out var cnf Config w, ok := windows[wopts.window] if !ok { w = &window{ callbacks: wopts.window, } w.loop = newEventLoop(w.callbacks, w.wakeup) w.callbacks.SetDriver(w) cnf.apply(unit.Metric{}, wopts.options) windows[wopts.window] = w } else { cnf = w.config } mainWindow.windows <- struct{}{} if w.view != 0 { w.detach(env) } w.view = view w.visible = false w.handle = cgo.NewHandle(w) w.loadConfig(env, class) w.setConfig(env, cnf) w.SetInputHint(w.inputHint) w.processEvent(AndroidViewEvent{View: uintptr(view)}) return C.jlong(w.handle) } //export Java_org_gioui_GioView_onDestroyView func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) { w := cgo.Handle(handle).Value().(*window) w.detach(env) } //export Java_org_gioui_GioView_onStopView func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) { w := cgo.Handle(handle).Value().(*window) w.started = false w.visible = false } //export Java_org_gioui_GioView_onStartView func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) { w := cgo.Handle(handle).Value().(*window) w.started = true if w.win != nil { w.setVisible(env) } } //export Java_org_gioui_GioView_onSurfaceDestroyed func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) { w := cgo.Handle(handle).Value().(*window) w.win = nil w.visible = false } //export Java_org_gioui_GioView_onSurfaceChanged func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) { w := cgo.Handle(handle).Value().(*window) w.win = C.ANativeWindow_fromSurface(env, surf) if w.started { w.setVisible(env) } } //export Java_org_gioui_GioView_onLowMemory func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) { runtime.GC() debug.FreeOSMemory() } //export Java_org_gioui_GioView_onConfigurationChanged func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) { w := cgo.Handle(view).Value().(*window) w.loadConfig(env, class) w.draw(env, true) } //export Java_org_gioui_GioView_onFrameCallback func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong) { w, exist := cgo.Handle(view).Value().(*window) if !exist { return } w.draw(env, false) } //export Java_org_gioui_GioView_onBack func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean { w := cgo.Handle(view).Value().(*window) if w.processEvent(key.Event{Name: key.NameBack}) { return C.JNI_TRUE } return C.JNI_FALSE } //export Java_org_gioui_GioView_onFocusChange func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) { w := cgo.Handle(view).Value().(*window) w.config.Focused = focus == C.JNI_TRUE w.processEvent(ConfigEvent{Config: w.config}) } //export Java_org_gioui_GioView_onWindowInsets func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) { w := cgo.Handle(view).Value().(*window) w.insets = pixelInsets{ top: int(top), bottom: int(bottom), left: int(left), right: int(right), } w.draw(env, true) } //export Java_org_gioui_GioView_initializeAccessibilityNodeInfo func Java_org_gioui_GioView_initializeAccessibilityNodeInfo(env *C.JNIEnv, class C.jclass, view C.jlong, virtID, screenX, screenY C.jint, info C.jobject) C.jobject { w := cgo.Handle(view).Value().(*window) semID := w.semIDFor(virtID) sem, found := w.callbacks.LookupSemantic(semID) if found { off := image.Pt(int(screenX), int(screenY)) if err := w.initAccessibilityNodeInfo(env, sem, off, info); err != nil { panic(err) } } return info } //export Java_org_gioui_GioView_onTouchExploration func Java_org_gioui_GioView_onTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong, x, y C.jfloat) { w := cgo.Handle(view).Value().(*window) semID, _ := w.callbacks.SemanticAt(f32.Pt(float32(x), float32(y))) if w.semantic.hoverID == semID { return } // Android expects ENTER before EXIT. if semID != 0 { callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_ENTER, jvalue(w.virtualIDFor(semID))) } if prevID := w.semantic.hoverID; prevID != 0 { callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(prevID))) } w.semantic.hoverID = semID } //export Java_org_gioui_GioView_onExitTouchExploration func Java_org_gioui_GioView_onExitTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong) { w := cgo.Handle(view).Value().(*window) if w.semantic.hoverID != 0 { callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(w.semantic.hoverID))) w.semantic.hoverID = 0 } } //export Java_org_gioui_GioView_onA11yFocus func Java_org_gioui_GioView_onA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) { w := cgo.Handle(view).Value().(*window) if semID := w.semIDFor(virtID); semID != w.semantic.focusID { w.semantic.focusID = semID // Android needs invalidate to refresh the TalkBack focus indicator. callVoidMethod(env, w.view, gioView.invalidate) } } //export Java_org_gioui_GioView_onClearA11yFocus func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) { w := cgo.Handle(view).Value().(*window) if w.semantic.focusID == w.semIDFor(virtID) { w.semantic.focusID = 0 } } func (w *window) ProcessEvent(e event.Event) { w.processEvent(e) } func (w *window) processEvent(e event.Event) bool { if !w.callbacks.ProcessEvent(e) { return false } w.loop.FlushEvents() return true } func (w *window) Event() event.Event { return w.loop.Event() } func (w *window) Invalidate() { w.loop.Invalidate() } func (w *window) Run(f func()) { w.loop.Run(f) } func (w *window) Frame(frame *op.Ops) { w.loop.Frame(frame) } func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem input.SemanticNode, off image.Point, info C.jobject) error { for _, ch := range sem.Children { err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID))) if err != nil { return err } } if sem.ParentID != 0 { if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setParent, jvalue(w.view), jvalue(w.virtualIDFor(sem.ParentID))); err != nil { return err } b := sem.Desc.Bounds.Add(off) rect, err := newObject(env, android.rect.cls, android.rect.cons, jvalue(b.Min.X), jvalue(b.Min.Y), jvalue(b.Max.X), jvalue(b.Max.Y), ) if err != nil { return err } if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setBoundsInScreen, jvalue(rect)); err != nil { return err } } d := sem.Desc if l := d.Label; l != "" { jlbl := javaString(env, l) if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setText, jvalue(jlbl)); err != nil { return err } } if d.Description != "" { jd := javaString(env, d.Description) if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setContentDescription, jvalue(jd)); err != nil { return err } } addAction := func(act C.jint) { if err := callVoidMethod(env, info, android.accessibilityNodeInfo.addAction, jvalue(act)); err != nil { panic(err) } } if d.Gestures&input.ClickGesture != 0 { addAction(ACTION_CLICK) } clsName := android.strings.androidViewView selectMethod := android.accessibilityNodeInfo.setChecked checkable := false switch d.Class { case semantic.Button: clsName = android.strings.androidWidgetButton case semantic.CheckBox: checkable = true clsName = android.strings.androidWidgetCheckBox case semantic.Editor: clsName = android.strings.androidWidgetEditText case semantic.RadioButton: selectMethod = android.accessibilityNodeInfo.setSelected clsName = android.strings.androidWidgetRadioButton case semantic.Switch: checkable = true clsName = android.strings.androidWidgetSwitch } if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setClassName, jvalue(clsName)); err != nil { panic(err) } if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setCheckable, jvalue(javaBool(checkable))); err != nil { panic(err) } if err := callVoidMethod(env, info, selectMethod, jvalue(javaBool(d.Selected))); err != nil { panic(err) } if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setEnabled, jvalue(javaBool(!d.Disabled))); err != nil { panic(err) } isFocus := w.semantic.focusID == sem.ID if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setAccessibilityFocused, jvalue(javaBool(isFocus))); err != nil { panic(err) } if isFocus { addAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS) } else { addAction(ACTION_ACCESSIBILITY_FOCUS) } return nil } func (w *window) virtualIDFor(id input.SemanticID) C.jint { if id == w.semantic.rootID { return HOST_VIEW_ID } return C.jint(id) } func (w *window) semIDFor(virtID C.jint) input.SemanticID { if virtID == HOST_VIEW_ID { return w.semantic.rootID } return input.SemanticID(virtID) } func (w *window) detach(env *C.JNIEnv) { callVoidMethod(env, w.view, gioView.unregister) w.processEvent(AndroidViewEvent{}) w.handle.Delete() C.jni_DeleteGlobalRef(env, w.view) w.view = 0 } func (w *window) setVisible(env *C.JNIEnv) { width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win) if width == 0 || height == 0 { return } w.visible = true w.draw(env, true) } func (w *window) setVisual(visID int) error { if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 { return errors.New("ANativeWindow_setBuffersGeometry failed") } return nil } func (w *window) nativeWindow() (*C.ANativeWindow, int, int) { width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win) return w.win, int(width), int(height) } func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) { dpi := int(C.jni_CallIntMethod(env, w.view, gioView.getDensity)) w.fontScale = float32(C.jni_CallFloatMethod(env, w.view, gioView.getFontScale)) switch dpi { case C.ACONFIGURATION_DENSITY_NONE, C.ACONFIGURATION_DENSITY_DEFAULT, C.ACONFIGURATION_DENSITY_ANY: // Assume standard density. w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM default: w.dpi = int(dpi) } } func (w *window) SetAnimating(anim bool) { w.animating = anim if anim { runInJVM(javaVM(), func(env *C.JNIEnv) { callVoidMethod(env, w.view, gioView.postFrameCallback) }) } } func (w *window) draw(env *C.JNIEnv, sync bool) { if !w.visible { return } size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win))) if size != w.config.Size { w.config.Size = size w.processEvent(ConfigEvent{Config: w.config}) } if size.X == 0 || size.Y == 0 { return } const inchPrDp = 1.0 / 160 ppdp := float32(w.dpi) * inchPrDp dppp := unit.Dp(1.0 / ppdp) insets := Insets{ Top: unit.Dp(w.insets.top) * dppp, Bottom: unit.Dp(w.insets.bottom) * dppp, Left: unit.Dp(w.insets.left) * dppp, Right: unit.Dp(w.insets.right) * dppp, } w.processEvent(frameEvent{ FrameEvent: FrameEvent{ Now: time.Now(), Size: w.config.Size, Insets: insets, Metric: unit.Metric{ PxPerDp: ppdp, PxPerSp: w.fontScale * ppdp, }, }, Sync: sync, }) if w.animating { callVoidMethod(env, w.view, gioView.postFrameCallback) } a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive) if err != nil { panic(err) } if a11yActive { if newR, oldR := w.callbacks.SemanticRoot(), w.semantic.rootID; newR != oldR { // Remap focus and hover. if oldR == w.semantic.hoverID { w.semantic.hoverID = newR } if oldR == w.semantic.focusID { w.semantic.focusID = newR } w.semantic.rootID = newR callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(newR))) } w.semantic.diffs = w.callbacks.AppendSemanticDiffs(w.semantic.diffs[:0]) for _, id := range w.semantic.diffs { callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(id))) } } } func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) { if jvm == nil { panic("nil JVM") } runtime.LockOSThread() defer runtime.UnlockOSThread() var env *C.JNIEnv if res := C.jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK { if res != C.JNI_EDETACHED { panic(fmt.Errorf("JNI GetEnv failed with error %d", res)) } if C.jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK { panic(errors.New("runInJVM: AttachCurrentThread failed")) } defer C.jni_DetachCurrentThread(jvm) } f(env) } func convertKeyCode(code C.jint) (key.Name, bool) { var n key.Name switch code { case C.AKEYCODE_FORWARD_DEL: n = key.NameDeleteForward case C.AKEYCODE_DEL: n = key.NameDeleteBackward case C.AKEYCODE_NUMPAD_ENTER: n = key.NameEnter case C.AKEYCODE_ENTER: n = key.NameReturn case C.AKEYCODE_CTRL_LEFT, C.AKEYCODE_CTRL_RIGHT: n = key.NameCtrl case C.AKEYCODE_SHIFT_LEFT, C.AKEYCODE_SHIFT_RIGHT: n = key.NameShift case C.AKEYCODE_ALT_LEFT, C.AKEYCODE_ALT_RIGHT: n = key.NameAlt case C.AKEYCODE_META_LEFT, C.AKEYCODE_META_RIGHT: n = key.NameSuper case C.AKEYCODE_DPAD_UP: n = key.NameUpArrow case C.AKEYCODE_DPAD_DOWN: n = key.NameDownArrow case C.AKEYCODE_DPAD_LEFT: n = key.NameLeftArrow case C.AKEYCODE_DPAD_RIGHT: n = key.NameRightArrow default: return "", false } return n, true } //export Java_org_gioui_GioView_onKeyEvent func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, pressed C.jboolean, t C.jlong) { w := cgo.Handle(handle).Value().(*window) if pressed == C.JNI_TRUE && keyCode == C.AKEYCODE_DPAD_CENTER { w.callbacks.ClickFocus() return } if n, ok := convertKeyCode(keyCode); ok { state := key.Release if pressed == C.JNI_TRUE { state = key.Press } w.processEvent(key.Event{Name: n, State: state}) } if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224). w.callbacks.EditorInsert(string(rune(r))) } } //export Java_org_gioui_GioView_onTouchEvent func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) { w := cgo.Handle(handle).Value().(*window) var kind pointer.Kind switch action { case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN: kind = pointer.Press case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP: kind = pointer.Release case C.AMOTION_EVENT_ACTION_CANCEL: kind = pointer.Cancel case C.AMOTION_EVENT_ACTION_MOVE: kind = pointer.Move case C.AMOTION_EVENT_ACTION_SCROLL: kind = pointer.Scroll default: return } var src pointer.Source var btns pointer.Buttons if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 { btns |= pointer.ButtonPrimary } if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 { btns |= pointer.ButtonSecondary } if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 { btns |= pointer.ButtonTertiary } switch tool { case C.AMOTION_EVENT_TOOL_TYPE_FINGER: src = pointer.Touch case C.AMOTION_EVENT_TOOL_TYPE_STYLUS: src = pointer.Touch case C.AMOTION_EVENT_TOOL_TYPE_MOUSE: src = pointer.Mouse case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN: // For example, triggered via 'adb shell input tap'. // Instead of discarding it, treat it as a touch event. src = pointer.Touch default: return } w.processEvent(pointer.Event{ Kind: kind, Source: src, Buttons: btns, PointerID: pointer.ID(pointerID), Time: time.Duration(t) * time.Millisecond, Position: f32.Point{X: float32(x), Y: float32(y)}, Scroll: f32.Pt(float32(scrollX), float32(scrollY)), }) } //export Java_org_gioui_GioView_imeSelectionStart func Java_org_gioui_GioView_imeSelectionStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { w := cgo.Handle(handle).Value().(*window) sel := w.callbacks.EditorState().Selection start := sel.Start if sel.End < sel.Start { start = sel.End } return C.jint(start) } //export Java_org_gioui_GioView_imeSelectionEnd func Java_org_gioui_GioView_imeSelectionEnd(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { w := cgo.Handle(handle).Value().(*window) sel := w.callbacks.EditorState().Selection end := sel.End if sel.End < sel.Start { end = sel.Start } return C.jint(end) } //export Java_org_gioui_GioView_imeComposingStart func Java_org_gioui_GioView_imeComposingStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { w := cgo.Handle(handle).Value().(*window) comp := w.callbacks.EditorState().compose start := comp.Start if e := comp.End; e < start { start = e } return C.jint(start) } //export Java_org_gioui_GioView_imeComposingEnd func Java_org_gioui_GioView_imeComposingEnd(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { w := cgo.Handle(handle).Value().(*window) comp := w.callbacks.EditorState().compose end := comp.End if s := comp.Start; s > end { end = s } return C.jint(end) } //export Java_org_gioui_GioView_imeSnippet func Java_org_gioui_GioView_imeSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jstring { w := cgo.Handle(handle).Value().(*window) snip := w.callbacks.EditorState().Snippet.Text return javaString(env, snip) } //export Java_org_gioui_GioView_imeSnippetStart func Java_org_gioui_GioView_imeSnippetStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { w := cgo.Handle(handle).Value().(*window) return C.jint(w.callbacks.EditorState().Snippet.Start) } //export Java_org_gioui_GioView_imeSetSnippet func Java_org_gioui_GioView_imeSetSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) { w := cgo.Handle(handle).Value().(*window) if start < 0 { start = 0 } if end < start { end = start } r := key.Range{Start: int(start), End: int(end)} w.callbacks.SetEditorSnippet(r) } //export Java_org_gioui_GioView_imeSetSelection func Java_org_gioui_GioView_imeSetSelection(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) { w := cgo.Handle(handle).Value().(*window) r := key.Range{Start: int(start), End: int(end)} w.callbacks.SetEditorSelection(r) } //export Java_org_gioui_GioView_imeSetComposingRegion func Java_org_gioui_GioView_imeSetComposingRegion(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) { w := cgo.Handle(handle).Value().(*window) w.callbacks.SetComposingRegion(key.Range{ Start: int(start), End: int(end), }) } //export Java_org_gioui_GioView_imeReplace func Java_org_gioui_GioView_imeReplace(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint, jtext C.jstring) { w := cgo.Handle(handle).Value().(*window) r := key.Range{Start: int(start), End: int(end)} text := goString(env, jtext) w.callbacks.EditorReplace(r, text) } //export Java_org_gioui_GioView_imeToRunes func Java_org_gioui_GioView_imeToRunes(env *C.JNIEnv, class C.jclass, handle C.jlong, chars C.jint) C.jint { w := cgo.Handle(handle).Value().(*window) state := w.callbacks.EditorState() return C.jint(state.RunesIndex(int(chars))) } //export Java_org_gioui_GioView_imeToUTF16 func Java_org_gioui_GioView_imeToUTF16(env *C.JNIEnv, class C.jclass, handle C.jlong, runes C.jint) C.jint { w := cgo.Handle(handle).Value().(*window) state := w.callbacks.EditorState() return C.jint(state.UTF16Index(int(runes))) } func (w *window) EditorStateChanged(old, new editorState) { runInJVM(javaVM(), func(env *C.JNIEnv) { if old.Snippet != new.Snippet { callVoidMethod(env, w.view, gioView.restartInput) return } if old.Selection.Range != new.Selection.Range { w.callbacks.SetComposingRegion(key.Range{Start: -1, End: -1}) callVoidMethod(env, w.view, gioView.updateSelection) } if old.Selection.Transform != new.Selection.Transform || old.Selection.Caret != new.Selection.Caret { sel := new.Selection m00, m01, m02, m10, m11, m12 := sel.Transform.Elems() f := func(v float32) jvalue { return jvalue(math.Float32bits(v)) } c := sel.Caret callVoidMethod(env, w.view, gioView.updateCaret, f(m00), f(m01), f(m02), f(m10), f(m11), f(m12), f(c.Pos.X), f(c.Pos.Y-c.Ascent), f(c.Pos.Y), f(c.Pos.Y+c.Descent)) } }) } func (w *window) ShowTextInput(show bool) { runInJVM(javaVM(), func(env *C.JNIEnv) { if show { callVoidMethod(env, w.view, gioView.showTextInput) } else { callVoidMethod(env, w.view, gioView.hideTextInput) } }) } func (w *window) SetInputHint(mode key.InputHint) { w.inputHint = mode // Constants defined at https://developer.android.com/reference/android/text/InputType. const ( TYPE_NULL = 0 TYPE_CLASS_TEXT = 1 TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32 TYPE_TEXT_VARIATION_URI = 16 TYPE_TEXT_VARIATION_PASSWORD = 128 TYPE_TEXT_FLAG_CAP_SENTENCES = 16384 TYPE_TEXT_FLAG_AUTO_CORRECT = 32768 TYPE_CLASS_NUMBER = 2 TYPE_NUMBER_FLAG_DECIMAL = 8192 TYPE_NUMBER_FLAG_SIGNED = 4096 TYPE_CLASS_PHONE = 3 ) runInJVM(javaVM(), func(env *C.JNIEnv) { var m jvalue switch mode { case key.HintText: m = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_AUTO_CORRECT | TYPE_TEXT_FLAG_CAP_SENTENCES case key.HintNumeric: m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED case key.HintEmail: m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS case key.HintURL: m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_URI case key.HintTelephone: m = TYPE_CLASS_PHONE case key.HintPassword: m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD default: m = TYPE_CLASS_TEXT } callVoidMethod(env, w.view, gioView.setInputHint, m) }) } func javaBool(b bool) C.jboolean { if b { return C.JNI_TRUE } else { return C.JNI_FALSE } } func javaString(env *C.JNIEnv, str string) C.jstring { utf16Chars := utf16.Encode([]rune(str)) var ptr *C.jchar if len(utf16Chars) > 0 { ptr = (*C.jchar)(unsafe.Pointer(&utf16Chars[0])) } return C.jni_NewString(env, ptr, C.int(len(utf16Chars))) } func varArgs(args []jvalue) *C.jvalue { if len(args) == 0 { return nil } return (*C.jvalue)(unsafe.Pointer(&args[0])) } func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error { C.jni_CallStaticVoidMethodA(env, cls, method, varArgs(args)) return exception(env) } func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) { res := C.jni_CallStaticObjectMethodA(env, cls, method, varArgs(args)) return res, exception(env) } func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error { C.jni_CallVoidMethodA(env, obj, method, varArgs(args)) return exception(env) } func callBooleanMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (bool, error) { res := C.jni_CallBooleanMethodA(env, obj, method, varArgs(args)) return res == C.JNI_TRUE, exception(env) } func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) { res := C.jni_CallObjectMethodA(env, obj, method, varArgs(args)) return res, exception(env) } func newObject(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) { res := C.jni_NewObjectA(env, cls, method, varArgs(args)) return res, exception(env) } // exception returns an error corresponding to the pending // exception, or nil if no exception is pending. The pending // exception is cleared. func exception(env *C.JNIEnv) error { thr := C.jni_ExceptionOccurred(env) if thr == 0 { return nil } C.jni_ExceptionClear(env) cls := getObjectClass(env, C.jobject(thr)) toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;") msg, err := callObjectMethod(env, C.jobject(thr), toString) if err != nil { return err } return errors.New(goString(env, C.jstring(msg))) } func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass { if obj == 0 { panic("null object") } cls := C.jni_GetObjectClass(env, C.jobject(obj)) if err := exception(env); err != nil { // GetObjectClass should never fail. panic(err) } return cls } // goString converts the JVM jstring to a Go string. func goString(env *C.JNIEnv, str C.jstring) string { if str == 0 { return "" } strlen := C.jni_GetStringLength(env, C.jstring(str)) chars := C.jni_GetStringChars(env, C.jstring(str)) utf16Chars := unsafe.Slice((*uint16)(unsafe.Pointer(chars)), strlen) utf8 := utf16.Decode(utf16Chars) return string(utf8) } func findClass(env *C.JNIEnv, name string) C.jclass { cn := C.CString(name) defer C.free(unsafe.Pointer(cn)) return C.jni_FindClass(env, cn) } func osMain() { } func newWindow(window *callbacks, options []Option) { mainWindow.in <- windowAndConfig{window, options} <-mainWindow.windows } func (w *window) WriteClipboard(mime string, s []byte) { runInJVM(javaVM(), func(env *C.JNIEnv) { jstr := javaString(env, string(s)) callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard, jvalue(android.appCtx), jvalue(jstr)) }) } func (w *window) ReadClipboard() { runInJVM(javaVM(), func(env *C.JNIEnv) { c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard, jvalue(android.appCtx)) if err != nil { return } content := goString(env, C.jstring(c)) w.processEvent(transfer.DataEvent{ Type: "application/text", Open: func() io.ReadCloser { return io.NopCloser(strings.NewReader(content)) }, }) }) } func (w *window) Configure(options []Option) { cnf := w.config cnf.apply(unit.Metric{}, options) runInJVM(javaVM(), func(env *C.JNIEnv) { w.setConfig(env, cnf) }) } func (w *window) setConfig(env *C.JNIEnv, cnf Config) { prev := w.config // Decorations are never disabled. cnf.Decorated = true if prev.Orientation != cnf.Orientation { w.config.Orientation = cnf.Orientation setOrientation(env, w.view, cnf.Orientation) } if prev.NavigationColor != cnf.NavigationColor { w.config.NavigationColor = cnf.NavigationColor setNavigationColor(env, w.view, cnf.NavigationColor) } if prev.StatusColor != cnf.StatusColor { w.config.StatusColor = cnf.StatusColor setStatusColor(env, w.view, cnf.StatusColor) } if prev.Mode != cnf.Mode { switch cnf.Mode { case Fullscreen: callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE) w.config.Mode = Fullscreen case Windowed: callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE) w.config.Mode = Windowed } } if cnf.Decorated != prev.Decorated { w.config.Decorated = cnf.Decorated } w.processEvent(ConfigEvent{Config: w.config}) } func (w *window) Perform(system.Action) {} func (w *window) SetCursor(cursor pointer.Cursor) { runInJVM(javaVM(), func(env *C.JNIEnv) { setCursor(env, w.view, cursor) }) } func (w *window) wakeup() { runOnMain(func(env *C.JNIEnv) { w.loop.Wakeup() w.loop.FlushEvents() }) } var androidCursor = [...]uint16{ pointer.CursorDefault: 1000, // TYPE_ARROW pointer.CursorNone: 0, pointer.CursorText: 1008, // TYPE_TEXT pointer.CursorVerticalText: 1009, // TYPE_VERTICAL_TEXT pointer.CursorPointer: 1002, // TYPE_HAND pointer.CursorCrosshair: 1007, // TYPE_CROSSHAIR pointer.CursorAllScroll: 1013, // TYPE_ALL_SCROLL pointer.CursorColResize: 1014, // TYPE_HORIZONTAL_DOUBLE_ARROW pointer.CursorRowResize: 1015, // TYPE_VERTICAL_DOUBLE_ARROW pointer.CursorGrab: 1020, // TYPE_GRAB pointer.CursorGrabbing: 1021, // TYPE_GRABBING pointer.CursorNotAllowed: 1012, // TYPE_NO_DROP pointer.CursorWait: 1004, // TYPE_WAIT pointer.CursorProgress: 1000, // TYPE_ARROW pointer.CursorNorthWestResize: 1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW pointer.CursorNorthEastResize: 1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW pointer.CursorSouthWestResize: 1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW pointer.CursorSouthEastResize: 1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW pointer.CursorNorthSouthResize: 1015, // TYPE_VERTICAL_DOUBLE_ARROW pointer.CursorEastWestResize: 1014, // TYPE_HORIZONTAL_DOUBLE_ARROW pointer.CursorWestResize: 1014, // TYPE_HORIZONTAL_DOUBLE_ARROW pointer.CursorEastResize: 1014, // TYPE_HORIZONTAL_DOUBLE_ARROW pointer.CursorNorthResize: 1015, // TYPE_VERTICAL_DOUBLE_ARROW pointer.CursorSouthResize: 1015, // TYPE_VERTICAL_DOUBLE_ARROW pointer.CursorNorthEastSouthWestResize: 1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW pointer.CursorNorthWestSouthEastResize: 1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW } func setCursor(env *C.JNIEnv, view C.jobject, cursor pointer.Cursor) { curID := androidCursor[cursor] callVoidMethod(env, view, gioView.setCursor, jvalue(curID)) } func setOrientation(env *C.JNIEnv, view C.jobject, mode Orientation) { var ( id int idFallback int // Used only for SDK 17 or older. ) // Constants defined at https://developer.android.com/reference/android/content/pm/ActivityInfo. switch mode { case AnyOrientation: id, idFallback = 2, 2 // SCREEN_ORIENTATION_USER case LandscapeOrientation: id, idFallback = 11, 0 // SCREEN_ORIENTATION_USER_LANDSCAPE (or SCREEN_ORIENTATION_LANDSCAPE) case PortraitOrientation: id, idFallback = 12, 1 // SCREEN_ORIENTATION_USER_PORTRAIT (or SCREEN_ORIENTATION_PORTRAIT) } callVoidMethod(env, view, gioView.setOrientation, jvalue(id), jvalue(idFallback)) } func setStatusColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) { callVoidMethod(env, view, gioView.setStatusColor, jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)), jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)), ) } func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) { callVoidMethod(env, view, gioView.setNavigationColor, jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)), jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)), ) } // runOnMain runs a function on the Java main thread. func runOnMain(f func(env *C.JNIEnv)) { go func() { mainFuncs <- f runInJVM(javaVM(), func(env *C.JNIEnv) { callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread) }) }() } //export Java_org_gioui_Gio_scheduleMainFuncs func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) { for { select { case f := <-mainFuncs: f(env) default: return } } } func (AndroidViewEvent) implementsViewEvent() {} func (AndroidViewEvent) ImplementsEvent() {} func (a AndroidViewEvent) Valid() bool { return a != (AndroidViewEvent{}) }