#include #include #include #include #include "jni_android.h" #include "_cgo_export.h" // Global context static jobject ctx = NULL; // g_view: global ref to the GioView, set in registerFragment. Used by // SetGestureExclusions to reach View.setSystemGestureExclusionRects. static jobject g_view = NULL; void registerFragment(JNIEnv *env, jobject view) { if (view == NULL) { // Detach signal (Gio sends an empty AndroidViewEvent when the // view is destroyed). The Go side already filters these; this // guard keeps the JNI calls safe if one ever slips through. return; } if (g_view == NULL) { g_view = (*env)->NewGlobalRef(env, view); } else if (g_view != view) { (*env)->DeleteGlobalRef(env, g_view); g_view = (*env)->NewGlobalRef(env, view); } jclass cls = (*env)->GetObjectClass(env, view); jmethodID mid = (*env)->GetMethodID(env, cls, "getContext", "()Landroid/content/Context;"); jobject l_ctx = (*env)->CallObjectMethod(env, view, mid); ctx = (*env)->NewGlobalRef(env, l_ctx); cls = (*env)->GetObjectClass(env, ctx); mid = (*env)->GetMethodID(env, cls, "getClassLoader", "()Ljava/lang/ClassLoader;"); jobject loader = (*env)->CallObjectMethod(env, ctx, mid); cls = (*env)->GetObjectClass(env, loader); mid = (*env)->GetMethodID(env, cls, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); jstring str = (*env)->NewStringUTF(env, "st/wow/git/logbook/Permissions"); cls = (*env)->CallObjectMethod(env, loader, mid, str); mid = (*env)->GetMethodID(env, cls, "", "(Landroid/view/View;)V"); jobject inst = (*env)->NewObject(env, cls, mid, view); } void CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) { (*env)->CallVoidMethod(env, obj, methodID); } jint GetEnv(JavaVM *vm, JNIEnv **env, jint version) { return (*vm)->GetEnv(vm, (void **)env, version); } jint AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) { return (*vm)->AttachCurrentThread(vm, p_env, thr_args); } jint DetachCurrentThread(JavaVM *vm) { return (*vm)->DetachCurrentThread(vm); } jobject NewGlobalRef(JNIEnv *env, jobject o) { return (*env)->NewGlobalRef(env, o); } // Open file in Termux's file editor via ACTION_SEND share intent. // env: JNIEnv* from your JNI callback // path: absolute filesystem path to the file (e.g. "/storage/emulated/0/foo.txt") void open_file_in_termux(JNIEnv *env, const char *path) { if (!ctx) return; jclass intentClass = (*env)->FindClass(env, "android/content/Intent"); if (!intentClass) return; // new Intent("android.intent.action.SEND") jmethodID intentInit = (*env)->GetMethodID(env, intentClass, "", "(Ljava/lang/String;)V"); if (!intentInit) return; jstring actionSend = (*env)->NewStringUTF(env, "android.intent.action.SEND"); jobject intent = (*env)->NewObject(env, intentClass, intentInit, actionSend); if (!intent) return; // intent.setType("text/plain"); jmethodID setType = (*env)->GetMethodID(env, intentClass, "setType", "(Ljava/lang/String;)Landroid/content/Intent;"); if (!setType) return; jstring type = (*env)->NewStringUTF(env, "text/plain"); (*env)->CallObjectMethod(env, intent, setType, type); // Uri uri = Uri.parse("file://" + path); jclass uriClass = (*env)->FindClass(env, "android/net/Uri"); if (!uriClass) return; jmethodID uriParse = (*env)->GetStaticMethodID(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); if (!uriParse) return; // Build "file://" + path size_t uriLen = strlen("file://") + strlen(path) + 1; char *uriStr = (char *)malloc(uriLen); if (!uriStr) return; strcpy(uriStr, "file://"); strcat(uriStr, path); jstring uriJstr = (*env)->NewStringUTF(env, uriStr); jobject uri = (*env)->CallStaticObjectMethod(env, uriClass, uriParse, uriJstr); free(uriStr); if (!uri) return; // intent.setData(uri); jmethodID setData = (*env)->GetMethodID(env, intentClass, "setData", "(Landroid/net/Uri;)Landroid/content/Intent;"); if (!setData) return; (*env)->CallObjectMethod(env, intent, setData, uri); // intent.putExtra("android.intent.extra.STREAM", uri.toString()); // Use putExtra(String, String) jmethodID putExtra = (*env)->GetMethodID(env, intentClass, "putExtra", "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;"); if (!putExtra) return; jstring extraKey = (*env)->NewStringUTF(env, "android.intent.extra.STREAM"); (*env)->CallObjectMethod(env, intent, putExtra, extraKey, uriJstr); // ctx.startActivity(intent); jclass contextClass = (*env)->GetObjectClass(env, ctx); jmethodID startActivity = (*env)->GetMethodID(env, contextClass, "startActivity", "(Landroid/content/Intent;)V"); if (!startActivity) return; (*env)->CallVoidMethod(env, ctx, startActivity, intent); } // Set the exclusion rects ON THE UI THREAD. This function is called from the // Go frame loop, which runs on its own OS thread (NOT the Android UI thread // — the JNI event pump merely enqueues events that the app goroutine drains). // setSystemGestureExclusionRects stores the rects on the view and triggers a // relayout; it is the RELAYOUT that reports them to the window manager, which // owns the edge back-gesture zone. A direct cross-thread call leaves the // window manager unaware, so the rects are applied through // view.post(PadExcl) — a Runnable (org.gioui.PadExcl, added by the build // scripts' smali patch) that runs on the UI thread. void SetGestureExclusions(JNIEnv *env, jint nrects, const jint *xyxy) { if (g_view == NULL) return; jclass verCls = (*env)->FindClass(env, "android/os/Build$VERSION"); if (verCls == NULL) return; jfieldID sdkF = (*env)->GetStaticFieldID(env, verCls, "SDK_INT", "I"); if (sdkF == NULL) return; if ((*env)->GetStaticIntField(env, verCls, sdkF) < 29) return; jclass rectCls = (*env)->FindClass(env, "android/graphics/Rect"); jmethodID rectInit = (*env)->GetMethodID(env, rectCls, "", "(IIII)V"); jclass listCls = (*env)->FindClass(env, "java/util/ArrayList"); jmethodID listInit = (*env)->GetMethodID(env, listCls, "", "()V"); jobject list = (*env)->NewObject(env, listCls, listInit); if (list == NULL) return; jmethodID addM = (*env)->GetMethodID(env, listCls, "add", "(Ljava/lang/Object;)Z"); for (int i = 0; i < nrects; i++) { jobject rect = (*env)->NewObject(env, rectCls, rectInit, xyxy[i * 4], xyxy[i * 4 + 1], xyxy[i * 4 + 2], xyxy[i * 4 + 3]); if (rect == NULL) continue; (*env)->CallBooleanMethod(env, list, addM, rect); (*env)->DeleteLocalRef(env, rect); } // Find the app class through the context's class loader. A bare // FindClass from this (non-Java) thread resolves against the BOOT // class loader only and would never find org.gioui.PadExcl. jclass ctxCls = (*env)->GetObjectClass(env, ctx); jmethodID gclM = (*env)->GetMethodID(env, ctxCls, "getClassLoader", "()Ljava/lang/ClassLoader;"); jobject loader = (*env)->CallObjectMethod(env, ctx, gclM); jclass loaderCls = (*env)->GetObjectClass(env, loader); jmethodID fcM = (*env)->GetMethodID(env, loaderCls, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); jstring clsName = (*env)->NewStringUTF(env, "org.gioui.PadExcl"); jclass padExclCls = (jclass)(*env)->CallObjectMethod(env, loader, fcM, clsName); if (padExclCls == NULL) { (*env)->ExceptionClear(env); (*env)->DeleteLocalRef(env, list); return; } jmethodID padExclInit = (*env)->GetMethodID(env, padExclCls, "", "(Landroid/view/View;Ljava/util/List;)V"); jobject runnable = (*env)->NewObject(env, padExclCls, padExclInit, g_view, list); if (runnable == NULL) { (*env)->ExceptionClear(env); (*env)->DeleteLocalRef(env, list); return; } jmethodID postM = (*env)->GetMethodID(env, (*env)->GetObjectClass(env, g_view), "post", "(Ljava/lang/Runnable;)Z"); (void)(*env)->CallBooleanMethod(env, g_view, postM, runnable); (*env)->ExceptionClear(env); (*env)->DeleteLocalRef(env, runnable); (*env)->DeleteLocalRef(env, list); } // Java_org_gioui_GioActivity_padFlushSession // // Called from GioActivity.onStop (injected by the build script): persist // the session snapshot before the process is likely killed. The Go side // (pad_flush_session) only enqueues a request on the logic goroutine's // channel, so this returns immediately on the UI thread. void Java_org_gioui_GioActivity_padFlushSession(JNIEnv *env, jclass cls) { pad_flush_session(); }