ndk_glue/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
2
3use log::Level;
4use ndk::input_queue::InputQueue;
5use ndk::looper::{FdEvent, ForeignLooper, ThreadLooper};
6use ndk::native_activity::NativeActivity;
7use ndk::native_window::NativeWindow;
8use ndk_sys::{AInputQueue, ANativeActivity, ANativeWindow, ARect};
9use once_cell::sync::Lazy;
10use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
11use std::ffi::{CStr, CString};
12use std::fmt;
13use std::fs::File;
14use std::io::{BufRead, BufReader};
15use std::ops::Deref;
16use std::os::raw;
17use std::os::unix::prelude::*;
18use std::ptr::NonNull;
19use std::sync::{Arc, Condvar, Mutex};
20use std::thread;
21
22#[cfg(feature = "logger")]
23pub use android_logger;
24#[cfg(feature = "logger")]
25pub use log;
26
27pub use ndk_macro::main;
28
29/// `ndk-glue` macros register the reading end of an event pipe with the
30/// main [`ThreadLooper`] under this `ident`.
31/// When returned from [`ThreadLooper::poll_*`][ThreadLooper::poll_once]
32/// an event can be retrieved from [`poll_events()`].
33pub const NDK_GLUE_LOOPER_EVENT_PIPE_IDENT: i32 = 0;
34
35/// The [`InputQueue`] received from Android is registered with the main
36/// [`ThreadLooper`] under this `ident`.
37/// When returned from [`ThreadLooper::poll_*`][ThreadLooper::poll_once]
38/// an event can be retrieved from [`input_queue()`].
39pub const NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT: i32 = 1;
40
41pub fn android_log(level: Level, tag: &CStr, msg: &CStr) {
42    let prio = match level {
43        Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR,
44        Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN,
45        Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO,
46        Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG,
47        Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE,
48    };
49    unsafe {
50        ndk_sys::__android_log_write(prio.0 as raw::c_int, tag.as_ptr(), msg.as_ptr());
51    }
52}
53
54static NATIVE_WINDOW: Lazy<RwLock<Option<NativeWindow>>> = Lazy::new(Default::default);
55static INPUT_QUEUE: Lazy<RwLock<Option<InputQueue>>> = Lazy::new(Default::default);
56static CONTENT_RECT: Lazy<RwLock<Rect>> = Lazy::new(Default::default);
57static LOOPER: Lazy<Mutex<Option<ForeignLooper>>> = Lazy::new(Default::default);
58
59static mut NATIVE_ACTIVITY: Option<NativeActivity> = None;
60
61/// This function accesses a `static` variable internally and must only be used if you are sure
62/// there is exactly one version of `ndk_glue` in your dependency tree.
63///
64/// If you need access to the `JavaVM` through [`NativeActivity::vm()`] or Activity `Context`
65/// through [`NativeActivity::activity()`], please use the [`ndk_context`] crate and its
66/// [`ndk_context::android_context()`] getter to acquire the `JavaVM` and `Context` instead.
67pub fn native_activity() -> &'static NativeActivity {
68    unsafe { NATIVE_ACTIVITY.as_ref().unwrap() }
69}
70
71pub struct LockReadGuard<T: ?Sized + 'static>(MappedRwLockReadGuard<'static, T>);
72
73impl<T> LockReadGuard<T> {
74    /// Transpose an [`Option`] wrapped inside a [`LockReadGuard`]
75    ///
76    /// This is a _read_ lock for which the contents can't change; hence allowing the user to only
77    /// check for [`None`] once and hold a lock containing `T` directly thereafter, without
78    /// subsequent infallible [`Option::unwrap()`]s.
79    fn from_wrapped_option(wrapped: RwLockReadGuard<'static, Option<T>>) -> Option<Self> {
80        RwLockReadGuard::try_map(wrapped, Option::as_ref)
81            .ok()
82            .map(Self)
83    }
84}
85
86impl<T: ?Sized> Deref for LockReadGuard<T> {
87    type Target = T;
88
89    fn deref(&self) -> &Self::Target {
90        &self.0
91    }
92}
93
94impl<T: ?Sized + fmt::Debug> fmt::Debug for LockReadGuard<T> {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        self.0.fmt(f)
97    }
98}
99
100impl<T: ?Sized + fmt::Display> fmt::Display for LockReadGuard<T> {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        self.0.fmt(f)
103    }
104}
105
106/// Returns a [`NativeWindow`] held inside a lock, preventing Android from freeing it immediately
107/// in [its `NativeWindow` destructor].
108///
109/// If the window is in use by e.g. a graphics API, make sure to hold on to this lock.
110///
111/// After receiving [`Event::WindowDestroyed`] `ndk-glue` will block in Android's [`NativeWindow`] destructor
112/// callback until the lock is released, returning to Android and allowing it to free the window.
113///
114/// [its `NativeWindow` destructor]: https://developer.android.com/ndk/reference/struct/a-native-activity-callbacks#onnativewindowdestroyed
115///
116/// # Warning
117/// This function accesses a `static` variable internally and must only be used if you are sure
118/// there is exactly one version of `ndk_glue` in your dependency tree.
119pub fn native_window() -> Option<LockReadGuard<NativeWindow>> {
120    LockReadGuard::from_wrapped_option(NATIVE_WINDOW.read())
121}
122
123/// Returns an [`InputQueue`] held inside a lock, preventing Android from freeing it immediately
124/// in [its `InputQueue` destructor].
125///
126/// After receiving [`Event::InputQueueDestroyed`] `ndk-glue` will block in Android's [`InputQueue`] destructor
127/// callback until the lock is released, returning to Android and allowing it to free the window.
128///
129/// [its `InputQueue` destructor]: https://developer.android.com/ndk/reference/struct/a-native-activity-callbacks#oninputqueuedestroyed
130///
131/// # Warning
132/// This function accesses a `static` variable internally and must only be used if you are sure
133/// there is exactly one version of `ndk_glue` in your dependency tree.
134pub fn input_queue() -> Option<LockReadGuard<InputQueue>> {
135    LockReadGuard::from_wrapped_option(INPUT_QUEUE.read())
136}
137
138/// This function accesses a `static` variable internally and must only be used if you are sure
139/// there is exactly one version of `ndk_glue` in your dependency tree.
140pub fn content_rect() -> Rect {
141    CONTENT_RECT.read().clone()
142}
143
144static PIPE: Lazy<[RawFd; 2]> = Lazy::new(|| {
145    let mut pipe: [RawFd; 2] = Default::default();
146    unsafe { libc::pipe(pipe.as_mut_ptr()) };
147    pipe
148});
149
150pub fn poll_events() -> Option<Event> {
151    unsafe {
152        let size = std::mem::size_of::<Event>();
153        let mut event = Event::Start;
154        if libc::read(PIPE[0], &mut event as *mut _ as *mut _, size) == size as _ {
155            Some(event)
156        } else {
157            None
158        }
159    }
160}
161
162unsafe fn wake(_activity: *mut ANativeActivity, event: Event) {
163    log::trace!("{:?}", event);
164    let size = std::mem::size_of::<Event>();
165    let res = libc::write(PIPE[1], &event as *const _ as *const _, size);
166    assert_eq!(res, size as _);
167}
168
169#[derive(Clone, Debug, Default, Eq, PartialEq)]
170pub struct Rect {
171    pub left: u32,
172    pub top: u32,
173    pub right: u32,
174    pub bottom: u32,
175}
176
177#[derive(Clone, Debug, Eq, PartialEq)]
178#[repr(u8)]
179pub enum Event {
180    Start,
181    Resume,
182    SaveInstanceState,
183    Pause,
184    Stop,
185    Destroy,
186    ConfigChanged,
187    LowMemory,
188    WindowLostFocus,
189    WindowHasFocus,
190    /// A [`NativeWindow`] is now available through [`native_window()`]. See that function for more
191    /// details about holding on to the returned [`LockReadGuard`].
192    ///
193    /// Be sure to release any resources (e.g. Vulkan/OpenGL graphics surfaces) created from
194    /// it followed by releasing this lock upon receiving [`Event::WindowDestroyed`].
195    WindowCreated,
196    WindowResized,
197    WindowRedrawNeeded,
198    /// If the window is in use by e.g. a graphics API, make sure the [`LockReadGuard`] from
199    /// [`native_window()`] is held on to until after freeing those resources.
200    ///
201    /// After receiving this [`Event`] `ndk_glue` will block inside its [`NativeWindow`] destructor
202    /// until that read-lock is released before returning to Android and allowing it to free the
203    /// window.
204    ///
205    /// From this point [`native_window()`] will return [`None`] until receiving
206    /// [`Event::WindowCreated`] again.
207    WindowDestroyed,
208    /// An [`InputQueue`] is now available through [`input_queue()`].
209    ///
210    /// Be sure to release the returned lock upon receiving [`Event::InputQueueDestroyed`].
211    InputQueueCreated,
212    /// After receiving this [`Event`] `ndk_glue` will block inside its [`InputQueue`] destructor
213    /// until the read-lock from [`input_queue()`] is released before returning to Android and
214    /// allowing it to free the input queue.
215    ///
216    /// From this point [`input_queue()`] will return [`None`] until receiving
217    /// [`Event::InputQueueCreated`] again.
218    InputQueueDestroyed,
219    ContentRectChanged,
220}
221
222/// # Safety
223/// `activity` must either be null (resulting in a safe panic)
224/// or a pointer to a valid Android `ANativeActivity`.
225pub unsafe fn init(
226    activity: *mut ANativeActivity,
227    _saved_state: *mut u8,
228    _saved_state_size: usize,
229    main: fn(),
230) {
231    let mut activity = NonNull::new(activity).unwrap();
232    let mut callbacks = activity.as_mut().callbacks.as_mut().unwrap();
233    callbacks.onStart = Some(on_start);
234    callbacks.onResume = Some(on_resume);
235    callbacks.onSaveInstanceState = Some(on_save_instance_state);
236    callbacks.onPause = Some(on_pause);
237    callbacks.onStop = Some(on_stop);
238    callbacks.onDestroy = Some(on_destroy);
239    callbacks.onWindowFocusChanged = Some(on_window_focus_changed);
240    callbacks.onNativeWindowCreated = Some(on_window_created);
241    callbacks.onNativeWindowResized = Some(on_window_resized);
242    callbacks.onNativeWindowRedrawNeeded = Some(on_window_redraw_needed);
243    callbacks.onNativeWindowDestroyed = Some(on_window_destroyed);
244    callbacks.onInputQueueCreated = Some(on_input_queue_created);
245    callbacks.onInputQueueDestroyed = Some(on_input_queue_destroyed);
246    callbacks.onContentRectChanged = Some(on_content_rect_changed);
247    callbacks.onConfigurationChanged = Some(on_configuration_changed);
248    callbacks.onLowMemory = Some(on_low_memory);
249
250    let activity = NativeActivity::from_ptr(activity);
251    ndk_context::initialize_android_context(activity.vm().cast(), activity.activity().cast());
252    NATIVE_ACTIVITY.replace(activity);
253
254    let mut logpipe: [RawFd; 2] = Default::default();
255    libc::pipe(logpipe.as_mut_ptr());
256    libc::dup2(logpipe[1], libc::STDOUT_FILENO);
257    libc::dup2(logpipe[1], libc::STDERR_FILENO);
258    thread::spawn(move || {
259        let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap();
260        let file = File::from_raw_fd(logpipe[0]);
261        let mut reader = BufReader::new(file);
262        let mut buffer = String::new();
263        loop {
264            buffer.clear();
265            if let Ok(len) = reader.read_line(&mut buffer) {
266                if len == 0 {
267                    break;
268                } else if let Ok(msg) = CString::new(buffer.clone()) {
269                    android_log(Level::Info, tag, &msg);
270                }
271            }
272        }
273    });
274
275    let looper_ready = Arc::new(Condvar::new());
276    let signal_looper_ready = looper_ready.clone();
277
278    thread::spawn(move || {
279        let looper = ThreadLooper::prepare();
280        let foreign = looper.into_foreign();
281        foreign
282            .add_fd(
283                PIPE[0],
284                NDK_GLUE_LOOPER_EVENT_PIPE_IDENT,
285                FdEvent::INPUT,
286                std::ptr::null_mut(),
287            )
288            .unwrap();
289
290        {
291            let mut locked_looper = LOOPER.lock().unwrap();
292            locked_looper.replace(foreign);
293            signal_looper_ready.notify_one();
294        }
295
296        main()
297    });
298
299    // Don't return from this function (`ANativeActivity_onCreate`) until the thread
300    // has created its `ThreadLooper` and assigned it to the static `LOOPER`
301    // variable. It will be used from `on_input_queue_created` as soon as this
302    // function returns.
303    let locked_looper = LOOPER.lock().unwrap();
304    let _mutex_guard = looper_ready
305        .wait_while(locked_looper, |looper| looper.is_none())
306        .unwrap();
307}
308
309unsafe extern "C" fn on_start(activity: *mut ANativeActivity) {
310    wake(activity, Event::Start);
311}
312
313unsafe extern "C" fn on_resume(activity: *mut ANativeActivity) {
314    wake(activity, Event::Resume);
315}
316
317unsafe extern "C" fn on_save_instance_state(
318    activity: *mut ANativeActivity,
319    _out_size: *mut ndk_sys::size_t,
320) -> *mut raw::c_void {
321    // TODO
322    wake(activity, Event::SaveInstanceState);
323    std::ptr::null_mut()
324}
325
326unsafe extern "C" fn on_pause(activity: *mut ANativeActivity) {
327    wake(activity, Event::Pause);
328}
329
330unsafe extern "C" fn on_stop(activity: *mut ANativeActivity) {
331    wake(activity, Event::Stop);
332}
333
334unsafe extern "C" fn on_destroy(activity: *mut ANativeActivity) {
335    wake(activity, Event::Destroy);
336    ndk_context::release_android_context();
337}
338
339unsafe extern "C" fn on_configuration_changed(activity: *mut ANativeActivity) {
340    wake(activity, Event::ConfigChanged);
341}
342
343unsafe extern "C" fn on_low_memory(activity: *mut ANativeActivity) {
344    wake(activity, Event::LowMemory);
345}
346
347unsafe extern "C" fn on_window_focus_changed(
348    activity: *mut ANativeActivity,
349    has_focus: raw::c_int,
350) {
351    let event = if has_focus == 0 {
352        Event::WindowLostFocus
353    } else {
354        Event::WindowHasFocus
355    };
356    wake(activity, event);
357}
358
359unsafe extern "C" fn on_window_created(activity: *mut ANativeActivity, window: *mut ANativeWindow) {
360    NATIVE_WINDOW
361        .write()
362        .replace(NativeWindow::clone_from_ptr(NonNull::new(window).unwrap()));
363    wake(activity, Event::WindowCreated);
364}
365
366unsafe extern "C" fn on_window_resized(
367    activity: *mut ANativeActivity,
368    _window: *mut ANativeWindow,
369) {
370    wake(activity, Event::WindowResized);
371}
372
373unsafe extern "C" fn on_window_redraw_needed(
374    activity: *mut ANativeActivity,
375    _window: *mut ANativeWindow,
376) {
377    wake(activity, Event::WindowRedrawNeeded);
378}
379
380unsafe extern "C" fn on_window_destroyed(
381    activity: *mut ANativeActivity,
382    window: *mut ANativeWindow,
383) {
384    wake(activity, Event::WindowDestroyed);
385    let mut native_window_guard = NATIVE_WINDOW.write();
386    assert_eq!(native_window_guard.as_ref().unwrap().ptr().as_ptr(), window);
387    native_window_guard.take();
388}
389
390unsafe extern "C" fn on_input_queue_created(
391    activity: *mut ANativeActivity,
392    queue: *mut AInputQueue,
393) {
394    let input_queue = InputQueue::from_ptr(NonNull::new(queue).unwrap());
395    let locked_looper = LOOPER.lock().unwrap();
396    // The looper should always be `Some` after `fn init()` returns, unless
397    // future code cleans it up and sets it back to `None` again.
398    let looper = locked_looper.as_ref().expect("Looper does not exist");
399    input_queue.attach_looper(looper, NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT);
400    INPUT_QUEUE.write().replace(input_queue);
401    wake(activity, Event::InputQueueCreated);
402}
403
404unsafe extern "C" fn on_input_queue_destroyed(
405    activity: *mut ANativeActivity,
406    queue: *mut AInputQueue,
407) {
408    wake(activity, Event::InputQueueDestroyed);
409    let mut input_queue_guard = INPUT_QUEUE.write();
410    assert_eq!(input_queue_guard.as_ref().unwrap().ptr().as_ptr(), queue);
411    let input_queue = InputQueue::from_ptr(NonNull::new(queue).unwrap());
412    input_queue.detach_looper();
413    input_queue_guard.take();
414}
415
416unsafe extern "C" fn on_content_rect_changed(activity: *mut ANativeActivity, rect: *const ARect) {
417    let rect = Rect {
418        left: (*rect).left as _,
419        top: (*rect).top as _,
420        right: (*rect).right as _,
421        bottom: (*rect).bottom as _,
422    };
423    *CONTENT_RECT.write() = rect;
424    wake(activity, Event::ContentRectChanged);
425}