Build libvidya for the phone, and hold the glue that boots it
The deps were already target-gated in the Cargo manifests — arboard has no Android backend, jni is only reachable there, winit's android-native-activity is what pulls in the glue that owns the event loop — and the BUCK files said none of it, because there was no Android configuration for them to say it about. Now there is. android/jolt_main.c and the vidya.h beside it come from the repo this ABI used to live in. They belong here: crates/jolt-vidya/src/android.rs already names android/jolt_main.c in its own comments and dlopens vidya_jolt_main out of it, so the two halves of the Android entry point were in different repositories. Every symbol the glue registers with Chez is exported by the library this now builds, so it moved unchanged apart from a path in a comment. `just ffi-android` stages the result where an APK build can find it, under the name vidya's own recipe used, because that is what frq's build-apk.sh calls. There is no Android libjoltmoq: the media plane is V4L2 and PipeWire, and the phone has neither.
d942053 parent: 0238622 modified
BUCK +18 -0 | @@ -20,3 +20,21 @@ filegroup( | ||
| 20 | 20 | srcs = [":libjoltmoq", ":libvidya"], |
| 21 | 21 | visibility = ["PUBLIC"], |
| 22 | 22 | ) |
| 23 | + | |
| 24 | +# The same C ABI, cross-compiled for the phone. The sources and the deps are | |
| 25 | +# the ones above; only the target configuration moves, which is what | |
| 26 | +# configured_alias is for — build scripts and proc macros still run on the | |
| 27 | +# host. There is no Android libjoltmoq: the media plane is V4L2 and PipeWire. | |
| 28 | +configured_alias( | |
| 29 | + name = "vidya-ffi-android", | |
| 30 | + actual = "//crates/jolt-vidya:vidya-ffi", | |
| 31 | + platform = "//platforms:android-arm64", | |
| 32 | + visibility = ["PUBLIC"], | |
| 33 | +) | |
| 34 | + | |
| 35 | +genrule( | |
| 36 | + name = "libvidya-android", | |
| 37 | + out = "libvidya.so", | |
| 38 | + cmd = "cp $(location :vidya-ffi-android[shared]) $OUT", | |
| 39 | + visibility = ["PUBLIC"], | |
| 40 | +) | |
| @@ -20,3 +20,21 @@ filegroup( | |||
| 20 | srcs = [":libjoltmoq", ":libvidya"], | 20 | srcs = [":libjoltmoq", ":libvidya"], |
| 21 | visibility = ["PUBLIC"], | 21 | visibility = ["PUBLIC"], |
| 22 | ) | 22 | ) |
| 23 | + | ||
| 24 | +# The same C ABI, cross-compiled for the phone. The sources and the deps are | ||
| 25 | +# the ones above; only the target configuration moves, which is what | ||
| 26 | +# configured_alias is for — build scripts and proc macros still run on the | ||
| 27 | +# host. There is no Android libjoltmoq: the media plane is V4L2 and PipeWire. | ||
| 28 | +configured_alias( | ||
| 29 | + name = "vidya-ffi-android", | ||
| 30 | + actual = "//crates/jolt-vidya:vidya-ffi", | ||
| 31 | + platform = "//platforms:android-arm64", | ||
| 32 | + visibility = ["PUBLIC"], | ||
| 33 | +) | ||
| 34 | + | ||
| 35 | +genrule( | ||
| 36 | + name = "libvidya-android", | ||
| 37 | + out = "libvidya.so", | ||
| 38 | + cmd = "cp $(location :vidya-ffi-android[shared]) $OUT", | ||
| 39 | + visibility = ["PUBLIC"], | ||
| 40 | +) | ||
added
android/jolt_main.c +154 -0 | new file mode 100644 | ||
| @@ -0,0 +1,154 @@ | ||
| 1 | +/* | |
| 2 | + * The Jolt half of the Android app: an embedded Chez runtime and the boot image | |
| 3 | + * built from the Jolt sources, reached from `libvidya.so`'s `android_main`. | |
| 4 | + * | |
| 5 | + * This is not the NativeActivity. On Android the activity's library must be the | |
| 6 | + * one holding android-activity's glue, which is `libvidya.so` — see | |
| 7 | + * `crates/jolt-vidya/src/android.rs`. That glue dlopens this library and calls | |
| 8 | + * `vidya_jolt_main`, so the entry point below is an ordinary C function rather | |
| 9 | + * than a `main`. | |
| 10 | + * | |
| 11 | + * Every Vidya symbol is registered with Chez explicitly. Jolt resolves a | |
| 12 | + * `defcfn` through Chez's foreign-entry table, and on Android nothing populates | |
| 13 | + * that table from the dynamic loader: the symbols live in a *different* shared | |
| 14 | + * object (`libvidya.so`, a DT_NEEDED of this one), which is exactly the case | |
| 15 | + * the loader will not answer for. Registering them by hand is what makes both | |
| 16 | + * ABIs — the push/pop one and the retained tree one glimmer binds — reachable. | |
| 17 | + */ | |
| 18 | + | |
| 19 | +#include <stddef.h> | |
| 20 | +#include <stdio.h> | |
| 21 | +#include <unistd.h> | |
| 22 | + | |
| 23 | +#include <android/log.h> | |
| 24 | +#include <pthread.h> | |
| 25 | + | |
| 26 | +#include "scheme.h" | |
| 27 | +#include "vidya.h" | |
| 28 | +#include "vidya_tree.h" | |
| 29 | + | |
| 30 | +/* The boot image, linked in as a binary blob by llvm-objcopy. */ | |
| 31 | +extern const unsigned char _binary_jolt_boot_start[]; | |
| 32 | +extern const unsigned char _binary_jolt_boot_end[]; | |
| 33 | + | |
| 34 | +#define REGISTER_VIDYA(name) Sregister_symbol(#name, (void *)&name) | |
| 35 | + | |
| 36 | +static void register_vidya_api(void) { | |
| 37 | + /* vidya.h */ | |
| 38 | + REGISTER_VIDYA(vidya_open); | |
| 39 | + REGISTER_VIDYA(vidya_close); | |
| 40 | + REGISTER_VIDYA(vidya_should_close); | |
| 41 | + REGISTER_VIDYA(vidya_set_target_fps); | |
| 42 | + REGISTER_VIDYA(vidya_set_mode); | |
| 43 | + REGISTER_VIDYA(vidya_get_mode); | |
| 44 | + REGISTER_VIDYA(vidya_load_font); | |
| 45 | + REGISTER_VIDYA(vidya_begin_frame); | |
| 46 | + REGISTER_VIDYA(vidya_end_frame); | |
| 47 | + REGISTER_VIDYA(vidya_page_begin); | |
| 48 | + REGISTER_VIDYA(vidya_page_end); | |
| 49 | + REGISTER_VIDYA(vidya_card_begin); | |
| 50 | + REGISTER_VIDYA(vidya_card_end); | |
| 51 | + REGISTER_VIDYA(vidya_gap); | |
| 52 | + REGISTER_VIDYA(vidya_separator); | |
| 53 | + REGISTER_VIDYA(vidya_title); | |
| 54 | + REGISTER_VIDYA(vidya_title_2); | |
| 55 | + REGISTER_VIDYA(vidya_body); | |
| 56 | + REGISTER_VIDYA(vidya_dim_label); | |
| 57 | + REGISTER_VIDYA(vidya_button); | |
| 58 | + REGISTER_VIDYA(vidya_checkbox); | |
| 59 | + REGISTER_VIDYA(vidya_checkbox_value); | |
| 60 | + REGISTER_VIDYA(vidya_status); | |
| 61 | + REGISTER_VIDYA(vidya_text_field); | |
| 62 | + | |
| 63 | + /* vidya_tree.h */ | |
| 64 | + REGISTER_VIDYA(vidya_tree_root); | |
| 65 | + REGISTER_VIDYA(vidya_node_new); | |
| 66 | + REGISTER_VIDYA(vidya_node_free); | |
| 67 | + REGISTER_VIDYA(vidya_node_exists); | |
| 68 | + REGISTER_VIDYA(vidya_node_set_str); | |
| 69 | + REGISTER_VIDYA(vidya_node_set_num); | |
| 70 | + REGISTER_VIDYA(vidya_node_set_bool); | |
| 71 | + REGISTER_VIDYA(vidya_node_clear_props); | |
| 72 | + REGISTER_VIDYA(vidya_node_get_str); | |
| 73 | + REGISTER_VIDYA(vidya_node_get_num); | |
| 74 | + REGISTER_VIDYA(vidya_node_get_bool); | |
| 75 | + REGISTER_VIDYA(vidya_node_tag); | |
| 76 | + REGISTER_VIDYA(vidya_node_child_count); | |
| 77 | + REGISTER_VIDYA(vidya_node_child_at); | |
| 78 | + REGISTER_VIDYA(vidya_node_append); | |
| 79 | + REGISTER_VIDYA(vidya_node_remove); | |
| 80 | + REGISTER_VIDYA(vidya_node_insert_after); | |
| 81 | + REGISTER_VIDYA(vidya_node_replace); | |
| 82 | + REGISTER_VIDYA(vidya_tree_frame); | |
| 83 | + REGISTER_VIDYA(vidya_tree_poll_event); | |
| 84 | + REGISTER_VIDYA(vidya_tree_event_node); | |
| 85 | + REGISTER_VIDYA(vidya_tree_event_name); | |
| 86 | + REGISTER_VIDYA(vidya_tree_event_text); | |
| 87 | + REGISTER_VIDYA(vidya_tree_event_num); | |
| 88 | + | |
| 89 | + /* Neither of these paints anything; they are the platform reached through | |
| 90 | + the same ABI. `vidya_open_url` is the one that matters here — signing in | |
| 91 | + leaves for a browser and comes back. */ | |
| 92 | + REGISTER_VIDYA(vidya_clipboard_image_png); | |
| 93 | + REGISTER_VIDYA(vidya_open_url); | |
| 94 | + REGISTER_VIDYA(vidya_pick_image); | |
| 95 | + REGISTER_VIDYA(vidya_picked_image); | |
| 96 | + | |
| 97 | + /* Live pixels, the window's own size, and the tree dump. Nothing here is | |
| 98 | + Android-specific; they are on this list because the list is the whole of | |
| 99 | + what an application can reach on Android, and a symbol left off it is not | |
| 100 | + a link error but a call that fails at the moment a screen needs it. */ | |
| 101 | + REGISTER_VIDYA(vidya_frame_rgba); | |
| 102 | + REGISTER_VIDYA(vidya_frame_drop); | |
| 103 | + REGISTER_VIDYA(vidya_screen_width); | |
| 104 | + REGISTER_VIDYA(vidya_screen_height); | |
| 105 | + REGISTER_VIDYA(vidya_tree_dump); | |
| 106 | +} | |
| 107 | + | |
| 108 | +/* | |
| 109 | + * Chez writes to stdout/stderr, which on Android goes nowhere. Pump both into | |
| 110 | + * logcat so a Scheme-level error is visible rather than a silent exit. | |
| 111 | + */ | |
| 112 | +static void *log_scheme_output(void *context) { | |
| 113 | + int fd = *(int *)context; | |
| 114 | + char buffer[1024]; | |
| 115 | + FILE *stream = fdopen(fd, "r"); | |
| 116 | + if (stream == NULL) return NULL; | |
| 117 | + | |
| 118 | + while (fgets(buffer, sizeof(buffer), stream) != NULL) { | |
| 119 | + __android_log_write(ANDROID_LOG_ERROR, "VidyaJolt", buffer); | |
| 120 | + } | |
| 121 | + fclose(stream); | |
| 122 | + return NULL; | |
| 123 | +} | |
| 124 | + | |
| 125 | +int vidya_jolt_main(void) { | |
| 126 | + static const char *argv[] = {"vidya", NULL}; | |
| 127 | + int output_pipe[2]; | |
| 128 | + pthread_t logger; | |
| 129 | + | |
| 130 | + if (pipe(output_pipe) == 0) { | |
| 131 | + dup2(output_pipe[1], STDOUT_FILENO); | |
| 132 | + dup2(output_pipe[1], STDERR_FILENO); | |
| 133 | + close(output_pipe[1]); | |
| 134 | + pthread_create(&logger, NULL, log_scheme_output, &output_pipe[0]); | |
| 135 | + pthread_detach(logger); | |
| 136 | + } | |
| 137 | + | |
| 138 | + __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", | |
| 139 | + "initializing embedded Chez runtime"); | |
| 140 | + Sscheme_init(NULL); | |
| 141 | + Sregister_boot_file_bytes( | |
| 142 | + "jolt", | |
| 143 | + (void *)_binary_jolt_boot_start, | |
| 144 | + (iptr)(_binary_jolt_boot_end - _binary_jolt_boot_start)); | |
| 145 | + Sbuild_heap(NULL, NULL); | |
| 146 | + register_vidya_api(); | |
| 147 | + | |
| 148 | + __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", "starting Jolt application"); | |
| 149 | + int status = Sscheme_start(1, argv); | |
| 150 | + __android_log_print(ANDROID_LOG_ERROR, "VidyaJolt", | |
| 151 | + "Jolt application exited with status %d", status); | |
| 152 | + Sscheme_deinit(); | |
| 153 | + return status; | |
| 154 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,154 @@ | |||
| 1 | +/* | ||
| 2 | + * The Jolt half of the Android app: an embedded Chez runtime and the boot image | ||
| 3 | + * built from the Jolt sources, reached from `libvidya.so`'s `android_main`. | ||
| 4 | + * | ||
| 5 | + * This is not the NativeActivity. On Android the activity's library must be the | ||
| 6 | + * one holding android-activity's glue, which is `libvidya.so` — see | ||
| 7 | + * `crates/jolt-vidya/src/android.rs`. That glue dlopens this library and calls | ||
| 8 | + * `vidya_jolt_main`, so the entry point below is an ordinary C function rather | ||
| 9 | + * than a `main`. | ||
| 10 | + * | ||
| 11 | + * Every Vidya symbol is registered with Chez explicitly. Jolt resolves a | ||
| 12 | + * `defcfn` through Chez's foreign-entry table, and on Android nothing populates | ||
| 13 | + * that table from the dynamic loader: the symbols live in a *different* shared | ||
| 14 | + * object (`libvidya.so`, a DT_NEEDED of this one), which is exactly the case | ||
| 15 | + * the loader will not answer for. Registering them by hand is what makes both | ||
| 16 | + * ABIs — the push/pop one and the retained tree one glimmer binds — reachable. | ||
| 17 | + */ | ||
| 18 | + | ||
| 19 | +#include <stddef.h> | ||
| 20 | +#include <stdio.h> | ||
| 21 | +#include <unistd.h> | ||
| 22 | + | ||
| 23 | +#include <android/log.h> | ||
| 24 | +#include <pthread.h> | ||
| 25 | + | ||
| 26 | +#include "scheme.h" | ||
| 27 | +#include "vidya.h" | ||
| 28 | +#include "vidya_tree.h" | ||
| 29 | + | ||
| 30 | +/* The boot image, linked in as a binary blob by llvm-objcopy. */ | ||
| 31 | +extern const unsigned char _binary_jolt_boot_start[]; | ||
| 32 | +extern const unsigned char _binary_jolt_boot_end[]; | ||
| 33 | + | ||
| 34 | +#define REGISTER_VIDYA(name) Sregister_symbol(#name, (void *)&name) | ||
| 35 | + | ||
| 36 | +static void register_vidya_api(void) { | ||
| 37 | + /* vidya.h */ | ||
| 38 | + REGISTER_VIDYA(vidya_open); | ||
| 39 | + REGISTER_VIDYA(vidya_close); | ||
| 40 | + REGISTER_VIDYA(vidya_should_close); | ||
| 41 | + REGISTER_VIDYA(vidya_set_target_fps); | ||
| 42 | + REGISTER_VIDYA(vidya_set_mode); | ||
| 43 | + REGISTER_VIDYA(vidya_get_mode); | ||
| 44 | + REGISTER_VIDYA(vidya_load_font); | ||
| 45 | + REGISTER_VIDYA(vidya_begin_frame); | ||
| 46 | + REGISTER_VIDYA(vidya_end_frame); | ||
| 47 | + REGISTER_VIDYA(vidya_page_begin); | ||
| 48 | + REGISTER_VIDYA(vidya_page_end); | ||
| 49 | + REGISTER_VIDYA(vidya_card_begin); | ||
| 50 | + REGISTER_VIDYA(vidya_card_end); | ||
| 51 | + REGISTER_VIDYA(vidya_gap); | ||
| 52 | + REGISTER_VIDYA(vidya_separator); | ||
| 53 | + REGISTER_VIDYA(vidya_title); | ||
| 54 | + REGISTER_VIDYA(vidya_title_2); | ||
| 55 | + REGISTER_VIDYA(vidya_body); | ||
| 56 | + REGISTER_VIDYA(vidya_dim_label); | ||
| 57 | + REGISTER_VIDYA(vidya_button); | ||
| 58 | + REGISTER_VIDYA(vidya_checkbox); | ||
| 59 | + REGISTER_VIDYA(vidya_checkbox_value); | ||
| 60 | + REGISTER_VIDYA(vidya_status); | ||
| 61 | + REGISTER_VIDYA(vidya_text_field); | ||
| 62 | + | ||
| 63 | + /* vidya_tree.h */ | ||
| 64 | + REGISTER_VIDYA(vidya_tree_root); | ||
| 65 | + REGISTER_VIDYA(vidya_node_new); | ||
| 66 | + REGISTER_VIDYA(vidya_node_free); | ||
| 67 | + REGISTER_VIDYA(vidya_node_exists); | ||
| 68 | + REGISTER_VIDYA(vidya_node_set_str); | ||
| 69 | + REGISTER_VIDYA(vidya_node_set_num); | ||
| 70 | + REGISTER_VIDYA(vidya_node_set_bool); | ||
| 71 | + REGISTER_VIDYA(vidya_node_clear_props); | ||
| 72 | + REGISTER_VIDYA(vidya_node_get_str); | ||
| 73 | + REGISTER_VIDYA(vidya_node_get_num); | ||
| 74 | + REGISTER_VIDYA(vidya_node_get_bool); | ||
| 75 | + REGISTER_VIDYA(vidya_node_tag); | ||
| 76 | + REGISTER_VIDYA(vidya_node_child_count); | ||
| 77 | + REGISTER_VIDYA(vidya_node_child_at); | ||
| 78 | + REGISTER_VIDYA(vidya_node_append); | ||
| 79 | + REGISTER_VIDYA(vidya_node_remove); | ||
| 80 | + REGISTER_VIDYA(vidya_node_insert_after); | ||
| 81 | + REGISTER_VIDYA(vidya_node_replace); | ||
| 82 | + REGISTER_VIDYA(vidya_tree_frame); | ||
| 83 | + REGISTER_VIDYA(vidya_tree_poll_event); | ||
| 84 | + REGISTER_VIDYA(vidya_tree_event_node); | ||
| 85 | + REGISTER_VIDYA(vidya_tree_event_name); | ||
| 86 | + REGISTER_VIDYA(vidya_tree_event_text); | ||
| 87 | + REGISTER_VIDYA(vidya_tree_event_num); | ||
| 88 | + | ||
| 89 | + /* Neither of these paints anything; they are the platform reached through | ||
| 90 | + the same ABI. `vidya_open_url` is the one that matters here — signing in | ||
| 91 | + leaves for a browser and comes back. */ | ||
| 92 | + REGISTER_VIDYA(vidya_clipboard_image_png); | ||
| 93 | + REGISTER_VIDYA(vidya_open_url); | ||
| 94 | + REGISTER_VIDYA(vidya_pick_image); | ||
| 95 | + REGISTER_VIDYA(vidya_picked_image); | ||
| 96 | + | ||
| 97 | + /* Live pixels, the window's own size, and the tree dump. Nothing here is | ||
| 98 | + Android-specific; they are on this list because the list is the whole of | ||
| 99 | + what an application can reach on Android, and a symbol left off it is not | ||
| 100 | + a link error but a call that fails at the moment a screen needs it. */ | ||
| 101 | + REGISTER_VIDYA(vidya_frame_rgba); | ||
| 102 | + REGISTER_VIDYA(vidya_frame_drop); | ||
| 103 | + REGISTER_VIDYA(vidya_screen_width); | ||
| 104 | + REGISTER_VIDYA(vidya_screen_height); | ||
| 105 | + REGISTER_VIDYA(vidya_tree_dump); | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +/* | ||
| 109 | + * Chez writes to stdout/stderr, which on Android goes nowhere. Pump both into | ||
| 110 | + * logcat so a Scheme-level error is visible rather than a silent exit. | ||
| 111 | + */ | ||
| 112 | +static void *log_scheme_output(void *context) { | ||
| 113 | + int fd = *(int *)context; | ||
| 114 | + char buffer[1024]; | ||
| 115 | + FILE *stream = fdopen(fd, "r"); | ||
| 116 | + if (stream == NULL) return NULL; | ||
| 117 | + | ||
| 118 | + while (fgets(buffer, sizeof(buffer), stream) != NULL) { | ||
| 119 | + __android_log_write(ANDROID_LOG_ERROR, "VidyaJolt", buffer); | ||
| 120 | + } | ||
| 121 | + fclose(stream); | ||
| 122 | + return NULL; | ||
| 123 | +} | ||
| 124 | + | ||
| 125 | +int vidya_jolt_main(void) { | ||
| 126 | + static const char *argv[] = {"vidya", NULL}; | ||
| 127 | + int output_pipe[2]; | ||
| 128 | + pthread_t logger; | ||
| 129 | + | ||
| 130 | + if (pipe(output_pipe) == 0) { | ||
| 131 | + dup2(output_pipe[1], STDOUT_FILENO); | ||
| 132 | + dup2(output_pipe[1], STDERR_FILENO); | ||
| 133 | + close(output_pipe[1]); | ||
| 134 | + pthread_create(&logger, NULL, log_scheme_output, &output_pipe[0]); | ||
| 135 | + pthread_detach(logger); | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", | ||
| 139 | + "initializing embedded Chez runtime"); | ||
| 140 | + Sscheme_init(NULL); | ||
| 141 | + Sregister_boot_file_bytes( | ||
| 142 | + "jolt", | ||
| 143 | + (void *)_binary_jolt_boot_start, | ||
| 144 | + (iptr)(_binary_jolt_boot_end - _binary_jolt_boot_start)); | ||
| 145 | + Sbuild_heap(NULL, NULL); | ||
| 146 | + register_vidya_api(); | ||
| 147 | + | ||
| 148 | + __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", "starting Jolt application"); | ||
| 149 | + int status = Sscheme_start(1, argv); | ||
| 150 | + __android_log_print(ANDROID_LOG_ERROR, "VidyaJolt", | ||
| 151 | + "Jolt application exited with status %d", status); | ||
| 152 | + Sscheme_deinit(); | ||
| 153 | + return status; | ||
| 154 | +} | ||
modified
crates/jolt-vidya/BUCK +16 -3 | @@ -13,9 +13,16 @@ rust_library( | ||
| 13 | 13 | visibility = ["PUBLIC"], |
| 14 | 14 | # Cargo renames the dependency so the cdylib itself can keep the `vidya` |
| 15 | 15 | # crate name; mirror that here. |
| 16 | - named_deps = {"vidya_core": "//crates/vidya-core:vidya"}, | |
| 16 | + named_deps = select({ | |
| 17 | + "DEFAULT": {"vidya_core": "//crates/vidya-core:vidya"}, | |
| 18 | + # jni is renamed in third-party/rust/Cargo.toml, for the reason given | |
| 19 | + # there; the crate is `jni` to the source either way. | |
| 20 | + "prelude//os:android": { | |
| 21 | + "jni": "//third-party/rust:jni-0-22", | |
| 22 | + "vidya_core": "//crates/vidya-core:vidya", | |
| 23 | + }, | |
| 24 | + }), | |
| 17 | 25 | deps = [ |
| 18 | - "//third-party/rust:arboard", | |
| 19 | 26 | "//third-party/rust:egui", |
| 20 | 27 | "//third-party/rust:egui-winit", |
| 21 | 28 | "//third-party/rust:egui_glow", |
| @@ -24,5 +31,11 @@ rust_library( | ||
| 24 | 31 | "//third-party/rust:glutin-winit", |
| 25 | 32 | "//third-party/rust:png", |
| 26 | 33 | "//third-party/rust:winit", |
| 27 | - ], | |
| 34 | + ] + select({ | |
| 35 | + # Reading a pasted picture off the clipboard, which arboard has no | |
| 36 | + # Android backend for; `vidya_open_url` is an ACTION_VIEW intent there | |
| 37 | + # and a JNI call. Mirrors the target-gated deps in Cargo.toml. | |
| 38 | + "DEFAULT": ["//third-party/rust:arboard"], | |
| 39 | + "prelude//os:android": [], | |
| 40 | + }), | |
| 28 | 41 | ) |
| @@ -13,9 +13,16 @@ rust_library( | |||
| 13 | visibility = ["PUBLIC"], | 13 | visibility = ["PUBLIC"], |
| 14 | # Cargo renames the dependency so the cdylib itself can keep the `vidya` | 14 | # Cargo renames the dependency so the cdylib itself can keep the `vidya` |
| 15 | # crate name; mirror that here. | 15 | # crate name; mirror that here. |
| 16 | - named_deps = {"vidya_core": "//crates/vidya-core:vidya"}, | 16 | + named_deps = select({ |
| 17 | + "DEFAULT": {"vidya_core": "//crates/vidya-core:vidya"}, | ||
| 18 | + # jni is renamed in third-party/rust/Cargo.toml, for the reason given | ||
| 19 | + # there; the crate is `jni` to the source either way. | ||
| 20 | + "prelude//os:android": { | ||
| 21 | + "jni": "//third-party/rust:jni-0-22", | ||
| 22 | + "vidya_core": "//crates/vidya-core:vidya", | ||
| 23 | + }, | ||
| 24 | + }), | ||
| 17 | deps = [ | 25 | deps = [ |
| 18 | - "//third-party/rust:arboard", | ||
| 19 | "//third-party/rust:egui", | 26 | "//third-party/rust:egui", |
| 20 | "//third-party/rust:egui-winit", | 27 | "//third-party/rust:egui-winit", |
| 21 | "//third-party/rust:egui_glow", | 28 | "//third-party/rust:egui_glow", |
| @@ -24,5 +31,11 @@ rust_library( | |||
| 24 | "//third-party/rust:glutin-winit", | 31 | "//third-party/rust:glutin-winit", |
| 25 | "//third-party/rust:png", | 32 | "//third-party/rust:png", |
| 26 | "//third-party/rust:winit", | 33 | "//third-party/rust:winit", |
| 27 | - ], | 34 | + ] + select({ |
| 35 | + # Reading a pasted picture off the clipboard, which arboard has no | ||
| 36 | + # Android backend for; `vidya_open_url` is an ACTION_VIEW intent there | ||
| 37 | + # and a JNI call. Mirrors the target-gated deps in Cargo.toml. | ||
| 38 | + "DEFAULT": ["//third-party/rust:arboard"], | ||
| 39 | + "prelude//os:android": [], | ||
| 40 | + }), | ||
| 28 | ) | 41 | ) |
added
crates/jolt-vidya/include/vidya.h +75 -0 | new file mode 100644 | ||
| @@ -0,0 +1,75 @@ | ||
| 1 | +#ifndef VIDYA_H | |
| 2 | +#define VIDYA_H | |
| 3 | + | |
| 4 | +#include <stddef.h> | |
| 5 | + | |
| 6 | +#if defined(_WIN32) | |
| 7 | +# if defined(VIDYA_BUILD) | |
| 8 | +# define VIDYA_API __declspec(dllexport) | |
| 9 | +# else | |
| 10 | +# define VIDYA_API __declspec(dllimport) | |
| 11 | +# endif | |
| 12 | +#elif defined(__GNUC__) | |
| 13 | +# define VIDYA_API __attribute__((visibility("default"))) | |
| 14 | +#else | |
| 15 | +# define VIDYA_API | |
| 16 | +#endif | |
| 17 | + | |
| 18 | +#ifdef __cplusplus | |
| 19 | +extern "C" { | |
| 20 | +#endif | |
| 21 | + | |
| 22 | +typedef enum VidyaMode { | |
| 23 | + VIDYA_DARK = 0, | |
| 24 | + VIDYA_LIGHT = 1 | |
| 25 | +} VidyaMode; | |
| 26 | + | |
| 27 | +typedef enum VidyaButtonKind { | |
| 28 | + VIDYA_BUTTON_DEFAULT = 0, | |
| 29 | + VIDYA_BUTTON_PRIMARY = 1, | |
| 30 | + VIDYA_BUTTON_DESTRUCTIVE = 2 | |
| 31 | +} VidyaButtonKind; | |
| 32 | + | |
| 33 | +/* Window and frame lifecycle. There is one UI context per process for now. | |
| 34 | + * All calls must remain on the window thread. */ | |
| 35 | +VIDYA_API int vidya_open(int width, int height, const char *title); | |
| 36 | +VIDYA_API void vidya_close(void); | |
| 37 | +VIDYA_API int vidya_should_close(void); | |
| 38 | +VIDYA_API void vidya_set_target_fps(int fps); | |
| 39 | +VIDYA_API void vidya_set_mode(int mode); | |
| 40 | +VIDYA_API int vidya_get_mode(void); | |
| 41 | +/* Replace the UI font. Returns 1 on success. atlas_size is the size the direct | |
| 42 | + * backend bakes its atlas at, which controls then scale down to the semantic | |
| 43 | + * type sizes; 32 is a good default. Call between frames, not inside one. */ | |
| 44 | +VIDYA_API int vidya_load_font(const char *path, int atlas_size); | |
| 45 | +VIDYA_API void vidya_begin_frame(void); | |
| 46 | +VIDYA_API void vidya_end_frame(void); | |
| 47 | + | |
| 48 | +/* A frame is a vertical immediate-mode page. Containers save and restore its | |
| 49 | + * horizontal bounds; every leaf advances the vertical cursor. */ | |
| 50 | +VIDYA_API void vidya_page_begin(float max_width); | |
| 51 | +VIDYA_API void vidya_page_end(void); | |
| 52 | +VIDYA_API void vidya_card_begin(void); | |
| 53 | +VIDYA_API void vidya_card_end(void); | |
| 54 | +VIDYA_API void vidya_gap(float pixels); | |
| 55 | +VIDYA_API void vidya_separator(void); | |
| 56 | + | |
| 57 | +/* Semantic text roles. */ | |
| 58 | +VIDYA_API void vidya_title(const char *text); | |
| 59 | +VIDYA_API void vidya_title_2(const char *text); | |
| 60 | +VIDYA_API void vidya_body(const char *text); | |
| 61 | +VIDYA_API void vidya_dim_label(const char *text); | |
| 62 | + | |
| 63 | +/* Controls return 1 only on activation/change during the current frame. */ | |
| 64 | +VIDYA_API int vidya_button(const char *label, int kind); | |
| 65 | +VIDYA_API int vidya_checkbox(const char *label, int *checked); | |
| 66 | +/* FFI-friendly checkbox variant: returns the current value after handling input. */ | |
| 67 | +VIDYA_API int vidya_checkbox_value(const char *label, int checked); | |
| 68 | +VIDYA_API void vidya_status(const char *label, int live); | |
| 69 | +VIDYA_API int vidya_text_field(char *text, size_t capacity, const char *placeholder); | |
| 70 | + | |
| 71 | +#ifdef __cplusplus | |
| 72 | +} | |
| 73 | +#endif | |
| 74 | + | |
| 75 | +#endif | |
| new file mode 100644 | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | +#ifndef VIDYA_H | ||
| 2 | +#define VIDYA_H | ||
| 3 | + | ||
| 4 | +#include <stddef.h> | ||
| 5 | + | ||
| 6 | +#if defined(_WIN32) | ||
| 7 | +# if defined(VIDYA_BUILD) | ||
| 8 | +# define VIDYA_API __declspec(dllexport) | ||
| 9 | +# else | ||
| 10 | +# define VIDYA_API __declspec(dllimport) | ||
| 11 | +# endif | ||
| 12 | +#elif defined(__GNUC__) | ||
| 13 | +# define VIDYA_API __attribute__((visibility("default"))) | ||
| 14 | +#else | ||
| 15 | +# define VIDYA_API | ||
| 16 | +#endif | ||
| 17 | + | ||
| 18 | +#ifdef __cplusplus | ||
| 19 | +extern "C" { | ||
| 20 | +#endif | ||
| 21 | + | ||
| 22 | +typedef enum VidyaMode { | ||
| 23 | + VIDYA_DARK = 0, | ||
| 24 | + VIDYA_LIGHT = 1 | ||
| 25 | +} VidyaMode; | ||
| 26 | + | ||
| 27 | +typedef enum VidyaButtonKind { | ||
| 28 | + VIDYA_BUTTON_DEFAULT = 0, | ||
| 29 | + VIDYA_BUTTON_PRIMARY = 1, | ||
| 30 | + VIDYA_BUTTON_DESTRUCTIVE = 2 | ||
| 31 | +} VidyaButtonKind; | ||
| 32 | + | ||
| 33 | +/* Window and frame lifecycle. There is one UI context per process for now. | ||
| 34 | + * All calls must remain on the window thread. */ | ||
| 35 | +VIDYA_API int vidya_open(int width, int height, const char *title); | ||
| 36 | +VIDYA_API void vidya_close(void); | ||
| 37 | +VIDYA_API int vidya_should_close(void); | ||
| 38 | +VIDYA_API void vidya_set_target_fps(int fps); | ||
| 39 | +VIDYA_API void vidya_set_mode(int mode); | ||
| 40 | +VIDYA_API int vidya_get_mode(void); | ||
| 41 | +/* Replace the UI font. Returns 1 on success. atlas_size is the size the direct | ||
| 42 | + * backend bakes its atlas at, which controls then scale down to the semantic | ||
| 43 | + * type sizes; 32 is a good default. Call between frames, not inside one. */ | ||
| 44 | +VIDYA_API int vidya_load_font(const char *path, int atlas_size); | ||
| 45 | +VIDYA_API void vidya_begin_frame(void); | ||
| 46 | +VIDYA_API void vidya_end_frame(void); | ||
| 47 | + | ||
| 48 | +/* A frame is a vertical immediate-mode page. Containers save and restore its | ||
| 49 | + * horizontal bounds; every leaf advances the vertical cursor. */ | ||
| 50 | +VIDYA_API void vidya_page_begin(float max_width); | ||
| 51 | +VIDYA_API void vidya_page_end(void); | ||
| 52 | +VIDYA_API void vidya_card_begin(void); | ||
| 53 | +VIDYA_API void vidya_card_end(void); | ||
| 54 | +VIDYA_API void vidya_gap(float pixels); | ||
| 55 | +VIDYA_API void vidya_separator(void); | ||
| 56 | + | ||
| 57 | +/* Semantic text roles. */ | ||
| 58 | +VIDYA_API void vidya_title(const char *text); | ||
| 59 | +VIDYA_API void vidya_title_2(const char *text); | ||
| 60 | +VIDYA_API void vidya_body(const char *text); | ||
| 61 | +VIDYA_API void vidya_dim_label(const char *text); | ||
| 62 | + | ||
| 63 | +/* Controls return 1 only on activation/change during the current frame. */ | ||
| 64 | +VIDYA_API int vidya_button(const char *label, int kind); | ||
| 65 | +VIDYA_API int vidya_checkbox(const char *label, int *checked); | ||
| 66 | +/* FFI-friendly checkbox variant: returns the current value after handling input. */ | ||
| 67 | +VIDYA_API int vidya_checkbox_value(const char *label, int checked); | ||
| 68 | +VIDYA_API void vidya_status(const char *label, int live); | ||
| 69 | +VIDYA_API int vidya_text_field(char *text, size_t capacity, const char *placeholder); | ||
| 70 | + | ||
| 71 | +#ifdef __cplusplus | ||
| 72 | +} | ||
| 73 | +#endif | ||
| 74 | + | ||
| 75 | +#endif | ||
modified
crates/vidya-core/BUCK +6 -1 | @@ -14,5 +14,10 @@ rust_library( | ||
| 14 | 14 | "//third-party/rust:egui", |
| 15 | 15 | "//third-party/rust:png", |
| 16 | 16 | "//third-party/rust:zip", |
| 17 | - ], | |
| 17 | + ] + select({ | |
| 18 | + "DEFAULT": [], | |
| 19 | + # `sync_system_chrome_from_android` reads the insets off the | |
| 20 | + # NativeActivity handle. Mirrors the target-gated dep in Cargo.toml. | |
| 21 | + "prelude//os:android": ["//third-party/rust:winit"], | |
| 22 | + }), | |
| 18 | 23 | ) |
| @@ -14,5 +14,10 @@ rust_library( | |||
| 14 | "//third-party/rust:egui", | 14 | "//third-party/rust:egui", |
| 15 | "//third-party/rust:png", | 15 | "//third-party/rust:png", |
| 16 | "//third-party/rust:zip", | 16 | "//third-party/rust:zip", |
| 17 | - ], | 17 | + ] + select({ |
| 18 | + "DEFAULT": [], | ||
| 19 | + # `sync_system_chrome_from_android` reads the insets off the | ||
| 20 | + # NativeActivity handle. Mirrors the target-gated dep in Cargo.toml. | ||
| 21 | + "prelude//os:android": ["//third-party/rust:winit"], | ||
| 22 | + }), | ||
| 18 | ) | 23 | ) |
modified
justfile +17 -0 | @@ -49,6 +49,23 @@ buck-build *args: | ||
| 49 | 49 | buck-libdir: |
| 50 | 50 | @echo "{{justfile_directory()}}/build/lib" |
| 51 | 51 | |
| 52 | +# libvidya alone, cross-compiled for a 64-bit Android device, staged where an | |
| 53 | +# APK build can pick it up. There is no libjoltmoq here: the media plane is | |
| 54 | +# V4L2 and PipeWire, neither of which the phone has. | |
| 55 | +# | |
| 56 | +# The NDK is fetched by buck2 from the pin in scripts/android-ndk.dotslash, so | |
| 57 | +# the only thing a machine needs installed for this is nothing. | |
| 58 | +ffi-android: | |
| 59 | + @just buck build //:libvidya-android | |
| 60 | + mkdir -p build/android/arm64-v8a | |
| 61 | + cp "$(just buck build --show-output //:libvidya-android | awk '/libvidya.so/{print $2}')" \ | |
| 62 | + build/android/arm64-v8a/libvidya.so | |
| 63 | + @echo "{{justfile_directory()}}/build/android/arm64-v8a" | |
| 64 | + | |
| 65 | +# Where an APK build finds the Android libvidya, and the glue beside it. | |
| 66 | +android-libdir: | |
| 67 | + @echo "{{justfile_directory()}}/build/android/arm64-v8a" | |
| 68 | + | |
| 52 | 69 | test *args: |
| 53 | 70 | cargo test --workspace {{args}} |
| 54 | 71 | |
| @@ -49,6 +49,23 @@ buck-build *args: | |||
| 49 | buck-libdir: | 49 | buck-libdir: |
| 50 | @echo "{{justfile_directory()}}/build/lib" | 50 | @echo "{{justfile_directory()}}/build/lib" |
| 51 | 51 | ||
| 52 | +# libvidya alone, cross-compiled for a 64-bit Android device, staged where an | ||
| 53 | +# APK build can pick it up. There is no libjoltmoq here: the media plane is | ||
| 54 | +# V4L2 and PipeWire, neither of which the phone has. | ||
| 55 | +# | ||
| 56 | +# The NDK is fetched by buck2 from the pin in scripts/android-ndk.dotslash, so | ||
| 57 | +# the only thing a machine needs installed for this is nothing. | ||
| 58 | +ffi-android: | ||
| 59 | + @just buck build //:libvidya-android | ||
| 60 | + mkdir -p build/android/arm64-v8a | ||
| 61 | + cp "$(just buck build --show-output //:libvidya-android | awk '/libvidya.so/{print $2}')" \ | ||
| 62 | + build/android/arm64-v8a/libvidya.so | ||
| 63 | + @echo "{{justfile_directory()}}/build/android/arm64-v8a" | ||
| 64 | + | ||
| 65 | +# Where an APK build finds the Android libvidya, and the glue beside it. | ||
| 66 | +android-libdir: | ||
| 67 | + @echo "{{justfile_directory()}}/build/android/arm64-v8a" | ||
| 68 | + | ||
| 52 | test *args: | 69 | test *args: |
| 53 | cargo test --workspace {{args}} | 70 | cargo test --workspace {{args}} |
| 54 | 71 | ||