summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2026-08-25 19:49:17 -0400
committerAndrew Opalach <andrew@akon.city> 2026-08-25 19:49:17 -0400
commit0c43dcac12f606c0ac46b5f87dd3de324ff9ef04 (patch)
tree41ac1567b05b7409b718c6f7df75ffe334da2377
parentf48520c10ba822e13ef119f626b85fc081f96124 (diff)
downloadstela-0c43dcac12f606c0ac46b5f87dd3de324ff9ef04.tar.gz
stela-0c43dcac12f606c0ac46b5f87dd3de324ff9ef04.tar.bz2
stela-0c43dcac12f606c0ac46b5f87dd3de324ff9ef04.zip
Very wip Android stuff
Signed-off-by: Andrew Opalach <andrew@akon.city>
-rw-r--r--src/window_glfm.c187
-rw-r--r--src/window_glfm.h2
2 files changed, 188 insertions, 1 deletions
diff --git a/src/window_glfm.c b/src/window_glfm.c
index a99dbcd..ad8baec 100644
--- a/src/window_glfm.c
+++ b/src/window_glfm.c
@@ -1,6 +1,9 @@
#define AL_LOG_SECTION "glfm"
#include <al/log.h>
+#include <al/array.h>
#include <nnwt/common.h>
+#include <nnwt/file.h>
+#include <nnwt/sort.h>
#include "window_glfm.h"
#include "common.h"
@@ -40,14 +43,184 @@ void stl_global_close(void)
{
}
+#define glfm__printException(jni) (*(jni))->ExceptionDescribe(jni)
+#define glfm__wasJavaExceptionThrown(jni) \
+ ((*(jni))->ExceptionCheck(jni) ? (glfm__printException(jni), (*(jni))->ExceptionClear(jni), true) : false)
+
+static jmethodID glfm__getJavaMethodID(JNIEnv *jni, jobject object, const char *name, const char *sig)
+{
+ if (!object) return NULL;
+ jclass class = (*jni)->GetObjectClass(jni, object);
+ jmethodID methodID = (*jni)->GetMethodID(jni, class, name, sig);
+ (*jni)->DeleteLocalRef(jni, class);
+ return glfm__wasJavaExceptionThrown(jni) ? NULL : methodID;
+}
+
+static jfieldID glfm__getJavaFieldID(JNIEnv *jni, jobject object, const char *name, const char *sig)
+{
+ if (!object) return NULL;
+ jclass class = (*jni)->GetObjectClass(jni, object);
+ jfieldID fieldID = (*jni)->GetFieldID(jni, class, name, sig);
+ (*jni)->DeleteLocalRef(jni, class);
+ return glfm__wasJavaExceptionThrown(jni) ? NULL : fieldID;
+}
+
+#define glfm__callJavaMethod(jni, object, methodName, methodSig, returnType, ...) \
+ (*(jni))->Call##returnType##Method(jni, object, \
+ glfm__getJavaMethodID(jni, object, methodName, methodSig), ##__VA_ARGS__)
+
+#define glfm__getJavaField(jni, object, fieldName, fieldSig, fieldType) \
+ (*(jni))->Get##fieldType##Field(jni, object, \
+ glfm__getJavaFieldID(jni, object, fieldName, fieldSig))
+
+static s32 jniGetFDFromFileDescriptor(JNIEnv *jni, jobject fileDescriptor)
+{
+ return glfm__getJavaField(jni, fileDescriptor, "descriptor", "I", Int);
+}
+
+static u32 parse_intent(ANativeActivity *activity, JNIEnv *jni)
+{
+ u32 main_arg_index = 0;
+ jobject intent = glfm__callJavaMethod(jni, activity->clazz, "getIntent",
+ "()Landroid/content/Intent;",
+ Object);
+ if (!intent) return 0;
+ jobject uri = glfm__callJavaMethod(jni, intent, "getData",
+ "()Landroid/net/Uri;",
+ Object);
+ if (!uri) return 0;
+ jobject scheme = glfm__callJavaMethod(jni, intent, "getScheme",
+ "()Ljava/lang/String;",
+ Object);
+ const char *c_str = (*jni)->GetStringUTFChars(jni, scheme, NULL);
+ bool is_content = al_strscmp(c_str, "content") == 0;
+ bool is_http = al_strscmp(c_str, "http") == 0;
+ (*jni)->ReleaseStringUTFChars(jni, scheme, c_str);
+ (*jni)->DeleteLocalRef(jni, scheme);
+ jobject realPath = NULL;
+ if (is_content) {
+ jstring extraName = (*jni)->NewStringUTF(jni, "real_path");
+ realPath = glfm__callJavaMethod(jni, intent, "getStringExtra",
+ "(Ljava/lang/String;)Ljava/lang/String;",
+ Object, extraName);
+ (*jni)->DeleteLocalRef(jni, extraName);
+ }
+ if (realPath) {
+ c_str = (*jni)->GetStringUTFChars(jni, realPath, NULL);
+ str main_arg;
+ al_str_from(&main_arg, c_str);
+ (*jni)->ReleaseStringUTFChars(jni, realPath, c_str);
+ (*jni)->DeleteLocalRef(jni, realPath);
+ u32 last = al_str_rfind(&main_arg, '/');
+ if (last != AL_STR_NO_POS) {
+ str sub = al_str_substr(&main_arg, 0, last);
+ struct nn_dir dir;
+ if (nn_dir_open(&dir, &sub)) {
+ struct nn_dir_entry entry;
+ while (nn_dir_read(&dir, &entry)) {
+ if (entry.type == NNWT_ENTRY_FILE) {
+ str arg;
+ nn_dir_entry_get_path(&entry, &dir, &arg);
+ al_array_push(global.args, arg);
+ }
+ }
+ nn_dir_close(&dir);
+ }
+ }
+ if (global.args.count > 0) {
+ al_array_sort(global.args, str, nn_numerical_compare);
+ str *arg;
+ al_array_foreach_ptr(global.args, i, arg) {
+ if (al_str_eq(arg, &main_arg)) {
+ main_arg_index = i;
+ break;
+ }
+ }
+ al_str_free(&main_arg);
+ } else {
+ al_array_push(global.args, main_arg);
+ }
+ } else {
+ str arg = AL_STR_NULL;
+ if (is_http) {
+ jobject path = glfm__callJavaMethod(jni, uri, "toString",
+ "()Ljava/lang/String;",
+ Object);
+ c_str = (*jni)->GetStringUTFChars(jni, path, NULL);
+ al_str_cat(&arg, &al_str_cr(c_str));
+ (*jni)->ReleaseStringUTFChars(jni, path, c_str);
+ (*jni)->DeleteLocalRef(jni, path);
+ al_array_push(global.args, arg);
+ } else {
+ // @TODO: Figure out the rules of closing the fd we get.
+ // https://developer.android.com/reference/android/os/ParcelFileDescriptor#detachFd()
+ // https://stackoverflow.com/questions/24701029/how-to-properly-pass-an-asset-filedescriptor-to-ffmpeg-using-jni-in-android
+ jobject contentResolver = glfm__callJavaMethod(jni, activity->clazz, "getContentResolver",
+ "()Landroid/content/ContentResolver;",
+ Object);
+ jstring mode = (*jni)->NewStringUTF(jni, "r");
+ jobject assetFileDescriptor = glfm__callJavaMethod(jni, contentResolver, "openAssetFileDescriptor",
+ "(Landroid/net/Uri;Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;",
+ Object, uri, mode);
+ (*jni)->DeleteLocalRef(jni, mode);
+ jobject fileDescriptor = glfm__callJavaMethod(jni, assetFileDescriptor, "getFileDescriptor",
+ "()Ljava/io/FileDescriptor;",
+ Object);
+ /*
+ long startOffset = glfm__callJavaMethod(jni, assetFileDescriptor, "getStartOffset",
+ "()J",
+ Long);
+ long length = glfm__callJavaMethod(jni, assetFileDescriptor, "getLength",
+ "()J",
+ Long);
+ */
+ s32 fd = jniGetFDFromFileDescriptor(jni, fileDescriptor);
+ al_str_from(&arg, "fd://");
+ al_str_cat(&arg, al_print_s32(fd));
+ al_array_push(global.args, arg);
+ }
+ }
+ return main_arg_index;
+}
+
+static bool get_android_temp_dir(ANativeActivity *activity, JNIEnv *jni)
+{
+ jobject cacheDir = glfm__callJavaMethod(jni, activity->clazz, "getCacheDir",
+ "()Ljava/io/File;",
+ Object);
+ if (!cacheDir) return false;
+ jobject absolutePath = glfm__callJavaMethod(jni, cacheDir, "getAbsolutePath",
+ "()Ljava/lang/String;",
+ Object);
+ if (!absolutePath) return false;
+ const char *c_str = (*jni)->GetStringUTFChars(jni, absolutePath, NULL);
+ nn_common_set_temp_directory(&al_str_cr(c_str));
+ (*jni)->ReleaseStringUTFChars(jni, absolutePath, c_str);
+ (*jni)->DeleteLocalRef(jni, absolutePath);
+ return true;
+}
+
static void onSurfaceCreated(GLFMDisplay *display, s32 width, s32 height)
{
(void)width;
(void)height;
+
+ JNIEnv *jni;
+ ANativeActivity *activity = glfmGetAndroidActivity(display, (void **)&jni);
+
if (!global.did_main) {
- stl_platform_main(0, NULL);
+ if (!get_android_temp_dir(activity, jni)) {
+ log_error("Failed to get temp directory.");
+ }
+ al_array_init(global.args);
+ global.main_arg_index = parse_intent(activity, jni);
+ str argv0;
+ al_str_from(&argv0, APPLICATION_NAME);
+ al_array_insert(global.args, 0, argv0);
+ stl_platform_main(global.args.count, global.args.data, &global.main_arg_index);
global.did_main = true;
}
+
GLFMRenderingAPI api = glfmGetRenderingAPI(display);
log_info("Created GLES context of version %s.",
api == GLFMRenderingAPIOpenGLES32 ? "3.2" :
@@ -104,6 +277,7 @@ static bool onKey(GLFMDisplay *display, GLFMKeyCode key, GLFMKeyAction action, s
case GLFMKeyCodeVolumeDown:
return stl_window_send_key_immediate_event(&glfm->w, mapped_action, STELA_KEY_LEFT);
case GLFMKeyCodeNavigationBack:
+ return false;
default:
break;
}
@@ -125,6 +299,16 @@ static bool onTouch(GLFMDisplay *display, s32 touch, GLFMTouchPhase phase, f64 x
return true;
}
+static void onSensor(GLFMDisplay *display, GLFMSensorEvent event)
+{
+ struct stl_window_glfm *glfm = (struct stl_window_glfm *)glfmGetUserData(display);
+ if (event.sensor == GLFMSensorGyroscope)
+ {
+ stl_window_send_pointer_event(&glfm->w, STELA_POINTER_MOVE,
+ event.vector.x * -75.0, event.vector.y * 75.0);
+ }
+}
+
void glfmMain(GLFMDisplay *display)
{
nn_common_init("glfmMain");
@@ -151,6 +335,7 @@ void glfmMain(GLFMDisplay *display)
glfmSetOrientationChangedFunc(display, onOrientationChanged);
glfmSetKeyFunc(display, onKey);
glfmSetTouchFunc(display, onTouch);
+ glfmSetSensorFunc(display, GLFMSensorGyroscope, onSensor);
}
static bool window_glfm_create_window(struct stl_window *window, u32 width, u32 height, u32 flags,
diff --git a/src/window_glfm.h b/src/window_glfm.h
index bfbda04..b9c6c69 100644
--- a/src/window_glfm.h
+++ b/src/window_glfm.h
@@ -7,6 +7,8 @@
struct stl_glfm_global {
GLFMDisplay *display;
bool did_main;
+ array(str) args;
+ u32 main_arg_index;
GLFMInterfaceOrientation orientation;
};