nandi/jolt-nativepublic Fork 0
a9f3673367a8f0d3dd7d6ab1d380484d1df795c1
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 · 185 lines · 7.0 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
Catch up with vidya c90f8af nandi 19d ago30/// Point `HOME` (and the XDG directories under it) at the app's own storage.
31///
32/// An Android process starts with none of them set, and an application written
33/// against a Unix — which is every application this library loads — reads a
34/// missing `HOME` as the empty string and writes to `/.cache`, `/.config`, or
35/// worse. Nothing there is writable, so caches, saved sessions and settings all
36/// fail quietly on the phone and nowhere else.
37///
38/// The app's internal data directory is what a desktop's home directory is
39/// here: private to the app, writable without a permission, and removed with
40/// it. Set only when unset, so an application that has its own idea keeps it.
41fn set_home_from(app: &AndroidApp) {
42 let Some(home) = app.internal_data_path() else {
43 log(ANDROID_LOG_ERROR, "vidya: no internal data path for HOME");
44 return;
45 };
46 let mut set = |key: &str, value: &std::path::Path| {
47 if std::env::var_os(key).is_none() {
48 // SAFETY: this runs before the application starts, on the only
49 // thread there is; nothing else can be reading the environment.
50 unsafe { std::env::set_var(key, value) };
51 }
52 };
53 set("HOME", &home);
54 set("XDG_CACHE_HOME", &home.join(".cache"));
55 set("XDG_CONFIG_HOME", &home.join(".config"));
56 set("XDG_DATA_HOME", &home.join(".local/share"));
57}
58
Bring vidya in cfd3e36 nandi 19d ago59/// The handle winit needs to build an event loop. `None` off Android's own
60/// thread, or before the glue has started.
61pub fn android_app() -> Option<AndroidApp> {
62 ANDROID_APP.lock().ok()?.clone()
63}
64
Let the media plane cross to the phone, camera and all fd0e21a nandi 18d ago65/// The process's `JavaVM`, for a library that is not this one.
66///
67/// `libjoltmoq.so` reaches Camera2 through JNI and cannot get here by itself:
68/// the handle arrives in [`android_main`], which only runs in the object
69/// holding android-activity's glue, and the usual way across — `ndk_context` —
70/// keeps its handles in a `static`, which is per shared object. So the glue in
71/// `android/jolt_main.c` reads them out through here and hands them on.
72///
73/// Null before the glue has started.
74#[no_mangle]
75pub extern "C" fn vidya_android_vm() -> *mut c_void {
76 match android_app() {
77 Some(app) => app.vm_as_ptr(),
78 None => std::ptr::null_mut(),
79 }
80}
81
82/// The running Activity, as the glue's own global reference — see
83/// [`vidya_android_vm`]. It is good for the lifetime of the process, which is
84/// what makes it safe to hand to another object; a local reference would die
85/// with the frame that made it.
86///
87/// Null before the glue has started.
88#[no_mangle]
89pub extern "C" fn vidya_android_activity() -> *mut c_void {
90 match android_app() {
91 Some(app) => app.activity_as_ptr(),
92 None => std::ptr::null_mut(),
93 }
94}
95
Bring vidya in cfd3e36 nandi 19d ago96extern "C" {
97 fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
98 fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
99 fn dlerror() -> *const c_char;
100 fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int;
101}
102
103const ANDROID_LOG_INFO: c_int = 4;
104const ANDROID_LOG_ERROR: c_int = 6;
105const RTLD_NOW: c_int = 2;
106
107/// What went wrong on the way to a platform call, for `adb logcat -s Vidya`.
108pub(crate) fn warn(message: &str) {
109 log(ANDROID_LOG_ERROR, message);
110}
111
112fn log(priority: c_int, message: &str) {
113 let (Ok(tag), Ok(text)) = (CString::new("Vidya"), CString::new(message)) else {
114 return;
115 };
116 // SAFETY: both pointers are NUL-terminated and live across the call.
117 unsafe { __android_log_write(priority, tag.as_ptr(), text.as_ptr()) };
118}
119
120fn last_dl_error() -> String {
121 // SAFETY: dlerror returns a borrowed C string, or null when nothing failed.
122 let err = unsafe { dlerror() };
123 if err.is_null() {
124 return "unknown error".to_owned();
125 }
126 unsafe { std::ffi::CStr::from_ptr(err) }
127 .to_string_lossy()
128 .into_owned()
129}
130
131/// Entry point for `android-activity`'s NativeActivity glue.
132///
133/// Returning from here finishes the activity, which looks like a one-frame
134/// flash, so a failure to reach the application is logged rather than silent.
135#[no_mangle]
136pub extern "C" fn android_main(app: AndroidApp) {
Catch up with vidya c90f8af nandi 19d ago137 set_home_from(&app);
138
Bring vidya in cfd3e36 nandi 19d ago139 if let Ok(mut slot) = ANDROID_APP.lock() {
140 *slot = Some(app);
141 }
142
143 log(ANDROID_LOG_INFO, "vidya: loading the Jolt application");
144
145 let Ok(name) = CString::new(APP_LIBRARY) else {
146 return;
147 };
148 // SAFETY: `name` is a NUL-terminated library name; the handle is only used
149 // to look up one symbol and is deliberately never closed — the application
150 // runs for the lifetime of the process.
151 let handle = unsafe { dlopen(name.as_ptr(), RTLD_NOW) };
152 if handle.is_null() {
153 log(
154 ANDROID_LOG_ERROR,
155 &format!("vidya: cannot load {APP_LIBRARY}: {}", last_dl_error()),
156 );
157 return;
158 }
159
160 let Ok(symbol) = CString::new("vidya_jolt_main") else {
161 return;
162 };
163 // SAFETY: as above; the result is checked before it is called.
164 let entry = unsafe { dlsym(handle, symbol.as_ptr()) };
165 if entry.is_null() {
166 log(
167 ANDROID_LOG_ERROR,
168 &format!(
169 "vidya: {APP_LIBRARY} has no vidya_jolt_main: {}",
170 last_dl_error()
171 ),
172 );
173 return;
174 }
175
176 // SAFETY: `vidya_jolt_main` is declared by the glue in android/jolt_main.c
177 // as `int vidya_jolt_main(void)`; it boots Chez and does not return until
178 // the application exits.
179 let entry: extern "C" fn() -> c_int = unsafe { std::mem::transmute(entry) };
180 let status = entry();
181 log(
182 ANDROID_LOG_INFO,
183 &format!("vidya: Jolt application exited with status {status}"),
184 );
185}