#define AL_LOG_SECTION "glfm" #include #include #include #include #include #include "window_glfm.h" #include "common.h" #include "keys.h" f64 stl_scale_scroll(f64 v, f64 factor) { return v / factor; } static struct stl_glfm_global global = { 0 }; void stl_set_output_discovered_callback(void (*output_discovered)(void *, struct stl_monitor *), void *userdata) { (void)output_discovered; (void)userdata; } bool stl_global_init(bool skip_graphics) { (void)skip_graphics; global.did_main = false; return false; } s32 stl_global_get_poll_fd(void) { al_assert_and_return(-1); } void stl_global_process_events(void) { al_assert(false); } 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_EMPTY; 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) { 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" : api == GLFMRenderingAPIOpenGLES31 ? "3.1" : api == GLFMRenderingAPIOpenGLES3 ? "3.0" : "2.0"); } static void onDraw(GLFMDisplay *display) { struct stl_window_glfm *glfm = (struct stl_window_glfm *)glfmGetUserData(display); stl_window_send_render_event(&glfm->w); } static void onSurfaceDestroyed(GLFMDisplay *display) { struct stl_window_glfm *glfm = (struct stl_window_glfm *)glfmGetUserData(display); stl_window_send_should_close_event(&glfm->w); } static void swap_dimensions_and_send_resize(struct stl_window_glfm *glfm) { SWAP(glfm->w.width, glfm->w.height); stl_window_send_resize_event(&glfm->w, glfm->w.width, glfm->w.height); } static void onOrientationChanged(GLFMDisplay *display, GLFMInterfaceOrientation orientation) { struct stl_window_glfm *glfm = (struct stl_window_glfm *)glfmGetUserData(display); if (orientation & (GLFMInterfaceOrientationPortrait | GLFMInterfaceOrientationPortraitUpsideDown)) { if (!(global.orientation & (GLFMInterfaceOrientationPortrait | GLFMInterfaceOrientationPortraitUpsideDown))) { swap_dimensions_and_send_resize(glfm); } } else if (orientation & (GLFMInterfaceOrientationLandscapeLeft | GLFMInterfaceOrientationLandscapeRight)) { if (!(global.orientation & (GLFMInterfaceOrientationLandscapeLeft | GLFMInterfaceOrientationLandscapeRight))) { swap_dimensions_and_send_resize(glfm); } } global.orientation = orientation; } static u8 glfm_to_stela_button_action[] = { [GLFMKeyActionPressed] = STELA_BUTTON_PRESSED, [GLFMKeyActionReleased] = STELA_BUTTON_RELEASED }; static bool onKey(GLFMDisplay *display, GLFMKeyCode key, GLFMKeyAction action, s32 modifiers) { struct stl_window_glfm *glfm = (struct stl_window_glfm *)glfmGetUserData(display); (void)modifiers; u8 mapped_action = glfm_to_stela_button_action[action]; switch (key) { case GLFMKeyCodeVolumeUp: return stl_window_send_key_immediate_event(&glfm->w, mapped_action, STELA_KEY_RIGHT); case GLFMKeyCodeVolumeDown: return stl_window_send_key_immediate_event(&glfm->w, mapped_action, STELA_KEY_LEFT); case GLFMKeyCodeNavigationBack: return false; default: break; } return true; } static u8 glfm_to_stela_touch[] = { [GLFMTouchPhaseHover] = STELA_TOUCH_HOVER, [GLFMTouchPhaseBegan] = STELA_TOUCH_BEGAN, [GLFMTouchPhaseMoved] = STELA_TOUCH_MOVED, [GLFMTouchPhaseEnded] = STELA_TOUCH_ENDED, [GLFMTouchPhaseCancelled] = STELA_TOUCH_CANCELLED }; static bool onTouch(GLFMDisplay *display, s32 touch, GLFMTouchPhase phase, f64 x, f64 y) { struct stl_window_glfm *glfm = (struct stl_window_glfm *)glfmGetUserData(display); stl_window_send_touch_event(&glfm->w, touch, glfm_to_stela_touch[phase], x, y); 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"); stl_global_init(false); global.display = display; #define STELA_INCLUDE_DEPTH_BUFFER glfmSetDisplayConfig(display, GLFMRenderingAPIOpenGLES32, GLFMColorFormatRGBA8888, #ifdef STELA_INCLUDE_DEPTH_BUFFER GLFMDepthFormat24, GLFMStencilFormat8, #else GLFMDepthFormatNone, GLFMStencilFormatNone, #endif GLFMMultisampleNone); global.orientation = glfmGetInterfaceOrientation(display); glfmSetDisplayChrome(display, GLFMUserInterfaceChromeNone); glfmSetMultitouchEnabled(display, true); glfmSetSurfaceCreatedFunc(display, onSurfaceCreated); glfmSetRenderFunc(display, onDraw); glfmSetSurfaceDestroyedFunc(display, onSurfaceDestroyed); 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, const char *monitor, const char *name, const char *app_id) { struct stl_window_glfm *glfm = (struct stl_window_glfm *)window; (void)width; (void)height; (void)monitor; (void)name; (void)app_id; glfmSetUserData(global.display, glfm); s32 display_width, display_height; glfmGetDisplaySize(global.display, &display_width, &display_height); al_assert(display_width > 0 && display_height > 0); stl_window_created(&glfm->w, (u32)display_width, (u32)display_height, flags); #ifdef STELA_USE_EGL glfm->w.egl_display = (EGLDisplay)glfmGetEglDisplay(global.display); glfm->w.egl_context = (EGLContext)glfmGetEglContext(global.display); #endif return true; } static void window_glfm_resize_internal(struct stl_window *window, u32 width, u32 height) { (void)window; (void)width; (void)height; } static void window_glfm_resize(struct stl_window *window, u32 width, u32 height) { (void)window; (void)width; (void)height; } static void window_glfm_toggle_fullscreen(struct stl_window *window) { (void)window; } #ifdef STELA_POLL_INLINE static void window_glfm_poll(struct stl_window *window, bool block) { (void)window; (void)block; } #ifdef STELA_PAUSE static void window_glfm_wake(struct stl_window *window) { (void)window; } #endif #endif static void window_glfm_process_events(struct stl_window *window) { (void)window; } #ifdef STELA_PAUSE static bool window_glfm_should_refresh(struct stl_window *window) { (void)window; return true; } #endif static void window_glfm_free(struct stl_window **window) { (void)window; } #if defined STELA_API_OPENGL static bool window_glfm_gl_loader_load(void *window) { (void)window; return true; } static bool window_glfm_gl_make_current(void *window) { (void)window; return true; } static void window_glfm_gl_release_current(void *window) { (void)window; } static void window_glfm_gl_swap_buffers(void *window) { struct stl_window_glfm *glfm = (struct stl_window_glfm *)window; (void)glfm; glfmSwapBuffers(global.display); } #endif struct stl_window *stl_window_create(void) { log_info("Wrapping GLFM window."); struct stl_window_glfm *glfm = al_alloc_object(struct stl_window_glfm); stl_window_init(&glfm->w); glfm->w.fullscreen = false; glfm->w.create_window = window_glfm_create_window; glfm->w.resize_internal = window_glfm_resize_internal; glfm->w.resize = window_glfm_resize; glfm->w.toggle_fullscreen = window_glfm_toggle_fullscreen; #ifdef STELA_POLL_INLINE glfm->w.poll = window_glfm_poll; #ifdef STELA_PAUSE glfm->w.wake = window_glfm_wake; #endif #endif glfm->w.process_events = window_glfm_process_events; #ifdef STELA_PAUSE glfm->w.should_refresh = window_glfm_should_refresh; #endif glfm->w.free = window_glfm_free; #if defined STELA_API_OPENGL glfm->w.get_gl_proc_address = glfmGetProcAddress; glfm->w.gl_loader_load = window_glfm_gl_loader_load; glfm->w.gl_make_current = window_glfm_gl_make_current; glfm->w.gl_release_current = window_glfm_gl_release_current; glfm->w.gl_swap_buffers = window_glfm_gl_swap_buffers; #endif return (struct stl_window *)glfm; }