nandi/jolt-nativepublic Fork 0
fc16d8e4f1cf0b5245d833bf4521e99d73b72df4
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

android.rs · 123 lines · 4.5 KBRust Blame HistoryRaw
Bring vidya in cfd3e36 nandi 19d ago1//! The Android entry point, and the `AndroidApp` the frame loop needs.
2//!
3//! On a desktop the caller owns `main` and this library is just a dependency.
4//! Android inverts that: the activity is created by the platform, and whoever
5//! owns the activity owns the event loop. winit cannot build one on Android
6//! without the `AndroidApp` handle that `android-activity`'s glue receives, and
7//! that glue only runs if *this* library is the NativeActivity's library.
8//!
9//! So on Android the roles swap. `libvidya.so` is the entry point named by the
10//! manifest, [`android_main`] stashes the handle [`build_event_loop`] later
11//! needs, and then hands control to the application — an embedded Jolt boot
12//! image in `libjoltapp.so`, which calls straight back into the C ABI below.
13//!
14//! The application is reached by `dlopen` rather than by linking. Linking would
15//! be a cycle — `libjoltapp.so` already needs this library for every `vidya_*`
16//! symbol it registers with Chez — and Android's loader resolves a dlopened
17//! library's dependencies without one.
18
19use std::ffi::{c_char, c_int, c_void, CString};
20use std::sync::Mutex;
21
22use winit::platform::android::activity::AndroidApp;
23
24/// The library holding the application's `vidya_jolt_main`.
25const APP_LIBRARY: &str = "libjoltapp.so";
26
27/// Set once, before any application code runs, and read on the loop thread.
28static ANDROID_APP: Mutex<Option<AndroidApp>> = Mutex::new(None);
29
30/// The handle winit needs to build an event loop. `None` off Android's own
31/// thread, or before the glue has started.
32pub fn android_app() -> Option<AndroidApp> {
33 ANDROID_APP.lock().ok()?.clone()
34}
35
36extern "C" {
37 fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
38 fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
39 fn dlerror() -> *const c_char;
40 fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
41}
42
43const ANDROID_LOG_INFO: c_int = 4;
44const ANDROID_LOG_ERROR: c_int = 6;
45const RTLD_NOW: c_int = 2;
46
47/// What went wrong on the way to a platform call, for `adb logcat -s Vidya`.
48pub(crate) fn warn(message: &str) {
49 log(ANDROID_LOG_ERROR, message);
50}
51
52fn log(priority: c_int, message: &str) {
53 let (Ok(tag), Ok(text)) = (CString::new("Vidya"), CString::new(message)) else {
54 return;
55 };
56 // SAFETY: both pointers are NUL-terminated and live across the call.
57 unsafe { __android_log_write(priority, tag.as_ptr(), text.as_ptr()) };
58}
59
60fn last_dl_error() -> String {
61 // SAFETY: dlerror returns a borrowed C string, or null when nothing failed.
62 let err = unsafe { dlerror() };
63 if err.is_null() {
64 return "unknown error".to_owned();
65 }
66 unsafe { std::ffi::CStr::from_ptr(err) }
67 .to_string_lossy()
68 .into_owned()
69}
70
71/// Entry point for `android-activity`'s NativeActivity glue.
72///
73/// Returning from here finishes the activity, which looks like a one-frame
74/// flash, so a failure to reach the application is logged rather than silent.
75#[no_mangle]
76pub extern "C" fn android_main(app: AndroidApp) {
77 if let Ok(mut slot) = ANDROID_APP.lock() {
78 *slot = Some(app);
79 }
80
81 log(ANDROID_LOG_INFO, "vidya: loading the Jolt application");
82
83 let Ok(name) = CString::new(APP_LIBRARY) else {
84 return;
85 };
86 // SAFETY: `name` is a NUL-terminated library name; the handle is only used
87 // to look up one symbol and is deliberately never closed — the application
88 // runs for the lifetime of the process.
89 let handle = unsafe { dlopen(name.as_ptr(), RTLD_NOW) };
90 if handle.is_null() {
91 log(
92 ANDROID_LOG_ERROR,
93 &format!("vidya: cannot load {APP_LIBRARY}: {}", last_dl_error()),
94 );
95 return;
96 }
97
98 let Ok(symbol) = CString::new("vidya_jolt_main") else {
99 return;
100 };
101 // SAFETY: as above; the result is checked before it is called.
102 let entry = unsafe { dlsym(handle, symbol.as_ptr()) };
103 if entry.is_null() {
104 log(
105 ANDROID_LOG_ERROR,
106 &format!(
107 "vidya: {APP_LIBRARY} has no vidya_jolt_main: {}",
108 last_dl_error()
109 ),
110 );
111 return;
112 }
113
114 // SAFETY: `vidya_jolt_main` is declared by the glue in android/jolt_main.c
115 // as `int vidya_jolt_main(void)`; it boots Chez and does not return until
116 // the application exits.
117 let entry: extern "C" fn() -> c_int = unsafe { std::mem::transmute(entry) };
118 let status = entry();
119 log(
120 ANDROID_LOG_INFO,
121 &format!("vidya: Jolt application exited with status {status}"),
122 );
123}