/* * The Jolt half of the Android app: an embedded Chez runtime and the boot image * built from the Jolt sources, reached from `libvidya.so`'s `android_main`. * * This is not the NativeActivity. On Android the activity's library must be the * one holding android-activity's glue, which is `libvidya.so` — see * `crates/jolt-vidya/src/android.rs`. That glue dlopens this library and calls * `vidya_jolt_main`, so the entry point below is an ordinary C function rather * than a `main`. * * Every foreign symbol is registered with Chez explicitly. Jolt resolves a * `defcfn` through Chez's foreign-entry table, and on Android nothing populates * that table from the dynamic loader: the symbols live in *different* shared * objects (`libvidya.so` and `libjoltmoq.so`, both DT_NEEDEDs of this one), * which is exactly the case the loader will not answer for. Registering them by * hand is what makes all three ABIs — the push/pop one, the retained tree one * glimmer binds, and the media plane — reachable. * * The media plane needs one thing more than a symbol table. Its camera is * Camera2, reached over JNI, and the handles that takes only arrive in * android-activity's glue inside libvidya. This file is the only place both * objects are in scope, so it is where they are handed across. */ #include #include #include #include #include /* JOLT_WITHOUT_MOQ builds this glue for an app that does not link * libjoltmoq. frq is one: its media plane is jolt now — MoQ through * libmoq_ffi, Opus, H.264, V4L2 and ALSA — so the Rust one is an object it * never opens, and seventeen megabytes of APK for nothing. Everything moq * below is behind this, because --no-undefined turns an unreferenced symbol * into a link error rather than a surprise on the phone. */ #ifndef JOLT_WITHOUT_MOQ #include "joltmoq.h" #endif #include "scheme.h" #include "vidya.h" #include "vidya_tree.h" /* The boot image, linked in as a binary blob by llvm-objcopy. */ extern const unsigned char _binary_jolt_boot_start[]; extern const unsigned char _binary_jolt_boot_end[]; #define REGISTER_VIDYA(name) Sregister_symbol(#name, (void *)&name) #define REGISTER_JOLTMOQ(name) Sregister_symbol(#name, (void *)&name) static void register_vidya_api(void) { /* vidya.h */ REGISTER_VIDYA(vidya_open); REGISTER_VIDYA(vidya_close); REGISTER_VIDYA(vidya_should_close); REGISTER_VIDYA(vidya_set_target_fps); REGISTER_VIDYA(vidya_set_mode); REGISTER_VIDYA(vidya_get_mode); REGISTER_VIDYA(vidya_load_font); REGISTER_VIDYA(vidya_begin_frame); REGISTER_VIDYA(vidya_end_frame); REGISTER_VIDYA(vidya_page_begin); REGISTER_VIDYA(vidya_page_end); REGISTER_VIDYA(vidya_card_begin); REGISTER_VIDYA(vidya_card_end); REGISTER_VIDYA(vidya_gap); REGISTER_VIDYA(vidya_separator); REGISTER_VIDYA(vidya_title); REGISTER_VIDYA(vidya_title_2); REGISTER_VIDYA(vidya_body); REGISTER_VIDYA(vidya_dim_label); REGISTER_VIDYA(vidya_button); REGISTER_VIDYA(vidya_checkbox); REGISTER_VIDYA(vidya_checkbox_value); REGISTER_VIDYA(vidya_status); REGISTER_VIDYA(vidya_text_field); /* vidya_tree.h */ REGISTER_VIDYA(vidya_tree_root); REGISTER_VIDYA(vidya_node_new); REGISTER_VIDYA(vidya_node_free); REGISTER_VIDYA(vidya_node_exists); REGISTER_VIDYA(vidya_node_set_str); REGISTER_VIDYA(vidya_node_set_num); REGISTER_VIDYA(vidya_node_set_bool); REGISTER_VIDYA(vidya_node_clear_props); REGISTER_VIDYA(vidya_node_get_str); REGISTER_VIDYA(vidya_node_get_num); REGISTER_VIDYA(vidya_node_get_bool); REGISTER_VIDYA(vidya_node_tag); REGISTER_VIDYA(vidya_node_child_count); REGISTER_VIDYA(vidya_node_child_at); REGISTER_VIDYA(vidya_node_append); REGISTER_VIDYA(vidya_node_remove); REGISTER_VIDYA(vidya_node_insert_after); REGISTER_VIDYA(vidya_node_replace); REGISTER_VIDYA(vidya_tree_frame); REGISTER_VIDYA(vidya_tree_poll_event); REGISTER_VIDYA(vidya_tree_event_node); REGISTER_VIDYA(vidya_tree_event_name); REGISTER_VIDYA(vidya_tree_event_text); REGISTER_VIDYA(vidya_tree_event_num); /* Neither of these paints anything; they are the platform reached through the same ABI. `vidya_open_url` is the one that matters here — signing in leaves for a browser and comes back. */ REGISTER_VIDYA(vidya_clipboard_image_png); REGISTER_VIDYA(vidya_open_url); REGISTER_VIDYA(vidya_pick_image); REGISTER_VIDYA(vidya_picked_image); /* Live pixels, the window's own size, and the tree dump. Nothing here is Android-specific; they are on this list because the list is the whole of what an application can reach on Android, and a symbol left off it is not a link error but a call that fails at the moment a screen needs it. */ REGISTER_VIDYA(vidya_set_title); REGISTER_VIDYA(vidya_frame_rgba); REGISTER_VIDYA(vidya_frame_drop); REGISTER_VIDYA(vidya_screen_width); REGISTER_VIDYA(vidya_screen_height); REGISTER_VIDYA(vidya_tree_dump); } #ifndef JOLT_WITHOUT_MOQ /* * The media plane's symbols, registered for the same reason the Vidya ones * above are: they live in libjoltmoq.so, a third object, and Android's loader * will not answer a foreign-entry lookup that crosses one. * * A call reaches none of this without it — `joltmoq_start` would be a defcfn * that resolves to nothing at the moment someone dials. */ static void register_joltmoq_api(void) { REGISTER_JOLTMOQ(joltmoq_init_logging); /* Lifecycle. */ REGISTER_JOLTMOQ(joltmoq_start); REGISTER_JOLTMOQ(joltmoq_stop); REGISTER_JOLTMOQ(joltmoq_is_live); /* In-call controls. */ REGISTER_JOLTMOQ(joltmoq_set_muted); REGISTER_JOLTMOQ(joltmoq_set_speaker_muted); REGISTER_JOLTMOQ(joltmoq_set_camera); REGISTER_JOLTMOQ(joltmoq_set_camera_device); REGISTER_JOLTMOQ(joltmoq_set_mic_device); REGISTER_JOLTMOQ(joltmoq_set_speaker_device); REGISTER_JOLTMOQ(joltmoq_mic_level); /* Status, drained rather than delivered. */ REGISTER_JOLTMOQ(joltmoq_poll_status); REGISTER_JOLTMOQ(joltmoq_status_text); REGISTER_JOLTMOQ(joltmoq_status_has_camera); REGISTER_JOLTMOQ(joltmoq_status_has_mic); /* Remote video, borrowed a frame at a time. */ REGISTER_JOLTMOQ(joltmoq_frame_poll); REGISTER_JOLTMOQ(joltmoq_frame_key); REGISTER_JOLTMOQ(joltmoq_frame_width); REGISTER_JOLTMOQ(joltmoq_frame_height); REGISTER_JOLTMOQ(joltmoq_frame_rgba); REGISTER_JOLTMOQ(joltmoq_video_keys); /* Devices. On the phone the cameras come from Camera2 and the microphone and speaker lists are empty — the platform picks those itself. */ REGISTER_JOLTMOQ(joltmoq_cameras); REGISTER_JOLTMOQ(joltmoq_microphones); REGISTER_JOLTMOQ(joltmoq_speakers); /* Dialling. */ REGISTER_JOLTMOQ(joltmoq_sfu_url); REGISTER_JOLTMOQ(joltmoq_can_dial); REGISTER_JOLTMOQ(joltmoq_new_instance); } #endif /* JOLT_WITHOUT_MOQ */ /* * Chez writes to stdout/stderr, which on Android goes nowhere. Pump both into * logcat so a Scheme-level error is visible rather than a silent exit. */ static void *log_scheme_output(void *context) { int fd = *(int *)context; char buffer[1024]; FILE *stream = fdopen(fd, "r"); if (stream == NULL) return NULL; while (fgets(buffer, sizeof(buffer), stream) != NULL) { __android_log_write(ANDROID_LOG_ERROR, "VidyaJolt", buffer); } fclose(stream); return NULL; } int vidya_jolt_main(void) { static const char *argv[] = {"vidya", NULL}; int output_pipe[2]; pthread_t logger; if (pipe(output_pipe) == 0) { dup2(output_pipe[1], STDOUT_FILENO); dup2(output_pipe[1], STDERR_FILENO); close(output_pipe[1]); pthread_create(&logger, NULL, log_scheme_output, &output_pipe[0]); pthread_detach(logger); } __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", "initializing embedded Chez runtime"); Sscheme_init(NULL); Sregister_boot_file_bytes( "jolt", (void *)_binary_jolt_boot_start, (iptr)(_binary_jolt_boot_end - _binary_jolt_boot_start)); Sbuild_heap(NULL, NULL); register_vidya_api(); #ifndef JOLT_WITHOUT_MOQ register_joltmoq_api(); #endif #ifndef JOLT_WITHOUT_MOQ /* The media plane's own handles, which it cannot get for itself: they arrive in android-activity's glue, and the glue is in libvidya.so. This is the only place both objects are in scope. Before Scheme starts, so no call can reach the camera ahead of it. Logged because there is otherwise no way to see it happen: joltmoq's own logging goes through env_logger, which nothing has initialised this early, so a failure here would be silent until a camera failed to open much later for reasons that would look like anything but this. */ void *moq_vm = vidya_android_vm(); void *moq_activity = vidya_android_activity(); __android_log_print(moq_vm && moq_activity ? ANDROID_LOG_INFO : ANDROID_LOG_ERROR, "VidyaJolt", "media plane JNI handles: vm=%p activity=%p", moq_vm, moq_activity); joltmoq_android_init(moq_vm, moq_activity); #endif __android_log_print(ANDROID_LOG_INFO, "VidyaJolt", "starting Jolt application"); int status = Sscheme_start(1, argv); __android_log_print(ANDROID_LOG_ERROR, "VidyaJolt", "Jolt application exited with status %d", status); Sscheme_deinit(); return status; }