From 10628d178d49225dc883b85c48c9906751bfbf56 Mon Sep 17 00:00:00 2001 From: Lumen Keyes Date: Tue, 6 May 2025 12:00:57 -0600 Subject: [PATCH 1/7] added minimal example for raylib --- examples/raylib/README.md | 15 + examples/raylib/android/AndroidManifest.xml | 32 ++ .../raylib/android/res/mipmap/ic_launcher.png | Bin 0 -> 4812 bytes .../raylib/android/res/values/strings.xml | 10 + examples/raylib/build.zig | 86 ++++ examples/raylib/build.zig.zon | 53 ++ examples/raylib/src/android_native_app_glue.c | 457 ++++++++++++++++++ examples/raylib/src/android_native_app_glue.h | 350 ++++++++++++++ examples/raylib/src/main.zig | 17 + 9 files changed, 1020 insertions(+) create mode 100644 examples/raylib/README.md create mode 100644 examples/raylib/android/AndroidManifest.xml create mode 100644 examples/raylib/android/res/mipmap/ic_launcher.png create mode 100644 examples/raylib/android/res/values/strings.xml create mode 100644 examples/raylib/build.zig create mode 100644 examples/raylib/build.zig.zon create mode 100644 examples/raylib/src/android_native_app_glue.c create mode 100644 examples/raylib/src/android_native_app_glue.h create mode 100644 examples/raylib/src/main.zig diff --git a/examples/raylib/README.md b/examples/raylib/README.md new file mode 100644 index 0000000..217ecf9 --- /dev/null +++ b/examples/raylib/README.md @@ -0,0 +1,15 @@ +A minimal example of using [zig-android-sdk](https://github.com/silbinarywolf/zig-android-sdk) to build raylib for android. + +Build for a single target with, e.g. `zig build -Dtarget=aarch64-linux-android`, or for all android targets with `zig build -Dandroid=true`. + +**Note**: +Due to [an upstream bug](https://github.com/ziglang/zig/issues/20476), you will probably receive a warning (or multiple warnings if building for multiple targets) like this: +``` +error: warning(link): unexpected LLD stderr: +ld.lld: warning: /.zig-cache/o/4227869d730f094811a7cdaaab535797/libraylib.a: archive member '/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/x86_64-linux-android/35/libGLESv2.so' is neither ET_REL nor LLVM bitcode +``` +You can ignore this error for now. + +You should probably source the `android_native_app_glue.c/.h` files from the version of the SDK you download, rather than using the included ones, to ensure you are using the most up-to-date versions. + + diff --git a/examples/raylib/android/AndroidManifest.xml b/examples/raylib/android/AndroidManifest.xml new file mode 100644 index 0000000..541cdc2 --- /dev/null +++ b/examples/raylib/android/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/examples/raylib/android/res/mipmap/ic_launcher.png b/examples/raylib/android/res/mipmap/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..7ea4bd87ea6b463338901e4dc0c9c496bdd46541 GIT binary patch literal 4812 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 + + + Zig Minimal + + com.zig.minimal + diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig new file mode 100644 index 0000000..a283340 --- /dev/null +++ b/examples/raylib/build.zig @@ -0,0 +1,86 @@ +const android = @import("android"); +const std = @import("std"); + +//this is targeting android API level 29. +//You may have to change the values here and in android/AndroidManifest.xml to target your desired API level +const android_api = "29"; +const android_version = .android10; + +pub fn build(b: *std.Build) void { + const root_target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + const android_targets = android.standardTargets(b, root_target); + + var root_target_single = [_]std.Build.ResolvedTarget{root_target}; + const targets: []std.Build.ResolvedTarget = if (android_targets.len == 0) + root_target_single[0..] + else + android_targets; + + const android_apk: ?*android.APK = blk: { + if (android_targets.len == 0) { + break :blk null; + } + const android_tools = android.Tools.create(b, .{ + .api_level = android_version, + .build_tools_version = "35.0.0", + .ndk_version = "27.2.12479018", + }); + const apk = android.APK.create(b, android_tools); + + const key_store_file = android_tools.createKeyStore(android.CreateKey.example()); + apk.setKeyStore(key_store_file); + apk.setAndroidManifest(b.path("android/AndroidManifest.xml")); + apk.addResourceDirectory(b.path("android/res")); + + break :blk apk; + }; + + for(targets) |target| { + if (target.result.abi.isAndroid()) { + const lib_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + + const lib = b.addLibrary(.{ + .linkage = .dynamic, + .name = "minimal_android_raylib", + .root_module = lib_mod, + }); + + b.installArtifact(lib); + lib.linkLibC(); + const raylib_dep = b.dependency("raylib_zig", .{ + .target = target, + .optimize = optimize, + .android_api_version = @as([]const u8, android_api) + }); + const raylib_artifact = raylib_dep.artifact("raylib"); + lib.linkLibrary(raylib_artifact); + + const raylib_mod = raylib_dep.module("raylib"); + lib.root_module.addImport("raylib", raylib_mod); + const apk: *android.APK = android_apk orelse @panic("Android APK should be initialized"); + + const android_dep = b.dependency("android", .{ + .optimize = optimize, + .target = target, + }); + lib.root_module.addImport("android", android_dep.module("android")); + lib.root_module.linkSystemLibrary("android", .{.preferred_link_mode = .dynamic}); + lib.root_module.linkSystemLibrary("EGL", .{.preferred_link_mode = .dynamic}); + lib.root_module.linkSystemLibrary("GLESv2", .{.preferred_link_mode = .dynamic}); + lib.root_module.linkSystemLibrary("log", .{ .preferred_link_mode = .dynamic }); + lib.root_module.addCSourceFile(.{ .file = b.path("src/android_native_app_glue.c")}); + + apk.addArtifact(lib); + } else { + //non-android build logic... + } + } + if (android_apk) |apk| { + apk.installApk(); + } +} diff --git a/examples/raylib/build.zig.zon b/examples/raylib/build.zig.zon new file mode 100644 index 0000000..2744849 --- /dev/null +++ b/examples/raylib/build.zig.zon @@ -0,0 +1,53 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .minimal_android_raylib, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + + // Together with name, this represents a globally unique package + // identifier. This field is generated by the Zig toolchain when the + // package is first created, and then *never changes*. This allows + // unambiguous detection of one package being an updated version of + // another. + // + // When forking a Zig project, this id should be regenerated (delete the + // field and run `zig build`) if the upstream project is still maintained. + // Otherwise, the fork is *hostile*, attempting to take control over the + // original project's identity. Thus it is recommended to leave the comment + // on the following line intact, so that it shows up in code reviews that + // modify the field. + .fingerprint = 0x7d5dc97ee62a8428, // Changing this has security and trust implications. + + // Tracks the earliest Zig version that the package considers to be a + // supported use case. + .minimum_zig_version = "0.14.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + .raylib_zig = .{ + .url = "git+https://github.com/lumenkeyes/raylib-zig#e421e2a16c7f965b8ff70c7db7a13bf9fe2d321c", + .hash = "raylib_zig-5.6.0-dev-KE8REAgyBQB4QKuE8SJZHBOw_KGcoLCybyyAIXRLCXKQ", + }, + .android = .{ .path = "../.." }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/examples/raylib/src/android_native_app_glue.c b/examples/raylib/src/android_native_app_glue.c new file mode 100644 index 0000000..1e63c5e --- /dev/null +++ b/examples/raylib/src/android_native_app_glue.c @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "android_native_app_glue.h" + +#include + +#include +#include +#include +#include + +#include + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__)) + +/* For debug builds, always enable the debug traces in this library */ +#ifndef NDEBUG +# define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __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->savedState != NULL) { + free(android_app->savedState); + android_app->savedState = NULL; + android_app->savedStateSize = 0; + } + 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)) { + LOGE("No data on command pipe!"); + return -1; + } + if (cmd == APP_CMD_SAVE_STATE) free_saved_state(android_app); + return cmd; +} + +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"); + 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_INPUT, 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"); + 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"); + 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", 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"); + AConfiguration_fromAssetManager(android_app->config, + android_app->activity->assetManager); + print_cur_config(android_app); + break; + + case APP_CMD_DESTROY: + LOGV("APP_CMD_DESTROY"); + 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"); + 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"); + 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", 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_INPUT; + 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 = calloc(1, 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", 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 struct android_app* ToApp(ANativeActivity* activity) { + return (struct android_app*) activity->instance; +} + +static void onDestroy(ANativeActivity* activity) { + LOGV("Destroy: %p", activity); + android_app_free(ToApp(activity)); +} + +static void onStart(ANativeActivity* activity) { + LOGV("Start: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_START); +} + +static void onResume(ANativeActivity* activity) { + LOGV("Resume: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_RESUME); +} + +static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { + LOGV("SaveInstanceState: %p", activity); + + struct android_app* android_app = ToApp(activity); + void* savedState = NULL; + 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", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE); +} + +static void onStop(ANativeActivity* activity) { + LOGV("Stop: %p", activity); + android_app_set_activity_state(ToApp(activity), APP_CMD_STOP); +} + +static void onConfigurationChanged(ANativeActivity* activity) { + LOGV("ConfigurationChanged: %p", activity); + android_app_write_cmd(ToApp(activity), APP_CMD_CONFIG_CHANGED); +} + +static void onContentRectChanged(ANativeActivity* activity, const ARect* r) { + LOGV("ContentRectChanged: l=%d,t=%d,r=%d,b=%d", r->left, r->top, r->right, r->bottom); + struct android_app* android_app = ToApp(activity); + pthread_mutex_lock(&android_app->mutex); + android_app->contentRect = *r; + pthread_mutex_unlock(&android_app->mutex); + android_app_write_cmd(ToApp(activity), APP_CMD_CONTENT_RECT_CHANGED); +} + +static void onLowMemory(ANativeActivity* activity) { + LOGV("LowMemory: %p", activity); + android_app_write_cmd(ToApp(activity), APP_CMD_LOW_MEMORY); +} + +static void onWindowFocusChanged(ANativeActivity* activity, int focused) { + LOGV("WindowFocusChanged: %p -- %d", activity, focused); + android_app_write_cmd(ToApp(activity), focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); +} + +static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowCreated: %p -- %p", activity, window); + android_app_set_window(ToApp(activity), window); +} + +static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowDestroyed: %p -- %p", activity, window); + android_app_set_window(ToApp(activity), NULL); +} + +static void onNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowRedrawNeeded: %p -- %p", activity, window); + android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_REDRAW_NEEDED); +} + +static void onNativeWindowResized(ANativeActivity* activity, ANativeWindow* window) { + LOGV("NativeWindowResized: %p -- %p", activity, window); + android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_RESIZED); +} + +static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { + LOGV("InputQueueCreated: %p -- %p", activity, queue); + android_app_set_input(ToApp(activity), queue); +} + +static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { + LOGV("InputQueueDestroyed: %p -- %p", activity, queue); + android_app_set_input(ToApp(activity), NULL); +} + +JNIEXPORT +void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize) { + LOGV("Creating: %p", activity); + + activity->callbacks->onConfigurationChanged = onConfigurationChanged; + activity->callbacks->onContentRectChanged = onContentRectChanged; + activity->callbacks->onDestroy = onDestroy; + activity->callbacks->onInputQueueCreated = onInputQueueCreated; + activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; + activity->callbacks->onLowMemory = onLowMemory; + activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; + activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; + activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; + activity->callbacks->onNativeWindowResized = onNativeWindowResized; + activity->callbacks->onPause = onPause; + activity->callbacks->onResume = onResume; + activity->callbacks->onSaveInstanceState = onSaveInstanceState; + activity->callbacks->onStart = onStart; + activity->callbacks->onStop = onStop; + activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; + + activity->instance = android_app_create(activity, savedState, savedStateSize); +} diff --git a/examples/raylib/src/android_native_app_glue.h b/examples/raylib/src/android_native_app_glue.h new file mode 100644 index 0000000..35a786e --- /dev/null +++ b/examples/raylib/src/android_native_app_glue.h @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * The native activity interface provided by + * 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 'android_native_app_glue' 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 object 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 identifier returned by + * ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT, + * respectively. + * + * Your application can use the same ALooper to listen to additional + * file-descriptors. They can either be callback based, or with return + * identifiers starting with LOOPER_ID_USER. + * + * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event, + * the returned data will point to an android_poll_source structure. You + * can call the process() function on it, and fill in android_app->onAppCmd + * and android_app->onInputEvent to be called for your own processing + * of the event. + * + * Alternatively, you can call the low-level functions to read and process + * the data directly... look at the process_cmd() and process_input() + * implementations in the glue to see how to do this. + * + * See the sample named "native-activity" that comes with the NDK with a + * full usage example. Also look at the JavaDoc of NativeActivity. + */ + +struct android_app; + +/** + * 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_INPUT. + int32_t id; + + // The android_app this ident is associated with. + struct android_app* app; + + // Function to call to perform the standard processing of data from + // this source. + void (*process)(struct android_app* app, struct android_poll_source* source); +}; + +/** + * This is the interface for the standard glue code of a threaded + * application. In this model, the application's code is running + * in its own thread separate from the main thread of the process. + * It is not required that this thread be associated with the Java + * VM, although it will need to be in order to make JNI calls any + * Java objects. + */ +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; + + // This is the last instance's saved state, as provided at creation time. + // It is NULL if there was no state. You can use this as you need; the + // memory will remain around until you call android_app_exec_cmd() for + // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL. + // These variables should only be changed when processing a APP_CMD_SAVE_STATE, + // at which point they will be initialized to NULL and you can malloc your + // state and place the information here. In that case the memory will be + // freed for you later. + 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, which + * is returned as an identifier from ALooper_pollOnce(). The data for this + * identifier is a pointer to an android_poll_source structure. + * 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, which is returned as an identifier from + * ALooper_pollOnce(). The data for this identifier is a pointer to an + * android_poll_source structure. These can be read via the inputQueue + * object of android_app. + */ + LOOPER_ID_INPUT = 2, + + /** + * Start of user-defined ALooper identifiers. + */ + LOOPER_ID_USER = 3, +}; + +enum { + /** + * Command from main thread: the AInputQueue has changed. Upon processing + * this command, android_app->inputQueue will be updated to the new queue + * (or NULL). + */ + APP_CMD_INPUT_CHANGED, + + /** + * Command from main thread: a new ANativeWindow is ready for use. Upon + * receiving this command, android_app->window will contain the new window + * surface. + */ + APP_CMD_INIT_WINDOW, + + /** + * Command from main thread: the existing ANativeWindow needs to be + * terminated. Upon receiving this command, android_app->window still + * contains the existing window; after calling android_app_exec_cmd + * it will be set to NULL. + */ + APP_CMD_TERM_WINDOW, + + /** + * Command from main thread: the current ANativeWindow has been resized. + * Please redraw with its new size. + */ + APP_CMD_WINDOW_RESIZED, + + /** + * Command from main thread: the system needs that the current ANativeWindow + * be redrawn. You should redraw the window before handing this to + * android_app_exec_cmd() in order to avoid transient drawing glitches. + */ + APP_CMD_WINDOW_REDRAW_NEEDED, + + /** + * Command from main thread: the content area of the window has changed, + * such as from the soft input window being shown or hidden. You can + * find the new content rect in android_app::contentRect. + */ + APP_CMD_CONTENT_RECT_CHANGED, + + /** + * Command from main thread: the app's activity window has gained + * input focus. + */ + APP_CMD_GAINED_FOCUS, + + /** + * Command from main thread: the app's activity window has lost + * input focus. + */ + APP_CMD_LOST_FOCUS, + + /** + * Command from main thread: the current device configuration has changed. + */ + APP_CMD_CONFIG_CHANGED, + + /** + * Command from main thread: the system is running low on memory. + * Try to reduce your memory use. + */ + APP_CMD_LOW_MEMORY, + + /** + * Command from main thread: the app's activity has been started. + */ + APP_CMD_START, + + /** + * Command from main thread: the app's activity has been resumed. + */ + APP_CMD_RESUME, + + /** + * Command from main thread: the app should generate a new saved state + * for itself, to restore from later if needed. If you have saved state, + * allocate it with malloc and place it in android_app.savedState with + * the size in android_app.savedStateSize. The will be freed for you + * later. + */ + APP_CMD_SAVE_STATE, + + /** + * Command from main thread: the app's activity has been paused. + */ + APP_CMD_PAUSE, + + /** + * Command from main thread: the app's activity has been stopped. + */ + APP_CMD_STOP, + + /** + * Command from main thread: the app's activity is being destroyed, + * and waiting for the app thread to clean up and exit before proceeding. + */ + 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 perform your own + * actions for the command after calling this function. + */ +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 done your own + * actions for the command before calling this function. + */ +void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); + +/** + * No-op function that used to be used to prevent the linker from stripping app + * glue code. No longer necessary, since __attribute__((visibility("default"))) + * does this for us. + */ +__attribute__(( + deprecated("Calls to app_dummy are no longer necessary. See " + "https://github.com/android-ndk/ndk/issues/381."))) 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 diff --git a/examples/raylib/src/main.zig b/examples/raylib/src/main.zig new file mode 100644 index 0000000..8e89035 --- /dev/null +++ b/examples/raylib/src/main.zig @@ -0,0 +1,17 @@ +const std = @import("std"); +const rl = @import("raylib"); +export fn main() callconv(.C) void { + const screenWidth = 800; + const screenHeight = 450; + rl.initWindow(screenWidth, screenHeight, "raylib-zig [core] example - basic window"); + defer rl.closeWindow(); + rl.setTargetFPS(60); + while (!rl.windowShouldClose()) { + rl.beginDrawing(); + defer rl.endDrawing(); + + rl.clearBackground(.white); + + rl.drawText("Congrats! You created your first window!", 190, 200, 20, .light_gray); + } +} From f6f172311f5c47a873c342abb03fa7ae3b4b697b Mon Sep 17 00:00:00 2001 From: Lumen Keyes Date: Wed, 7 May 2025 10:59:20 -0600 Subject: [PATCH 2/7] Changes as requested --- examples/raylib/README.md | 54 ++- examples/raylib/android/AndroidManifest.xml | 4 +- .../raylib/android/res/values/strings.xml | 4 +- examples/raylib/build.zig | 77 +-- examples/raylib/build.zig.zon | 43 +- examples/raylib/src/android_native_app_glue.c | 457 ------------------ examples/raylib/src/android_native_app_glue.h | 350 -------------- examples/raylib/src/main.zig | 8 +- 8 files changed, 104 insertions(+), 893 deletions(-) delete mode 100644 examples/raylib/src/android_native_app_glue.c delete mode 100644 examples/raylib/src/android_native_app_glue.h diff --git a/examples/raylib/README.md b/examples/raylib/README.md index 217ecf9..f61d924 100644 --- a/examples/raylib/README.md +++ b/examples/raylib/README.md @@ -1,7 +1,4 @@ -A minimal example of using [zig-android-sdk](https://github.com/silbinarywolf/zig-android-sdk) to build raylib for android. - -Build for a single target with, e.g. `zig build -Dtarget=aarch64-linux-android`, or for all android targets with `zig build -Dandroid=true`. - +# Raylib Example **Note**: Due to [an upstream bug](https://github.com/ziglang/zig/issues/20476), you will probably receive a warning (or multiple warnings if building for multiple targets) like this: ``` @@ -10,6 +7,53 @@ ld.lld: warning: /.zig-cache/o/4227869d730f094811a7cdaaab535797 ``` You can ignore this error for now. -You should probably source the `android_native_app_glue.c/.h` files from the version of the SDK you download, rather than using the included ones, to ensure you are using the most up-to-date versions. +### Build, install to test one target against a local emulator and run + +```sh +zig build -Dtarget=x86_64-linux-android +adb install ./zig-out/bin/raylib.apk +adb shell am start -S -W -n com.zig.raylib/android.app.NativeActivity +``` + +### Build and install for all supported Android targets + +```sh +zig build -Dandroid=true +adb install ./zig-out/bin/raylib.apk +``` + +### Build and run natively on your operating system + +```sh +zig build run +``` + +### Uninstall your application + +If installing your application fails with something like: +``` +adb: failed to install ./zig-out/bin/raylib.apk: Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package com.zig.raylib signatures do not match newer version; ignoring!] +``` + +```sh +adb uninstall "com.zig.raylib" +``` + +### View logs of application + +Powershell (app doesn't need to be running) +```sh +adb logcat | Select-String com.zig.raylib: +``` + +Bash (app doesn't need running to be running) +```sh +adb logcat com.zig.raylib:D *:S +``` + +Bash (app must be running, logs everything by the process including modules) +```sh +adb logcat --pid=`adb shell pidof -s com.zig.raylib` +``` diff --git a/examples/raylib/android/AndroidManifest.xml b/examples/raylib/android/AndroidManifest.xml index 541cdc2..514547b 100644 --- a/examples/raylib/android/AndroidManifest.xml +++ b/examples/raylib/android/AndroidManifest.xml @@ -1,10 +1,8 @@ + package="com.zig.raylib"> - - - Zig Minimal + Zig Raylib - com.zig.minimal + com.zig.raylib diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index a283340..7c9324d 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -1,10 +1,11 @@ const android = @import("android"); const std = @import("std"); -//this is targeting android API level 29. -//You may have to change the values here and in android/AndroidManifest.xml to target your desired API level -const android_api = "29"; -const android_version = .android10; +//This is targeting android version 10 / API level 29. +//Change the value here and in android/AndroidManifest.xml to target your desired API level +const android_version: android.APILevel = .android10; +const android_api = std.fmt.comptimePrint("{}", .{@intFromEnum(android_version)}); +const exe_name = "raylib"; pub fn build(b: *std.Build) void { const root_target = b.standardTargetOptions(.{}); @@ -23,8 +24,8 @@ pub fn build(b: *std.Build) void { } const android_tools = android.Tools.create(b, .{ .api_level = android_version, - .build_tools_version = "35.0.0", - .ndk_version = "27.2.12479018", + .build_tools_version = "35.0.1", + .ndk_version = "29.0.13113456", }); const apk = android.APK.create(b, android_tools); @@ -36,48 +37,52 @@ pub fn build(b: *std.Build) void { break :blk apk; }; - for(targets) |target| { - if (target.result.abi.isAndroid()) { - const lib_mod = b.createModule(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); + for (targets) |target| { + const lib_mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }); + const lib = b.addLibrary(.{ + .linkage = .dynamic, + .name = exe_name, + .root_module = lib_mod, + }); + lib.linkLibC(); + b.installArtifact(lib); - const lib = b.addLibrary(.{ - .linkage = .dynamic, - .name = "minimal_android_raylib", - .root_module = lib_mod, - }); + const android_ndk_path = b.fmt("{s}/ndk/{s}", .{ android_apk.?.tools.android_sdk_path, android_apk.?.tools.ndk_version }); + const raylib_dep = if (target.result.abi.isAndroid()) (b.dependency("raylib_zig", .{ .target = target, .optimize = optimize, .android_api_version = @as([]const u8, android_api), .android_ndk = @as([]const u8, android_ndk_path) })) else (b.dependency("raylib_zig", .{ + .target = target, + .optimize = optimize, + })); + const raylib_artifact = raylib_dep.artifact("raylib"); + lib.linkLibrary(raylib_artifact); + const raylib_mod = raylib_dep.module("raylib"); + lib.root_module.addImport("raylib", raylib_mod); - b.installArtifact(lib); - lib.linkLibC(); - const raylib_dep = b.dependency("raylib_zig", .{ - .target = target, - .optimize = optimize, - .android_api_version = @as([]const u8, android_api) - }); - const raylib_artifact = raylib_dep.artifact("raylib"); - lib.linkLibrary(raylib_artifact); + lib.root_module.linkSystemLibrary("EGL", .{ .preferred_link_mode = .dynamic }); + lib.root_module.linkSystemLibrary("GLESv2", .{ .preferred_link_mode = .dynamic }); - const raylib_mod = raylib_dep.module("raylib"); - lib.root_module.addImport("raylib", raylib_mod); + if (target.result.abi.isAndroid()) { const apk: *android.APK = android_apk orelse @panic("Android APK should be initialized"); - const android_dep = b.dependency("android", .{ .optimize = optimize, .target = target, }); + lib.root_module.linkSystemLibrary("android", .{ .preferred_link_mode = .dynamic }); lib.root_module.addImport("android", android_dep.module("android")); - lib.root_module.linkSystemLibrary("android", .{.preferred_link_mode = .dynamic}); - lib.root_module.linkSystemLibrary("EGL", .{.preferred_link_mode = .dynamic}); - lib.root_module.linkSystemLibrary("GLESv2", .{.preferred_link_mode = .dynamic}); - lib.root_module.linkSystemLibrary("log", .{ .preferred_link_mode = .dynamic }); - lib.root_module.addCSourceFile(.{ .file = b.path("src/android_native_app_glue.c")}); + const native_app_glue_dir: std.Build.LazyPath = .{ .cwd_relative = b.fmt("{s}/sources/android/native_app_glue", .{android_ndk_path}) }; + lib.root_module.addCSourceFile(.{ .file = native_app_glue_dir.path(b, "android_native_app_glue.c") }); + lib.root_module.addIncludePath(native_app_glue_dir); + + lib.root_module.linkSystemLibrary("log", .{ .preferred_link_mode = .dynamic }); apk.addArtifact(lib); } else { - //non-android build logic... + const exe = b.addExecutable(.{ .name = exe_name, .optimize = optimize, .target = target }); + exe.linkLibrary(lib); + b.installArtifact(exe); } } if (android_apk) |apk| { diff --git a/examples/raylib/build.zig.zon b/examples/raylib/build.zig.zon index 2744849..948fc39 100644 --- a/examples/raylib/build.zig.zon +++ b/examples/raylib/build.zig.zon @@ -1,44 +1,12 @@ .{ - // This is the default name used by packages depending on this one. For - // example, when a user runs `zig fetch --save `, this field is used - // as the key in the `dependencies` table. Although the user can choose a - // different name, most users will stick with this provided value. - // - // It is redundant to include "zig" in this name because it is already - // within the Zig package namespace. - .name = .minimal_android_raylib, - - // This is a [Semantic Version](https://semver.org/). - // In a future version of Zig it will be used for package deduplication. + .name = .raylib, .version = "0.0.0", - - // Together with name, this represents a globally unique package - // identifier. This field is generated by the Zig toolchain when the - // package is first created, and then *never changes*. This allows - // unambiguous detection of one package being an updated version of - // another. - // - // When forking a Zig project, this id should be regenerated (delete the - // field and run `zig build`) if the upstream project is still maintained. - // Otherwise, the fork is *hostile*, attempting to take control over the - // original project's identity. Thus it is recommended to leave the comment - // on the following line intact, so that it shows up in code reviews that - // modify the field. - .fingerprint = 0x7d5dc97ee62a8428, // Changing this has security and trust implications. - - // Tracks the earliest Zig version that the package considers to be a - // supported use case. .minimum_zig_version = "0.14.0", - - // This field is optional. - // Each dependency must either provide a `url` and `hash`, or a `path`. - // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. - // Once all dependencies are fetched, `zig build` no longer requires - // internet connectivity. + .fingerprint = 0x13035e5cf42e313f, .dependencies = .{ .raylib_zig = .{ - .url = "git+https://github.com/lumenkeyes/raylib-zig#e421e2a16c7f965b8ff70c7db7a13bf9fe2d321c", - .hash = "raylib_zig-5.6.0-dev-KE8REAgyBQB4QKuE8SJZHBOw_KGcoLCybyyAIXRLCXKQ", + .url = "git+https://github.com/lumenkeyes/raylib-zig#59263ae0f96f54fa96ba4cb1f77cd3b06cc8fa94", + .hash = "raylib_zig-5.6.0-dev-KE8REG8yBQDVpWX-ireikKit_2m0_E6DkdWGCssSXKHa", }, .android = .{ .path = "../.." }, }, @@ -46,8 +14,5 @@ "build.zig", "build.zig.zon", "src", - // For example... - //"LICENSE", - //"README.md", }, } diff --git a/examples/raylib/src/android_native_app_glue.c b/examples/raylib/src/android_native_app_glue.c deleted file mode 100644 index 1e63c5e..0000000 --- a/examples/raylib/src/android_native_app_glue.c +++ /dev/null @@ -1,457 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "android_native_app_glue.h" - -#include - -#include -#include -#include -#include - -#include - -#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) -#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__)) - -/* For debug builds, always enable the debug traces in this library */ -#ifndef NDEBUG -# define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __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->savedState != NULL) { - free(android_app->savedState); - android_app->savedState = NULL; - android_app->savedStateSize = 0; - } - 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)) { - LOGE("No data on command pipe!"); - return -1; - } - if (cmd == APP_CMD_SAVE_STATE) free_saved_state(android_app); - return cmd; -} - -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"); - 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_INPUT, 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"); - 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"); - 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", 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"); - AConfiguration_fromAssetManager(android_app->config, - android_app->activity->assetManager); - print_cur_config(android_app); - break; - - case APP_CMD_DESTROY: - LOGV("APP_CMD_DESTROY"); - 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"); - 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"); - 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", 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_INPUT; - 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 = calloc(1, 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", 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 struct android_app* ToApp(ANativeActivity* activity) { - return (struct android_app*) activity->instance; -} - -static void onDestroy(ANativeActivity* activity) { - LOGV("Destroy: %p", activity); - android_app_free(ToApp(activity)); -} - -static void onStart(ANativeActivity* activity) { - LOGV("Start: %p", activity); - android_app_set_activity_state(ToApp(activity), APP_CMD_START); -} - -static void onResume(ANativeActivity* activity) { - LOGV("Resume: %p", activity); - android_app_set_activity_state(ToApp(activity), APP_CMD_RESUME); -} - -static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { - LOGV("SaveInstanceState: %p", activity); - - struct android_app* android_app = ToApp(activity); - void* savedState = NULL; - 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", activity); - android_app_set_activity_state(ToApp(activity), APP_CMD_PAUSE); -} - -static void onStop(ANativeActivity* activity) { - LOGV("Stop: %p", activity); - android_app_set_activity_state(ToApp(activity), APP_CMD_STOP); -} - -static void onConfigurationChanged(ANativeActivity* activity) { - LOGV("ConfigurationChanged: %p", activity); - android_app_write_cmd(ToApp(activity), APP_CMD_CONFIG_CHANGED); -} - -static void onContentRectChanged(ANativeActivity* activity, const ARect* r) { - LOGV("ContentRectChanged: l=%d,t=%d,r=%d,b=%d", r->left, r->top, r->right, r->bottom); - struct android_app* android_app = ToApp(activity); - pthread_mutex_lock(&android_app->mutex); - android_app->contentRect = *r; - pthread_mutex_unlock(&android_app->mutex); - android_app_write_cmd(ToApp(activity), APP_CMD_CONTENT_RECT_CHANGED); -} - -static void onLowMemory(ANativeActivity* activity) { - LOGV("LowMemory: %p", activity); - android_app_write_cmd(ToApp(activity), APP_CMD_LOW_MEMORY); -} - -static void onWindowFocusChanged(ANativeActivity* activity, int focused) { - LOGV("WindowFocusChanged: %p -- %d", activity, focused); - android_app_write_cmd(ToApp(activity), focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); -} - -static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowCreated: %p -- %p", activity, window); - android_app_set_window(ToApp(activity), window); -} - -static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowDestroyed: %p -- %p", activity, window); - android_app_set_window(ToApp(activity), NULL); -} - -static void onNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowRedrawNeeded: %p -- %p", activity, window); - android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_REDRAW_NEEDED); -} - -static void onNativeWindowResized(ANativeActivity* activity, ANativeWindow* window) { - LOGV("NativeWindowResized: %p -- %p", activity, window); - android_app_write_cmd(ToApp(activity), APP_CMD_WINDOW_RESIZED); -} - -static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { - LOGV("InputQueueCreated: %p -- %p", activity, queue); - android_app_set_input(ToApp(activity), queue); -} - -static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { - LOGV("InputQueueDestroyed: %p -- %p", activity, queue); - android_app_set_input(ToApp(activity), NULL); -} - -JNIEXPORT -void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize) { - LOGV("Creating: %p", activity); - - activity->callbacks->onConfigurationChanged = onConfigurationChanged; - activity->callbacks->onContentRectChanged = onContentRectChanged; - activity->callbacks->onDestroy = onDestroy; - activity->callbacks->onInputQueueCreated = onInputQueueCreated; - activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; - activity->callbacks->onLowMemory = onLowMemory; - activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; - activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; - activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; - activity->callbacks->onNativeWindowResized = onNativeWindowResized; - activity->callbacks->onPause = onPause; - activity->callbacks->onResume = onResume; - activity->callbacks->onSaveInstanceState = onSaveInstanceState; - activity->callbacks->onStart = onStart; - activity->callbacks->onStop = onStop; - activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; - - activity->instance = android_app_create(activity, savedState, savedStateSize); -} diff --git a/examples/raylib/src/android_native_app_glue.h b/examples/raylib/src/android_native_app_glue.h deleted file mode 100644 index 35a786e..0000000 --- a/examples/raylib/src/android_native_app_glue.h +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * The native activity interface provided by - * 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 'android_native_app_glue' 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 object 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 identifier returned by - * ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT, - * respectively. - * - * Your application can use the same ALooper to listen to additional - * file-descriptors. They can either be callback based, or with return - * identifiers starting with LOOPER_ID_USER. - * - * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event, - * the returned data will point to an android_poll_source structure. You - * can call the process() function on it, and fill in android_app->onAppCmd - * and android_app->onInputEvent to be called for your own processing - * of the event. - * - * Alternatively, you can call the low-level functions to read and process - * the data directly... look at the process_cmd() and process_input() - * implementations in the glue to see how to do this. - * - * See the sample named "native-activity" that comes with the NDK with a - * full usage example. Also look at the JavaDoc of NativeActivity. - */ - -struct android_app; - -/** - * 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_INPUT. - int32_t id; - - // The android_app this ident is associated with. - struct android_app* app; - - // Function to call to perform the standard processing of data from - // this source. - void (*process)(struct android_app* app, struct android_poll_source* source); -}; - -/** - * This is the interface for the standard glue code of a threaded - * application. In this model, the application's code is running - * in its own thread separate from the main thread of the process. - * It is not required that this thread be associated with the Java - * VM, although it will need to be in order to make JNI calls any - * Java objects. - */ -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; - - // This is the last instance's saved state, as provided at creation time. - // It is NULL if there was no state. You can use this as you need; the - // memory will remain around until you call android_app_exec_cmd() for - // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL. - // These variables should only be changed when processing a APP_CMD_SAVE_STATE, - // at which point they will be initialized to NULL and you can malloc your - // state and place the information here. In that case the memory will be - // freed for you later. - 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, which - * is returned as an identifier from ALooper_pollOnce(). The data for this - * identifier is a pointer to an android_poll_source structure. - * 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, which is returned as an identifier from - * ALooper_pollOnce(). The data for this identifier is a pointer to an - * android_poll_source structure. These can be read via the inputQueue - * object of android_app. - */ - LOOPER_ID_INPUT = 2, - - /** - * Start of user-defined ALooper identifiers. - */ - LOOPER_ID_USER = 3, -}; - -enum { - /** - * Command from main thread: the AInputQueue has changed. Upon processing - * this command, android_app->inputQueue will be updated to the new queue - * (or NULL). - */ - APP_CMD_INPUT_CHANGED, - - /** - * Command from main thread: a new ANativeWindow is ready for use. Upon - * receiving this command, android_app->window will contain the new window - * surface. - */ - APP_CMD_INIT_WINDOW, - - /** - * Command from main thread: the existing ANativeWindow needs to be - * terminated. Upon receiving this command, android_app->window still - * contains the existing window; after calling android_app_exec_cmd - * it will be set to NULL. - */ - APP_CMD_TERM_WINDOW, - - /** - * Command from main thread: the current ANativeWindow has been resized. - * Please redraw with its new size. - */ - APP_CMD_WINDOW_RESIZED, - - /** - * Command from main thread: the system needs that the current ANativeWindow - * be redrawn. You should redraw the window before handing this to - * android_app_exec_cmd() in order to avoid transient drawing glitches. - */ - APP_CMD_WINDOW_REDRAW_NEEDED, - - /** - * Command from main thread: the content area of the window has changed, - * such as from the soft input window being shown or hidden. You can - * find the new content rect in android_app::contentRect. - */ - APP_CMD_CONTENT_RECT_CHANGED, - - /** - * Command from main thread: the app's activity window has gained - * input focus. - */ - APP_CMD_GAINED_FOCUS, - - /** - * Command from main thread: the app's activity window has lost - * input focus. - */ - APP_CMD_LOST_FOCUS, - - /** - * Command from main thread: the current device configuration has changed. - */ - APP_CMD_CONFIG_CHANGED, - - /** - * Command from main thread: the system is running low on memory. - * Try to reduce your memory use. - */ - APP_CMD_LOW_MEMORY, - - /** - * Command from main thread: the app's activity has been started. - */ - APP_CMD_START, - - /** - * Command from main thread: the app's activity has been resumed. - */ - APP_CMD_RESUME, - - /** - * Command from main thread: the app should generate a new saved state - * for itself, to restore from later if needed. If you have saved state, - * allocate it with malloc and place it in android_app.savedState with - * the size in android_app.savedStateSize. The will be freed for you - * later. - */ - APP_CMD_SAVE_STATE, - - /** - * Command from main thread: the app's activity has been paused. - */ - APP_CMD_PAUSE, - - /** - * Command from main thread: the app's activity has been stopped. - */ - APP_CMD_STOP, - - /** - * Command from main thread: the app's activity is being destroyed, - * and waiting for the app thread to clean up and exit before proceeding. - */ - 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 perform your own - * actions for the command after calling this function. - */ -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 done your own - * actions for the command before calling this function. - */ -void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); - -/** - * No-op function that used to be used to prevent the linker from stripping app - * glue code. No longer necessary, since __attribute__((visibility("default"))) - * does this for us. - */ -__attribute__(( - deprecated("Calls to app_dummy are no longer necessary. See " - "https://github.com/android-ndk/ndk/issues/381."))) 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 diff --git a/examples/raylib/src/main.zig b/examples/raylib/src/main.zig index 8e89035..4c5fe4a 100644 --- a/examples/raylib/src/main.zig +++ b/examples/raylib/src/main.zig @@ -1,5 +1,11 @@ const std = @import("std"); const rl = @import("raylib"); + +//Other than exporting this function and changing the calling convention, you +//can write your code fairly normally. +// +//The main function is not allowed to return zig errors, so you will have to +//use "catch @panic()" or create other error handling functionality. export fn main() callconv(.C) void { const screenWidth = 800; const screenHeight = 450; @@ -13,5 +19,5 @@ export fn main() callconv(.C) void { rl.clearBackground(.white); rl.drawText("Congrats! You created your first window!", 190, 200, 20, .light_gray); - } + } } From af49aed5e5706cf58898c7e60f7a308c93833601 Mon Sep 17 00:00:00 2001 From: Lumen Keyes Date: Wed, 7 May 2025 11:11:36 -0600 Subject: [PATCH 3/7] Fix null value usage --- examples/raylib/build.zig | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index 7c9324d..da22f03 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -51,10 +51,17 @@ pub fn build(b: *std.Build) void { lib.linkLibC(); b.installArtifact(lib); - const android_ndk_path = b.fmt("{s}/ndk/{s}", .{ android_apk.?.tools.android_sdk_path, android_apk.?.tools.ndk_version }); - const raylib_dep = if (target.result.abi.isAndroid()) (b.dependency("raylib_zig", .{ .target = target, .optimize = optimize, .android_api_version = @as([]const u8, android_api), .android_ndk = @as([]const u8, android_ndk_path) })) else (b.dependency("raylib_zig", .{ - .target = target, - .optimize = optimize, + const android_ndk_path = if(android_apk) |apk| (b.fmt("{s}/ndk/{s}", .{ apk.tools.android_sdk_path, apk.tools.ndk_version })) else ""; + const raylib_dep = if (target.result.abi.isAndroid()) ( + b.dependency("raylib_zig", .{ + .target = target, + .optimize = optimize, + .android_api_version = @as([]const u8, android_api), + .android_ndk = @as([]const u8, android_ndk_path) + })) else ( + b.dependency("raylib_zig", .{ + .target = target, + .optimize = optimize, })); const raylib_artifact = raylib_dep.artifact("raylib"); lib.linkLibrary(raylib_artifact); From 9cbe2be7364c31ab194c842673bd323cb1791989 Mon Sep 17 00:00:00 2001 From: Lumen Keyes Date: Wed, 7 May 2025 11:15:46 -0600 Subject: [PATCH 4/7] add run step --- examples/raylib/build.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index da22f03..3717943 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -90,6 +90,10 @@ pub fn build(b: *std.Build) void { const exe = b.addExecutable(.{ .name = exe_name, .optimize = optimize, .target = target }); exe.linkLibrary(lib); b.installArtifact(exe); + + const run_exe = b.addRunArtifact(exe); + const run_step = b.step("run", "Run the application"); + run_step.dependOn(&run_exe.step); } } if (android_apk) |apk| { From 243ab6a4638a95e01cdc285e9647a1e5d14ba7fe Mon Sep 17 00:00:00 2001 From: Lumen Keyes Date: Wed, 14 May 2025 08:47:08 -0600 Subject: [PATCH 5/7] changes as requested EGL should be linked at the library level now, and only for the appropriate targets. Also some conditional compilation as suggested. --- examples/raylib/build.zig | 3 --- examples/raylib/build.zig.zon | 6 +++--- examples/raylib/src/main.zig | 30 +++++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index 3717943..07a5abb 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -68,9 +68,6 @@ pub fn build(b: *std.Build) void { const raylib_mod = raylib_dep.module("raylib"); lib.root_module.addImport("raylib", raylib_mod); - lib.root_module.linkSystemLibrary("EGL", .{ .preferred_link_mode = .dynamic }); - lib.root_module.linkSystemLibrary("GLESv2", .{ .preferred_link_mode = .dynamic }); - if (target.result.abi.isAndroid()) { const apk: *android.APK = android_apk orelse @panic("Android APK should be initialized"); const android_dep = b.dependency("android", .{ diff --git a/examples/raylib/build.zig.zon b/examples/raylib/build.zig.zon index 948fc39..7766afd 100644 --- a/examples/raylib/build.zig.zon +++ b/examples/raylib/build.zig.zon @@ -4,9 +4,9 @@ .minimum_zig_version = "0.14.0", .fingerprint = 0x13035e5cf42e313f, .dependencies = .{ - .raylib_zig = .{ - .url = "git+https://github.com/lumenkeyes/raylib-zig#59263ae0f96f54fa96ba4cb1f77cd3b06cc8fa94", - .hash = "raylib_zig-5.6.0-dev-KE8REG8yBQDVpWX-ireikKit_2m0_E6DkdWGCssSXKHa", + .raylib_zig = .{ + .url = "git+https://github.com/lumenkeyes/raylib-zig#b00d4c2b973665e3a88c2565b6cd63c56d0173c2", + .hash = "raylib_zig-5.6.0-dev-KE8REPwqBQC35iWa2sFblBUNWkTlEi1gjit9dtyOLu_b" }, .android = .{ .path = "../.." }, }, diff --git a/examples/raylib/src/main.zig b/examples/raylib/src/main.zig index 4c5fe4a..6064fe9 100644 --- a/examples/raylib/src/main.zig +++ b/examples/raylib/src/main.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const builtin = @import("builtin"); +const android = @import("android"); const rl = @import("raylib"); //Other than exporting this function and changing the calling convention, you @@ -6,7 +8,7 @@ const rl = @import("raylib"); // //The main function is not allowed to return zig errors, so you will have to //use "catch @panic()" or create other error handling functionality. -export fn main() callconv(.C) void { +pub fn main() void { const screenWidth = 800; const screenHeight = 450; rl.initWindow(screenWidth, screenHeight, "raylib-zig [core] example - basic window"); @@ -21,3 +23,29 @@ export fn main() callconv(.C) void { rl.drawText("Congrats! You created your first window!", 190, 200, 20, .light_gray); } } + +/// custom panic handler for Android +pub const panic = if (builtin.abi.isAndroid()) + android.panic +else + std.debug.FullPanic(std.debug.defaultPanic); + +/// custom standard options for Android +pub const std_options: std.Options = if (builtin.abi.isAndroid()) + .{ + .logFn = android.logFn, + } +else + .{}; + +comptime { + if (builtin.abi.isAndroid()) { + // Setup exported C-function as defined in AndroidManifest.xml + // ie. + @export(&androidMain, .{ .name = "main" }); + } +} + +fn androidMain() callconv(.c) c_int { + return std.start.callMain(); +} From 9af0b37b6fcdc28fa4a0d90b63a892531c904ca4 Mon Sep 17 00:00:00 2001 From: Lumen Keyes Date: Wed, 14 May 2025 19:05:56 -0600 Subject: [PATCH 6/7] Remove old comment --- examples/raylib/src/main.zig | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/raylib/src/main.zig b/examples/raylib/src/main.zig index 6064fe9..98c0dc8 100644 --- a/examples/raylib/src/main.zig +++ b/examples/raylib/src/main.zig @@ -3,11 +3,6 @@ const builtin = @import("builtin"); const android = @import("android"); const rl = @import("raylib"); -//Other than exporting this function and changing the calling convention, you -//can write your code fairly normally. -// -//The main function is not allowed to return zig errors, so you will have to -//use "catch @panic()" or create other error handling functionality. pub fn main() void { const screenWidth = 800; const screenHeight = 450; From 18407511e981a5991a115131e23aed3e674d1bec Mon Sep 17 00:00:00 2001 From: Lumen Keyes Date: Wed, 21 May 2025 12:12:33 -0600 Subject: [PATCH 7/7] Fix missing entry point Fixes an issue where zig was unable to find the "main" entry point for non-android builds. --- examples/raylib/build.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/raylib/build.zig b/examples/raylib/build.zig index 07a5abb..4b054b8 100644 --- a/examples/raylib/build.zig +++ b/examples/raylib/build.zig @@ -57,11 +57,12 @@ pub fn build(b: *std.Build) void { .target = target, .optimize = optimize, .android_api_version = @as([]const u8, android_api), - .android_ndk = @as([]const u8, android_ndk_path) + .android_ndk = @as([]const u8, android_ndk_path), })) else ( b.dependency("raylib_zig", .{ .target = target, .optimize = optimize, + .shared = true })); const raylib_artifact = raylib_dep.artifact("raylib"); lib.linkLibrary(raylib_artifact); @@ -84,8 +85,7 @@ pub fn build(b: *std.Build) void { lib.root_module.linkSystemLibrary("log", .{ .preferred_link_mode = .dynamic }); apk.addArtifact(lib); } else { - const exe = b.addExecutable(.{ .name = exe_name, .optimize = optimize, .target = target }); - exe.linkLibrary(lib); + const exe = b.addExecutable(.{ .name = exe_name, .optimize = optimize, .root_module = lib_mod }); b.installArtifact(exe); const run_exe = b.addRunArtifact(exe);