~eliasnaur/gio

app: add Android-specific Run method on *Window v1 PROPOSED

Greg Pomerantz: 1
 app: add Android-specific Run method on *Window

 8 files changed, 98 insertions(+), 23 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio/patches/9080/mbox | git am -3
Learn more about email & git

[PATCH] app: add Android-specific Run method on *Window Export this patch

Run runs a function on the main UI thread that requires access to an
Android activity. A nil activity is returned if none is available.

Signed-off-by: Greg Pomerantz <gmp.gio@wow.st>
---
 app/app.go                           |  7 -------
 app/app_android.go                   | 35 +++++++++++++++++++++++++++++++++++
 app/internal/window/GioActivity.java |  2 +-
 app/internal/window/GioView.java     | 28 +++++++++++++++++++++-------
 app/internal/window/handle.go        |  7 -------
 app/internal/window/os_android.c     | 11 ++++++++++-
 app/internal/window/os_android.go    | 27 +++++++++++++++++++++++++++
 app/internal/window/os_android.h     |  4 ++++
 8 files changed, 98 insertions(+), 23 deletions(-)
 create mode 100644 app/app_android.go
 delete mode 100644 app/internal/window/handle.go

diff --git a/app/app.go b/app/app.go
index 9a18ec4..71bc57b 100644
--- a/app/app.go
+++ b/app/app.go
@@ -9,13 +9,6 @@ import (
	"gioui.org/app/internal/window"
)

type Handle window.Handle

// PlatformHandle returns the platform specific Handle.
func PlatformHandle() *Handle {
	return (*Handle)(window.PlatformHandle)
}

// extraArgs contains extra arguments to append to
// os.Args. The arguments are separated with |.
// Useful for running programs on mobiles where the
diff --git a/app/app_android.go b/app/app_android.go
new file mode 100644
index 0000000..b41bb43
--- /dev/null
+++ b/app/app_android.go
@@ -0,0 +1,35 @@
package app

import (
	"gioui.org/app/internal/window"
)

type Handle window.Handle

// PlatformHandle returns the Android platform-specific Handle.
func PlatformHandle() *Handle {
	return (*Handle)(window.PlatformHandle)
}

// JNIEnv is a struct wrapping a pointer to a JNI environment with an
// underlying type of *C.JNIEnv.
type JNIEnv struct {
	JNIEnv uintptr
}

// Activity is a struct that wraps a pointer to an Android Activity with an
// underlying type of C.jobject.
type Activity struct {
	Activity uintptr
}

// Run runs a function that needs access to the Android Activity for the
// window. The function is run on the UI thread and should not block. The
// function must be prepared to deal with a nil activity if none is
// available. Run returns immediately and does not wait for the completion
// of f.
func (w *Window) Run(f func(env JNIEnv, activity Activity)) {
	window.Run(w.driver, func(e, a uintptr) {
		f(JNIEnv{e}, Activity{a})
	})
}
diff --git a/app/internal/window/GioActivity.java b/app/internal/window/GioActivity.java
index 2813d80..1a91f81 100644
--- a/app/internal/window/GioActivity.java
+++ b/app/internal/window/GioActivity.java
@@ -33,7 +33,7 @@ public class GioActivity extends Activity {

	@Override public void onStart() {
		super.onStart();
		view.start();
		view.start(this);
	}

	@Override public void onStop() {
diff --git a/app/internal/window/GioView.java b/app/internal/window/GioView.java
index 82bc36f..535ad58 100644
--- a/app/internal/window/GioView.java
+++ b/app/internal/window/GioView.java
@@ -2,6 +2,7 @@

package org.gioui;

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
@@ -32,6 +33,7 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
	private final InputMethodManager imm;
	private final Handler handler;
	private long nhandle;
	private Activity activity;

	private static synchronized void initialize(Context appCtx) {
		synchronized (initLock) {
@@ -51,18 +53,18 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
		}
	}

	public GioView(Context context) {
		this(context, null);
	public GioView(Activity activity) {
		this(activity, null);
	}

	public GioView(Context context, AttributeSet attrs) {
		super(context, attrs);
	public GioView(Activity activity, AttributeSet attrs) {
		super(activity, attrs);
		// Late initialization of the Go runtime to wait for a valid context.
		initialize(context.getApplicationContext());
		initialize(activity.getApplicationContext());

		nhandle = onCreateView(this);
		handler = new Handler();
		imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
		imm = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
		setFocusable(true);
		setFocusableInTouchMode(true);
		setOnFocusChangeListener(new View.OnFocusChangeListener() {
@@ -181,15 +183,18 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
		return getResources().getConfiguration().fontScale;
	}

	void start() {
	void start(Activity act) {
		this.activity = act;
		onStartView(nhandle);
	}

	void stop() {
		this.activity = null;
		onStopView(nhandle);
	}

	void destroy() {
		this.activity = null;
		getHolder().removeCallback(callbacks);
		onDestroyView(nhandle);
		nhandle = 0;
@@ -207,6 +212,14 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
		return onBack(nhandle);
	}

	public void Run(long funcPtr) {
		handler.post(new Runnable() {
			public void run() {
				runCallback(activity, funcPtr);
			}
		});
	}

	static private native long onCreateView(GioView view);
	static private native void onDestroyView(long handle);
	static private native void onStartView(long handle);
@@ -222,6 +235,7 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
	static private native boolean onBack(long handle);
	static private native void onFocusChange(long handle, boolean focus);
	static private native void runGoMain(byte[] dataDir, Context context);
	static private native void runCallback(Activity activity, long funcPtr);

	private static class InputConnection extends BaseInputConnection {
		private final Editable editable;
diff --git a/app/internal/window/handle.go b/app/internal/window/handle.go
deleted file mode 100644
index 5d7b1a1..0000000
--- a/app/internal/window/handle.go
@@ -1,7 +0,0 @@
// +build !android

package window

var PlatformHandle *Handle

type Handle struct{}
diff --git a/app/internal/window/os_android.c b/app/internal/window/os_android.c
index 5150ab6..b20ebea 100644
--- a/app/internal/window/os_android.c
+++ b/app/internal/window/os_android.c
@@ -94,7 +94,12 @@ JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
			.name = "onFocusChange",
			.signature = "(JZ)V",
			.fnPtr = onFocusChange
		}
		},
		{
			.name = "runCallback",
			.signature = "(Landroid/app/Activity;J)V",
			.fnPtr = runCallback
		},
	};
	if ((*env)->RegisterNatives(env, viewClass, methods, sizeof(methods)/sizeof(methods[0])) != 0) {
		return -1;
@@ -166,3 +171,7 @@ void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes)
jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
	return (*env)->GetArrayLength(env, arr);
}

void gio_jni_CallGioViewRun(JNIEnv *env, jobject view, jmethodID mid, runcb_t f) {
	(*env)->CallVoidMethod(env, view, mid, (jlong)f);
}
diff --git a/app/internal/window/os_android.go b/app/internal/window/os_android.go
index 5cce88d..693c3f3 100644
--- a/app/internal/window/os_android.go
+++ b/app/internal/window/os_android.go
@@ -54,6 +54,7 @@ type window struct {
	mhideTextInput                 C.jmethodID
	mpostFrameCallback             C.jmethodID
	mpostFrameCallbackOnMainThread C.jmethodID
	mRun                           C.jmethodID
}

var dataDirChan = make(chan string, 1)
@@ -99,6 +100,31 @@ func runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.j
	runMain()
}

func Run(d Driver, f func(a, b uintptr)) {
	if d == nil {
		go f(0, 0)
		return
	}

	// app/Window.Driver will always be a *window on Android, so this type-
	// assertion is safe as long as Driver is not a nil interface.
	w := d.(*window)

	if w == nil {
		go f(0, 0)
		return
	}
	runInJVM(func(env *C.JNIEnv) {
		C.gio_jni_CallGioViewRun(env, w.view, w.mRun, (*[0]byte)(unsafe.Pointer(&f)))
	})
}

//export runCallback
func runCallback(env *C.JNIEnv, class C.jclass, activity C.jobject, funcPtr C.runcb_t) {
	f := (*func(a, b uintptr))(unsafe.Pointer(funcPtr))
	(*f)(uintptr(unsafe.Pointer(env)), uintptr(unsafe.Pointer(activity)))
}

func GetDataDir() string {
	return <-dataDirChan
}
@@ -119,6 +145,7 @@ func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
		mhideTextInput:                 jniGetMethodID(env, class, "hideTextInput", "()V"),
		mpostFrameCallback:             jniGetMethodID(env, class, "postFrameCallback", "()V"),
		mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"),
		mRun:                           jniGetMethodID(env, class, "Run", "(J)V"),
	}
	wopts := <-mainWindow.out
	w.callbacks = wopts.window
diff --git a/app/internal/window/os_android.h b/app/internal/window/os_android.h
index 288d633..a31ec4b 100644
--- a/app/internal/window/os_android.h
+++ b/app/internal/window/os_android.h
@@ -17,3 +17,7 @@ __attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod_J(JNIEnv *en
__attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
__attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
__attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);

typedef void (*runcb_t)(JNIEnv *env, jobject activity);

void gio_jni_CallGioViewRun(JNIEnv *env, jobject view, jmethodID mid, runcb_t f);
-- 
2.16.2