Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
151
third-party/vendor/android-activity/src/config.rs
vendored
Normal file
151
third-party/vendor/android-activity/src/config.rs
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
use core::fmt;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use ndk::configuration::{
|
||||
Configuration, Keyboard, KeysHidden, LayoutDir, NavHidden, Navigation, Orientation, ScreenLong,
|
||||
ScreenSize, Touchscreen, UiModeNight, UiModeType,
|
||||
};
|
||||
|
||||
/// A (cheaply clonable) reference to this application's [`ndk::configuration::Configuration`]
|
||||
///
|
||||
/// This provides a thread-safe way to access the latest configuration state for
|
||||
/// an application without deeply copying the large [`ndk::configuration::Configuration`] struct.
|
||||
///
|
||||
/// If the application is notified of configuration changes then those changes
|
||||
/// will become visible via pre-existing configuration references.
|
||||
#[derive(Clone)]
|
||||
pub struct ConfigurationRef {
|
||||
config: Arc<RwLock<Configuration>>,
|
||||
}
|
||||
impl PartialEq for ConfigurationRef {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if Arc::ptr_eq(&self.config, &other.config) {
|
||||
true
|
||||
} else {
|
||||
let other_guard = other.config.read().unwrap();
|
||||
self.config.read().unwrap().eq(&*other_guard)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Eq for ConfigurationRef {}
|
||||
unsafe impl Send for ConfigurationRef {}
|
||||
unsafe impl Sync for ConfigurationRef {}
|
||||
|
||||
impl fmt::Debug for ConfigurationRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.config.read().unwrap().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigurationRef {
|
||||
pub(crate) fn new(config: Configuration) -> Self {
|
||||
Self {
|
||||
config: Arc::new(RwLock::new(config)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn replace(&self, src: Configuration) {
|
||||
self.config.write().unwrap().copy(&src);
|
||||
}
|
||||
|
||||
// Returns a deep copy of the full application configuration
|
||||
pub fn copy(&self) -> Configuration {
|
||||
let mut dest = Configuration::new();
|
||||
dest.copy(&self.config.read().unwrap());
|
||||
dest
|
||||
}
|
||||
/// Returns the country code, as a [`String`] of two characters, if set
|
||||
pub fn country(&self) -> Option<String> {
|
||||
self.config.read().unwrap().country()
|
||||
}
|
||||
|
||||
/// Returns the screen density in dpi.
|
||||
///
|
||||
/// On some devices it can return values outside of the density enum.
|
||||
pub fn density(&self) -> Option<u32> {
|
||||
self.config.read().unwrap().density()
|
||||
}
|
||||
|
||||
/// Returns the keyboard type.
|
||||
pub fn keyboard(&self) -> Keyboard {
|
||||
self.config.read().unwrap().keyboard()
|
||||
}
|
||||
|
||||
/// Returns keyboard visibility/availability.
|
||||
pub fn keys_hidden(&self) -> KeysHidden {
|
||||
self.config.read().unwrap().keys_hidden()
|
||||
}
|
||||
|
||||
/// Returns the language, as a `String` of two characters, if a language is set
|
||||
pub fn language(&self) -> Option<String> {
|
||||
self.config.read().unwrap().language()
|
||||
}
|
||||
|
||||
/// Returns the layout direction
|
||||
pub fn layout_direction(&self) -> LayoutDir {
|
||||
self.config.read().unwrap().layout_direction()
|
||||
}
|
||||
|
||||
/// Returns the mobile country code.
|
||||
pub fn mcc(&self) -> i32 {
|
||||
self.config.read().unwrap().mcc()
|
||||
}
|
||||
|
||||
/// Returns the mobile network code, if one is defined
|
||||
pub fn mnc(&self) -> Option<i32> {
|
||||
self.config.read().unwrap().mnc()
|
||||
}
|
||||
|
||||
pub fn nav_hidden(&self) -> NavHidden {
|
||||
self.config.read().unwrap().nav_hidden()
|
||||
}
|
||||
|
||||
pub fn navigation(&self) -> Navigation {
|
||||
self.config.read().unwrap().navigation()
|
||||
}
|
||||
|
||||
pub fn orientation(&self) -> Orientation {
|
||||
self.config.read().unwrap().orientation()
|
||||
}
|
||||
|
||||
pub fn screen_height_dp(&self) -> Option<i32> {
|
||||
self.config.read().unwrap().screen_height_dp()
|
||||
}
|
||||
|
||||
pub fn screen_width_dp(&self) -> Option<i32> {
|
||||
self.config.read().unwrap().screen_width_dp()
|
||||
}
|
||||
|
||||
pub fn screen_long(&self) -> ScreenLong {
|
||||
self.config.read().unwrap().screen_long()
|
||||
}
|
||||
|
||||
#[cfg(feature = "api-level-30")]
|
||||
pub fn screen_round(&self) -> ScreenRound {
|
||||
self.config.read().unwrap().screen_round()
|
||||
}
|
||||
|
||||
pub fn screen_size(&self) -> ScreenSize {
|
||||
self.config.read().unwrap().screen_size()
|
||||
}
|
||||
|
||||
pub fn sdk_version(&self) -> i32 {
|
||||
self.config.read().unwrap().sdk_version()
|
||||
}
|
||||
|
||||
pub fn smallest_screen_width_dp(&self) -> Option<i32> {
|
||||
self.config.read().unwrap().smallest_screen_width_dp()
|
||||
}
|
||||
|
||||
pub fn touchscreen(&self) -> Touchscreen {
|
||||
self.config.read().unwrap().touchscreen()
|
||||
}
|
||||
|
||||
pub fn ui_mode_night(&self) -> UiModeNight {
|
||||
self.config.read().unwrap().ui_mode_night()
|
||||
}
|
||||
|
||||
pub fn ui_mode_type(&self) -> UiModeType {
|
||||
self.config.read().unwrap().ui_mode_type()
|
||||
}
|
||||
}
|
||||
32
third-party/vendor/android-activity/src/game_activity/ffi.rs
vendored
Normal file
32
third-party/vendor/android-activity/src/game_activity/ffi.rs
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//! The bindings are pre-generated and the right one for the platform is selected at compile time.
|
||||
|
||||
// Bindgen lints
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(improper_ctypes)]
|
||||
#![allow(clippy::all)]
|
||||
// Temporarily allow UB nullptr dereference in bindgen layout tests until fixed upstream:
|
||||
// https://github.com/rust-lang/rust-bindgen/pull/2055
|
||||
// https://github.com/rust-lang/rust-bindgen/pull/2064
|
||||
#![allow(deref_nullptr)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use jni_sys::*;
|
||||
use libc::{pthread_cond_t, pthread_mutex_t, pthread_t, size_t};
|
||||
use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect};
|
||||
|
||||
#[cfg(all(
|
||||
any(target_os = "android", feature = "test"),
|
||||
any(target_arch = "arm", target_arch = "armv7")
|
||||
))]
|
||||
include!("ffi_arm.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "aarch64"))]
|
||||
include!("ffi_aarch64.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86"))]
|
||||
include!("ffi_i686.rs");
|
||||
|
||||
#[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86_64"))]
|
||||
include!("ffi_x86_64.rs");
|
||||
7880
third-party/vendor/android-activity/src/game_activity/ffi_aarch64.rs
vendored
Normal file
7880
third-party/vendor/android-activity/src/game_activity/ffi_aarch64.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
8359
third-party/vendor/android-activity/src/game_activity/ffi_arm.rs
vendored
Normal file
8359
third-party/vendor/android-activity/src/game_activity/ffi_arm.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
10105
third-party/vendor/android-activity/src/game_activity/ffi_i686.rs
vendored
Normal file
10105
third-party/vendor/android-activity/src/game_activity/ffi_i686.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
10184
third-party/vendor/android-activity/src/game_activity/ffi_x86_64.rs
vendored
Normal file
10184
third-party/vendor/android-activity/src/game_activity/ffi_x86_64.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
1411
third-party/vendor/android-activity/src/game_activity/input.rs
vendored
Normal file
1411
third-party/vendor/android-activity/src/game_activity/input.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
679
third-party/vendor/android-activity/src/game_activity/mod.rs
vendored
Normal file
679
third-party/vendor/android-activity/src/game_activity/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,679 @@
|
|||
#![cfg(feature = "game-activity")]
|
||||
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::prelude::*;
|
||||
use std::panic::catch_unwind;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
use std::{ptr, thread};
|
||||
|
||||
use libc::c_void;
|
||||
use log::{error, trace, Level};
|
||||
|
||||
use jni_sys::*;
|
||||
|
||||
use ndk_sys::ALooper_wake;
|
||||
use ndk_sys::{ALooper, ALooper_pollAll};
|
||||
|
||||
use ndk::asset::AssetManager;
|
||||
use ndk::configuration::Configuration;
|
||||
use ndk::native_window::NativeWindow;
|
||||
|
||||
use crate::util::{abort_on_panic, android_log, log_panic};
|
||||
use crate::{
|
||||
util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
|
||||
};
|
||||
|
||||
mod ffi;
|
||||
|
||||
pub mod input;
|
||||
use input::{Axis, InputEvent, KeyEvent, MotionEvent};
|
||||
|
||||
// The only time it's safe to update the android_app->savedState pointer is
|
||||
// while handling a SaveState event, so this API is only exposed for those
|
||||
// events...
|
||||
#[derive(Debug)]
|
||||
pub struct StateSaver<'a> {
|
||||
app: &'a AndroidAppInner,
|
||||
}
|
||||
|
||||
impl<'a> StateSaver<'a> {
|
||||
pub fn store(&self, state: &'a [u8]) {
|
||||
// android_native_app_glue specifically expects savedState to have been allocated
|
||||
// via libc::malloc since it will automatically handle freeing the data once it
|
||||
// has been handed over to the Java Activity / main thread.
|
||||
unsafe {
|
||||
let app_ptr = self.app.native_app.as_ptr();
|
||||
|
||||
// In case the application calls store() multiple times for some reason we
|
||||
// make sure to free any pre-existing state...
|
||||
if !(*app_ptr).savedState.is_null() {
|
||||
libc::free((*app_ptr).savedState);
|
||||
(*app_ptr).savedState = ptr::null_mut();
|
||||
(*app_ptr).savedStateSize = 0;
|
||||
}
|
||||
|
||||
let buf = libc::malloc(state.len());
|
||||
if buf.is_null() {
|
||||
panic!("Failed to allocate save_state buffer");
|
||||
}
|
||||
|
||||
// Since it's a byte array there's no special alignment requirement here.
|
||||
//
|
||||
// Since we re-define `buf` we ensure it's not possible to access the buffer
|
||||
// via its original pointer for the lifetime of the slice.
|
||||
{
|
||||
let buf: &mut [u8] = std::slice::from_raw_parts_mut(buf.cast(), state.len());
|
||||
buf.copy_from_slice(state);
|
||||
}
|
||||
|
||||
(*app_ptr).savedState = buf;
|
||||
(*app_ptr).savedStateSize = state.len() as _;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StateLoader<'a> {
|
||||
app: &'a AndroidAppInner,
|
||||
}
|
||||
impl<'a> StateLoader<'a> {
|
||||
pub fn load(&self) -> Option<Vec<u8>> {
|
||||
unsafe {
|
||||
let app_ptr = self.app.native_app.as_ptr();
|
||||
if !(*app_ptr).savedState.is_null() && (*app_ptr).savedStateSize > 0 {
|
||||
let buf: &mut [u8] = std::slice::from_raw_parts_mut(
|
||||
(*app_ptr).savedState.cast(),
|
||||
(*app_ptr).savedStateSize as usize,
|
||||
);
|
||||
let state = buf.to_vec();
|
||||
Some(state)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AndroidAppWaker {
|
||||
// The looper pointer is owned by the android_app and effectively
|
||||
// has a 'static lifetime, and the ALooper_wake C API is thread
|
||||
// safe, so this can be cloned safely and is send + sync safe
|
||||
looper: NonNull<ALooper>,
|
||||
}
|
||||
unsafe impl Send for AndroidAppWaker {}
|
||||
unsafe impl Sync for AndroidAppWaker {}
|
||||
|
||||
impl AndroidAppWaker {
|
||||
pub fn wake(&self) {
|
||||
unsafe {
|
||||
ALooper_wake(self.looper.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AndroidApp {
|
||||
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>) -> Self {
|
||||
// Note: we don't use from_ptr since we don't own the android_app.config
|
||||
// and need to keep in mind that the Drop handler is going to call
|
||||
// AConfiguration_delete()
|
||||
let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config));
|
||||
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(AndroidAppInner {
|
||||
native_app: NativeAppGlue { ptr },
|
||||
config: ConfigurationRef::new(config),
|
||||
native_window: Default::default(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NativeAppGlue {
|
||||
ptr: NonNull<ffi::android_app>,
|
||||
}
|
||||
impl Deref for NativeAppGlue {
|
||||
type Target = NonNull<ffi::android_app>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.ptr
|
||||
}
|
||||
}
|
||||
unsafe impl Send for NativeAppGlue {}
|
||||
unsafe impl Sync for NativeAppGlue {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AndroidAppInner {
|
||||
native_app: NativeAppGlue,
|
||||
config: ConfigurationRef,
|
||||
native_window: RwLock<Option<NativeWindow>>,
|
||||
}
|
||||
|
||||
impl AndroidAppInner {
|
||||
pub fn vm_as_ptr(&self) -> *mut c_void {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
unsafe { (*(*app_ptr).activity).vm as _ }
|
||||
}
|
||||
|
||||
pub fn activity_as_ptr(&self) -> *mut c_void {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
unsafe { (*(*app_ptr).activity).javaGameActivity as _ }
|
||||
}
|
||||
|
||||
pub fn native_window(&self) -> Option<NativeWindow> {
|
||||
self.native_window.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&self, timeout: Option<Duration>, mut callback: F)
|
||||
where
|
||||
F: FnMut(PollEvent),
|
||||
{
|
||||
trace!("poll_events");
|
||||
|
||||
unsafe {
|
||||
let native_app = &self.native_app;
|
||||
|
||||
let mut fd: i32 = 0;
|
||||
let mut events: i32 = 0;
|
||||
let mut source: *mut core::ffi::c_void = ptr::null_mut();
|
||||
|
||||
let timeout_milliseconds = if let Some(timeout) = timeout {
|
||||
timeout.as_millis() as i32
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
trace!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}");
|
||||
let id = ALooper_pollAll(
|
||||
timeout_milliseconds,
|
||||
&mut fd,
|
||||
&mut events,
|
||||
&mut source as *mut *mut core::ffi::c_void,
|
||||
);
|
||||
match id {
|
||||
ffi::ALOOPER_POLL_WAKE => {
|
||||
trace!("ALooper_pollAll returned POLL_WAKE");
|
||||
|
||||
if ffi::android_app_input_available_wake_up(native_app.as_ptr()) {
|
||||
log::debug!("Notifying Input Available");
|
||||
callback(PollEvent::Main(MainEvent::InputAvailable));
|
||||
}
|
||||
|
||||
callback(PollEvent::Wake);
|
||||
}
|
||||
ffi::ALOOPER_POLL_CALLBACK => {
|
||||
// ALooper_pollAll is documented to handle all callback sources internally so it should
|
||||
// never return a _CALLBACK source id...
|
||||
error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)");
|
||||
}
|
||||
ffi::ALOOPER_POLL_TIMEOUT => {
|
||||
trace!("ALooper_pollAll returned POLL_TIMEOUT");
|
||||
callback(PollEvent::Timeout);
|
||||
}
|
||||
ffi::ALOOPER_POLL_ERROR => {
|
||||
// If we have an IO error with our pipe to the main Java thread that's surely
|
||||
// not something we can recover from
|
||||
panic!("ALooper_pollAll returned POLL_ERROR");
|
||||
}
|
||||
id if id >= 0 => {
|
||||
match id as u32 {
|
||||
ffi::NativeAppGlueLooperId_LOOPER_ID_MAIN => {
|
||||
trace!("ALooper_pollAll returned ID_MAIN");
|
||||
let source: *mut ffi::android_poll_source = source.cast();
|
||||
if !source.is_null() {
|
||||
let cmd_i = ffi::android_app_read_cmd(native_app.as_ptr());
|
||||
|
||||
let cmd = match cmd_i as u32 {
|
||||
//NativeAppGlueAppCmd_UNUSED_APP_CMD_INPUT_CHANGED => AndroidAppMainEvent::InputChanged,
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_INIT_WINDOW => {
|
||||
MainEvent::InitWindow {}
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_TERM_WINDOW => {
|
||||
MainEvent::TerminateWindow {}
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_RESIZED => {
|
||||
MainEvent::WindowResized {}
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_REDRAW_NEEDED => {
|
||||
MainEvent::RedrawNeeded {}
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_CONTENT_RECT_CHANGED => {
|
||||
MainEvent::ContentRectChanged {}
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_GAINED_FOCUS => {
|
||||
MainEvent::GainedFocus
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_LOST_FOCUS => {
|
||||
MainEvent::LostFocus
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_CONFIG_CHANGED => {
|
||||
MainEvent::ConfigChanged {}
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_LOW_MEMORY => {
|
||||
MainEvent::LowMemory
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_START => MainEvent::Start,
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_RESUME => MainEvent::Resume {
|
||||
loader: StateLoader { app: self },
|
||||
},
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_SAVE_STATE => {
|
||||
MainEvent::SaveState {
|
||||
saver: StateSaver { app: self },
|
||||
}
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_PAUSE => MainEvent::Pause,
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_STOP => MainEvent::Stop,
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_DESTROY => MainEvent::Destroy,
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_INSETS_CHANGED => {
|
||||
MainEvent::InsetsChanged {}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
trace!("Read ID_MAIN command {cmd_i} = {cmd:?}");
|
||||
|
||||
trace!("Calling android_app_pre_exec_cmd({cmd_i})");
|
||||
ffi::android_app_pre_exec_cmd(native_app.as_ptr(), cmd_i);
|
||||
match cmd {
|
||||
MainEvent::ConfigChanged { .. } => {
|
||||
self.config.replace(Configuration::clone_from_ptr(
|
||||
NonNull::new_unchecked((*native_app.as_ptr()).config),
|
||||
));
|
||||
}
|
||||
MainEvent::InitWindow { .. } => {
|
||||
let win_ptr = (*native_app.as_ptr()).window;
|
||||
// It's important that we use ::clone_from_ptr() here
|
||||
// because NativeWindow has a Drop implementation that
|
||||
// will unconditionally _release() the native window
|
||||
*self.native_window.write().unwrap() =
|
||||
Some(NativeWindow::clone_from_ptr(
|
||||
NonNull::new(win_ptr).unwrap(),
|
||||
));
|
||||
}
|
||||
MainEvent::TerminateWindow { .. } => {
|
||||
*self.native_window.write().unwrap() = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
trace!("Invoking callback for ID_MAIN command = {:?}", cmd);
|
||||
callback(PollEvent::Main(cmd));
|
||||
|
||||
trace!("Calling android_app_post_exec_cmd({cmd_i})");
|
||||
ffi::android_app_post_exec_cmd(native_app.as_ptr(), cmd_i);
|
||||
} else {
|
||||
panic!("ALooper_pollAll returned ID_MAIN event with NULL android_poll_source!");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Ignoring spurious ALooper event source: id = {id}, fd = {fd}, events = {events:?}, data = {source:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Spurious ALooper_pollAll return value {id} (ignored)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_window_flags(
|
||||
&self,
|
||||
add_flags: WindowManagerFlags,
|
||||
remove_flags: WindowManagerFlags,
|
||||
) {
|
||||
unsafe {
|
||||
let activity = (*self.native_app.as_ptr()).activity;
|
||||
ffi::GameActivity_setWindowFlags(activity, add_flags.bits(), remove_flags.bits())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move into a trait
|
||||
pub fn show_soft_input(&self, show_implicit: bool) {
|
||||
unsafe {
|
||||
let activity = (*self.native_app.as_ptr()).activity;
|
||||
let flags = if show_implicit {
|
||||
ffi::ShowImeFlags_SHOW_IMPLICIT
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ffi::GameActivity_showSoftInput(activity, flags);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move into a trait
|
||||
pub fn hide_soft_input(&self, hide_implicit_only: bool) {
|
||||
unsafe {
|
||||
let activity = (*self.native_app.as_ptr()).activity;
|
||||
let flags = if hide_implicit_only {
|
||||
ffi::HideImeFlags_HIDE_IMPLICIT_ONLY
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ffi::GameActivity_hideSoftInput(activity, flags);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_motion_axis(&mut self, axis: Axis) {
|
||||
unsafe { ffi::GameActivityPointerAxes_enableAxis(axis as i32) }
|
||||
}
|
||||
|
||||
pub fn disable_motion_axis(&mut self, axis: Axis) {
|
||||
unsafe { ffi::GameActivityPointerAxes_disableAxis(axis as i32) }
|
||||
}
|
||||
|
||||
pub fn create_waker(&self) -> AndroidAppWaker {
|
||||
unsafe {
|
||||
// From the application's pov we assume the app_ptr and looper pointer
|
||||
// have static lifetimes and we can safely assume they are never NULL.
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
AndroidAppWaker {
|
||||
looper: NonNull::new_unchecked((*app_ptr).looper),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(&self) -> ConfigurationRef {
|
||||
self.config.clone()
|
||||
}
|
||||
|
||||
pub fn content_rect(&self) -> Rect {
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
Rect {
|
||||
left: (*app_ptr).contentRect.left,
|
||||
right: (*app_ptr).contentRect.right,
|
||||
top: (*app_ptr).contentRect.top,
|
||||
bottom: (*app_ptr).contentRect.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn asset_manager(&self) -> AssetManager {
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
let am_ptr = NonNull::new_unchecked((*(*app_ptr).activity).assetManager);
|
||||
AssetManager::from_ptr(am_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input_events<F>(&self, mut callback: F)
|
||||
where
|
||||
F: FnMut(&InputEvent) -> InputStatus,
|
||||
{
|
||||
let buf = unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
let input_buffer = ffi::android_app_swap_input_buffers(app_ptr);
|
||||
if input_buffer.is_null() {
|
||||
return;
|
||||
}
|
||||
InputBuffer::from_ptr(NonNull::new_unchecked(input_buffer))
|
||||
};
|
||||
|
||||
let mut keys_iter = KeyEventsLendingIterator::new(&buf);
|
||||
while let Some(key_event) = keys_iter.next() {
|
||||
callback(&InputEvent::KeyEvent(key_event));
|
||||
}
|
||||
let mut motion_iter = MotionEventsLendingIterator::new(&buf);
|
||||
while let Some(motion_event) = motion_iter.next() {
|
||||
callback(&InputEvent::MotionEvent(motion_event));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
util::try_get_path_from_ptr((*(*app_ptr).activity).internalDataPath)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
util::try_get_path_from_ptr((*(*app_ptr).activity).externalDataPath)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn obb_path(&self) -> Option<std::path::PathBuf> {
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
util::try_get_path_from_ptr((*(*app_ptr).activity).obbPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MotionEventsLendingIterator<'a> {
|
||||
pos: usize,
|
||||
count: usize,
|
||||
buffer: &'a InputBuffer<'a>,
|
||||
}
|
||||
|
||||
// A kind of lending iterator but since our MSRV is 1.60 we can't handle this
|
||||
// via a generic trait. The iteration of motion events is entirely private
|
||||
// though so this is ok for now.
|
||||
impl<'a> MotionEventsLendingIterator<'a> {
|
||||
fn new(buffer: &'a InputBuffer<'a>) -> Self {
|
||||
Self {
|
||||
pos: 0,
|
||||
count: buffer.motion_events_count(),
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
fn next(&mut self) -> Option<MotionEvent<'a>> {
|
||||
if self.pos < self.count {
|
||||
let ga_event = unsafe { &(*self.buffer.ptr.as_ptr()).motionEvents[self.pos] };
|
||||
let event = MotionEvent::new(ga_event);
|
||||
self.pos += 1;
|
||||
Some(event)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyEventsLendingIterator<'a> {
|
||||
pos: usize,
|
||||
count: usize,
|
||||
buffer: &'a InputBuffer<'a>,
|
||||
}
|
||||
|
||||
// A kind of lending iterator but since our MSRV is 1.60 we can't handle this
|
||||
// via a generic trait. The iteration of key events is entirely private
|
||||
// though so this is ok for now.
|
||||
impl<'a> KeyEventsLendingIterator<'a> {
|
||||
fn new(buffer: &'a InputBuffer<'a>) -> Self {
|
||||
Self {
|
||||
pos: 0,
|
||||
count: buffer.key_events_count(),
|
||||
buffer,
|
||||
}
|
||||
}
|
||||
fn next(&mut self) -> Option<KeyEvent<'a>> {
|
||||
if self.pos < self.count {
|
||||
let ga_event = unsafe { &(*self.buffer.ptr.as_ptr()).keyEvents[self.pos] };
|
||||
let event = KeyEvent::new(ga_event);
|
||||
self.pos += 1;
|
||||
Some(event)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InputBuffer<'a> {
|
||||
ptr: NonNull<ffi::android_input_buffer>,
|
||||
_lifetime: PhantomData<&'a ffi::android_input_buffer>,
|
||||
}
|
||||
|
||||
impl<'a> InputBuffer<'a> {
|
||||
pub(crate) fn from_ptr(ptr: NonNull<ffi::android_input_buffer>) -> InputBuffer<'a> {
|
||||
Self {
|
||||
ptr,
|
||||
_lifetime: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn motion_events_count(&self) -> usize {
|
||||
unsafe { (*self.ptr.as_ptr()).motionEventsCount as usize }
|
||||
}
|
||||
|
||||
pub fn key_events_count(&self) -> usize {
|
||||
unsafe { (*self.ptr.as_ptr()).keyEventsCount as usize }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for InputBuffer<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ffi::android_app_clear_motion_events(self.ptr.as_ptr());
|
||||
ffi::android_app_clear_key_events(self.ptr.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rust doesn't give us a clean way to directly export symbols from C/C++
|
||||
// so we rename the C/C++ symbols and re-export these JNI entrypoints from
|
||||
// Rust...
|
||||
//
|
||||
// https://github.com/rust-lang/rfcs/issues/2771
|
||||
extern "C" {
|
||||
pub fn Java_com_google_androidgamesdk_GameActivity_loadNativeCode_C(
|
||||
env: *mut JNIEnv,
|
||||
javaGameActivity: jobject,
|
||||
path: jstring,
|
||||
funcName: jstring,
|
||||
internalDataDir: jstring,
|
||||
obbDir: jstring,
|
||||
externalDataDir: jstring,
|
||||
jAssetMgr: jobject,
|
||||
savedState: jbyteArray,
|
||||
) -> jlong;
|
||||
|
||||
pub fn GameActivity_onCreate_C(
|
||||
activity: *mut ffi::GameActivity,
|
||||
savedState: *mut ::std::os::raw::c_void,
|
||||
savedStateSize: libc::size_t,
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn Java_com_google_androidgamesdk_GameActivity_loadNativeCode(
|
||||
env: *mut JNIEnv,
|
||||
java_game_activity: jobject,
|
||||
path: jstring,
|
||||
func_name: jstring,
|
||||
internal_data_dir: jstring,
|
||||
obb_dir: jstring,
|
||||
external_data_dir: jstring,
|
||||
jasset_mgr: jobject,
|
||||
saved_state: jbyteArray,
|
||||
) -> jni_sys::jlong {
|
||||
Java_com_google_androidgamesdk_GameActivity_loadNativeCode_C(
|
||||
env,
|
||||
java_game_activity,
|
||||
path,
|
||||
func_name,
|
||||
internal_data_dir,
|
||||
obb_dir,
|
||||
external_data_dir,
|
||||
jasset_mgr,
|
||||
saved_state,
|
||||
)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn GameActivity_onCreate(
|
||||
activity: *mut ffi::GameActivity,
|
||||
saved_state: *mut ::std::os::raw::c_void,
|
||||
saved_state_size: libc::size_t,
|
||||
) {
|
||||
GameActivity_onCreate_C(activity, saved_state, saved_state_size);
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
pub fn android_main(app: AndroidApp);
|
||||
}
|
||||
|
||||
// This is a spring board between android_native_app_glue and the user's
|
||||
// `app_main` function. This is run on a dedicated thread spawned
|
||||
// by android_native_app_glue.
|
||||
#[no_mangle]
|
||||
#[allow(unused_unsafe)] // Otherwise rust 1.64 moans about using unsafe{} in unsafe functions
|
||||
pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) {
|
||||
abort_on_panic(|| {
|
||||
// Maybe make this stdout/stderr redirection an optional / opt-in feature?...
|
||||
let mut logpipe: [RawFd; 2] = Default::default();
|
||||
libc::pipe(logpipe.as_mut_ptr());
|
||||
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
|
||||
libc::dup2(logpipe[1], libc::STDERR_FILENO);
|
||||
thread::spawn(move || {
|
||||
let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap();
|
||||
let file = File::from_raw_fd(logpipe[0]);
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
buffer.clear();
|
||||
if let Ok(len) = reader.read_line(&mut buffer) {
|
||||
if len == 0 {
|
||||
break;
|
||||
} else if let Ok(msg) = CString::new(buffer.clone()) {
|
||||
android_log(Level::Info, tag, &msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let jvm = unsafe {
|
||||
let jvm = (*(*native_app).activity).vm;
|
||||
let activity: jobject = (*(*native_app).activity).javaGameActivity;
|
||||
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
|
||||
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached
|
||||
// to the thread yet. Attach before calling the applications main function
|
||||
// so they can safely make JNI calls
|
||||
let mut jenv_out: *mut core::ffi::c_void = std::ptr::null_mut();
|
||||
if let Some(attach_current_thread) = (*(*jvm)).AttachCurrentThread {
|
||||
attach_current_thread(jvm, &mut jenv_out, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
jvm
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let app = AndroidApp::from_ptr(NonNull::new(native_app).unwrap());
|
||||
|
||||
// We want to specifically catch any panic from the application's android_main
|
||||
// so we can finish + destroy the Activity gracefully via the JVM
|
||||
catch_unwind(|| {
|
||||
// XXX: If we were in control of the Java Activity subclass then
|
||||
// we could potentially run the android_main function via a Java native method
|
||||
// springboard (e.g. call an Activity subclass method that calls a jni native
|
||||
// method that then just calls android_main()) that would make sure there was
|
||||
// a Java frame at the base of our call stack which would then be recognised
|
||||
// when calling FindClass to lookup a suitable classLoader, instead of
|
||||
// defaulting to the system loader. Without this then it's difficult for native
|
||||
// code to look up non-standard Java classes.
|
||||
android_main(app);
|
||||
})
|
||||
.unwrap_or_else(|panic| log_panic(panic));
|
||||
|
||||
// Let JVM know that our Activity can be destroyed before detaching from the JVM
|
||||
//
|
||||
// "Note that this method can be called from any thread; it will send a message
|
||||
// to the main thread of the process where the Java finish call will take place"
|
||||
ffi::GameActivity_finish((*native_app).activity);
|
||||
|
||||
if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread {
|
||||
detach_current_thread(jvm);
|
||||
}
|
||||
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
})
|
||||
}
|
||||
83
third-party/vendor/android-activity/src/input.rs
vendored
Normal file
83
third-party/vendor/android-activity/src/input.rs
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use bitflags::bitflags;
|
||||
use num_enum::{IntoPrimitive, TryFromPrimitive};
|
||||
|
||||
pub use crate::activity_impl::input::*;
|
||||
|
||||
/// An enum representing the source of an [`MotionEvent`] or [`KeyEvent`]
|
||||
///
|
||||
/// See [the InputDevice docs](https://developer.android.com/reference/android/view/InputDevice#SOURCE_ANY)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
|
||||
#[repr(u32)]
|
||||
pub enum Source {
|
||||
BluetoothStylus = 0x0000c002,
|
||||
Dpad = 0x00000201,
|
||||
/// Either a gamepad or a joystick
|
||||
Gamepad = 0x00000401,
|
||||
Hdmi = 0x02000001,
|
||||
/// Either a gamepad or a joystick
|
||||
Joystick = 0x01000010,
|
||||
/// Pretty much any device with buttons. Query the keyboard type to determine
|
||||
/// if it has alphabetic keys and can be used for text entry.
|
||||
Keyboard = 0x00000101,
|
||||
/// A pointing device, such as a mouse or trackpad
|
||||
Mouse = 0x00002002,
|
||||
/// A pointing device, such as a mouse or trackpad whose relative motions should be treated as navigation events
|
||||
MouseRelative = 0x00020004,
|
||||
/// An input device akin to a scroll wheel
|
||||
RotaryEncoder = 0x00400000,
|
||||
Sensor = 0x04000000,
|
||||
Stylus = 0x00004002,
|
||||
Touchpad = 0x00100008,
|
||||
Touchscreen = 0x00001002,
|
||||
TouchNavigation = 0x00200000,
|
||||
Trackball = 0x00010004,
|
||||
|
||||
Unknown = 0,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct SourceFlags: u32 {
|
||||
const CLASS_MASK = 0x000000ff;
|
||||
|
||||
const BUTTON = 0x00000001;
|
||||
const POINTER = 0x00000002;
|
||||
const TRACKBALL = 0x00000004;
|
||||
const POSITION = 0x00000008;
|
||||
const JOYSTICK = 0x00000010;
|
||||
const NONE = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum representing the class of a [`MotionEvent`] or [`KeyEvent`] source
|
||||
///
|
||||
/// See [the InputDevice docs](https://developer.android.com/reference/android/view/InputDevice#SOURCE_CLASS_MASK)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Class {
|
||||
None,
|
||||
Button,
|
||||
Pointer,
|
||||
Trackball,
|
||||
Position,
|
||||
Joystick,
|
||||
}
|
||||
|
||||
impl From<u32> for Class {
|
||||
fn from(source: u32) -> Self {
|
||||
let class = SourceFlags::from_bits_truncate(source);
|
||||
match class {
|
||||
SourceFlags::BUTTON => Class::Button,
|
||||
SourceFlags::POINTER => Class::Pointer,
|
||||
SourceFlags::TRACKBALL => Class::Trackball,
|
||||
SourceFlags::POSITION => Class::Position,
|
||||
SourceFlags::JOYSTICK => Class::Joystick,
|
||||
_ => Class::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Source> for Class {
|
||||
fn from(source: Source) -> Self {
|
||||
let source: u32 = source.into();
|
||||
source.into()
|
||||
}
|
||||
}
|
||||
674
third-party/vendor/android-activity/src/lib.rs
vendored
Normal file
674
third-party/vendor/android-activity/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
//! A glue layer for building standalone, Rust applications on Android
|
||||
//!
|
||||
//! This crate provides a "glue" layer for building native Rust
|
||||
//! applications on Android, supporting multiple [`Activity`] base classes.
|
||||
//! It's comparable to [`android_native_app_glue.c`][ndk_concepts]
|
||||
//! for C/C++ applications.
|
||||
//!
|
||||
//! Currently the crate supports two `Activity` base classes:
|
||||
//! 1. [`NativeActivity`] - Built in to Android, this doesn't require compiling any Java or Kotlin code.
|
||||
//! 2. [`GameActivity`] - From the Android Game Development Kit, it has more
|
||||
//! sophisticated input handling support than `NativeActivity`. `GameActivity`
|
||||
//! is also based on the `AndroidAppCompat` class which can help with supporting
|
||||
//! a wider range of devices.
|
||||
//!
|
||||
//! Standalone applications based on this crate need to be built as `cdylib` libraries, like:
|
||||
//! ```
|
||||
//! [lib]
|
||||
//! crate_type=["cdylib"]
|
||||
//! ```
|
||||
//!
|
||||
//! and implement a `#[no_mangle]` `android_main` entry point like this:
|
||||
//! ```rust
|
||||
//! #[no_mangle]
|
||||
//! fn android_main(app: AndroidApp) {
|
||||
//!
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Once your application's `Activity` class has loaded and it calls `onCreate` then
|
||||
//! `android-activity` will spawn a dedicated thread to run your `android_main` function,
|
||||
//! separate from the Java thread that created the corresponding `Activity`.
|
||||
//!
|
||||
//! [`AndroidApp`] provides an interface to query state for the application as
|
||||
//! well as monitor events, such as lifecycle and input events, that are
|
||||
//! marshalled between the Java thread that owns the `Activity` and the native
|
||||
//! thread that runs the `android_main()` code.
|
||||
//!
|
||||
//! # Main Thread Initialization
|
||||
//!
|
||||
//! Before `android_main()` is called, the following application state
|
||||
//! is also initialized:
|
||||
//!
|
||||
//! 1. An I/O thread is spawned that will handle redirecting standard input
|
||||
//! and output to the Android log, visible via `logcat`.
|
||||
//! 2. A `JavaVM` and `Activity` instance will be associated with the [`ndk_context`] crate
|
||||
//! so that other, independent, Rust crates are able to find a JavaVM
|
||||
//! for making JNI calls.
|
||||
//! 3. The `JavaVM` will be attached to the native thread
|
||||
//! 4. A [Looper] is attached to the Rust native thread.
|
||||
//!
|
||||
//!
|
||||
//! These are undone after `android_main()` returns
|
||||
//!
|
||||
//! [`Activity`]: https://developer.android.com/reference/android/app/Activity
|
||||
//! [`NativeActivity`]: https://developer.android.com/reference/android/app/NativeActivity
|
||||
//! [ndk_concepts]: https://developer.android.com/ndk/guides/concepts#naa
|
||||
//! [`GameActivity`]: https://developer.android.com/games/agdk/integrate-game-activity
|
||||
//! [Looper]: https://developer.android.com/reference/android/os/Looper
|
||||
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use libc::c_void;
|
||||
use ndk::asset::AssetManager;
|
||||
use ndk::native_window::NativeWindow;
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
compile_error!("android-activity only supports compiling for Android");
|
||||
|
||||
#[cfg(all(feature = "game-activity", feature = "native-activity"))]
|
||||
compile_error!(
|
||||
r#"The "game-activity" and "native-activity" features cannot be enabled at the same time"#
|
||||
);
|
||||
#[cfg(all(
|
||||
not(any(feature = "game-activity", feature = "native-activity")),
|
||||
not(doc)
|
||||
))]
|
||||
compile_error!(
|
||||
r#"Either "game-activity" or "native-activity" must be enabled as features
|
||||
|
||||
If you have set one of these features then this error indicates that Cargo is trying to
|
||||
link together multiple implementations of android-activity (with incompatible versions)
|
||||
which is not supported.
|
||||
|
||||
Since android-activity is responsible for the `android_main` entrypoint of your application
|
||||
then there can only be a single implementation of android-activity linked with your application.
|
||||
|
||||
You can use `cargo tree` (e.g. via `cargo ndk -t arm64-v8a tree`) to identify why multiple
|
||||
versions have been resolved.
|
||||
|
||||
You may need to add a `[patch]` into your Cargo.toml to ensure a specific version of
|
||||
android-activity is used across all of your application's crates."#
|
||||
);
|
||||
|
||||
#[cfg(any(feature = "native-activity", doc))]
|
||||
mod native_activity;
|
||||
#[cfg(any(feature = "native-activity", doc))]
|
||||
use native_activity as activity_impl;
|
||||
|
||||
#[cfg(feature = "game-activity")]
|
||||
mod game_activity;
|
||||
#[cfg(feature = "game-activity")]
|
||||
use game_activity as activity_impl;
|
||||
|
||||
pub mod input;
|
||||
|
||||
mod config;
|
||||
pub use config::ConfigurationRef;
|
||||
|
||||
mod util;
|
||||
|
||||
/// A rectangle with integer edge coordinates. Used to represent window insets, for example.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Rect {
|
||||
pub left: i32,
|
||||
pub top: i32,
|
||||
pub right: i32,
|
||||
pub bottom: i32,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// An empty `Rect` with all components set to zero.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rect> for ndk_sys::ARect {
|
||||
fn from(rect: Rect) -> Self {
|
||||
Self {
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ndk_sys::ARect> for Rect {
|
||||
fn from(arect: ndk_sys::ARect) -> Self {
|
||||
Self {
|
||||
left: arect.left,
|
||||
right: arect.right,
|
||||
top: arect.top,
|
||||
bottom: arect.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use activity_impl::StateLoader;
|
||||
pub use activity_impl::StateSaver;
|
||||
|
||||
/// An application event delivered during [`AndroidApp::poll_events`]
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub enum MainEvent<'a> {
|
||||
/// New input events are available via [`AndroidApp::input_events()`]
|
||||
///
|
||||
/// _Note: Even if more input is received this event will not be resent
|
||||
/// until [`AndroidApp::input_events()`] has been called, which enables
|
||||
/// applications to batch up input processing without there being lots of
|
||||
/// redundant event loop wake ups._
|
||||
///
|
||||
/// [`AndroidApp::input_events()`]: AndroidApp::input_events
|
||||
InputAvailable,
|
||||
|
||||
/// Command from main thread: a new [`NativeWindow`] is ready for use. Upon
|
||||
/// receiving this command, [`AndroidApp::native_window()`] will return the new window
|
||||
#[non_exhaustive]
|
||||
InitWindow {},
|
||||
|
||||
/// Command from main thread: the existing [`NativeWindow`] needs to be
|
||||
/// terminated. Upon receiving this command, [`AndroidApp::native_window()`] still
|
||||
/// returns the existing window; after returning from the [`AndroidApp::poll_events()`]
|
||||
/// callback then [`AndroidApp::native_window()`] will return `None`.
|
||||
#[non_exhaustive]
|
||||
TerminateWindow {},
|
||||
|
||||
// TODO: include the prev and new size in the event
|
||||
/// Command from main thread: the current [`NativeWindow`] has been resized.
|
||||
/// Please redraw with its new size.
|
||||
#[non_exhaustive]
|
||||
WindowResized {},
|
||||
|
||||
/// Command from main thread: the current [`NativeWindow`] needs to be redrawn.
|
||||
/// You should redraw the window before the [`AndroidApp::poll_events()`]
|
||||
/// callback returns in order to avoid transient drawing glitches.
|
||||
#[non_exhaustive]
|
||||
RedrawNeeded {},
|
||||
|
||||
/// Command from main thread: the content area of the window has changed,
|
||||
/// such as from the soft input window being shown or hidden. You can
|
||||
/// get the new content rect by calling [`AndroidApp::content_rect()`]
|
||||
#[non_exhaustive]
|
||||
ContentRectChanged {},
|
||||
|
||||
/// Command from main thread: the app's activity window has gained
|
||||
/// input focus.
|
||||
GainedFocus,
|
||||
|
||||
/// Command from main thread: the app's activity window has lost
|
||||
/// input focus.
|
||||
LostFocus,
|
||||
|
||||
/// Command from main thread: the current device configuration has changed.
|
||||
/// You can get a copy of the latest [`ndk::configuration::Configuration`] by calling
|
||||
/// [`AndroidApp::config()`]
|
||||
#[non_exhaustive]
|
||||
ConfigChanged {},
|
||||
|
||||
/// Command from main thread: the system is running low on memory.
|
||||
/// Try to reduce your memory use.
|
||||
LowMemory,
|
||||
|
||||
/// Command from main thread: the app's activity has been started.
|
||||
Start,
|
||||
|
||||
/// Command from main thread: the app's activity has been resumed.
|
||||
#[non_exhaustive]
|
||||
Resume { loader: StateLoader<'a> },
|
||||
|
||||
/// Command from main thread: the app should generate a new saved state
|
||||
/// for itself, to restore from later if needed. If you have saved state,
|
||||
/// allocate it with malloc and place it in android_app.savedState with
|
||||
/// the size in android_app.savedStateSize. The will be freed for you
|
||||
/// later.
|
||||
#[non_exhaustive]
|
||||
SaveState { saver: StateSaver<'a> },
|
||||
|
||||
/// Command from main thread: the app's activity has been paused.
|
||||
Pause,
|
||||
|
||||
/// Command from main thread: the app's activity has been stopped.
|
||||
Stop,
|
||||
|
||||
/// Command from main thread: the app's activity is being destroyed,
|
||||
/// and waiting for the app thread to clean up and exit before proceeding.
|
||||
Destroy,
|
||||
|
||||
/// Command from main thread: the app's insets have changed.
|
||||
#[non_exhaustive]
|
||||
InsetsChanged {},
|
||||
}
|
||||
|
||||
/// An event delivered during [`AndroidApp::poll_events`]
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum PollEvent<'a> {
|
||||
Wake,
|
||||
Timeout,
|
||||
Main(MainEvent<'a>),
|
||||
}
|
||||
|
||||
/// Indicates whether an application has handled or ignored an event
|
||||
///
|
||||
/// If an event is not handled by an application then some default handling may happen.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InputStatus {
|
||||
Handled,
|
||||
Unhandled,
|
||||
}
|
||||
|
||||
use activity_impl::AndroidAppInner;
|
||||
pub use activity_impl::AndroidAppWaker;
|
||||
|
||||
bitflags! {
|
||||
/// Flags for [`AndroidApp::set_window_flags`]
|
||||
/// as per the [android.view.WindowManager.LayoutParams Java API](https://developer.android.com/reference/android/view/WindowManager.LayoutParams)
|
||||
pub struct WindowManagerFlags: u32 {
|
||||
/// As long as this window is visible to the user, allow the lock
|
||||
/// screen to activate while the screen is on. This can be used
|
||||
/// independently, or in combination with
|
||||
/// [`Self::KEEP_SCREEN_ON`] and/or [`Self::SHOW_WHEN_LOCKED`]
|
||||
const ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
|
||||
|
||||
/// Everything behind this window will be dimmed. */
|
||||
const DIM_BEHIND = 0x00000002;
|
||||
|
||||
/// Blur everything behind this window.
|
||||
#[deprecated = "Blurring is no longer supported"]
|
||||
const BLUR_BEHIND = 0x00000004;
|
||||
|
||||
/// This window won't ever get key input focus, so the
|
||||
/// user can not send key or other button events to it. Those will
|
||||
/// instead go to whatever focusable window is behind it. This flag
|
||||
/// will also enable [`Self::NOT_TOUCH_MODAL`] whether or not
|
||||
/// that is explicitly set.
|
||||
///
|
||||
/// Setting this flag also implies that the window will not need to
|
||||
/// interact with
|
||||
/// a soft input method, so it will be Z-ordered and positioned
|
||||
/// independently of any active input method (typically this means it
|
||||
/// gets Z-ordered on top of the input method, so it can use the full
|
||||
/// screen for its content and cover the input method if needed. You
|
||||
/// can use [`Self::ALT_FOCUSABLE_IM`] to modify this
|
||||
/// behavior.
|
||||
const NOT_FOCUSABLE = 0x00000008;
|
||||
|
||||
/// This window can never receive touch events.
|
||||
const NOT_TOUCHABLE = 0x00000010;
|
||||
|
||||
/// Even when this window is focusable (if
|
||||
/// [`Self::NOT_FOCUSABLE`] is not set), allow any pointer
|
||||
/// events outside of the window to be sent to the windows behind it.
|
||||
/// Otherwise it will consume all pointer events itself, regardless of
|
||||
/// whether they are inside of the window.
|
||||
const NOT_TOUCH_MODAL = 0x00000020;
|
||||
|
||||
/// When set, if the device is asleep when the touch
|
||||
/// screen is pressed, you will receive this first touch event. Usually
|
||||
/// the first touch event is consumed by the system since the user can
|
||||
/// not see what they are pressing on.
|
||||
#[deprecated]
|
||||
const TOUCHABLE_WHEN_WAKING = 0x00000040;
|
||||
|
||||
/// As long as this window is visible to the user, keep
|
||||
/// the device's screen turned on and bright.
|
||||
const KEEP_SCREEN_ON = 0x00000080;
|
||||
|
||||
/// Place the window within the entire screen, ignoring
|
||||
/// decorations around the border (such as the status bar). The
|
||||
/// window must correctly position its contents to take the screen
|
||||
/// decoration into account.
|
||||
const LAYOUT_IN_SCREEN = 0x00000100;
|
||||
|
||||
/// Allows the window to extend outside of the screen.
|
||||
const LAYOUT_NO_LIMITS = 0x00000200;
|
||||
|
||||
/// Hide all screen decorations (such as the status
|
||||
/// bar) while this window is displayed. This allows the window to
|
||||
/// use the entire display space for itself -- the status bar will
|
||||
/// be hidden when an app window with this flag set is on the top
|
||||
/// layer. A fullscreen window will ignore a value of
|
||||
/// [`Self::SOFT_INPUT_ADJUST_RESIZE`] the window will stay
|
||||
/// fullscreen and will not resize.
|
||||
const FULLSCREEN = 0x00000400;
|
||||
|
||||
/// Override [`Self::FULLSCREEN`] and force the
|
||||
/// screen decorations (such as the status bar) to be shown.
|
||||
const FORCE_NOT_FULLSCREEN = 0x00000800;
|
||||
/// Turn on dithering when compositing this window to
|
||||
/// the screen.
|
||||
#[deprecated="This flag is no longer used"]
|
||||
const DITHER = 0x00001000;
|
||||
|
||||
/// Treat the content of the window as secure, preventing
|
||||
/// it from appearing in screenshots or from being viewed on non-secure
|
||||
/// displays.
|
||||
const SECURE = 0x00002000;
|
||||
|
||||
/// A special mode where the layout parameters are used
|
||||
/// to perform scaling of the surface when it is composited to the
|
||||
/// screen.
|
||||
const SCALED = 0x00004000;
|
||||
|
||||
/// Intended for windows that will often be used when the user is
|
||||
/// holding the screen against their face, it will aggressively
|
||||
/// filter the event stream to prevent unintended presses in this
|
||||
/// situation that may not be desired for a particular window, when
|
||||
/// such an event stream is detected, the application will receive
|
||||
/// a `AMOTION_EVENT_ACTION_CANCEL` to indicate this so
|
||||
/// applications can handle this accordingly by taking no action on
|
||||
/// the event until the finger is released.
|
||||
const IGNORE_CHEEK_PRESSES = 0x00008000;
|
||||
|
||||
/// A special option only for use in combination with
|
||||
/// [`Self::LAYOUT_IN_SCREEN`]. When requesting layout in
|
||||
/// the screen your window may appear on top of or behind screen decorations
|
||||
/// such as the status bar. By also including this flag, the window
|
||||
/// manager will report the inset rectangle needed to ensure your
|
||||
/// content is not covered by screen decorations.
|
||||
const LAYOUT_INSET_DECOR = 0x00010000;
|
||||
|
||||
/// Invert the state of [`Self::NOT_FOCUSABLE`] with
|
||||
/// respect to how this window interacts with the current method.
|
||||
/// That is, if [`Self::NOT_FOCUSABLE`] is set and this flag is set,
|
||||
/// then the window will behave as if it needs to interact with the
|
||||
/// input method and thus be placed behind/away from it; if
|
||||
/// [`Self::NOT_FOCUSABLE`] is not set and this flag is set,
|
||||
/// then the window will behave as if it doesn't need to interact
|
||||
/// with the input method and can be placed to use more space and
|
||||
/// cover the input method.
|
||||
const ALT_FOCUSABLE_IM = 0x00020000;
|
||||
|
||||
/// If you have set [`Self::NOT_TOUCH_MODAL`], you
|
||||
/// can set this flag to receive a single special MotionEvent with
|
||||
/// the action
|
||||
/// `AMOTION_EVENT_ACTION_OUTSIDE` for
|
||||
/// touches that occur outside of your window. Note that you will not
|
||||
/// receive the full down/move/up gesture, only the location of the
|
||||
/// first down as an `AMOTION_EVENT_ACTION_OUTSIDE`.
|
||||
const WATCH_OUTSIDE_TOUCH = 0x00040000;
|
||||
|
||||
/// Special flag to let windows be shown when the screen
|
||||
/// is locked. This will let application windows take precedence over
|
||||
/// key guard or any other lock screens. Can be used with
|
||||
/// [`Self::KEEP_SCREEN_ON`] to turn screen on and display
|
||||
/// windows directly before showing the key guard window. Can be used with
|
||||
/// [`Self::DISMISS_KEYGUARD`] to automatically fully
|
||||
/// dismiss non-secure key guards. This flag only applies to the top-most
|
||||
/// full-screen window.
|
||||
const SHOW_WHEN_LOCKED = 0x00080000;
|
||||
|
||||
/// Ask that the system wallpaper be shown behind
|
||||
/// your window. The window surface must be translucent to be able
|
||||
/// to actually see the wallpaper behind it; this flag just ensures
|
||||
/// that the wallpaper surface will be there if this window actually
|
||||
/// has translucent regions.
|
||||
const SHOW_WALLPAPER = 0x00100000;
|
||||
|
||||
/// When set as a window is being added or made
|
||||
/// visible, once the window has been shown then the system will
|
||||
/// poke the power manager's user activity (as if the user had woken
|
||||
/// up the device) to turn the screen on.
|
||||
const TURN_SCREEN_ON = 0x00200000;
|
||||
|
||||
/// When set the window will cause the key guard to
|
||||
/// be dismissed, only if it is not a secure lock key guard. Because such
|
||||
/// a key guard is not needed for security, it will never re-appear if
|
||||
/// the user navigates to another window (in contrast to
|
||||
/// [`Self::SHOW_WHEN_LOCKED`], which will only temporarily
|
||||
/// hide both secure and non-secure key guards but ensure they reappear
|
||||
/// when the user moves to another UI that doesn't hide them).
|
||||
/// If the key guard is currently active and is secure (requires an
|
||||
/// unlock pattern) then the user will still need to confirm it before
|
||||
/// seeing this window, unless [`Self::SHOW_WHEN_LOCKED`] has
|
||||
/// also been set.
|
||||
const DISMISS_KEYGUARD = 0x00400000;
|
||||
}
|
||||
}
|
||||
|
||||
/// The top-level state and interface for a native Rust application
|
||||
///
|
||||
/// `AndroidApp` provides an interface to query state for the application as
|
||||
/// well as monitor events, such as lifecycle and input events, that are
|
||||
/// marshalled between the Java thread that owns the `Activity` and the native
|
||||
/// thread that runs the `android_main()` code.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AndroidApp {
|
||||
pub(crate) inner: Arc<RwLock<AndroidAppInner>>,
|
||||
}
|
||||
|
||||
impl PartialEq for AndroidApp {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
impl Eq for AndroidApp {}
|
||||
|
||||
impl Hash for AndroidApp {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
Arc::as_ptr(&self.inner).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl AndroidApp {
|
||||
/// Queries the current [`NativeWindow`] for the application.
|
||||
///
|
||||
/// This will only return `Some(window)` between
|
||||
/// [`MainEvent::InitWindow`] and [`MainEvent::TerminateWindow`]
|
||||
/// events.
|
||||
pub fn native_window(&self) -> Option<NativeWindow> {
|
||||
self.inner.read().unwrap().native_window()
|
||||
}
|
||||
|
||||
/// Returns a pointer to the Java Virtual Machine, for making JNI calls
|
||||
///
|
||||
/// This returns a pointer to the Java Virtual Machine which can be used
|
||||
/// with the [`jni`] crate (or similar crates) to make JNI calls that bridge
|
||||
/// between native Rust code and Java/Kotlin code running within the JVM.
|
||||
///
|
||||
/// If you use the [`jni`] crate you can wrap this as a [`JavaVM`] via:
|
||||
/// ```ignore
|
||||
/// # use jni::JavaVM;
|
||||
/// # let app: AndroidApp = todo!();
|
||||
/// let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr()) };
|
||||
/// ```
|
||||
///
|
||||
/// [`jni`]: https://crates.io/crates/jni
|
||||
/// [`JavaVM`]: https://docs.rs/jni/latest/jni/struct.JavaVM.html
|
||||
pub fn vm_as_ptr(&self) -> *mut c_void {
|
||||
self.inner.read().unwrap().vm_as_ptr()
|
||||
}
|
||||
|
||||
/// Returns a JNI object reference for this application's JVM `Activity` as a pointer
|
||||
///
|
||||
/// If you use the [`jni`] crate you can wrap this as an object reference via:
|
||||
/// ```ignore
|
||||
/// # use jni::objects::JObject;
|
||||
/// # let app: AndroidApp = todo!();
|
||||
/// let activity = unsafe { JObject::from_raw(app.activity_as_ptr()) };
|
||||
/// ```
|
||||
///
|
||||
/// # JNI Safety
|
||||
///
|
||||
/// Note that the object reference will be a JNI global reference, not a
|
||||
/// local reference and it should not be deleted. Don't wrap the reference
|
||||
/// in an [`AutoLocal`] which would try to explicitly delete the reference
|
||||
/// when dropped. Similarly, don't wrap the reference as a [`GlobalRef`]
|
||||
/// which would also try to explicitly delete the reference when dropped.
|
||||
///
|
||||
/// [`jni`]: https://crates.io/crates/jni
|
||||
/// [`AutoLocal`]: https://docs.rs/jni/latest/jni/objects/struct.AutoLocal.html
|
||||
/// [`GlobalRef`]: https://docs.rs/jni/latest/jni/objects/struct.GlobalRef.html
|
||||
pub fn activity_as_ptr(&self) -> *mut c_void {
|
||||
self.inner.read().unwrap().activity_as_ptr()
|
||||
}
|
||||
|
||||
/// Polls for any events associated with this [AndroidApp] and processes those events
|
||||
/// (such as lifecycle events) via the given `callback`.
|
||||
///
|
||||
/// It's important to use this API for polling, and not call [`ALooper_pollAll`] directly since
|
||||
/// some events require pre- and post-processing either side of the callback. For correct
|
||||
/// behavior events should be handled immediately, before returning from the callback and
|
||||
/// not simply queued for batch processing later. For example the existing [`NativeWindow`]
|
||||
/// is accessible during a [`MainEvent::TerminateWindow`] callback and will be
|
||||
/// set to `None` once the callback returns, and this is also synchronized with the Java
|
||||
/// main thread. The [`MainEvent::SaveState`] event is also synchronized with the
|
||||
/// Java main thread.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This must only be called from your `android_main()` thread and it may panic if called
|
||||
/// from another thread.
|
||||
///
|
||||
/// [`ALooper_pollAll`]: ndk::looper::ThreadLooper::poll_all
|
||||
pub fn poll_events<F>(&self, timeout: Option<Duration>, callback: F)
|
||||
where
|
||||
F: FnMut(PollEvent),
|
||||
{
|
||||
self.inner.read().unwrap().poll_events(timeout, callback);
|
||||
}
|
||||
|
||||
/// Creates a means to wake up the main loop while it is blocked waiting for
|
||||
/// events within [`AndroidApp::poll_events()`].
|
||||
pub fn create_waker(&self) -> activity_impl::AndroidAppWaker {
|
||||
self.inner.read().unwrap().create_waker()
|
||||
}
|
||||
|
||||
/// Returns a (cheaply clonable) reference to this application's [`ndk::configuration::Configuration`]
|
||||
pub fn config(&self) -> ConfigurationRef {
|
||||
self.inner.read().unwrap().config()
|
||||
}
|
||||
|
||||
/// Queries the current content rectangle of the window; this is the area where the
|
||||
/// window's content should be placed to be seen by the user.
|
||||
pub fn content_rect(&self) -> Rect {
|
||||
self.inner.read().unwrap().content_rect()
|
||||
}
|
||||
|
||||
/// Queries the Asset Manager instance for the application.
|
||||
///
|
||||
/// Use this to access binary assets bundled inside your application's .apk file.
|
||||
pub fn asset_manager(&self) -> AssetManager {
|
||||
self.inner.read().unwrap().asset_manager()
|
||||
}
|
||||
|
||||
/// Change the window flags of the given activity.
|
||||
///
|
||||
/// Note that some flags must be set before the window decoration is created,
|
||||
/// see
|
||||
/// `<https://developer.android.com/reference/android/view/Window#setFlags(int,%20int)>`.
|
||||
pub fn set_window_flags(
|
||||
&self,
|
||||
add_flags: WindowManagerFlags,
|
||||
remove_flags: WindowManagerFlags,
|
||||
) {
|
||||
self.inner
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_window_flags(add_flags, remove_flags);
|
||||
}
|
||||
|
||||
/// Enable additional input axis
|
||||
///
|
||||
/// To reduce overhead, by default only [`input::Axis::X`] and [`input::Axis::Y`] are enabled
|
||||
/// and other axis should be enabled explicitly.
|
||||
pub fn enable_motion_axis(&self, axis: input::Axis) {
|
||||
self.inner.write().unwrap().enable_motion_axis(axis);
|
||||
}
|
||||
|
||||
/// Disable input axis
|
||||
///
|
||||
/// To reduce overhead, by default only [`input::Axis::X`] and [`input::Axis::Y`] are enabled
|
||||
/// and other axis should be enabled explicitly.
|
||||
pub fn disable_motion_axis(&self, axis: input::Axis) {
|
||||
self.inner.write().unwrap().disable_motion_axis(axis);
|
||||
}
|
||||
|
||||
/// Explicitly request that the current input method's soft input area be
|
||||
/// shown to the user, if needed.
|
||||
///
|
||||
/// Call this if the user interacts with your view in such a way that they
|
||||
/// have expressed they would like to start performing input into it.
|
||||
pub fn show_soft_input(&self, show_implicit: bool) {
|
||||
self.inner.read().unwrap().show_soft_input(show_implicit);
|
||||
}
|
||||
|
||||
/// Request to hide the soft input window from the context of the window
|
||||
/// that is currently accepting input.
|
||||
///
|
||||
/// This should be called as a result of the user doing some action that
|
||||
/// fairly explicitly requests to have the input window hidden.
|
||||
pub fn hide_soft_input(&self, hide_implicit_only: bool) {
|
||||
self.inner
|
||||
.read()
|
||||
.unwrap()
|
||||
.hide_soft_input(hide_implicit_only);
|
||||
}
|
||||
|
||||
/// Query and process all out-standing input event
|
||||
///
|
||||
/// `callback` should return [`InputStatus::Unhandled`] for any input events that aren't directly
|
||||
/// handled by the application, or else [`InputStatus::Handled`]. Unhandled events may lead to a
|
||||
/// fallback interpretation of the event.
|
||||
///
|
||||
/// Applications are generally either expected to call this in-sync with their rendering or
|
||||
/// in response to a [`MainEvent::InputAvailable`] event being delivered. _Note though that your
|
||||
/// application is will only be delivered a single [`MainEvent::InputAvailable`] event between calls
|
||||
/// to this API._
|
||||
///
|
||||
/// To reduce overhead, by default only [`input::Axis::X`] and [`input::Axis::Y`] are enabled
|
||||
/// and other axis should be enabled explicitly via [`Self::enable_motion_axis`].
|
||||
pub fn input_events<F>(&self, callback: F)
|
||||
where
|
||||
F: FnMut(&input::InputEvent) -> InputStatus,
|
||||
{
|
||||
self.inner.read().unwrap().input_events(callback)
|
||||
}
|
||||
|
||||
/// The user-visible SDK version of the framework
|
||||
///
|
||||
/// Also referred to as [`Build.VERSION_CODES`](https://developer.android.com/reference/android/os/Build.VERSION_CODES)
|
||||
pub fn sdk_version() -> i32 {
|
||||
let mut prop = android_properties::getprop("ro.build.version.sdk");
|
||||
if let Some(val) = prop.value() {
|
||||
val.parse::<i32>()
|
||||
.expect("Failed to parse ro.build.version.sdk property")
|
||||
} else {
|
||||
panic!("Couldn't read ro.build.version.sdk system property");
|
||||
}
|
||||
}
|
||||
|
||||
/// Path to this application's internal data directory
|
||||
pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
|
||||
self.inner.read().unwrap().internal_data_path()
|
||||
}
|
||||
|
||||
/// Path to this application's external data directory
|
||||
pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
|
||||
self.inner.read().unwrap().external_data_path()
|
||||
}
|
||||
|
||||
/// Path to the directory containing the application's OBB files (if any).
|
||||
pub fn obb_path(&self) -> Option<std::path::PathBuf> {
|
||||
self.inner.read().unwrap().obb_path()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_app_is_send_sync() {
|
||||
fn needs_send_sync<T: Send + Sync>() {}
|
||||
needs_send_sync::<AndroidApp>();
|
||||
}
|
||||
943
third-party/vendor/android-activity/src/native_activity/glue.rs
vendored
Normal file
943
third-party/vendor/android-activity/src/native_activity/glue.rs
vendored
Normal file
|
|
@ -0,0 +1,943 @@
|
|||
//! This 'glue' layer acts as an IPC shim between the JVM main thread and the Rust
|
||||
//! main thread. Notifying Rust of lifecycle events from the JVM and handling
|
||||
//! synchronization between the two threads.
|
||||
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
ops::Deref,
|
||||
os::unix::prelude::{FromRawFd, RawFd},
|
||||
panic::catch_unwind,
|
||||
ptr::{self, NonNull},
|
||||
sync::{Arc, Condvar, Mutex, Weak},
|
||||
};
|
||||
|
||||
use log::Level;
|
||||
use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow};
|
||||
|
||||
use crate::{
|
||||
util::android_log,
|
||||
util::{abort_on_panic, log_panic},
|
||||
ConfigurationRef,
|
||||
};
|
||||
|
||||
use super::{AndroidApp, Rect};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub enum AppCmd {
|
||||
InputQueueChanged = 0,
|
||||
InitWindow = 1,
|
||||
TermWindow = 2,
|
||||
WindowResized = 3,
|
||||
WindowRedrawNeeded = 4,
|
||||
ContentRectChanged = 5,
|
||||
GainedFocus = 6,
|
||||
LostFocus = 7,
|
||||
ConfigChanged = 8,
|
||||
LowMemory = 9,
|
||||
Start = 10,
|
||||
Resume = 11,
|
||||
SaveState = 12,
|
||||
Pause = 13,
|
||||
Stop = 14,
|
||||
Destroy = 15,
|
||||
}
|
||||
impl TryFrom<i8> for AppCmd {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: i8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(AppCmd::InputQueueChanged),
|
||||
1 => Ok(AppCmd::InitWindow),
|
||||
2 => Ok(AppCmd::TermWindow),
|
||||
3 => Ok(AppCmd::WindowResized),
|
||||
4 => Ok(AppCmd::WindowRedrawNeeded),
|
||||
5 => Ok(AppCmd::ContentRectChanged),
|
||||
6 => Ok(AppCmd::GainedFocus),
|
||||
7 => Ok(AppCmd::LostFocus),
|
||||
8 => Ok(AppCmd::ConfigChanged),
|
||||
9 => Ok(AppCmd::LowMemory),
|
||||
10 => Ok(AppCmd::Start),
|
||||
11 => Ok(AppCmd::Resume),
|
||||
12 => Ok(AppCmd::SaveState),
|
||||
13 => Ok(AppCmd::Pause),
|
||||
14 => Ok(AppCmd::Stop),
|
||||
15 => Ok(AppCmd::Destroy),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
pub enum State {
|
||||
Init,
|
||||
Start,
|
||||
Resume,
|
||||
Pause,
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WaitableNativeActivityState {
|
||||
pub activity: *mut ndk_sys::ANativeActivity,
|
||||
|
||||
pub mutex: Mutex<NativeActivityState>,
|
||||
pub cond: Condvar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NativeActivityGlue {
|
||||
pub inner: Arc<WaitableNativeActivityState>,
|
||||
}
|
||||
unsafe impl Send for NativeActivityGlue {}
|
||||
unsafe impl Sync for NativeActivityGlue {}
|
||||
|
||||
impl Deref for NativeActivityGlue {
|
||||
type Target = WaitableNativeActivityState;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeActivityGlue {
|
||||
pub fn new(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
saved_state: *const libc::c_void,
|
||||
saved_state_size: libc::size_t,
|
||||
) -> Self {
|
||||
let glue = Self {
|
||||
inner: Arc::new(WaitableNativeActivityState::new(
|
||||
activity,
|
||||
saved_state,
|
||||
saved_state_size,
|
||||
)),
|
||||
};
|
||||
|
||||
let weak_ref = Arc::downgrade(&glue.inner);
|
||||
let weak_ptr = Weak::into_raw(weak_ref);
|
||||
unsafe {
|
||||
(*activity).instance = weak_ptr as *mut _;
|
||||
|
||||
(*(*activity).callbacks).onDestroy = Some(on_destroy);
|
||||
(*(*activity).callbacks).onStart = Some(on_start);
|
||||
(*(*activity).callbacks).onResume = Some(on_resume);
|
||||
(*(*activity).callbacks).onSaveInstanceState = Some(on_save_instance_state);
|
||||
(*(*activity).callbacks).onPause = Some(on_pause);
|
||||
(*(*activity).callbacks).onStop = Some(on_stop);
|
||||
(*(*activity).callbacks).onConfigurationChanged = Some(on_configuration_changed);
|
||||
(*(*activity).callbacks).onLowMemory = Some(on_low_memory);
|
||||
(*(*activity).callbacks).onWindowFocusChanged = Some(on_window_focus_changed);
|
||||
(*(*activity).callbacks).onNativeWindowCreated = Some(on_native_window_created);
|
||||
(*(*activity).callbacks).onNativeWindowResized = Some(on_native_window_resized);
|
||||
(*(*activity).callbacks).onNativeWindowRedrawNeeded =
|
||||
Some(on_native_window_redraw_needed);
|
||||
(*(*activity).callbacks).onNativeWindowDestroyed = Some(on_native_window_destroyed);
|
||||
(*(*activity).callbacks).onInputQueueCreated = Some(on_input_queue_created);
|
||||
(*(*activity).callbacks).onInputQueueDestroyed = Some(on_input_queue_destroyed);
|
||||
(*(*activity).callbacks).onContentRectChanged = Some(on_content_rect_changed);
|
||||
}
|
||||
|
||||
glue
|
||||
}
|
||||
|
||||
/// Returns the file descriptor that needs to be polled by the Rust main thread
|
||||
/// for events/commands from the JVM thread
|
||||
pub fn cmd_read_fd(&self) -> libc::c_int {
|
||||
self.mutex.lock().unwrap().msg_read
|
||||
}
|
||||
|
||||
/// For the Rust main thread to read a single pending command sent from the JVM main thread
|
||||
pub fn read_cmd(&self) -> Option<AppCmd> {
|
||||
self.inner.mutex.lock().unwrap().read_cmd()
|
||||
}
|
||||
|
||||
/// For the Rust main thread to get an [`InputQueue`] that wraps the AInputQueue pointer
|
||||
/// we have and at the same time ensure that the input queue is attached to the given looper.
|
||||
///
|
||||
/// NB: it's expected that the input queue is detached as soon as we know there is new
|
||||
/// input (knowing the app will be notified) and only re-attached when the application
|
||||
/// reads the input (to avoid lots of redundant wake ups)
|
||||
pub fn looper_attached_input_queue(
|
||||
&self,
|
||||
looper: *mut ndk_sys::ALooper,
|
||||
ident: libc::c_int,
|
||||
) -> Option<InputQueue> {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
if guard.input_queue.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// Reattach the input queue to the looper so future input will again deliver an
|
||||
// `InputAvailable` event.
|
||||
guard.attach_input_queue_to_looper(looper, ident);
|
||||
Some(InputQueue::from_ptr(NonNull::new_unchecked(
|
||||
guard.input_queue,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detach_input_queue_from_looper(&self) {
|
||||
unsafe {
|
||||
self.inner
|
||||
.mutex
|
||||
.lock()
|
||||
.unwrap()
|
||||
.detach_input_queue_from_looper();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(&self) -> ConfigurationRef {
|
||||
self.mutex.lock().unwrap().config.clone()
|
||||
}
|
||||
|
||||
pub fn content_rect(&self) -> Rect {
|
||||
self.mutex.lock().unwrap().content_rect.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The status of the native thread that's created to run
|
||||
/// `android_main`
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NativeThreadState {
|
||||
/// The `android_main` thread hasn't been created yet
|
||||
Init,
|
||||
/// The `android_main` thread has been spawned and started running
|
||||
Running,
|
||||
/// The `android_main` thread has finished
|
||||
Stopped,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NativeActivityState {
|
||||
pub msg_read: libc::c_int,
|
||||
pub msg_write: libc::c_int,
|
||||
pub config: super::ConfigurationRef,
|
||||
pub saved_state: Vec<u8>,
|
||||
pub input_queue: *mut ndk_sys::AInputQueue,
|
||||
pub window: Option<NativeWindow>,
|
||||
pub content_rect: ndk_sys::ARect,
|
||||
pub activity_state: State,
|
||||
pub destroy_requested: bool,
|
||||
pub thread_state: NativeThreadState,
|
||||
pub app_has_saved_state: bool,
|
||||
|
||||
/// Set as soon as the Java main thread notifies us of an
|
||||
/// `onDestroyed` callback.
|
||||
pub destroyed: bool,
|
||||
pub redraw_needed: bool,
|
||||
pub pending_input_queue: *mut ndk_sys::AInputQueue,
|
||||
pub pending_window: Option<NativeWindow>,
|
||||
}
|
||||
|
||||
impl NativeActivityState {
|
||||
pub fn read_cmd(&mut self) -> Option<AppCmd> {
|
||||
let mut cmd_i: i8 = 0;
|
||||
loop {
|
||||
match unsafe { libc::read(self.msg_read, &mut cmd_i as *mut _ as *mut _, 1) } {
|
||||
1 => {
|
||||
let cmd = AppCmd::try_from(cmd_i);
|
||||
return match cmd {
|
||||
Ok(cmd) => Some(cmd),
|
||||
Err(_) => {
|
||||
log::error!("Spurious, unknown NativeActivityGlue cmd: {}", cmd_i);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
-1 => {
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.kind() != std::io::ErrorKind::Interrupted {
|
||||
log::error!("Failure reading NativeActivityGlue cmd: {}", err);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
count => {
|
||||
log::error!(
|
||||
"Spurious read of {count} bytes while reading NativeActivityGlue cmd"
|
||||
);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_cmd(&mut self, cmd: AppCmd) {
|
||||
let cmd = cmd as i8;
|
||||
loop {
|
||||
match unsafe { libc::write(self.msg_write, &cmd as *const _ as *const _, 1) } {
|
||||
1 => break,
|
||||
-1 => {
|
||||
let err = std::io::Error::last_os_error();
|
||||
if err.kind() != std::io::ErrorKind::Interrupted {
|
||||
log::error!("Failure writing NativeActivityGlue cmd: {}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
count => {
|
||||
log::error!(
|
||||
"Spurious write of {count} bytes while writing NativeActivityGlue cmd"
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn attach_input_queue_to_looper(
|
||||
&mut self,
|
||||
looper: *mut ndk_sys::ALooper,
|
||||
ident: libc::c_int,
|
||||
) {
|
||||
if !self.input_queue.is_null() {
|
||||
log::trace!("Attaching input queue to looper");
|
||||
ndk_sys::AInputQueue_attachLooper(
|
||||
self.input_queue,
|
||||
looper,
|
||||
ident,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn detach_input_queue_from_looper(&mut self) {
|
||||
if !self.input_queue.is_null() {
|
||||
log::trace!("Detaching input queue from looper");
|
||||
ndk_sys::AInputQueue_detachLooper(self.input_queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WaitableNativeActivityState {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("WaitableNativeActivityState::drop!");
|
||||
unsafe {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.detach_input_queue_from_looper();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WaitableNativeActivityState {
|
||||
///////////////////////////////
|
||||
// Java-side callback handling
|
||||
///////////////////////////////
|
||||
|
||||
pub fn new(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
saved_state_in: *const libc::c_void,
|
||||
saved_state_size: libc::size_t,
|
||||
) -> Self {
|
||||
let mut msgpipe: [libc::c_int; 2] = [-1, -1];
|
||||
unsafe {
|
||||
if libc::pipe(msgpipe.as_mut_ptr()) != 0 {
|
||||
panic!(
|
||||
"could not create Rust <-> Java IPC pipe: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let saved_state = unsafe {
|
||||
std::slice::from_raw_parts(saved_state_in as *const u8, saved_state_size as _)
|
||||
};
|
||||
|
||||
let config = unsafe {
|
||||
let config = ndk_sys::AConfiguration_new();
|
||||
ndk_sys::AConfiguration_fromAssetManager(config, (*activity).assetManager);
|
||||
|
||||
let config = super::ConfigurationRef::new(Configuration::from_ptr(
|
||||
NonNull::new_unchecked(config),
|
||||
));
|
||||
log::trace!("Config: {:#?}", config);
|
||||
config
|
||||
};
|
||||
|
||||
Self {
|
||||
activity,
|
||||
mutex: Mutex::new(NativeActivityState {
|
||||
msg_read: msgpipe[0],
|
||||
msg_write: msgpipe[1],
|
||||
config,
|
||||
saved_state: saved_state.into(),
|
||||
input_queue: ptr::null_mut(),
|
||||
window: None,
|
||||
content_rect: Rect::empty().into(),
|
||||
activity_state: State::Init,
|
||||
destroy_requested: false,
|
||||
thread_state: NativeThreadState::Init,
|
||||
app_has_saved_state: false,
|
||||
destroyed: false,
|
||||
redraw_needed: false,
|
||||
pending_input_queue: ptr::null_mut(),
|
||||
pending_window: None,
|
||||
}),
|
||||
cond: Condvar::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_destroyed(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.destroyed = true;
|
||||
|
||||
unsafe {
|
||||
guard.write_cmd(AppCmd::Destroy);
|
||||
while guard.thread_state != NativeThreadState::Stopped {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
|
||||
libc::close(guard.msg_read);
|
||||
guard.msg_read = -1;
|
||||
libc::close(guard.msg_write);
|
||||
guard.msg_write = -1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn notify_config_changed(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.write_cmd(AppCmd::ConfigChanged);
|
||||
}
|
||||
|
||||
pub fn notify_low_memory(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.write_cmd(AppCmd::LowMemory);
|
||||
}
|
||||
|
||||
pub fn notify_focus_changed(&self, focused: bool) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.write_cmd(if focused {
|
||||
AppCmd::GainedFocus
|
||||
} else {
|
||||
AppCmd::LostFocus
|
||||
});
|
||||
}
|
||||
|
||||
pub fn notify_window_resized(&self, native_window: *mut ndk_sys::ANativeWindow) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
// set_window always syncs .pending_window back to .window before returning. This callback
|
||||
// from Android can never arrive at an interim state, and validates that Android:
|
||||
// 1. Only provides resizes in between onNativeWindowCreated and onNativeWindowDestroyed;
|
||||
// 2. Doesn't call it on a bogus window pointer that we don't know about.
|
||||
debug_assert_eq!(guard.window.as_ref().unwrap().ptr().as_ptr(), native_window);
|
||||
guard.write_cmd(AppCmd::WindowResized);
|
||||
}
|
||||
|
||||
pub fn notify_window_redraw_needed(&self, native_window: *mut ndk_sys::ANativeWindow) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
// set_window always syncs .pending_window back to .window before returning. This callback
|
||||
// from Android can never arrive at an interim state, and validates that Android:
|
||||
// 1. Only provides resizes in between onNativeWindowCreated and onNativeWindowDestroyed;
|
||||
// 2. Doesn't call it on a bogus window pointer that we don't know about.
|
||||
debug_assert_eq!(guard.window.as_ref().unwrap().ptr().as_ptr(), native_window);
|
||||
guard.write_cmd(AppCmd::WindowRedrawNeeded);
|
||||
}
|
||||
|
||||
unsafe fn set_input(&self, input_queue: *mut ndk_sys::AInputQueue) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
// The pending_input_queue state should only be set while in this method, and since
|
||||
// it doesn't allow re-entrance and is cleared before returning then we expect
|
||||
// this to be null
|
||||
debug_assert!(
|
||||
guard.pending_input_queue.is_null(),
|
||||
"InputQueue update clash"
|
||||
);
|
||||
|
||||
guard.pending_input_queue = input_queue;
|
||||
guard.write_cmd(AppCmd::InputQueueChanged);
|
||||
while guard.input_queue != guard.pending_input_queue {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
guard.pending_input_queue = ptr::null_mut();
|
||||
}
|
||||
|
||||
unsafe fn set_window(&self, window: Option<NativeWindow>) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
// The pending_window state should only be set while in this method, and since
|
||||
// it doesn't allow re-entrance and is cleared before returning then we expect
|
||||
// this to be None
|
||||
debug_assert!(guard.pending_window.is_none(), "NativeWindow update clash");
|
||||
|
||||
if guard.window.is_some() {
|
||||
guard.write_cmd(AppCmd::TermWindow);
|
||||
}
|
||||
guard.pending_window = window;
|
||||
if guard.pending_window.is_some() {
|
||||
guard.write_cmd(AppCmd::InitWindow);
|
||||
}
|
||||
while guard.window != guard.pending_window {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
guard.pending_window = None;
|
||||
}
|
||||
|
||||
unsafe fn set_content_rect(&self, rect: *const ndk_sys::ARect) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.content_rect = *rect;
|
||||
guard.write_cmd(AppCmd::ContentRectChanged);
|
||||
}
|
||||
|
||||
unsafe fn set_activity_state(&self, state: State) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
let cmd = match state {
|
||||
State::Init => panic!("Can't explicitly transition into 'init' state"),
|
||||
State::Start => AppCmd::Start,
|
||||
State::Resume => AppCmd::Resume,
|
||||
State::Pause => AppCmd::Pause,
|
||||
State::Stop => AppCmd::Stop,
|
||||
};
|
||||
guard.write_cmd(cmd);
|
||||
|
||||
while guard.activity_state != state {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn request_save_state(&self) -> (*mut libc::c_void, libc::size_t) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
// The state_saved flag should only be set while in this method, and since
|
||||
// it doesn't allow re-entrance and is cleared before returning then we expect
|
||||
// this to be None
|
||||
debug_assert!(!guard.app_has_saved_state, "SaveState request clash");
|
||||
guard.write_cmd(AppCmd::SaveState);
|
||||
while !guard.app_has_saved_state {
|
||||
guard = self.cond.wait(guard).unwrap();
|
||||
}
|
||||
guard.app_has_saved_state = false;
|
||||
|
||||
// `ANativeActivity` explicitly documents that it expects save state to be
|
||||
// given via a `malloc()` allocated pointer since it will automatically
|
||||
// `free()` the state after it has been converted to a buffer for the JVM.
|
||||
if !guard.saved_state.is_empty() {
|
||||
let saved_state_size = guard.saved_state.len() as _;
|
||||
let saved_state_src_ptr = guard.saved_state.as_ptr();
|
||||
unsafe {
|
||||
let saved_state = libc::malloc(saved_state_size);
|
||||
assert!(
|
||||
!saved_state.is_null(),
|
||||
"Failed to allocate {} bytes for restoring saved application state",
|
||||
saved_state_size
|
||||
);
|
||||
libc::memcpy(saved_state, saved_state_src_ptr as _, saved_state_size);
|
||||
(saved_state, saved_state_size)
|
||||
}
|
||||
} else {
|
||||
(ptr::null_mut(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn saved_state(&self) -> Option<Vec<u8>> {
|
||||
let guard = self.mutex.lock().unwrap();
|
||||
if !guard.saved_state.is_empty() {
|
||||
Some(guard.saved_state.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_saved_state(&self, state: &[u8]) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
|
||||
guard.saved_state.clear();
|
||||
guard.saved_state.extend_from_slice(state);
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// Rust-side event loop
|
||||
////////////////////////////
|
||||
|
||||
pub fn notify_main_thread_running(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.thread_state = NativeThreadState::Running;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
|
||||
pub fn notify_main_thread_stopped_running(&self) {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.thread_state = NativeThreadState::Stopped;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
|
||||
pub unsafe fn pre_exec_cmd(
|
||||
&self,
|
||||
cmd: AppCmd,
|
||||
looper: *mut ndk_sys::ALooper,
|
||||
input_queue_ident: libc::c_int,
|
||||
) {
|
||||
log::trace!("Pre: AppCmd::{:#?}", cmd);
|
||||
match cmd {
|
||||
AppCmd::InputQueueChanged => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.detach_input_queue_from_looper();
|
||||
guard.input_queue = guard.pending_input_queue;
|
||||
if !guard.input_queue.is_null() {
|
||||
guard.attach_input_queue_to_looper(looper, input_queue_ident);
|
||||
}
|
||||
self.cond.notify_one();
|
||||
}
|
||||
AppCmd::InitWindow => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.window = guard.pending_window.clone();
|
||||
self.cond.notify_one();
|
||||
}
|
||||
AppCmd::Resume | AppCmd::Start | AppCmd::Pause | AppCmd::Stop => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.activity_state = match cmd {
|
||||
AppCmd::Start => State::Start,
|
||||
AppCmd::Pause => State::Pause,
|
||||
AppCmd::Resume => State::Resume,
|
||||
AppCmd::Stop => State::Stop,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.cond.notify_one();
|
||||
}
|
||||
AppCmd::ConfigChanged => {
|
||||
let guard = self.mutex.lock().unwrap();
|
||||
let config = ndk_sys::AConfiguration_new();
|
||||
ndk_sys::AConfiguration_fromAssetManager(config, (*self.activity).assetManager);
|
||||
let config = Configuration::from_ptr(NonNull::new_unchecked(config));
|
||||
guard.config.replace(config);
|
||||
log::debug!("Config: {:#?}", guard.config);
|
||||
}
|
||||
AppCmd::Destroy => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.destroy_requested = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn post_exec_cmd(&self, cmd: AppCmd) {
|
||||
log::trace!("Post: AppCmd::{:#?}", cmd);
|
||||
match cmd {
|
||||
AppCmd::TermWindow => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.window = None;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
AppCmd::SaveState => {
|
||||
let mut guard = self.mutex.lock().unwrap();
|
||||
guard.app_has_saved_state = true;
|
||||
self.cond.notify_one();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
pub fn android_main(app: AndroidApp);
|
||||
}
|
||||
|
||||
unsafe fn try_with_waitable_activity_ref(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
closure: impl FnOnce(Arc<WaitableNativeActivityState>),
|
||||
) {
|
||||
assert!(!(*activity).instance.is_null());
|
||||
let weak_ptr: *const WaitableNativeActivityState = (*activity).instance.cast();
|
||||
let weak_ref = Weak::from_raw(weak_ptr);
|
||||
if let Some(waitable_activity) = weak_ref.upgrade() {
|
||||
closure(waitable_activity);
|
||||
} else {
|
||||
log::error!("Ignoring spurious JVM callback after last activity reference was dropped!")
|
||||
}
|
||||
let _ = weak_ref.into_raw();
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_destroy(activity: *mut ndk_sys::ANativeActivity) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("Destroy: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_destroyed()
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_start(activity: *mut ndk_sys::ANativeActivity) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("Start: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_activity_state(State::Start);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_resume(activity: *mut ndk_sys::ANativeActivity) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("Resume: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_activity_state(State::Resume);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_save_instance_state(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
out_len: *mut ndk_sys::size_t,
|
||||
) -> *mut libc::c_void {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("SaveInstanceState: {:p}\n", activity);
|
||||
*out_len = 0;
|
||||
let mut ret = ptr::null_mut();
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
let (state, len) = waitable_activity.request_save_state();
|
||||
*out_len = len as ndk_sys::size_t;
|
||||
ret = state
|
||||
});
|
||||
|
||||
log::debug!("Saved state = {:p}, len = {}", ret, *out_len);
|
||||
ret
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_pause(activity: *mut ndk_sys::ANativeActivity) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("Pause: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_activity_state(State::Pause);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_stop(activity: *mut ndk_sys::ANativeActivity) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("Stop: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_activity_state(State::Stop);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_configuration_changed(activity: *mut ndk_sys::ANativeActivity) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("ConfigurationChanged: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_config_changed();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_low_memory(activity: *mut ndk_sys::ANativeActivity) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("LowMemory: {:p}\n", activity);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_low_memory();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_window_focus_changed(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
focused: libc::c_int,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("WindowFocusChanged: {:p} -- {}\n", activity, focused);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_focus_changed(focused != 0);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_native_window_created(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
window: *mut ndk_sys::ANativeWindow,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("NativeWindowCreated: {:p} -- {:p}\n", activity, window);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
// Use clone_from_ptr to acquire additional ownership on the NativeWindow,
|
||||
// which will unconditionally be _release()'d on Drop.
|
||||
let window = NativeWindow::clone_from_ptr(NonNull::new_unchecked(window));
|
||||
waitable_activity.set_window(Some(window));
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_native_window_resized(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
window: *mut ndk_sys::ANativeWindow,
|
||||
) {
|
||||
log::debug!("NativeWindowResized: {:p} -- {:p}\n", activity, window);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_window_resized(window);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_native_window_redraw_needed(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
window: *mut ndk_sys::ANativeWindow,
|
||||
) {
|
||||
log::debug!("NativeWindowRedrawNeeded: {:p} -- {:p}\n", activity, window);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.notify_window_redraw_needed(window)
|
||||
});
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_native_window_destroyed(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
window: *mut ndk_sys::ANativeWindow,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("NativeWindowDestroyed: {:p} -- {:p}\n", activity, window);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_window(None);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_input_queue_created(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
queue: *mut ndk_sys::AInputQueue,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("InputQueueCreated: {:p} -- {:p}\n", activity, queue);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_input(queue);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_input_queue_destroyed(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
queue: *mut ndk_sys::AInputQueue,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
log::debug!("InputQueueDestroyed: {:p} -- {:p}\n", activity, queue);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_input(ptr::null_mut());
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_content_rect_changed(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
rect: *const ndk_sys::ARect,
|
||||
) {
|
||||
log::debug!("ContentRectChanged: {:p} -- {:p}\n", activity, rect);
|
||||
try_with_waitable_activity_ref(activity, |waitable_activity| {
|
||||
waitable_activity.set_content_rect(rect)
|
||||
});
|
||||
}
|
||||
|
||||
/// This is the native entrypoint for our cdylib library that `ANativeActivity` will look for via `dlsym`
|
||||
#[no_mangle]
|
||||
#[allow(unused_unsafe)] // Otherwise rust 1.64 moans about using unsafe{} in unsafe functions
|
||||
extern "C" fn ANativeActivity_onCreate(
|
||||
activity: *mut ndk_sys::ANativeActivity,
|
||||
saved_state: *const libc::c_void,
|
||||
saved_state_size: libc::size_t,
|
||||
) {
|
||||
abort_on_panic(|| {
|
||||
// Maybe make this stdout/stderr redirection an optional / opt-in feature?...
|
||||
unsafe {
|
||||
let mut logpipe: [RawFd; 2] = Default::default();
|
||||
libc::pipe(logpipe.as_mut_ptr());
|
||||
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
|
||||
libc::dup2(logpipe[1], libc::STDERR_FILENO);
|
||||
std::thread::spawn(move || {
|
||||
let tag = CStr::from_bytes_with_nul(b"RustStdoutStderr\0").unwrap();
|
||||
let file = File::from_raw_fd(logpipe[0]);
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut buffer = String::new();
|
||||
loop {
|
||||
buffer.clear();
|
||||
if let Ok(len) = reader.read_line(&mut buffer) {
|
||||
if len == 0 {
|
||||
break;
|
||||
} else if let Ok(msg) = CString::new(buffer.clone()) {
|
||||
android_log(Level::Info, tag, &msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
"Creating: {:p}, saved_state = {:p}, save_state_size = {}",
|
||||
activity,
|
||||
saved_state,
|
||||
saved_state_size
|
||||
);
|
||||
|
||||
// Conceptually we associate a glue reference with the JVM main thread, and another
|
||||
// reference with the Rust main thread
|
||||
let jvm_glue = NativeActivityGlue::new(activity, saved_state, saved_state_size);
|
||||
|
||||
let rust_glue = jvm_glue.clone();
|
||||
// Let us Send the NativeActivity pointer to the Rust main() thread without a wrapper type
|
||||
let activity_ptr: libc::intptr_t = activity as _;
|
||||
|
||||
// Note: we drop the thread handle which will detach the thread
|
||||
std::thread::spawn(move || {
|
||||
let activity: *mut ndk_sys::ANativeActivity = activity_ptr as *mut _;
|
||||
|
||||
let jvm = unsafe {
|
||||
let na = activity;
|
||||
let jvm = (*na).vm;
|
||||
let activity = (*na).clazz; // Completely bogus name; this is the _instance_ not class pointer
|
||||
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
|
||||
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached
|
||||
// to the thread yet. Attach before calling the applications main function
|
||||
// so they can safely make JNI calls
|
||||
let mut jenv_out: *mut core::ffi::c_void = std::ptr::null_mut();
|
||||
if let Some(attach_current_thread) = (*(*jvm)).AttachCurrentThread {
|
||||
attach_current_thread(jvm, &mut jenv_out, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
jvm
|
||||
};
|
||||
|
||||
let app = AndroidApp::new(rust_glue.clone());
|
||||
|
||||
rust_glue.notify_main_thread_running();
|
||||
|
||||
unsafe {
|
||||
// We want to specifically catch any panic from the application's android_main
|
||||
// so we can finish + destroy the Activity gracefully via the JVM
|
||||
catch_unwind(|| {
|
||||
// XXX: If we were in control of the Java Activity subclass then
|
||||
// we could potentially run the android_main function via a Java native method
|
||||
// springboard (e.g. call an Activity subclass method that calls a jni native
|
||||
// method that then just calls android_main()) that would make sure there was
|
||||
// a Java frame at the base of our call stack which would then be recognised
|
||||
// when calling FindClass to lookup a suitable classLoader, instead of
|
||||
// defaulting to the system loader. Without this then it's difficult for native
|
||||
// code to look up non-standard Java classes.
|
||||
android_main(app);
|
||||
})
|
||||
.unwrap_or_else(|panic| log_panic(panic));
|
||||
|
||||
// Let JVM know that our Activity can be destroyed before detaching from the JVM
|
||||
//
|
||||
// "Note that this method can be called from any thread; it will send a message
|
||||
// to the main thread of the process where the Java finish call will take place"
|
||||
ndk_sys::ANativeActivity_finish(activity);
|
||||
|
||||
if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread {
|
||||
detach_current_thread(jvm);
|
||||
}
|
||||
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
|
||||
rust_glue.notify_main_thread_stopped_running();
|
||||
});
|
||||
|
||||
// Wait for thread to start.
|
||||
let mut guard = jvm_glue.mutex.lock().unwrap();
|
||||
|
||||
// Don't specifically wait for `Running` just in case `android_main` returns
|
||||
// immediately and the state is set to `Stopped`
|
||||
while guard.thread_state == NativeThreadState::Init {
|
||||
guard = jvm_glue.cond.wait(guard).unwrap();
|
||||
}
|
||||
})
|
||||
}
|
||||
340
third-party/vendor/android-activity/src/native_activity/input.rs
vendored
Normal file
340
third-party/vendor/android-activity/src/native_activity/input.rs
vendored
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
pub use ndk::event::{
|
||||
Axis, ButtonState, EdgeFlags, KeyAction, KeyEventFlags, Keycode, MetaState, MotionAction,
|
||||
MotionEventFlags, Pointer, PointersIter,
|
||||
};
|
||||
|
||||
use crate::input::{Class, Source};
|
||||
|
||||
/// A motion event
|
||||
///
|
||||
/// For general discussion of motion events in Android, see [the relevant
|
||||
/// javadoc](https://developer.android.com/reference/android/view/MotionEvent).
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct MotionEvent<'a> {
|
||||
ndk_event: ndk::event::MotionEvent,
|
||||
_lifetime: PhantomData<&'a ndk::event::MotionEvent>,
|
||||
}
|
||||
impl<'a> MotionEvent<'a> {
|
||||
pub(crate) fn new(ndk_event: ndk::event::MotionEvent) -> Self {
|
||||
Self {
|
||||
ndk_event,
|
||||
_lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
pub(crate) fn into_ndk_event(self) -> ndk::event::MotionEvent {
|
||||
self.ndk_event
|
||||
}
|
||||
|
||||
/// Get the source of the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source {
|
||||
// XXX: we use `AInputEvent_getSource` directly (instead of calling
|
||||
// ndk_event.source()) since we have our own `Source` enum that we
|
||||
// share between backends, which may not exactly match the ndk crate's
|
||||
// `Source` enum.
|
||||
let source =
|
||||
unsafe { ndk_sys::AInputEvent_getSource(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
source.try_into().unwrap_or(Source::Unknown)
|
||||
}
|
||||
|
||||
/// Get the class of the event source.
|
||||
///
|
||||
#[inline]
|
||||
pub fn class(&self) -> Class {
|
||||
Class::from(self.source())
|
||||
}
|
||||
|
||||
/// Get the device id associated with the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn device_id(&self) -> i32 {
|
||||
self.ndk_event.device_id()
|
||||
}
|
||||
|
||||
/// Returns the motion action associated with the event.
|
||||
///
|
||||
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionMasked())
|
||||
#[inline]
|
||||
pub fn action(&self) -> MotionAction {
|
||||
self.ndk_event.action()
|
||||
}
|
||||
|
||||
/// Returns the pointer index of an `Up` or `Down` event.
|
||||
///
|
||||
/// Pointer indices can change per motion event. For an identifier that stays the same, see
|
||||
/// [`Pointer::pointer_id()`].
|
||||
///
|
||||
/// This only has a meaning when the [action](Self::action) is one of [`Up`](MotionAction::Up),
|
||||
/// [`Down`](MotionAction::Down), [`PointerUp`](MotionAction::PointerUp),
|
||||
/// or [`PointerDown`](MotionAction::PointerDown).
|
||||
#[inline]
|
||||
pub fn pointer_index(&self) -> usize {
|
||||
self.ndk_event.pointer_index()
|
||||
}
|
||||
|
||||
/*
|
||||
/// Returns the pointer id associated with the given pointer index.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getpointerid)
|
||||
// TODO: look at output with out-of-range pointer index
|
||||
// Probably -1 though
|
||||
pub fn pointer_id_for(&self, pointer_index: usize) -> i32 {
|
||||
unsafe { ndk_sys::AMotionEvent_getPointerId(self.ndk_event.ptr.as_ptr(), pointer_index) }
|
||||
}
|
||||
*/
|
||||
|
||||
/// Returns the number of pointers in this event
|
||||
///
|
||||
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getPointerCount())
|
||||
#[inline]
|
||||
pub fn pointer_count(&self) -> usize {
|
||||
self.ndk_event.pointer_count()
|
||||
}
|
||||
|
||||
/// An iterator over the pointers in this motion event
|
||||
#[inline]
|
||||
pub fn pointers(&self) -> PointersIter<'_> {
|
||||
self.ndk_event.pointers()
|
||||
}
|
||||
|
||||
/// The pointer at a given pointer index. Panics if the pointer index is out of bounds.
|
||||
///
|
||||
/// If you need to loop over all the pointers, prefer the [`pointers()`](Self::pointers) method.
|
||||
#[inline]
|
||||
pub fn pointer_at_index(&self, index: usize) -> Pointer<'_> {
|
||||
self.ndk_event.pointer_at_index(index)
|
||||
}
|
||||
|
||||
/*
|
||||
XXX: Not currently supported with GameActivity so we don't currently expose for NativeActivity
|
||||
either, for consistency.
|
||||
|
||||
/// Returns the size of the history contained in this event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_gethistorysize)
|
||||
#[inline]
|
||||
pub fn history_size(&self) -> usize {
|
||||
self.ndk_event.history_size()
|
||||
}
|
||||
|
||||
/// An iterator over the historical events contained in this event.
|
||||
#[inline]
|
||||
pub fn history(&self) -> HistoricalMotionEventsIter<'_> {
|
||||
self.ndk_event.history()
|
||||
}
|
||||
*/
|
||||
|
||||
/// Returns the state of any modifier keys that were pressed during the event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getmetastate)
|
||||
#[inline]
|
||||
pub fn meta_state(&self) -> MetaState {
|
||||
self.ndk_event.meta_state()
|
||||
}
|
||||
|
||||
/// Returns the button state during this event, as a bitfield.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getbuttonstate)
|
||||
#[inline]
|
||||
pub fn button_state(&self) -> ButtonState {
|
||||
self.ndk_event.button_state()
|
||||
}
|
||||
|
||||
/// Returns the time of the start of this gesture, in the `java.lang.System.nanoTime()` time
|
||||
/// base
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getdowntime)
|
||||
#[inline]
|
||||
pub fn down_time(&self) -> i64 {
|
||||
self.ndk_event.down_time()
|
||||
}
|
||||
|
||||
/// Returns a bitfield indicating which edges were touched by this event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getedgeflags)
|
||||
#[inline]
|
||||
pub fn edge_flags(&self) -> EdgeFlags {
|
||||
self.ndk_event.edge_flags()
|
||||
}
|
||||
|
||||
/// Returns the time of this event, in the `java.lang.System.nanoTime()` time base
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_geteventtime)
|
||||
#[inline]
|
||||
pub fn event_time(&self) -> i64 {
|
||||
self.ndk_event.event_time()
|
||||
}
|
||||
|
||||
/// The flags associated with a motion event.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getflags)
|
||||
#[inline]
|
||||
pub fn flags(&self) -> MotionEventFlags {
|
||||
self.ndk_event.flags()
|
||||
}
|
||||
|
||||
/* Missing from GameActivity currently...
|
||||
/// Returns the offset in the x direction between the coordinates and the raw coordinates
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxoffset)
|
||||
#[inline]
|
||||
pub fn x_offset(&self) -> f32 {
|
||||
self.ndk_event.x_offset()
|
||||
}
|
||||
|
||||
/// Returns the offset in the y direction between the coordinates and the raw coordinates
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyoffset)
|
||||
#[inline]
|
||||
pub fn y_offset(&self) -> f32 {
|
||||
self.ndk_event.y_offset()
|
||||
}
|
||||
*/
|
||||
|
||||
/// Returns the precision of the x value of the coordinates
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getxprecision)
|
||||
#[inline]
|
||||
pub fn x_precision(&self) -> f32 {
|
||||
self.ndk_event.x_precision()
|
||||
}
|
||||
|
||||
/// Returns the precision of the y value of the coordinates
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getyprecision)
|
||||
#[inline]
|
||||
pub fn y_precision(&self) -> f32 {
|
||||
self.ndk_event.y_precision()
|
||||
}
|
||||
}
|
||||
|
||||
/// A key event
|
||||
///
|
||||
/// For general discussion of key events in Android, see [the relevant
|
||||
/// javadoc](https://developer.android.com/reference/android/view/KeyEvent).
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct KeyEvent<'a> {
|
||||
ndk_event: ndk::event::KeyEvent,
|
||||
_lifetime: PhantomData<&'a ndk::event::KeyEvent>,
|
||||
}
|
||||
impl<'a> KeyEvent<'a> {
|
||||
pub(crate) fn new(ndk_event: ndk::event::KeyEvent) -> Self {
|
||||
Self {
|
||||
ndk_event,
|
||||
_lifetime: PhantomData,
|
||||
}
|
||||
}
|
||||
pub(crate) fn into_ndk_event(self) -> ndk::event::KeyEvent {
|
||||
self.ndk_event
|
||||
}
|
||||
|
||||
/// Get the source of the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source {
|
||||
// XXX: we use `AInputEvent_getSource` directly (instead of calling
|
||||
// ndk_event.source()) since we have our own `Source` enum that we
|
||||
// share between backends, which may not exactly match the ndk crate's
|
||||
// `Source` enum.
|
||||
let source =
|
||||
unsafe { ndk_sys::AInputEvent_getSource(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
source.try_into().unwrap_or(Source::Unknown)
|
||||
}
|
||||
|
||||
/// Get the class of the event source.
|
||||
///
|
||||
#[inline]
|
||||
pub fn class(&self) -> Class {
|
||||
Class::from(self.source())
|
||||
}
|
||||
|
||||
/// Get the device id associated with the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn device_id(&self) -> i32 {
|
||||
self.ndk_event.device_id()
|
||||
}
|
||||
|
||||
/// Returns the key action associated with the event.
|
||||
///
|
||||
/// See [the KeyEvent docs](https://developer.android.com/reference/android/view/KeyEvent#getAction())
|
||||
#[inline]
|
||||
pub fn action(&self) -> KeyAction {
|
||||
self.ndk_event.action()
|
||||
}
|
||||
|
||||
/// Returns the last time the key was pressed. This is on the scale of
|
||||
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getdowntime)
|
||||
#[inline]
|
||||
pub fn down_time(&self) -> i64 {
|
||||
self.ndk_event.down_time()
|
||||
}
|
||||
|
||||
/// Returns the time this event occured. This is on the scale of
|
||||
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_geteventtime)
|
||||
#[inline]
|
||||
pub fn event_time(&self) -> i64 {
|
||||
self.ndk_event.event_time()
|
||||
}
|
||||
|
||||
/// Returns the keycode associated with this key event
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getkeycode)
|
||||
#[inline]
|
||||
pub fn key_code(&self) -> Keycode {
|
||||
self.ndk_event.key_code()
|
||||
}
|
||||
|
||||
/// Returns the number of repeats of a key.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getrepeatcount)
|
||||
#[inline]
|
||||
pub fn repeat_count(&self) -> i32 {
|
||||
self.ndk_event.repeat_count()
|
||||
}
|
||||
|
||||
/// Returns the hardware keycode of a key. This varies from device to device.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getscancode)
|
||||
#[inline]
|
||||
pub fn scan_code(&self) -> i32 {
|
||||
self.ndk_event.scan_code()
|
||||
}
|
||||
}
|
||||
|
||||
// We use our own wrapper type for input events to have better consistency
|
||||
// with GameActivity and ensure the enum can be extended without needing a
|
||||
// semver bump
|
||||
/// Enum of possible input events
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum InputEvent<'a> {
|
||||
MotionEvent(self::MotionEvent<'a>),
|
||||
KeyEvent(self::KeyEvent<'a>),
|
||||
}
|
||||
413
third-party/vendor/android-activity/src/native_activity/mod.rs
vendored
Normal file
413
third-party/vendor/android-activity/src/native_activity/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,413 @@
|
|||
#![cfg(any(feature = "native-activity", doc))]
|
||||
|
||||
use std::ptr;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use libc::c_void;
|
||||
use log::{error, trace};
|
||||
use ndk::{asset::AssetManager, native_window::NativeWindow};
|
||||
|
||||
use crate::{
|
||||
util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
|
||||
};
|
||||
|
||||
pub mod input;
|
||||
|
||||
mod glue;
|
||||
use self::glue::NativeActivityGlue;
|
||||
|
||||
pub const LOOPER_ID_MAIN: libc::c_int = 1;
|
||||
pub const LOOPER_ID_INPUT: libc::c_int = 2;
|
||||
//pub const LOOPER_ID_USER: ::std::os::raw::c_uint = 3;
|
||||
|
||||
/// An interface for saving application state during [MainEvent::SaveState] events
|
||||
///
|
||||
/// This interface is only available temporarily while handling a [MainEvent::SaveState] event.
|
||||
#[derive(Debug)]
|
||||
pub struct StateSaver<'a> {
|
||||
app: &'a AndroidAppInner,
|
||||
}
|
||||
|
||||
impl<'a> StateSaver<'a> {
|
||||
/// Stores the given `state` such that it will be available to load the next
|
||||
/// time that the application resumes.
|
||||
pub fn store(&self, state: &'a [u8]) {
|
||||
self.app.native_activity.set_saved_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface for loading application state during [MainEvent::Resume] events
|
||||
///
|
||||
/// This interface is only available temporarily while handling a [MainEvent::Resume] event.
|
||||
#[derive(Debug)]
|
||||
pub struct StateLoader<'a> {
|
||||
app: &'a AndroidAppInner,
|
||||
}
|
||||
impl<'a> StateLoader<'a> {
|
||||
/// Returns whatever state was saved during the last [MainEvent::SaveState] event or `None`
|
||||
pub fn load(&self) -> Option<Vec<u8>> {
|
||||
self.app.native_activity.saved_state()
|
||||
}
|
||||
}
|
||||
|
||||
/// A means to wake up the main thread while it is blocked waiting for I/O
|
||||
#[derive(Clone)]
|
||||
pub struct AndroidAppWaker {
|
||||
// The looper pointer is owned by the android_app and effectively
|
||||
// has a 'static lifetime, and the ALooper_wake C API is thread
|
||||
// safe, so this can be cloned safely and is send + sync safe
|
||||
looper: NonNull<ndk_sys::ALooper>,
|
||||
}
|
||||
unsafe impl Send for AndroidAppWaker {}
|
||||
unsafe impl Sync for AndroidAppWaker {}
|
||||
|
||||
impl AndroidAppWaker {
|
||||
/// Interrupts the main thread if it is blocked within [`AndroidApp::poll_events()`]
|
||||
///
|
||||
/// If [`AndroidApp::poll_events()`] is interrupted it will invoke the poll
|
||||
/// callback with a [PollEvent::Wake][wake_event] event.
|
||||
///
|
||||
/// [wake_event]: crate::PollEvent::Wake
|
||||
pub fn wake(&self) {
|
||||
unsafe {
|
||||
ndk_sys::ALooper_wake(self.looper.as_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AndroidApp {
|
||||
pub(crate) fn new(native_activity: NativeActivityGlue) -> Self {
|
||||
let app = Self {
|
||||
inner: Arc::new(RwLock::new(AndroidAppInner {
|
||||
native_activity,
|
||||
looper: Looper {
|
||||
ptr: ptr::null_mut(),
|
||||
},
|
||||
})),
|
||||
};
|
||||
|
||||
{
|
||||
let mut guard = app.inner.write().unwrap();
|
||||
|
||||
let main_fd = guard.native_activity.cmd_read_fd();
|
||||
unsafe {
|
||||
guard.looper.ptr = ndk_sys::ALooper_prepare(
|
||||
ndk_sys::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as libc::c_int,
|
||||
);
|
||||
ndk_sys::ALooper_addFd(
|
||||
guard.looper.ptr,
|
||||
main_fd,
|
||||
LOOPER_ID_MAIN,
|
||||
ndk_sys::ALOOPER_EVENT_INPUT as libc::c_int,
|
||||
None,
|
||||
//&mut guard.cmd_poll_source as *mut _ as *mut _);
|
||||
ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Looper {
|
||||
pub ptr: *mut ndk_sys::ALooper,
|
||||
}
|
||||
unsafe impl Send for Looper {}
|
||||
unsafe impl Sync for Looper {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AndroidAppInner {
|
||||
pub(crate) native_activity: NativeActivityGlue,
|
||||
looper: Looper,
|
||||
}
|
||||
|
||||
impl AndroidAppInner {
|
||||
pub(crate) fn vm_as_ptr(&self) -> *mut c_void {
|
||||
unsafe { (*self.native_activity.activity).vm as _ }
|
||||
}
|
||||
|
||||
pub(crate) fn activity_as_ptr(&self) -> *mut c_void {
|
||||
// "clazz" is a completely bogus name; this is the _instance_ not class pointer
|
||||
unsafe { (*self.native_activity.activity).clazz as _ }
|
||||
}
|
||||
|
||||
pub(crate) fn native_activity(&self) -> *const ndk_sys::ANativeActivity {
|
||||
self.native_activity.activity
|
||||
}
|
||||
|
||||
pub(crate) fn looper(&self) -> *mut ndk_sys::ALooper {
|
||||
self.looper.ptr
|
||||
}
|
||||
|
||||
pub fn native_window(&self) -> Option<NativeWindow> {
|
||||
self.native_activity.mutex.lock().unwrap().window.clone()
|
||||
}
|
||||
|
||||
pub fn poll_events<F>(&self, timeout: Option<Duration>, mut callback: F)
|
||||
where
|
||||
F: FnMut(PollEvent),
|
||||
{
|
||||
trace!("poll_events");
|
||||
|
||||
unsafe {
|
||||
let mut fd: i32 = 0;
|
||||
let mut events: i32 = 0;
|
||||
let mut source: *mut core::ffi::c_void = ptr::null_mut();
|
||||
|
||||
let timeout_milliseconds = if let Some(timeout) = timeout {
|
||||
timeout.as_millis() as i32
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
|
||||
trace!("Calling ALooper_pollAll, timeout = {timeout_milliseconds}");
|
||||
assert!(
|
||||
!ndk_sys::ALooper_forThread().is_null(),
|
||||
"Application tried to poll events from non-main thread"
|
||||
);
|
||||
let id = ndk_sys::ALooper_pollAll(
|
||||
timeout_milliseconds,
|
||||
&mut fd,
|
||||
&mut events,
|
||||
&mut source as *mut *mut core::ffi::c_void,
|
||||
);
|
||||
trace!("pollAll id = {id}");
|
||||
match id {
|
||||
ndk_sys::ALOOPER_POLL_WAKE => {
|
||||
trace!("ALooper_pollAll returned POLL_WAKE");
|
||||
callback(PollEvent::Wake);
|
||||
}
|
||||
ndk_sys::ALOOPER_POLL_CALLBACK => {
|
||||
// ALooper_pollAll is documented to handle all callback sources internally so it should
|
||||
// never return a _CALLBACK source id...
|
||||
error!("Spurious ALOOPER_POLL_CALLBACK from ALopper_pollAll() (ignored)");
|
||||
}
|
||||
ndk_sys::ALOOPER_POLL_TIMEOUT => {
|
||||
trace!("ALooper_pollAll returned POLL_TIMEOUT");
|
||||
callback(PollEvent::Timeout);
|
||||
}
|
||||
ndk_sys::ALOOPER_POLL_ERROR => {
|
||||
// If we have an IO error with our pipe to the main Java thread that's surely
|
||||
// not something we can recover from
|
||||
panic!("ALooper_pollAll returned POLL_ERROR");
|
||||
}
|
||||
id if id >= 0 => {
|
||||
match id {
|
||||
LOOPER_ID_MAIN => {
|
||||
trace!("ALooper_pollAll returned ID_MAIN");
|
||||
if let Some(ipc_cmd) = self.native_activity.read_cmd() {
|
||||
let main_cmd = match ipc_cmd {
|
||||
// We don't forward info about the AInputQueue to apps since it's
|
||||
// an implementation details that's also not compatible with
|
||||
// GameActivity
|
||||
glue::AppCmd::InputQueueChanged => None,
|
||||
|
||||
glue::AppCmd::InitWindow => Some(MainEvent::InitWindow {}),
|
||||
glue::AppCmd::TermWindow => Some(MainEvent::TerminateWindow {}),
|
||||
glue::AppCmd::WindowResized => {
|
||||
Some(MainEvent::WindowResized {})
|
||||
}
|
||||
glue::AppCmd::WindowRedrawNeeded => {
|
||||
Some(MainEvent::RedrawNeeded {})
|
||||
}
|
||||
glue::AppCmd::ContentRectChanged => {
|
||||
Some(MainEvent::ContentRectChanged {})
|
||||
}
|
||||
glue::AppCmd::GainedFocus => Some(MainEvent::GainedFocus),
|
||||
glue::AppCmd::LostFocus => Some(MainEvent::LostFocus),
|
||||
glue::AppCmd::ConfigChanged => {
|
||||
Some(MainEvent::ConfigChanged {})
|
||||
}
|
||||
glue::AppCmd::LowMemory => Some(MainEvent::LowMemory),
|
||||
glue::AppCmd::Start => Some(MainEvent::Start),
|
||||
glue::AppCmd::Resume => Some(MainEvent::Resume {
|
||||
loader: StateLoader { app: self },
|
||||
}),
|
||||
glue::AppCmd::SaveState => Some(MainEvent::SaveState {
|
||||
saver: StateSaver { app: self },
|
||||
}),
|
||||
glue::AppCmd::Pause => Some(MainEvent::Pause),
|
||||
glue::AppCmd::Stop => Some(MainEvent::Stop),
|
||||
glue::AppCmd::Destroy => Some(MainEvent::Destroy),
|
||||
};
|
||||
|
||||
trace!("Calling pre_exec_cmd({ipc_cmd:#?})");
|
||||
self.native_activity.pre_exec_cmd(
|
||||
ipc_cmd,
|
||||
self.looper(),
|
||||
LOOPER_ID_INPUT,
|
||||
);
|
||||
|
||||
if let Some(main_cmd) = main_cmd {
|
||||
trace!("Invoking callback for ID_MAIN command = {main_cmd:?}");
|
||||
callback(PollEvent::Main(main_cmd));
|
||||
}
|
||||
|
||||
trace!("Calling post_exec_cmd({ipc_cmd:#?})");
|
||||
self.native_activity.post_exec_cmd(ipc_cmd);
|
||||
}
|
||||
}
|
||||
LOOPER_ID_INPUT => {
|
||||
trace!("ALooper_pollAll returned ID_INPUT");
|
||||
|
||||
// To avoid spamming the application with event loop iterations notifying them of
|
||||
// input events then we only send one `InputAvailable` per iteration of input
|
||||
// handling. We re-attach the looper when the application calls
|
||||
// `AndroidApp::input_events()`
|
||||
self.native_activity.detach_input_queue_from_looper();
|
||||
callback(PollEvent::Main(MainEvent::InputAvailable))
|
||||
}
|
||||
_ => {
|
||||
error!("Ignoring spurious ALooper event source: id = {id}, fd = {fd}, events = {events:?}, data = {source:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Spurious ALooper_pollAll return value {id} (ignored)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_waker(&self) -> AndroidAppWaker {
|
||||
unsafe {
|
||||
// From the application's pov we assume the looper pointer has a static
|
||||
// lifetimes and we can safely assume it is never NULL.
|
||||
AndroidAppWaker {
|
||||
looper: NonNull::new_unchecked(self.looper.ptr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(&self) -> ConfigurationRef {
|
||||
self.native_activity.config()
|
||||
}
|
||||
|
||||
pub fn content_rect(&self) -> Rect {
|
||||
self.native_activity.content_rect()
|
||||
}
|
||||
|
||||
pub fn asset_manager(&self) -> AssetManager {
|
||||
unsafe {
|
||||
let activity_ptr = self.native_activity.activity;
|
||||
let am_ptr = NonNull::new_unchecked((*activity_ptr).assetManager);
|
||||
AssetManager::from_ptr(am_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_window_flags(
|
||||
&self,
|
||||
add_flags: WindowManagerFlags,
|
||||
remove_flags: WindowManagerFlags,
|
||||
) {
|
||||
let na = self.native_activity();
|
||||
let na_mut = na as *mut ndk_sys::ANativeActivity;
|
||||
unsafe {
|
||||
ndk_sys::ANativeActivity_setWindowFlags(
|
||||
na_mut.cast(),
|
||||
add_flags.bits(),
|
||||
remove_flags.bits(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move into a trait
|
||||
pub fn show_soft_input(&self, show_implicit: bool) {
|
||||
let na = self.native_activity();
|
||||
unsafe {
|
||||
let flags = if show_implicit {
|
||||
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ndk_sys::ANativeActivity_showSoftInput(na as *mut _, flags);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move into a trait
|
||||
pub fn hide_soft_input(&self, hide_implicit_only: bool) {
|
||||
let na = self.native_activity();
|
||||
unsafe {
|
||||
let flags = if hide_implicit_only {
|
||||
ndk_sys::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ndk_sys::ANativeActivity_hideSoftInput(na as *mut _, flags);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_motion_axis(&self, _axis: input::Axis) {
|
||||
// NOP - The InputQueue API doesn't let us optimize which axis values are read
|
||||
}
|
||||
|
||||
pub fn disable_motion_axis(&self, _axis: input::Axis) {
|
||||
// NOP - The InputQueue API doesn't let us optimize which axis values are read
|
||||
}
|
||||
|
||||
pub fn input_events<F>(&self, mut callback: F)
|
||||
where
|
||||
F: FnMut(&input::InputEvent) -> InputStatus,
|
||||
{
|
||||
// Get the InputQueue for the NativeActivity (if there is one) and also ensure
|
||||
// the queue is re-attached to our event Looper (so new input events will again
|
||||
// trigger a wake up)
|
||||
let queue = self
|
||||
.native_activity
|
||||
.looper_attached_input_queue(self.looper(), LOOPER_ID_INPUT);
|
||||
let queue = match queue {
|
||||
Some(queue) => queue,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Note: we basically ignore errors from get_event() currently. Looking
|
||||
// at the source code for Android's InputQueue, the only error that
|
||||
// can be returned here is 'WOULD_BLOCK', which we want to just treat as
|
||||
// meaning the queue is empty.
|
||||
//
|
||||
// ref: https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/jni/android_view_InputQueue.cpp
|
||||
//
|
||||
while let Ok(Some(event)) = queue.get_event() {
|
||||
if let Some(ndk_event) = queue.pre_dispatch(event) {
|
||||
let event = match ndk_event {
|
||||
ndk::event::InputEvent::MotionEvent(e) => {
|
||||
input::InputEvent::MotionEvent(input::MotionEvent::new(e))
|
||||
}
|
||||
ndk::event::InputEvent::KeyEvent(e) => {
|
||||
input::InputEvent::KeyEvent(input::KeyEvent::new(e))
|
||||
}
|
||||
};
|
||||
let handled = callback(&event);
|
||||
|
||||
let ndk_event = match event {
|
||||
input::InputEvent::MotionEvent(e) => {
|
||||
ndk::event::InputEvent::MotionEvent(e.into_ndk_event())
|
||||
}
|
||||
input::InputEvent::KeyEvent(e) => {
|
||||
ndk::event::InputEvent::KeyEvent(e.into_ndk_event())
|
||||
}
|
||||
};
|
||||
queue.finish_event(ndk_event, matches!(handled, InputStatus::Handled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn internal_data_path(&self) -> Option<std::path::PathBuf> {
|
||||
let na = self.native_activity();
|
||||
unsafe { util::try_get_path_from_ptr((*na).internalDataPath) }
|
||||
}
|
||||
|
||||
pub fn external_data_path(&self) -> Option<std::path::PathBuf> {
|
||||
let na = self.native_activity();
|
||||
unsafe { util::try_get_path_from_ptr((*na).externalDataPath) }
|
||||
}
|
||||
|
||||
pub fn obb_path(&self) -> Option<std::path::PathBuf> {
|
||||
let na = self.native_activity();
|
||||
unsafe { util::try_get_path_from_ptr((*na).obbPath) }
|
||||
}
|
||||
}
|
||||
70
third-party/vendor/android-activity/src/util.rs
vendored
Normal file
70
third-party/vendor/android-activity/src/util.rs
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use log::Level;
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
};
|
||||
|
||||
pub fn try_get_path_from_ptr(path: *const c_char) -> Option<std::path::PathBuf> {
|
||||
if path.is_null() {
|
||||
return None;
|
||||
}
|
||||
let cstr = unsafe {
|
||||
let cstr_slice = CStr::from_ptr(path.cast());
|
||||
cstr_slice.to_str().ok()?
|
||||
};
|
||||
if cstr.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(std::path::PathBuf::from(cstr))
|
||||
}
|
||||
|
||||
pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) {
|
||||
let prio = match level {
|
||||
Level::Error => ndk_sys::android_LogPriority::ANDROID_LOG_ERROR,
|
||||
Level::Warn => ndk_sys::android_LogPriority::ANDROID_LOG_WARN,
|
||||
Level::Info => ndk_sys::android_LogPriority::ANDROID_LOG_INFO,
|
||||
Level::Debug => ndk_sys::android_LogPriority::ANDROID_LOG_DEBUG,
|
||||
Level::Trace => ndk_sys::android_LogPriority::ANDROID_LOG_VERBOSE,
|
||||
};
|
||||
unsafe {
|
||||
ndk_sys::__android_log_write(prio.0 as libc::c_int, tag.as_ptr(), msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
|
||||
let rust_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") };
|
||||
|
||||
if let Some(panic) = panic.downcast_ref::<String>() {
|
||||
if let Ok(msg) = CString::new(panic.clone()) {
|
||||
android_log(Level::Error, rust_panic, &msg);
|
||||
}
|
||||
} else if let Ok(panic) = panic.downcast::<&str>() {
|
||||
if let Ok(msg) = CString::new(*panic) {
|
||||
android_log(Level::Error, rust_panic, &msg);
|
||||
}
|
||||
} else {
|
||||
let unknown_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"UnknownPanic\0") };
|
||||
android_log(Level::Error, unknown_panic, unsafe {
|
||||
CStr::from_bytes_with_nul_unchecked(b"\0")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Run a closure and abort the program if it panics.
|
||||
///
|
||||
/// This is generally used to ensure Rust callbacks won't unwind past the JNI boundary, which leads
|
||||
/// to undefined behaviour.
|
||||
///
|
||||
/// TODO(rib): throw a Java exception instead of aborting. An Android Activity does not necessarily
|
||||
/// own the entire process because other application Services (or even Activities) may run in
|
||||
/// threads within the same process, and so we're tearing down too much by aborting the process.
|
||||
pub(crate) fn abort_on_panic<R>(f: impl FnOnce() -> R) -> R {
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|panic| {
|
||||
// Try logging the panic before aborting
|
||||
//
|
||||
// Just in case our attempt to log a panic could itself cause a panic we use a
|
||||
// second catch_unwind here.
|
||||
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| log_panic(panic)));
|
||||
std::process::abort();
|
||||
})
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue