diff options
11 files changed, 771 insertions, 2 deletions
diff --git a/tests/projects/android/native_app/raylib_custom_glue/android/AndroidManifest.xml b/tests/projects/android/native_app/raylib_custom_glue/android/AndroidManifest.xml new file mode 100644 index 000000000..2c521f2ed --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/android/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="com.raylib.custom_glue" + + android:versionCode="15" + android:versionName="1.5"> + + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" /> + + <uses-permission android:name="android.permission.SET_RELEASE_APP"/> + + <application + android:label="RaylibCustomGlue" android:debuggable="true" android:hasCode="false" + tools:replace="android:icon,android:theme,android:allowBackup,label" + android:icon="@mipmap/icon"> + + <activity android:name="android.app.NativeActivity" + android:label="RaylibCustomGlue" + android:configChanges="orientation|keyboardHidden|screenSize|screenLayout" + android:screenOrientation="portrait" + android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> + <meta-data android:name="android.app.lib_name" + android:value="raydemo_custom_glue" /> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest>
\ No newline at end of file diff --git a/tests/projects/android/native_app/raylib_custom_glue/android/debug.jks b/tests/projects/android/native_app/raylib_custom_glue/android/debug.jks Binary files differnew file mode 100644 index 000000000..0a18aa012 --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/android/debug.jks diff --git a/tests/projects/android/native_app/raylib_custom_glue/android/res/mipmap-mdpi/icon.png b/tests/projects/android/native_app/raylib_custom_glue/android/res/mipmap-mdpi/icon.png Binary files differnew file mode 100644 index 000000000..743413831 --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/android/res/mipmap-mdpi/icon.png diff --git a/tests/projects/android/native_app/raylib_custom_glue/android/res/mipmap/icon.png b/tests/projects/android/native_app/raylib_custom_glue/android/res/mipmap/icon.png Binary files differnew file mode 100644 index 000000000..743413831 --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/android/res/mipmap/icon.png diff --git a/tests/projects/android/native_app/raylib_custom_glue/android/res/values/strings.xml b/tests/projects/android/native_app/raylib_custom_glue/android/res/values/strings.xml new file mode 100644 index 000000000..bc0a9bfbf --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/android/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">Raylib Basic</string> +</resources>
\ No newline at end of file diff --git a/tests/projects/android/native_app/raylib_custom_glue/android/res/values/styles.xml b/tests/projects/android/native_app/raylib_custom_glue/android/res/values/styles.xml new file mode 100644 index 000000000..621547380 --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/android/res/values/styles.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="AppTheme" parent="android:Theme.NoTitleBar.Fullscreen"> + <item name="android:windowBackground">@android:color/black</item> + <item name="android:colorPrimary">#FF6200EE</item> + <item name="android:colorPrimaryDark">#FF3700B3</item> + <item name="android:colorAccent">#FF03DAC5</item> + </style> +</resources>
\ No newline at end of file diff --git a/tests/projects/android/native_app/raylib_custom_glue/src/android_native_app_glue.c b/tests/projects/android/native_app/raylib_custom_glue/src/android_native_app_glue.c new file mode 100644 index 000000000..bdf856b39 --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/src/android_native_app_glue.c @@ -0,0 +1,418 @@ +#include "android_native_app_glue.h" +#include <android/log.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "raydemo_custom_glue", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "raydemo_custom_glue", __VA_ARGS__)) + +/* For debug builds, always enable the debug traces in this library */ +#ifndef NDEBUG +# define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "raydemo_custom_glue", __VA_ARGS__)) +#else +# define LOGV(...) ((void)0) +#endif + +static void free_saved_state(struct android_app* android_app) { + pthread_mutex_lock(&android_app->mutex); + if (android_app->stateSaved) { + android_app->stateSaved = 0; + pthread_cond_broadcast(&android_app->cond); + } + pthread_mutex_unlock(&android_app->mutex); +} + +int8_t android_app_read_cmd(struct android_app* android_app) { + int8_t cmd; + if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) { + switch (cmd) { + case APP_CMD_SAVE_STATE: + free_saved_state(android_app); + break; + } + return cmd; + } + LOGE("No data on command pipe!"); + return -1; +} + +static void print_cur_config(struct android_app* android_app) { + char lang[2], country[2]; + AConfiguration_getLanguage(android_app->config, lang); + AConfiguration_getCountry(android_app->config, country); + + LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d " + "keys=%d nav=%d keyshid=%d navhid=%d sdk=%d size=%d long=%d " + "modetype=%d modenight=%d", + AConfiguration_getMcc(android_app->config), + AConfiguration_getMnc(android_app->config), + lang[0], lang[1], country[0], country[1], + AConfiguration_getOrientation(android_app->config), + AConfiguration_getTouchscreen(android_app->config), + AConfiguration_getDensity(android_app->config), + AConfiguration_getKeyboard(android_app->config), + AConfiguration_getNavigation(android_app->config), + AConfiguration_getKeysHidden(android_app->config), + AConfiguration_getNavHidden(android_app->config), + AConfiguration_getSdkVersion(android_app->config), + AConfiguration_getScreenSize(android_app->config), + AConfiguration_getScreenLong(android_app->config), + AConfiguration_getUiModeType(android_app->config), + AConfiguration_getUiModeNight(android_app->config)); +} + +void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { + switch (cmd) { + case APP_CMD_INPUT_CHANGED: + LOGV("APP_CMD_INPUT_CHANGED\n"); + pthread_mutex_lock(&android_app->mutex); + if (android_app->inputQueue != NULL) { + AInputQueue_detachLooper(android_app->inputQueue); + } + android_app->inputQueue = android_app->pendingInputQueue; + if (android_app->inputQueue != NULL) { + LOGV("Attaching input queue to looper"); + AInputQueue_attachLooper(android_app->inputQueue, + android_app->looper, LOOPER_ID_EVENT, NULL, + &android_app->inputPollSource); + } + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_INIT_WINDOW: + LOGV("APP_CMD_INIT_WINDOW\n"); + pthread_mutex_lock(&android_app->mutex); + android_app->window = android_app->pendingWindow; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_TERM_WINDOW: + LOGV("APP_CMD_TERM_WINDOW\n"); + pthread_cond_broadcast(&android_app->cond); + break; + + case APP_CMD_RESUME: + case APP_CMD_START: + case APP_CMD_PAUSE: + case APP_CMD_STOP: + LOGV("activityState=%d\n", cmd); + pthread_mutex_lock(&android_app->mutex); + android_app->activityState = cmd; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_CONFIG_CHANGED: + LOGV("APP_CMD_CONFIG_CHANGED\n"); + AConfiguration_fromAssetManager(android_app->config, + android_app->activity->assetManager); + print_cur_config(android_app); + break; + + case APP_CMD_DESTROY: + LOGV("APP_CMD_DESTROY\n"); + android_app->destroyRequested = 1; + break; + } +} + +void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) { + switch (cmd) { + case APP_CMD_TERM_WINDOW: + LOGV("APP_CMD_TERM_WINDOW\n"); + pthread_mutex_lock(&android_app->mutex); + android_app->window = NULL; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_SAVE_STATE: + LOGV("APP_CMD_SAVE_STATE\n"); + pthread_mutex_lock(&android_app->mutex); + android_app->stateSaved = 1; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + break; + + case APP_CMD_RESUME: + free_saved_state(android_app); + break; + } +} + +void app_dummy() { + +} + +static void android_app_destroy(struct android_app* android_app) { + LOGV("android_app_destroy!"); + free_saved_state(android_app); + pthread_mutex_lock(&android_app->mutex); + if (android_app->inputQueue != NULL) { + AInputQueue_detachLooper(android_app->inputQueue); + } + AConfiguration_delete(android_app->config); + android_app->destroyed = 1; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + // Can't touch android_app object after this. +} + +static void process_input(struct android_app* app, struct android_poll_source* source) { + AInputEvent* event = NULL; + while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) { + LOGV("New input event: type=%d\n", AInputEvent_getType(event)); + if (AInputQueue_preDispatchEvent(app->inputQueue, event)) { + continue; + } + int32_t handled = 0; + if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event); + AInputQueue_finishEvent(app->inputQueue, event, handled); + } +} + +static void process_cmd(struct android_app* app, struct android_poll_source* source) { + int8_t cmd = android_app_read_cmd(app); + android_app_pre_exec_cmd(app, cmd); + if (app->onAppCmd != NULL) app->onAppCmd(app, cmd); + android_app_post_exec_cmd(app, cmd); +} + +static void* android_app_entry(void* param) { + struct android_app* android_app = (struct android_app*)param; + + android_app->config = AConfiguration_new(); + AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager); + + print_cur_config(android_app); + + android_app->cmdPollSource.id = LOOPER_ID_MAIN; + android_app->cmdPollSource.app = android_app; + android_app->cmdPollSource.process = process_cmd; + android_app->inputPollSource.id = LOOPER_ID_EVENT; + android_app->inputPollSource.app = android_app; + android_app->inputPollSource.process = process_input; + + ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); + ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, + &android_app->cmdPollSource); + android_app->looper = looper; + + pthread_mutex_lock(&android_app->mutex); + android_app->running = 1; + pthread_cond_broadcast(&android_app->cond); + pthread_mutex_unlock(&android_app->mutex); + + android_main(android_app); + + android_app_destroy(android_app); + return NULL; +} + +// -------------------------------------------------------------------- +// Native activity interaction (called from main thread) +// -------------------------------------------------------------------- + +static struct android_app* android_app_create(ANativeActivity* activity, + void* savedState, size_t savedStateSize) { + struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app)); + memset(android_app, 0, sizeof(struct android_app)); + android_app->activity = activity; + + pthread_mutex_init(&android_app->mutex, NULL); + pthread_cond_init(&android_app->cond, NULL); + + if (savedState != NULL) { + android_app->savedState = malloc(savedStateSize); + android_app->savedStateSize = savedStateSize; + memcpy(android_app->savedState, savedState, savedStateSize); + } + + int msgpipe[2]; + if (pipe(msgpipe)) { + LOGE("could not create pipe: %s", strerror(errno)); + return NULL; + } + android_app->msgread = msgpipe[0]; + android_app->msgwrite = msgpipe[1]; + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&android_app->thread, &attr, android_app_entry, android_app); + + // Wait for thread to start. + pthread_mutex_lock(&android_app->mutex); + while (!android_app->running) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); + + return android_app; +} + +static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) { + if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { + LOGE("Failure writing android_app cmd: %s\n", strerror(errno)); + } +} + +static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) { + pthread_mutex_lock(&android_app->mutex); + android_app->pendingInputQueue = inputQueue; + android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED); + while (android_app->inputQueue != android_app->pendingInputQueue) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { + pthread_mutex_lock(&android_app->mutex); + if (android_app->pendingWindow != NULL) { + android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW); + } + android_app->pendingWindow = window; + if (window != NULL) { + android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW); + } + while (android_app->window != android_app->pendingWindow) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) { + pthread_mutex_lock(&android_app->mutex); + android_app_write_cmd(android_app, cmd); + while (android_app->activityState != cmd) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); +} + +static void android_app_free(struct android_app* android_app) { + pthread_mutex_lock(&android_app->mutex); + android_app_write_cmd(android_app, APP_CMD_DESTROY); + while (!android_app->destroyed) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + pthread_mutex_unlock(&android_app->mutex); + + close(android_app->msgread); + close(android_app->msgwrite); + pthread_cond_destroy(&android_app->cond); + pthread_mutex_destroy(&android_app->mutex); + free(android_app); +} + +static void onDestroy(ANativeActivity* activity) { + LOGV("Destroy: %p\n", activity); + android_app_free((struct android_app*)activity->instance); +} + +static void onStart(ANativeActivity* activity) { + LOGV("Start: %p\n", activity); + android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START); +} + +static void onResume(ANativeActivity* activity) { + LOGV("Resume: %p\n", activity); + android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME); +} + +static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { + struct android_app* android_app = (struct android_app*)activity->instance; + void* savedState = NULL; + + LOGV("SaveInstanceState: %p\n", activity); + pthread_mutex_lock(&android_app->mutex); + android_app->stateSaved = 0; + android_app_write_cmd(android_app, APP_CMD_SAVE_STATE); + while (!android_app->stateSaved) { + pthread_cond_wait(&android_app->cond, &android_app->mutex); + } + + if (android_app->savedState != NULL) { + savedState = android_app->savedState; + *outLen = android_app->savedStateSize; + android_app->savedState = NULL; + android_app->savedStateSize = 0; + } + + pthread_mutex_unlock(&android_app->mutex); + + return savedState; +} + +static void onPause(ANativeActivity* activity) { + LOGV("Pause: %p\n", activity); + android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE); +} + +static void onStop(ANativeActivity* activity) { + LOGV("Stop: %p\n", activity); + android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP); +} + +static void onConfigurationChanged(ANativeActivity* activity) { + struct android_app* android_app = (struct android_app*)activity->instance; + LOGV("ConfigurationChanged: %p\n", activity); + android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED); +} + +static void onLowMemory(ANativeActivity* activity) { + struct android_app* android_app = (struct android_app*)activity->instance; + LOGV("LowMemory: %p\n", activity); + android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY); +} + +static void onWindowFocusChanged(ANativeActivity* activity, int focused) { + LOGV("WindowFocusChanged: %p -- %d\n", activity, focused); + android_app_write_cmd((struct android_app*)activity->instance, + focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); +} + +static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowCreated: %p -- %p\n", activity, window); + android_app_set_window((struct android_app*)activity->instance, window); +} + +static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window); + android_app_set_window((struct android_app*)activity->instance, NULL); +} + +static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { + LOGV("InputQueueCreated: %p -- %p\n", activity, queue); + android_app_set_input((struct android_app*)activity->instance, queue); +} + +static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { + LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue); + android_app_set_input((struct android_app*)activity->instance, NULL); +} + +void ANativeActivity_onCreate(ANativeActivity* activity, + void* savedState, size_t savedStateSize) { + LOGV("Creating: %p\n", activity); + activity->callbacks->onDestroy = onDestroy; + activity->callbacks->onStart = onStart; + activity->callbacks->onResume = onResume; + activity->callbacks->onSaveInstanceState = onSaveInstanceState; + activity->callbacks->onPause = onPause; + activity->callbacks->onStop = onStop; + activity->callbacks->onConfigurationChanged = onConfigurationChanged; + activity->callbacks->onLowMemory = onLowMemory; + activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; + activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; + activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; + activity->callbacks->onInputQueueCreated = onInputQueueCreated; + activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; + + activity->instance = android_app_create(activity, savedState, savedStateSize); +} diff --git a/tests/projects/android/native_app/raylib_custom_glue/src/android_native_app_glue.h b/tests/projects/android/native_app/raylib_custom_glue/src/android_native_app_glue.h new file mode 100644 index 000000000..a13034f43 --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/src/android_native_app_glue.h @@ -0,0 +1,234 @@ +#ifndef _ANDROID_NATIVE_APP_GLUE_H +#define _ANDROID_NATIVE_APP_GLUE_H + +#include <poll.h> +#include <pthread.h> +#include <sched.h> + +#include <android/native_activity.h> +#include <android/looper.h> +#include <android/configuration.h> +#include <android/rect.h> +#include <android/native_window.h> +#include <android/input.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The native activity interface provided by <android/native_activity.h> + * is based on a set of application-provided callbacks that will be called + * by the Activity's main thread when certain events occur. + * + * This means that each one of this callbacks _should_ _not_ block, or they + * risk having the system force-close the application. This programming + * model is direct, lightweight, but constraining. + * + * The 'threaded_native_app' static library is used to provide a different + * execution model where the application can implement its own main event + * loop in a different thread instead. Here's how it works: + * + * 1/ The application must provide a function named "android_main()" that + * will be called when the activity is created, in a new thread that is + * distinct from the activity's main thread. + * + * 2/ android_main() receives a pointer to a valid "android_app" structure + * that contains references to other important objects, e.g. the + * ANativeActivity obejct instance the application is running in. + * + * 3/ the "android_app" object holds an ALooper instance that already + * listens to two important things: + * + * - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX + * declarations below. + * + * - input events coming from the AInputQueue attached to the activity. + * + * Each of these correspond to an ALooper callback that returns a "data" + * value of LOOPER_ID_MAIN and LOOPER_ID_EVENT, respectively. + * + * Your application can use the same ALooper to listen to additionnal + * file-descriptors. + * + * 4/ Whenever you receive a LOOPER_ID_MAIN event from the ALooper, your + * code should call the function android_app_read_cmd() to read the + * command value and act upon it. This is normally done by calling + * android_app_exec_cmd() directly. + * + * XXX: MAKE THIS STUFF MORE CLEAR !! + * + * 5/ Whenever you receive a LOOPER_ID_EVENT event from the ALooper, you + * should read one event from the AInputQueue with AInputQueue_getEvent(). + * + * See the sample named "native-activity" that comes with the NDK with a + * full usage example. + * + */ + +/** + * Data associated with an ALooper fd that will be returned as the "outData" + * when that source has data ready. + */ +struct android_poll_source { + // The identifier of this source. May be LOOPER_ID_MAIN or + // LOOPER_ID_EVENT. + int32_t id; + + // The android_app structure associated with this source. + struct android_app* app; + + // Function to call to process this source when data is available. + void (*process)(struct android_app* app, struct android_poll_source* source); +}; + +struct android_app { + // The application can place a pointer to its own state object + // here if it likes. + void* userData; + + // Fill this in with the function to process main app commands (APP_CMD_*) + void (*onAppCmd)(struct android_app* app, int32_t cmd); + + // Fill this in with the function to process input events. At this point + // the event has already been pre-dispatched, and it will be finished upon + // return. Return 1 if you have handled the event, 0 for any default + // dispatching. + int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); + + // The ANativeActivity object instance that this app is running in. + ANativeActivity* activity; + + // The current configuration the app is running in. + AConfiguration* config; + + // The last saved state that was given at creation time. + void* savedState; + size_t savedStateSize; + + // The ALooper associated with the app's thread. + ALooper* looper; + + // When non-NULL, this is the input queue from which the app will + // receive user input events. + AInputQueue* inputQueue; + + // When non-NULL, this is the window surface that the app can draw in. + ANativeWindow* window; + + // Current content rectangle of the window; this is the area where the + // window's content should be placed to be seen by the user. + ARect contentRect; + + // Current state of the app's activity. May be either APP_CMD_START, + // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below. + int activityState; + + // This is non-zero when the application's NativeActivity is being + // destroyed and waiting for the app thread to complete. + int destroyRequested; + + // ------------------------------------------------- + // Below are "private" implementation of the glue code. + + pthread_mutex_t mutex; + pthread_cond_t cond; + + int msgread; + int msgwrite; + + pthread_t thread; + + struct android_poll_source cmdPollSource; + struct android_poll_source inputPollSource; + + int running; + int stateSaved; + int destroyed; + int redrawNeeded; + AInputQueue* pendingInputQueue; + ANativeWindow* pendingWindow; + ARect pendingContentRect; +}; + +enum { + /** + * Looper data ID of commands coming from the app's main thread. + * These can be retrieved and processed with android_app_read_cmd() + * and android_app_exec_cmd(). + */ + LOOPER_ID_MAIN = 1, + + /** + * Looper data ID of events coming from the AInputQueue of the + * application's window. These can be read via the inputQueue + * object of android_app. + */ + LOOPER_ID_EVENT = 2, + + /** + * Looper data ID of events coming from the AInputQueue of the + * application's window. These can be read via the inputQueue + * object of android_app. + */ + LOOPER_ID_USER = 3, +}; + +enum { + APP_CMD_INPUT_CHANGED, + APP_CMD_INIT_WINDOW, + APP_CMD_TERM_WINDOW, + APP_CMD_WINDOW_RESIZED, + APP_CMD_WINDOW_REDRAW_NEEDED, + APP_CMD_CONTENT_RECT_CHANGED, + APP_CMD_GAINED_FOCUS, + APP_CMD_LOST_FOCUS, + APP_CMD_CONFIG_CHANGED, + APP_CMD_LOW_MEMORY, + APP_CMD_START, + APP_CMD_RESUME, + APP_CMD_SAVE_STATE, + APP_CMD_PAUSE, + APP_CMD_STOP, + APP_CMD_DESTROY, +}; + +/** + * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next + * app command message. + */ +int8_t android_app_read_cmd(struct android_app* android_app); + +/** + * Call with the command returned by android_app_read_cmd() to do the + * initial pre-processing of the given command. You can use this for + * your own normal default behavior for the command, and then inspect + * it yourself to do your own processing. This function may block for + * certain commands (e.g. to wait for the app's graphics context to be + * initialized) and will returns true if it blocked. + */ +void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd); + +/** + * Call with the command returned by android_app_read_cmd() to do the + * final post-processing of the given command. You must have first called + * android_app_pre_exec_cmd() before calling this function. + */ +void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); + +/** + * Dummy function you can call to ensure glue code isn't stripped. + */ +void app_dummy(); + +/** + * This is the function that application code must implement, representing + * the main entry to the app. + */ +extern void android_main(struct android_app* app); + +#ifdef __cplusplus +} +#endif + +#endif /* _ANDROID_NATIVE_APP_GLUE_H */ diff --git a/tests/projects/android/native_app/raylib_custom_glue/src/main.cpp b/tests/projects/android/native_app/raylib_custom_glue/src/main.cpp new file mode 100644 index 000000000..0094d9e3c --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/src/main.cpp @@ -0,0 +1,32 @@ +#include <raylib.h> +#include <android/log.h> + +#define LOG_TAG "raydemo_basic" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) + +int main(int argc, char** argv) { + InitWindow(0, 0, "Hello, xmake for raylib android!"); + SetTargetFPS(60); + LOGI("raylib application is running!"); + + while (!WindowShouldClose()) { + BeginDrawing(); + ClearBackground(BLACK); + + // Draw FPS + const char* fpsText = TextFormat("%2i FPS", GetFPS()); + int fpsWidth = MeasureText(fpsText, 40); + DrawText(fpsText, GetScreenWidth() / 2 - fpsWidth / 2, 30, 40, GREEN); + + // Draw centered text + int fontSize = 50; + const char* text = "Hello, xmake for raylib android!"; + int textWidth = MeasureText(text, fontSize); + DrawText(text, GetScreenWidth() / 2 - textWidth / 2, 100, fontSize, BLUE); + + EndDrawing(); + } + + CloseWindow(); + return 0; +} diff --git a/tests/projects/android/native_app/raylib_custom_glue/xmake.lua b/tests/projects/android/native_app/raylib_custom_glue/xmake.lua new file mode 100644 index 000000000..3b112f8fe --- /dev/null +++ b/tests/projects/android/native_app/raylib_custom_glue/xmake.lua @@ -0,0 +1,20 @@ +add_rules("mode.debug", "mode.release") + +add_requires("raylib 5.5.0") + +target("raydemo_custom_glue") + set_kind("binary") + set_languages("c++17") + add_files("src/main.cpp", "src/android_native_app_glue.c") + add_syslinks("log") + add_packages("raylib") + add_rules("android.native_app", { + android_sdk_version = "35", + android_manifest = "android/AndroidManifest.xml", + android_res = "android/res", + keystore = "android/debug.jks", + keystore_pass = "123456", + package_name = "com.raylib.custom_glue", + native_app_glue = false, + logcat_filters = {"raydemo_custom_glue", "raylib"} + }) diff --git a/xmake/rules/platform/android/package.lua b/xmake/rules/platform/android/package.lua index 00142a85f..42e4ac80e 100644 --- a/xmake/rules/platform/android/package.lua +++ b/xmake/rules/platform/android/package.lua @@ -35,6 +35,19 @@ function main(target, opt) assert(android_sdk_version, "android sdk version not set") assert(android_manifest, "android manifest not set") + if not path.is_absolute(android_manifest) then + android_manifest = path.join(target:scriptdir(), android_manifest) + end + if keystore and not path.is_absolute(keystore) then + keystore = path.join(target:scriptdir(), keystore) + end + if android_res and not path.is_absolute(android_res) then + android_res = path.join(target:scriptdir(), android_res) + end + if android_assets and not path.is_absolute(android_assets) then + android_assets = path.join(target:scriptdir(), android_assets) + end + local final_apk = path.join(target:targetdir(), target:basename() .. ".apk") -- add dependencies @@ -55,7 +68,15 @@ function main(target, opt) os.mkdir(path.join(tmp_path, "lib", target:arch())) -- copy the target library to temp folder with name libmain.so - local libfile = path.join(tmp_path, "lib", target:arch(), "libmain.so") + local libname = "libmain.so" + local manifest_xml = io.readfile(android_manifest) + if manifest_xml then + local lib_name_meta = manifest_xml:match('android:name="android.app.lib_name"%s+android:value="([^"]+)"') + if lib_name_meta then + libname = "lib" .. lib_name_meta .. ".so" + end + end + local libfile = path.join(tmp_path, "lib", target:arch(), libname) os.cp(target:targetfile(), libfile) -- get android tool path @@ -87,7 +108,7 @@ function main(target, opt) os.vrunv(aapt, aapt_argv) -- pack libs - os.vrunv(aapt, {"add", "res_only.apk", "lib/" .. target:arch() .."/libmain.so"}, {curdir = tmp_path}) + os.vrunv(aapt, {"add", "res_only.apk", "lib/" .. target:arch() .."/" .. libname}, {curdir = tmp_path}) -- align apk local aligned_apk = path.join(tmp_path, "unsigned.apk") |
