mirror of
https://github.com/rust-mobile/android-activity.git
synced 2026-07-05 06:17:27 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 946dcdc919 | |||
| c18a4dd135 | |||
| 4827e0b17b | |||
| a8637e3f25 | |||
| aaf0e8b30a | |||
| 46d2fa87c3 | |||
| 249e7d1834 |
@@ -17,7 +17,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# See top README for MSRV policy
|
||||
rust-version: [1.73.0, stable]
|
||||
rust-version: [1.85.0, stable]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -3,3 +3,6 @@ resolver = "2"
|
||||
members = ["android-activity"]
|
||||
|
||||
exclude = ["examples"]
|
||||
|
||||
[patch.crates-io]
|
||||
jni = { git = "https://github.com/jni-rs/jni-rs.git", branch = "release-0.22" }
|
||||
|
||||
@@ -6,19 +6,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- input: Replaced custom types with their `ndk` crate equivalent.
|
||||
> [!NOTE]
|
||||
> These types existed because the `ndk` crate didn't provide them in an extensible way. Now that they have the `#[non_exhaustive]` flag and contain a `__Unknown(T)` variant to provide lossless conversions, and not to mention use an ABI type that matches how it is being used by most functions (when the original constants were defined in a "typeless" way), the `ndk` types are used and reexported once again.
|
||||
### Added
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Relevant breaking changes**:
|
||||
> - `repr()` types for some `enum`s have changed to match the ABI type that is used by most functions that are returning or consuming this wrapper type.
|
||||
> - `Source::is_xxx_class()` functions are replaced by querying `Source::class()` and comparing against variants from the returned `SourceClass` `bitflags` enum.
|
||||
> - `SourceFlags::TRACKBALL` (from `Source::is_trackball_class()`) is named `SourceClass::NAVIGATION` in the `ndk`.
|
||||
|
||||
- rust-version bumped to 1.73.0 ([#193](https://github.com/rust-mobile/android-activity/pull/193))
|
||||
- The `ndk` and `ndk-sys` crates are now re-exported under `android_activity::ndk` and `android_activity::ndk_sys` ([#194](https://github.com/rust-mobile/android-activity/pull/194))
|
||||
- input: TextInputAction enum representing action button types on soft keyboards.
|
||||
- input: InputEvent::TextAction event for handling action button presses from soft keyboards.
|
||||
|
||||
### Changed
|
||||
|
||||
- rust-version bumped to 1.85.0 ([#193](https://github.com/rust-mobile/android-activity/pull/193), TODO: link)
|
||||
- GameActivity updated to 4.0.0 (requires the corresponding 4.0.0 `.aar` release from Google) ([#191](https://github.com/rust-mobile/android-activity/pull/191))
|
||||
|
||||
## [0.6.0] - 2024-04-26
|
||||
|
||||
@@ -16,10 +16,7 @@ include = [
|
||||
"/src",
|
||||
]
|
||||
|
||||
# Even though we could technically still build with 1.69, 1.73 has a fix for the
|
||||
# definition of the `stat` struct on Android, and so it seems worthwhile drawing
|
||||
# a line under that to ensure android-activity applications have that fix.
|
||||
rust-version = "1.73.0"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[features]
|
||||
# Note: we don't enable any backend by default since features
|
||||
@@ -32,13 +29,12 @@ default = []
|
||||
game-activity = []
|
||||
native-activity = []
|
||||
api-level-30 = ["ndk/api-level-30"]
|
||||
api-level-33 = ["api-level-30", "ndk/api-level-33"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
jni-sys = "0.3"
|
||||
cesu8 = "1"
|
||||
jni = "0.21"
|
||||
jni = "0.22"
|
||||
jni-sys = "0.4.1"
|
||||
ndk-sys = "0.6.0"
|
||||
ndk = { version = "0.9.0", default-features = false }
|
||||
ndk-context = "0.1.1"
|
||||
|
||||
+4
@@ -226,6 +226,10 @@ struct android_app {
|
||||
* thread, so we can't say that this is only valid within the `APP_CMD_` handler.
|
||||
*/
|
||||
int editorAction;
|
||||
/**
|
||||
* true when editorAction has been set
|
||||
*/
|
||||
bool pendingEditorAction;
|
||||
|
||||
/**
|
||||
* Current state of the app's activity. May be either APP_CMD_START,
|
||||
|
||||
+8
-2
@@ -726,9 +726,15 @@ static bool onEditorAction(GameActivity* activity, int action) {
|
||||
// XXX: this is a racy design that could lose InputConnection actions if the
|
||||
// application doesn't manage to look at app->editorAction before another
|
||||
// action is delivered.
|
||||
if (android_app->pendingEditorAction) {
|
||||
LOGW("Dropping editor action %d because previous action %d not yet "
|
||||
"handled",
|
||||
action, android_app->editorAction);
|
||||
}
|
||||
android_app->editorAction = action;
|
||||
// TODO: buffer these actions like other input events
|
||||
//notifyInput(android_app);
|
||||
android_app->pendingEditorAction = true;
|
||||
notifyInput(android_app);
|
||||
// TODO: buffer IME text events and editor actions like other input events
|
||||
|
||||
//android_app_write_cmd(android_app, APP_CMD_EDITOR_ACTION);
|
||||
pthread_mutex_unlock(&android_app->mutex);
|
||||
|
||||
@@ -15,7 +15,7 @@ while read ARCH && read TARGET ; do
|
||||
|
||||
# --module-raw-line 'use '
|
||||
bindgen game-activity-ffi.h -o src/game_activity/ffi_$ARCH.rs \
|
||||
--rust-target '1.73.0' \
|
||||
--rust-target '1.85.0' \
|
||||
--blocklist-item 'JNI\w+' \
|
||||
--blocklist-item 'C?_?JNIEnv' \
|
||||
--blocklist-item '_?JavaVM' \
|
||||
|
||||
@@ -27,6 +27,10 @@ pub(crate) enum InternalAppError {
|
||||
JniError(jni::errors::JniError),
|
||||
#[error("A Java Exception was thrown via a JNI method call")]
|
||||
JniException(String),
|
||||
// For internal errors that don't lead to a Java exception but are
|
||||
// still JNI related.
|
||||
#[error("A bad argument was passed to a JNI method: {0}")]
|
||||
JniBadArgument(String),
|
||||
#[error("A Java VM error")]
|
||||
JvmError(jni::errors::Error),
|
||||
#[error("Input unavailable")]
|
||||
@@ -51,6 +55,7 @@ impl From<InternalAppError> for AppError {
|
||||
match value {
|
||||
InternalAppError::JniError(err) => AppError::JavaError(err.to_string()),
|
||||
InternalAppError::JniException(msg) => AppError::JavaError(msg),
|
||||
InternalAppError::JniBadArgument(msg) => AppError::JavaError(msg),
|
||||
InternalAppError::JvmError(err) => AppError::JavaError(err.to_string()),
|
||||
InternalAppError::InputUnavailable => AppError::InputUnavailable,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#![allow(deref_nullptr)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use jni_sys::*;
|
||||
use jni::sys::*;
|
||||
use libc::{pthread_cond_t, pthread_mutex_t, pthread_t};
|
||||
use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -13,12 +13,10 @@
|
||||
// The `Class` was also bound differently to `android-ndk-rs` considering how the class is defined
|
||||
// by masking bits from the `Source`.
|
||||
|
||||
use ndk::event::ButtonState;
|
||||
|
||||
use crate::activity_impl::ffi::{GameActivityKeyEvent, GameActivityMotionEvent};
|
||||
use crate::input::{
|
||||
Axis, Button, EdgeFlags, KeyAction, KeyEventFlags, Keycode, MetaState, MotionAction,
|
||||
MotionEventFlags, Pointer, PointersIter, Source, ToolType,
|
||||
Axis, Button, ButtonState, EdgeFlags, KeyAction, KeyEventFlags, Keycode, MetaState,
|
||||
MotionAction, MotionEventFlags, Pointer, PointersIter, Source, ToolType,
|
||||
};
|
||||
|
||||
// Note: try to keep this wrapper API compatible with the AInputEvent API if possible
|
||||
@@ -29,6 +27,7 @@ pub enum InputEvent<'a> {
|
||||
MotionEvent(MotionEvent<'a>),
|
||||
KeyEvent(KeyEvent<'a>),
|
||||
TextEvent(crate::input::TextInputState),
|
||||
TextAction(crate::input::TextInputAction),
|
||||
}
|
||||
|
||||
/// A motion event.
|
||||
@@ -49,7 +48,7 @@ impl<'a> MotionEvent<'a> {
|
||||
///
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source {
|
||||
let source = self.ga_event.source;
|
||||
let source = self.ga_event.source as u32;
|
||||
source.into()
|
||||
}
|
||||
|
||||
@@ -65,7 +64,7 @@ impl<'a> MotionEvent<'a> {
|
||||
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionMasked())
|
||||
#[inline]
|
||||
pub fn action(&self) -> MotionAction {
|
||||
let action = self.ga_event.action & ndk_sys::AMOTION_EVENT_ACTION_MASK as i32;
|
||||
let action = self.ga_event.action as u32 & ndk_sys::AMOTION_EVENT_ACTION_MASK;
|
||||
action.into()
|
||||
}
|
||||
|
||||
@@ -178,7 +177,6 @@ impl<'a> MotionEvent<'a> {
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getbuttonstate)
|
||||
#[inline]
|
||||
// TODO: Button enum to signify only one bitflag can be set?
|
||||
pub fn button_state(&self) -> ButtonState {
|
||||
ButtonState(self.ga_event.buttonState as u32)
|
||||
}
|
||||
@@ -281,7 +279,7 @@ impl PointerImpl<'_> {
|
||||
#[inline]
|
||||
pub fn axis_value(&self, axis: Axis) -> f32 {
|
||||
let pointer = &self.event.ga_event.pointers[self.index];
|
||||
let axis: i32 = axis.into();
|
||||
let axis: u32 = axis.into();
|
||||
pointer.axisValues[axis as usize]
|
||||
}
|
||||
|
||||
@@ -300,7 +298,8 @@ impl PointerImpl<'_> {
|
||||
#[inline]
|
||||
pub fn tool_type(&self) -> ToolType {
|
||||
let pointer = &self.event.ga_event.pointers[self.index];
|
||||
pointer.toolType.into()
|
||||
let tool_type = pointer.toolType as u32;
|
||||
tool_type.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -667,7 +666,7 @@ impl<'a> KeyEvent<'a> {
|
||||
///
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source {
|
||||
let source = self.ga_event.source;
|
||||
let source = self.ga_event.source as u32;
|
||||
source.into()
|
||||
}
|
||||
|
||||
@@ -683,13 +682,13 @@ impl<'a> KeyEvent<'a> {
|
||||
/// See [the KeyEvent docs](https://developer.android.com/reference/android/view/KeyEvent#getAction())
|
||||
#[inline]
|
||||
pub fn action(&self) -> KeyAction {
|
||||
let action = self.ga_event.action;
|
||||
let action = self.ga_event.action as u32;
|
||||
action.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn action_button(&self) -> KeyAction {
|
||||
let action = self.ga_event.action;
|
||||
let action = self.ga_event.action as u32;
|
||||
action.into()
|
||||
}
|
||||
|
||||
@@ -719,7 +718,7 @@ impl<'a> KeyEvent<'a> {
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getkeycode)
|
||||
#[inline]
|
||||
pub fn key_code(&self) -> Keycode {
|
||||
let keycode = self.ga_event.keyCode;
|
||||
let keycode = self.ga_event.keyCode as u32;
|
||||
keycode.into()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,12 @@ use std::sync::Weak;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use jni::objects::JObject;
|
||||
use jni::refs::Global;
|
||||
use libc::c_void;
|
||||
use log::{error, trace};
|
||||
|
||||
use jni_sys::*;
|
||||
use jni::sys::*;
|
||||
|
||||
use ndk_sys::ALooper_wake;
|
||||
use ndk_sys::{ALooper, ALooper_pollAll};
|
||||
@@ -21,9 +23,11 @@ use ndk::configuration::Configuration;
|
||||
use ndk::native_window::NativeWindow;
|
||||
|
||||
use crate::error::InternalResult;
|
||||
use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding};
|
||||
use crate::jni_utils::{self, CloneJavaVM};
|
||||
use crate::util::{abort_on_panic, forward_stdio_to_logcat, log_panic, try_get_path_from_ptr};
|
||||
use crate::input::{device_key_character_map, Axis, KeyCharacterMap, TextInputAction};
|
||||
use crate::util::{
|
||||
abort_on_panic, forward_stdio_to_logcat, init_android_main_thread, log_panic,
|
||||
try_get_path_from_ptr,
|
||||
};
|
||||
use crate::{
|
||||
AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
|
||||
};
|
||||
@@ -119,32 +123,31 @@ impl AndroidAppWaker {
|
||||
}
|
||||
|
||||
impl AndroidApp {
|
||||
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>, jvm: CloneJavaVM) -> Self {
|
||||
let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp
|
||||
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>, jvm: jni::JavaVM) -> Self {
|
||||
// We attach to the thread before creating the AndroidApp
|
||||
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
|
||||
if let Err(err) = crate::input::jni_init(env) {
|
||||
panic!("Failed to init JNI bindings: {err:?}");
|
||||
};
|
||||
|
||||
let key_map_binding = match KeyCharacterMapBinding::new(&mut env) {
|
||||
Ok(b) => b,
|
||||
Err(err) => {
|
||||
panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}");
|
||||
}
|
||||
};
|
||||
// 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));
|
||||
|
||||
// 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 {
|
||||
jvm,
|
||||
native_app: NativeAppGlue { ptr },
|
||||
config: ConfigurationRef::new(config),
|
||||
native_window: Default::default(),
|
||||
key_map_binding: Arc::new(key_map_binding),
|
||||
key_maps: Mutex::new(HashMap::new()),
|
||||
input_receiver: Mutex::new(None),
|
||||
})),
|
||||
}
|
||||
Ok(Self {
|
||||
inner: Arc::new(RwLock::new(AndroidAppInner {
|
||||
jvm: jvm.clone(),
|
||||
native_app: NativeAppGlue { ptr },
|
||||
config: ConfigurationRef::new(config),
|
||||
native_window: Default::default(),
|
||||
key_maps: Mutex::new(HashMap::new()),
|
||||
input_receiver: Mutex::new(None),
|
||||
})),
|
||||
})
|
||||
})
|
||||
.expect("Failed to create AndroidApp instance")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,9 +177,6 @@ impl NativeAppGlue {
|
||||
};
|
||||
let out_ptr = &mut out_state as *mut TextInputState;
|
||||
|
||||
let app_ptr = self.as_ptr();
|
||||
(*app_ptr).textInputState = 0;
|
||||
|
||||
// NEON WARNING:
|
||||
//
|
||||
// It's not clearly documented but the GameActivity API over the
|
||||
@@ -204,6 +204,14 @@ impl NativeAppGlue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_text_input_state(&self) -> TextInputState {
|
||||
unsafe {
|
||||
let app_ptr = self.as_ptr();
|
||||
(*app_ptr).textInputState = 0;
|
||||
}
|
||||
self.text_input_state()
|
||||
}
|
||||
|
||||
// TODO: move into a trait
|
||||
pub fn set_text_input_state(&self, state: TextInputState) {
|
||||
unsafe {
|
||||
@@ -247,18 +255,27 @@ impl NativeAppGlue {
|
||||
ffi::GameActivity_setTextInputState(activity, &ffi_state as *const _);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_pending_editor_action(&self) -> Option<i32> {
|
||||
unsafe {
|
||||
let app_ptr = self.as_ptr();
|
||||
if (*app_ptr).pendingEditorAction {
|
||||
(*app_ptr).pendingEditorAction = false;
|
||||
Some((*app_ptr).editorAction)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AndroidAppInner {
|
||||
pub(crate) jvm: CloneJavaVM,
|
||||
pub(crate) jvm: jni::JavaVM,
|
||||
native_app: NativeAppGlue,
|
||||
config: ConfigurationRef,
|
||||
native_window: RwLock<Option<NativeWindow>>,
|
||||
|
||||
/// Shared JNI bindings for the `KeyCharacterMap` class
|
||||
key_map_binding: Arc<KeyCharacterMapBinding>,
|
||||
|
||||
/// A table of `KeyCharacterMap`s per `InputDevice` ID
|
||||
/// these are used to be able to map key presses to unicode
|
||||
/// characters
|
||||
@@ -351,46 +368,61 @@ impl AndroidAppInner {
|
||||
let cmd = match cmd_i as ffi::NativeAppGlueAppCmd {
|
||||
//NativeAppGlueAppCmd_UNUSED_APP_CMD_INPUT_CHANGED => AndroidAppMainEvent::InputChanged,
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_INIT_WINDOW => {
|
||||
MainEvent::InitWindow {}
|
||||
Some(MainEvent::InitWindow {})
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_TERM_WINDOW => {
|
||||
MainEvent::TerminateWindow {}
|
||||
Some(MainEvent::TerminateWindow {})
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_RESIZED => {
|
||||
MainEvent::WindowResized {}
|
||||
Some(MainEvent::WindowResized {})
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_WINDOW_REDRAW_NEEDED => {
|
||||
MainEvent::RedrawNeeded {}
|
||||
Some(MainEvent::RedrawNeeded {})
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_CONTENT_RECT_CHANGED => {
|
||||
MainEvent::ContentRectChanged {}
|
||||
Some(MainEvent::ContentRectChanged {})
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_GAINED_FOCUS => {
|
||||
MainEvent::GainedFocus
|
||||
Some(MainEvent::GainedFocus)
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_LOST_FOCUS => {
|
||||
MainEvent::LostFocus
|
||||
Some(MainEvent::LostFocus)
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_CONFIG_CHANGED => {
|
||||
MainEvent::ConfigChanged {}
|
||||
Some(MainEvent::ConfigChanged {})
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_LOW_MEMORY => {
|
||||
MainEvent::LowMemory
|
||||
Some(MainEvent::LowMemory)
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_START => {
|
||||
Some(MainEvent::Start)
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_RESUME => {
|
||||
Some(MainEvent::Resume {
|
||||
loader: StateLoader { app: self },
|
||||
})
|
||||
}
|
||||
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 {
|
||||
Some(MainEvent::SaveState {
|
||||
saver: StateSaver { app: self },
|
||||
}
|
||||
})
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_PAUSE => {
|
||||
Some(MainEvent::Pause)
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_STOP => Some(MainEvent::Stop),
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_DESTROY => {
|
||||
Some(MainEvent::Destroy)
|
||||
}
|
||||
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 {}
|
||||
Some(MainEvent::InsetsChanged {})
|
||||
}
|
||||
ffi::NativeAppGlueAppCmd_APP_CMD_SOFTWARE_KB_VIS_CHANGED => {
|
||||
// NOOP: we ignore these events because they are driven by a
|
||||
// potentially-unreliable heuristic (based on watching for
|
||||
// inset changes) and we don't currently have a public event
|
||||
// for exposing this state.
|
||||
None
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
@@ -399,30 +431,35 @@ impl AndroidAppInner {
|
||||
|
||||
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));
|
||||
if let Some(cmd) = cmd {
|
||||
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);
|
||||
@@ -533,11 +570,7 @@ impl AndroidAppInner {
|
||||
let key_map = match guard.entry(device_id) {
|
||||
std::collections::hash_map::Entry::Occupied(occupied) => occupied.get().clone(),
|
||||
std::collections::hash_map::Entry::Vacant(vacant) => {
|
||||
let character_map = jni_utils::device_key_character_map(
|
||||
self.jvm.clone(),
|
||||
self.key_map_binding.clone(),
|
||||
device_id,
|
||||
)?;
|
||||
let character_map = device_key_character_map(self.jvm.clone(), device_id)?;
|
||||
vacant.insert(character_map.clone());
|
||||
character_map
|
||||
}
|
||||
@@ -547,11 +580,13 @@ impl AndroidAppInner {
|
||||
}
|
||||
|
||||
pub fn enable_motion_axis(&mut self, axis: Axis) {
|
||||
unsafe { ffi::GameActivityPointerAxes_enableAxis(axis.into()) }
|
||||
let axis: u32 = axis.into();
|
||||
unsafe { ffi::GameActivityPointerAxes_enableAxis(axis as i32) }
|
||||
}
|
||||
|
||||
pub fn disable_motion_axis(&mut self, axis: Axis) {
|
||||
unsafe { ffi::GameActivityPointerAxes_disableAxis(axis.into()) }
|
||||
let axis: u32 = axis.into();
|
||||
unsafe { ffi::GameActivityPointerAxes_disableAxis(axis as i32) }
|
||||
}
|
||||
|
||||
pub fn create_waker(&self) -> AndroidAppWaker {
|
||||
@@ -782,7 +817,8 @@ impl<'a> From<Arc<InputReceiver>> for InputIteratorInner<'a> {
|
||||
_receiver: receiver,
|
||||
buffered,
|
||||
native_app,
|
||||
text_event_checked: false,
|
||||
ime_text_input_state_checked: false,
|
||||
ime_editor_action_checked: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -799,7 +835,8 @@ pub(crate) struct InputIteratorInner<'a> {
|
||||
|
||||
buffered: Option<BufferedEvents<'a>>,
|
||||
native_app: NativeAppGlue,
|
||||
text_event_checked: bool,
|
||||
ime_text_input_state_checked: bool,
|
||||
ime_editor_action_checked: bool,
|
||||
}
|
||||
|
||||
impl InputIteratorInner<'_> {
|
||||
@@ -819,8 +856,10 @@ impl InputIteratorInner<'_> {
|
||||
self.buffered = None;
|
||||
}
|
||||
|
||||
if !self.text_event_checked {
|
||||
self.text_event_checked = true;
|
||||
// We make sure any input state changes are sent before we check
|
||||
// for editor actions, so actions will apply to the latest state.
|
||||
if !self.ime_text_input_state_checked {
|
||||
self.ime_text_input_state_checked = true;
|
||||
unsafe {
|
||||
let app_ptr = self.native_app.as_ptr();
|
||||
|
||||
@@ -832,12 +871,21 @@ impl InputIteratorInner<'_> {
|
||||
// the compiler isn't reordering code so this gets flagged
|
||||
// before the java main thread really updates the state.
|
||||
if (*app_ptr).textInputState != 0 {
|
||||
let state = self.native_app.text_input_state(); // Will clear .textInputState
|
||||
let state = self.native_app.take_text_input_state(); // Will clear .textInputState
|
||||
let _ = callback(&InputEvent::TextEvent(state));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.ime_editor_action_checked {
|
||||
self.ime_editor_action_checked = true;
|
||||
if let Some(action) = self.native_app.take_pending_editor_action() {
|
||||
let _ = callback(&InputEvent::TextAction(TextInputAction::from(action)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -876,7 +924,7 @@ pub unsafe extern "C" fn Java_com_google_androidgamesdk_GameActivity_initializeN
|
||||
jasset_mgr: jobject,
|
||||
saved_state: jbyteArray,
|
||||
java_config: jobject,
|
||||
) -> jni_sys::jlong {
|
||||
) -> jlong {
|
||||
Java_com_google_androidgamesdk_GameActivity_initializeNativeCode_C(
|
||||
env,
|
||||
java_game_activity,
|
||||
@@ -910,53 +958,56 @@ pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) {
|
||||
abort_on_panic(|| {
|
||||
let _join_log_forwarder = forward_stdio_to_logcat();
|
||||
|
||||
let jvm = unsafe {
|
||||
let (jvm, jni_activity) = unsafe {
|
||||
let jvm = (*(*native_app).activity).vm;
|
||||
let activity: jobject = (*(*native_app).activity).javaGameActivity;
|
||||
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
|
||||
|
||||
let jvm = CloneJavaVM::from_raw(jvm).unwrap();
|
||||
// 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
|
||||
jvm.attach_current_thread_permanently().unwrap();
|
||||
jvm
|
||||
(jni::JavaVM::from_raw(jvm), activity)
|
||||
};
|
||||
// Note: At this point we can assume jni::JavaVM::singleton is initialized
|
||||
|
||||
unsafe {
|
||||
// Name thread - this needs to happen here after attaching to a JVM thread,
|
||||
// since that changes the thread name to something like "Thread-2".
|
||||
let thread_name = std::ffi::CStr::from_bytes_with_nul(b"android_main\0").unwrap();
|
||||
libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
|
||||
// Note: the GameActivity implementation will have already attached the main thread to the
|
||||
// JVM before calling _rust_glue_entry so we don't to set the thread name via
|
||||
// attach_current_thread_with_config since that won't actually create a new attachment.
|
||||
//
|
||||
// Calling .attach_current_thread will ensure that the `jni` crate knows about the
|
||||
// attachment, as a convenience.
|
||||
jvm.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
// SAFETY: We know jni_activity is a valid JNI global ref to an Activity instance
|
||||
let jni_activity = unsafe { env.as_cast_raw::<Global<JObject>>(&jni_activity)? };
|
||||
|
||||
let app = AndroidApp::from_ptr(NonNull::new(native_app).unwrap(), jvm.clone());
|
||||
if let Err(err) = init_android_main_thread(&jvm, &jni_activity) {
|
||||
eprintln!("Failed to name Java thread and set thread context class loader: {err}");
|
||||
}
|
||||
|
||||
// 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(log_panic);
|
||||
unsafe {
|
||||
let app = AndroidApp::from_ptr(NonNull::new(native_app).unwrap(), jvm.clone());
|
||||
// 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(log_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);
|
||||
// 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);
|
||||
|
||||
// This should detach automatically but lets detach explicitly to avoid depending
|
||||
// on the TLS trickery in `jni-rs`
|
||||
jvm.detach_current_thread();
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.expect("Failed to attach thread to JVM");
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
pub use ndk::event::{
|
||||
Axis, EdgeFlags, KeyAction, KeyEventFlags, Keycode, MetaState, MotionAction, MotionEventFlags,
|
||||
Source, SourceClass, ToolType,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
|
||||
pub use crate::activity_impl::input::*;
|
||||
use crate::InputStatus;
|
||||
@@ -9,6 +6,238 @@ use crate::InputStatus;
|
||||
mod sdk;
|
||||
pub use sdk::*;
|
||||
|
||||
/// An enum representing the source of an [`MotionEvent`] or [`KeyEvent`]
|
||||
///
|
||||
/// See [the InputDevice docs](https://developer.android.com/reference/android/view/InputDevice#SOURCE_ANY)
|
||||
///
|
||||
/// # Android Extensible Enum
|
||||
///
|
||||
/// This is a runtime [extensible enum](`crate#android-extensible-enums`) and
|
||||
/// should be handled similar to a `#[non_exhaustive]` enum to maintain
|
||||
/// forwards compatibility.
|
||||
///
|
||||
/// This implements `Into<u32>` and `From<u32>` for converting to/from Android
|
||||
/// SDK integer values.
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
#[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,
|
||||
|
||||
// We need to consider that the enum variants may be extended across
|
||||
// different versions of Android (i.e. effectively at runtime) but at the
|
||||
// same time we don't want it to be an API break to extend this enum in
|
||||
// future releases of `android-activity` with new variants from the latest
|
||||
// NDK/SDK.
|
||||
//
|
||||
// We can't just use `#[non_exhaustive]` because that only really helps
|
||||
// when adding new variants in sync with android-activity releases.
|
||||
//
|
||||
// On the other hand we also can't rely on a catch-all `Unknown(u32)` that
|
||||
// only really helps with unknown variants seen at runtime.
|
||||
//
|
||||
// What we aim for instead is to have a hidden catch-all variant that
|
||||
// is considered (practically) unmatchable so code is forced to have
|
||||
// a `unknown => {}` catch-all pattern match that will cover unknown variants
|
||||
// either in the form of Rust variants added in future versions or
|
||||
// in the form of an `__Unknown(u32)` integer that represents an unknown
|
||||
// variant seen at runtime.
|
||||
//
|
||||
// Any `unknown => {}` pattern match can rely on `IntoPrimitive` to convert
|
||||
// the `unknown` variant to the integer that comes from the Android SDK
|
||||
// in case that values needs to be passed on, even without knowing its
|
||||
// semantic meaning at compile time.
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
// ndk_sys doesn't currently have the `TRACKBALL` flag so we define our
|
||||
// own internal class constants for now
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
impl Source {
|
||||
#[inline]
|
||||
pub fn is_button_class(self) -> bool {
|
||||
let class = SourceFlags::from_bits_truncate(self.into());
|
||||
class.contains(SourceFlags::BUTTON)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_pointer_class(self) -> bool {
|
||||
let class = SourceFlags::from_bits_truncate(self.into());
|
||||
class.contains(SourceFlags::POINTER)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_trackball_class(self) -> bool {
|
||||
let class = SourceFlags::from_bits_truncate(self.into());
|
||||
class.contains(SourceFlags::TRACKBALL)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_position_class(self) -> bool {
|
||||
let class = SourceFlags::from_bits_truncate(self.into());
|
||||
class.contains(SourceFlags::POSITION)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_joystick_class(self) -> bool {
|
||||
let class = SourceFlags::from_bits_truncate(self.into());
|
||||
class.contains(SourceFlags::JOYSTICK)
|
||||
}
|
||||
}
|
||||
|
||||
/// A bitfield representing the state of modifier keys during an event.
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-25)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct MetaState(pub u32);
|
||||
|
||||
impl MetaState {
|
||||
#[inline]
|
||||
pub fn alt_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_ALT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn alt_left_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_ALT_LEFT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn alt_right_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_ALT_RIGHT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn shift_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_SHIFT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn shift_left_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_SHIFT_LEFT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn shift_right_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_SHIFT_RIGHT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn sym_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_SYM_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn function_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_FUNCTION_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn ctrl_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_CTRL_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn ctrl_left_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_CTRL_LEFT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn ctrl_right_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_CTRL_RIGHT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn meta_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_META_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn meta_left_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_META_LEFT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn meta_right_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_META_RIGHT_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn caps_lock_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_CAPS_LOCK_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn num_lock_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_NUM_LOCK_ON != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn scroll_lock_on(self) -> bool {
|
||||
self.0 & ndk_sys::AMETA_SCROLL_LOCK_ON != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ndk::event::MetaState> for MetaState {
|
||||
fn from(value: ndk::event::MetaState) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A motion action.
|
||||
///
|
||||
/// See [the NDK
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-29)
|
||||
///
|
||||
/// # Android Extensible Enum
|
||||
///
|
||||
/// This is a runtime [extensible enum](`crate#android-extensible-enums`) and
|
||||
/// should be handled similar to a `#[non_exhaustive]` enum to maintain
|
||||
/// forwards compatibility.
|
||||
///
|
||||
/// This implements `Into<u32>` and `From<u32>` for converting to/from Android
|
||||
/// SDK integer values.
|
||||
///
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
#[repr(u32)]
|
||||
pub enum MotionAction {
|
||||
Down = ndk_sys::AMOTION_EVENT_ACTION_DOWN,
|
||||
Up = ndk_sys::AMOTION_EVENT_ACTION_UP,
|
||||
Move = ndk_sys::AMOTION_EVENT_ACTION_MOVE,
|
||||
Cancel = ndk_sys::AMOTION_EVENT_ACTION_CANCEL,
|
||||
Outside = ndk_sys::AMOTION_EVENT_ACTION_OUTSIDE,
|
||||
PointerDown = ndk_sys::AMOTION_EVENT_ACTION_POINTER_DOWN,
|
||||
PointerUp = ndk_sys::AMOTION_EVENT_ACTION_POINTER_UP,
|
||||
HoverMove = ndk_sys::AMOTION_EVENT_ACTION_HOVER_MOVE,
|
||||
Scroll = ndk_sys::AMOTION_EVENT_ACTION_SCROLL,
|
||||
HoverEnter = ndk_sys::AMOTION_EVENT_ACTION_HOVER_ENTER,
|
||||
HoverExit = ndk_sys::AMOTION_EVENT_ACTION_HOVER_EXIT,
|
||||
ButtonPress = ndk_sys::AMOTION_EVENT_ACTION_BUTTON_PRESS,
|
||||
ButtonRelease = ndk_sys::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// Identifies buttons that are associated with motion events.
|
||||
///
|
||||
/// See [the NDK
|
||||
@@ -40,6 +269,606 @@ pub enum Button {
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// An axis of a motion event.
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-32)
|
||||
///
|
||||
/// # Android Extensible Enum
|
||||
///
|
||||
/// This is a runtime [extensible enum](`crate#android-extensible-enums`) and
|
||||
/// should be handled similar to a `#[non_exhaustive]` enum to maintain
|
||||
/// forwards compatibility.
|
||||
///
|
||||
/// This implements `Into<u32>` and `From<u32>` for converting to/from Android
|
||||
/// SDK integer values.
|
||||
///
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
#[repr(u32)]
|
||||
pub enum Axis {
|
||||
X = ndk_sys::AMOTION_EVENT_AXIS_X,
|
||||
Y = ndk_sys::AMOTION_EVENT_AXIS_Y,
|
||||
Pressure = ndk_sys::AMOTION_EVENT_AXIS_PRESSURE,
|
||||
Size = ndk_sys::AMOTION_EVENT_AXIS_SIZE,
|
||||
TouchMajor = ndk_sys::AMOTION_EVENT_AXIS_TOUCH_MAJOR,
|
||||
TouchMinor = ndk_sys::AMOTION_EVENT_AXIS_TOUCH_MINOR,
|
||||
ToolMajor = ndk_sys::AMOTION_EVENT_AXIS_TOOL_MAJOR,
|
||||
ToolMinor = ndk_sys::AMOTION_EVENT_AXIS_TOOL_MINOR,
|
||||
Orientation = ndk_sys::AMOTION_EVENT_AXIS_ORIENTATION,
|
||||
Vscroll = ndk_sys::AMOTION_EVENT_AXIS_VSCROLL,
|
||||
Hscroll = ndk_sys::AMOTION_EVENT_AXIS_HSCROLL,
|
||||
Z = ndk_sys::AMOTION_EVENT_AXIS_Z,
|
||||
Rx = ndk_sys::AMOTION_EVENT_AXIS_RX,
|
||||
Ry = ndk_sys::AMOTION_EVENT_AXIS_RY,
|
||||
Rz = ndk_sys::AMOTION_EVENT_AXIS_RZ,
|
||||
HatX = ndk_sys::AMOTION_EVENT_AXIS_HAT_X,
|
||||
HatY = ndk_sys::AMOTION_EVENT_AXIS_HAT_Y,
|
||||
Ltrigger = ndk_sys::AMOTION_EVENT_AXIS_LTRIGGER,
|
||||
Rtrigger = ndk_sys::AMOTION_EVENT_AXIS_RTRIGGER,
|
||||
Throttle = ndk_sys::AMOTION_EVENT_AXIS_THROTTLE,
|
||||
Rudder = ndk_sys::AMOTION_EVENT_AXIS_RUDDER,
|
||||
Wheel = ndk_sys::AMOTION_EVENT_AXIS_WHEEL,
|
||||
Gas = ndk_sys::AMOTION_EVENT_AXIS_GAS,
|
||||
Brake = ndk_sys::AMOTION_EVENT_AXIS_BRAKE,
|
||||
Distance = ndk_sys::AMOTION_EVENT_AXIS_DISTANCE,
|
||||
Tilt = ndk_sys::AMOTION_EVENT_AXIS_TILT,
|
||||
Scroll = ndk_sys::AMOTION_EVENT_AXIS_SCROLL,
|
||||
RelativeX = ndk_sys::AMOTION_EVENT_AXIS_RELATIVE_X,
|
||||
RelativeY = ndk_sys::AMOTION_EVENT_AXIS_RELATIVE_Y,
|
||||
Generic1 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_1,
|
||||
Generic2 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_2,
|
||||
Generic3 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_3,
|
||||
Generic4 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_4,
|
||||
Generic5 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_5,
|
||||
Generic6 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_6,
|
||||
Generic7 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_7,
|
||||
Generic8 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_8,
|
||||
Generic9 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_9,
|
||||
Generic10 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_10,
|
||||
Generic11 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_11,
|
||||
Generic12 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_12,
|
||||
Generic13 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_13,
|
||||
Generic14 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_14,
|
||||
Generic15 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_15,
|
||||
Generic16 = ndk_sys::AMOTION_EVENT_AXIS_GENERIC_16,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// The tool type of a pointer.
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-48)
|
||||
///
|
||||
/// # Android Extensible Enum
|
||||
///
|
||||
/// This is a runtime [extensible enum](`crate#android-extensible-enums`) and
|
||||
/// should be handled similar to a `#[non_exhaustive]` enum to maintain
|
||||
/// forwards compatibility.
|
||||
///
|
||||
/// Implements `Into<u32>` and `From<u32>` for converting to/from Android SDK
|
||||
/// integer values.
|
||||
///
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
#[repr(u32)]
|
||||
pub enum ToolType {
|
||||
/// Unknown tool type.
|
||||
///
|
||||
/// This constant is used when the tool type is not known or is not relevant, such as for a trackball or other non-pointing device.
|
||||
Unknown = ndk_sys::AMOTION_EVENT_TOOL_TYPE_UNKNOWN,
|
||||
|
||||
/// The tool is a finger.
|
||||
Finger = ndk_sys::AMOTION_EVENT_TOOL_TYPE_FINGER,
|
||||
|
||||
/// The tool is a stylus.
|
||||
Stylus = ndk_sys::AMOTION_EVENT_TOOL_TYPE_STYLUS,
|
||||
|
||||
/// The tool is a mouse.
|
||||
Mouse = ndk_sys::AMOTION_EVENT_TOOL_TYPE_MOUSE,
|
||||
|
||||
/// The tool is an eraser or a stylus being used in an inverted posture.
|
||||
Eraser = ndk_sys::AMOTION_EVENT_TOOL_TYPE_ERASER,
|
||||
|
||||
/// The tool is a palm and should be rejected
|
||||
Palm = ndk_sys::AMOTION_EVENT_TOOL_TYPE_PALM,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// A bitfield representing the state of buttons during a motion event.
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-33)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ButtonState(pub u32);
|
||||
|
||||
impl ButtonState {
|
||||
#[inline]
|
||||
pub fn primary(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_BUTTON_PRIMARY != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn secondary(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_BUTTON_SECONDARY != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn teriary(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_BUTTON_TERTIARY != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn back(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_BUTTON_BACK != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn forward(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_BUTTON_FORWARD != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn stylus_primary(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_BUTTON_STYLUS_PRIMARY != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn stylus_secondary(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_BUTTON_STYLUS_SECONDARY != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ndk::event::ButtonState> for ButtonState {
|
||||
fn from(value: ndk::event::ButtonState) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A bitfield representing which edges were touched by a motion event.
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-31)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct EdgeFlags(pub u32);
|
||||
|
||||
impl EdgeFlags {
|
||||
#[inline]
|
||||
pub fn top(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_EDGE_FLAG_TOP != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn bottom(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_EDGE_FLAG_BOTTOM != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn left(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_EDGE_FLAG_LEFT != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn right(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_EDGE_FLAG_RIGHT != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ndk::event::EdgeFlags> for EdgeFlags {
|
||||
fn from(value: ndk::event::EdgeFlags) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Flags associated with this [`MotionEvent`].
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-30)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct MotionEventFlags(pub u32);
|
||||
|
||||
impl MotionEventFlags {
|
||||
#[inline]
|
||||
pub fn window_is_obscured(self) -> bool {
|
||||
self.0 & ndk_sys::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ndk::event::MotionEventFlags> for MotionEventFlags {
|
||||
fn from(value: ndk::event::MotionEventFlags) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Key actions.
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-27)
|
||||
///
|
||||
/// # Android Extensible Enum
|
||||
///
|
||||
/// This is a runtime [extensible enum](`crate#android-extensible-enums`) and
|
||||
/// should be handled similar to a `#[non_exhaustive]` enum to maintain
|
||||
/// forwards compatibility.
|
||||
///
|
||||
/// Implements `Into<u32>` and `From<u32>` for converting to/from Android SDK
|
||||
/// integer values.
|
||||
///
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
#[repr(u32)]
|
||||
pub enum KeyAction {
|
||||
Down = ndk_sys::AKEY_EVENT_ACTION_DOWN,
|
||||
Up = ndk_sys::AKEY_EVENT_ACTION_UP,
|
||||
Multiple = ndk_sys::AKEY_EVENT_ACTION_MULTIPLE,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// Key codes.
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-39)
|
||||
///
|
||||
/// # Android Extensible Enum
|
||||
///
|
||||
/// This is a runtime [extensible enum](`crate#android-extensible-enums`) and
|
||||
/// should be handled similar to a `#[non_exhaustive]` enum to maintain
|
||||
/// forwards compatibility.
|
||||
///
|
||||
/// Implements `Into<u32>` and `From<u32>` for converting to/from Android SDK
|
||||
/// integer values.
|
||||
///
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
#[repr(u32)]
|
||||
pub enum Keycode {
|
||||
Unknown = ndk_sys::AKEYCODE_UNKNOWN,
|
||||
SoftLeft = ndk_sys::AKEYCODE_SOFT_LEFT,
|
||||
SoftRight = ndk_sys::AKEYCODE_SOFT_RIGHT,
|
||||
Home = ndk_sys::AKEYCODE_HOME,
|
||||
Back = ndk_sys::AKEYCODE_BACK,
|
||||
Call = ndk_sys::AKEYCODE_CALL,
|
||||
Endcall = ndk_sys::AKEYCODE_ENDCALL,
|
||||
Keycode0 = ndk_sys::AKEYCODE_0,
|
||||
Keycode1 = ndk_sys::AKEYCODE_1,
|
||||
Keycode2 = ndk_sys::AKEYCODE_2,
|
||||
Keycode3 = ndk_sys::AKEYCODE_3,
|
||||
Keycode4 = ndk_sys::AKEYCODE_4,
|
||||
Keycode5 = ndk_sys::AKEYCODE_5,
|
||||
Keycode6 = ndk_sys::AKEYCODE_6,
|
||||
Keycode7 = ndk_sys::AKEYCODE_7,
|
||||
Keycode8 = ndk_sys::AKEYCODE_8,
|
||||
Keycode9 = ndk_sys::AKEYCODE_9,
|
||||
Star = ndk_sys::AKEYCODE_STAR,
|
||||
Pound = ndk_sys::AKEYCODE_POUND,
|
||||
DpadUp = ndk_sys::AKEYCODE_DPAD_UP,
|
||||
DpadDown = ndk_sys::AKEYCODE_DPAD_DOWN,
|
||||
DpadLeft = ndk_sys::AKEYCODE_DPAD_LEFT,
|
||||
DpadRight = ndk_sys::AKEYCODE_DPAD_RIGHT,
|
||||
DpadCenter = ndk_sys::AKEYCODE_DPAD_CENTER,
|
||||
VolumeUp = ndk_sys::AKEYCODE_VOLUME_UP,
|
||||
VolumeDown = ndk_sys::AKEYCODE_VOLUME_DOWN,
|
||||
Power = ndk_sys::AKEYCODE_POWER,
|
||||
Camera = ndk_sys::AKEYCODE_CAMERA,
|
||||
Clear = ndk_sys::AKEYCODE_CLEAR,
|
||||
A = ndk_sys::AKEYCODE_A,
|
||||
B = ndk_sys::AKEYCODE_B,
|
||||
C = ndk_sys::AKEYCODE_C,
|
||||
D = ndk_sys::AKEYCODE_D,
|
||||
E = ndk_sys::AKEYCODE_E,
|
||||
F = ndk_sys::AKEYCODE_F,
|
||||
G = ndk_sys::AKEYCODE_G,
|
||||
H = ndk_sys::AKEYCODE_H,
|
||||
I = ndk_sys::AKEYCODE_I,
|
||||
J = ndk_sys::AKEYCODE_J,
|
||||
K = ndk_sys::AKEYCODE_K,
|
||||
L = ndk_sys::AKEYCODE_L,
|
||||
M = ndk_sys::AKEYCODE_M,
|
||||
N = ndk_sys::AKEYCODE_N,
|
||||
O = ndk_sys::AKEYCODE_O,
|
||||
P = ndk_sys::AKEYCODE_P,
|
||||
Q = ndk_sys::AKEYCODE_Q,
|
||||
R = ndk_sys::AKEYCODE_R,
|
||||
S = ndk_sys::AKEYCODE_S,
|
||||
T = ndk_sys::AKEYCODE_T,
|
||||
U = ndk_sys::AKEYCODE_U,
|
||||
V = ndk_sys::AKEYCODE_V,
|
||||
W = ndk_sys::AKEYCODE_W,
|
||||
X = ndk_sys::AKEYCODE_X,
|
||||
Y = ndk_sys::AKEYCODE_Y,
|
||||
Z = ndk_sys::AKEYCODE_Z,
|
||||
Comma = ndk_sys::AKEYCODE_COMMA,
|
||||
Period = ndk_sys::AKEYCODE_PERIOD,
|
||||
AltLeft = ndk_sys::AKEYCODE_ALT_LEFT,
|
||||
AltRight = ndk_sys::AKEYCODE_ALT_RIGHT,
|
||||
ShiftLeft = ndk_sys::AKEYCODE_SHIFT_LEFT,
|
||||
ShiftRight = ndk_sys::AKEYCODE_SHIFT_RIGHT,
|
||||
Tab = ndk_sys::AKEYCODE_TAB,
|
||||
Space = ndk_sys::AKEYCODE_SPACE,
|
||||
Sym = ndk_sys::AKEYCODE_SYM,
|
||||
Explorer = ndk_sys::AKEYCODE_EXPLORER,
|
||||
Envelope = ndk_sys::AKEYCODE_ENVELOPE,
|
||||
Enter = ndk_sys::AKEYCODE_ENTER,
|
||||
Del = ndk_sys::AKEYCODE_DEL,
|
||||
Grave = ndk_sys::AKEYCODE_GRAVE,
|
||||
Minus = ndk_sys::AKEYCODE_MINUS,
|
||||
Equals = ndk_sys::AKEYCODE_EQUALS,
|
||||
LeftBracket = ndk_sys::AKEYCODE_LEFT_BRACKET,
|
||||
RightBracket = ndk_sys::AKEYCODE_RIGHT_BRACKET,
|
||||
Backslash = ndk_sys::AKEYCODE_BACKSLASH,
|
||||
Semicolon = ndk_sys::AKEYCODE_SEMICOLON,
|
||||
Apostrophe = ndk_sys::AKEYCODE_APOSTROPHE,
|
||||
Slash = ndk_sys::AKEYCODE_SLASH,
|
||||
At = ndk_sys::AKEYCODE_AT,
|
||||
Num = ndk_sys::AKEYCODE_NUM,
|
||||
Headsethook = ndk_sys::AKEYCODE_HEADSETHOOK,
|
||||
Focus = ndk_sys::AKEYCODE_FOCUS,
|
||||
Plus = ndk_sys::AKEYCODE_PLUS,
|
||||
Menu = ndk_sys::AKEYCODE_MENU,
|
||||
Notification = ndk_sys::AKEYCODE_NOTIFICATION,
|
||||
Search = ndk_sys::AKEYCODE_SEARCH,
|
||||
MediaPlayPause = ndk_sys::AKEYCODE_MEDIA_PLAY_PAUSE,
|
||||
MediaStop = ndk_sys::AKEYCODE_MEDIA_STOP,
|
||||
MediaNext = ndk_sys::AKEYCODE_MEDIA_NEXT,
|
||||
MediaPrevious = ndk_sys::AKEYCODE_MEDIA_PREVIOUS,
|
||||
MediaRewind = ndk_sys::AKEYCODE_MEDIA_REWIND,
|
||||
MediaFastForward = ndk_sys::AKEYCODE_MEDIA_FAST_FORWARD,
|
||||
Mute = ndk_sys::AKEYCODE_MUTE,
|
||||
PageUp = ndk_sys::AKEYCODE_PAGE_UP,
|
||||
PageDown = ndk_sys::AKEYCODE_PAGE_DOWN,
|
||||
Pictsymbols = ndk_sys::AKEYCODE_PICTSYMBOLS,
|
||||
SwitchCharset = ndk_sys::AKEYCODE_SWITCH_CHARSET,
|
||||
ButtonA = ndk_sys::AKEYCODE_BUTTON_A,
|
||||
ButtonB = ndk_sys::AKEYCODE_BUTTON_B,
|
||||
ButtonC = ndk_sys::AKEYCODE_BUTTON_C,
|
||||
ButtonX = ndk_sys::AKEYCODE_BUTTON_X,
|
||||
ButtonY = ndk_sys::AKEYCODE_BUTTON_Y,
|
||||
ButtonZ = ndk_sys::AKEYCODE_BUTTON_Z,
|
||||
ButtonL1 = ndk_sys::AKEYCODE_BUTTON_L1,
|
||||
ButtonR1 = ndk_sys::AKEYCODE_BUTTON_R1,
|
||||
ButtonL2 = ndk_sys::AKEYCODE_BUTTON_L2,
|
||||
ButtonR2 = ndk_sys::AKEYCODE_BUTTON_R2,
|
||||
ButtonThumbl = ndk_sys::AKEYCODE_BUTTON_THUMBL,
|
||||
ButtonThumbr = ndk_sys::AKEYCODE_BUTTON_THUMBR,
|
||||
ButtonStart = ndk_sys::AKEYCODE_BUTTON_START,
|
||||
ButtonSelect = ndk_sys::AKEYCODE_BUTTON_SELECT,
|
||||
ButtonMode = ndk_sys::AKEYCODE_BUTTON_MODE,
|
||||
Escape = ndk_sys::AKEYCODE_ESCAPE,
|
||||
ForwardDel = ndk_sys::AKEYCODE_FORWARD_DEL,
|
||||
CtrlLeft = ndk_sys::AKEYCODE_CTRL_LEFT,
|
||||
CtrlRight = ndk_sys::AKEYCODE_CTRL_RIGHT,
|
||||
CapsLock = ndk_sys::AKEYCODE_CAPS_LOCK,
|
||||
ScrollLock = ndk_sys::AKEYCODE_SCROLL_LOCK,
|
||||
MetaLeft = ndk_sys::AKEYCODE_META_LEFT,
|
||||
MetaRight = ndk_sys::AKEYCODE_META_RIGHT,
|
||||
Function = ndk_sys::AKEYCODE_FUNCTION,
|
||||
Sysrq = ndk_sys::AKEYCODE_SYSRQ,
|
||||
Break = ndk_sys::AKEYCODE_BREAK,
|
||||
MoveHome = ndk_sys::AKEYCODE_MOVE_HOME,
|
||||
MoveEnd = ndk_sys::AKEYCODE_MOVE_END,
|
||||
Insert = ndk_sys::AKEYCODE_INSERT,
|
||||
Forward = ndk_sys::AKEYCODE_FORWARD,
|
||||
MediaPlay = ndk_sys::AKEYCODE_MEDIA_PLAY,
|
||||
MediaPause = ndk_sys::AKEYCODE_MEDIA_PAUSE,
|
||||
MediaClose = ndk_sys::AKEYCODE_MEDIA_CLOSE,
|
||||
MediaEject = ndk_sys::AKEYCODE_MEDIA_EJECT,
|
||||
MediaRecord = ndk_sys::AKEYCODE_MEDIA_RECORD,
|
||||
F1 = ndk_sys::AKEYCODE_F1,
|
||||
F2 = ndk_sys::AKEYCODE_F2,
|
||||
F3 = ndk_sys::AKEYCODE_F3,
|
||||
F4 = ndk_sys::AKEYCODE_F4,
|
||||
F5 = ndk_sys::AKEYCODE_F5,
|
||||
F6 = ndk_sys::AKEYCODE_F6,
|
||||
F7 = ndk_sys::AKEYCODE_F7,
|
||||
F8 = ndk_sys::AKEYCODE_F8,
|
||||
F9 = ndk_sys::AKEYCODE_F9,
|
||||
F10 = ndk_sys::AKEYCODE_F10,
|
||||
F11 = ndk_sys::AKEYCODE_F11,
|
||||
F12 = ndk_sys::AKEYCODE_F12,
|
||||
NumLock = ndk_sys::AKEYCODE_NUM_LOCK,
|
||||
Numpad0 = ndk_sys::AKEYCODE_NUMPAD_0,
|
||||
Numpad1 = ndk_sys::AKEYCODE_NUMPAD_1,
|
||||
Numpad2 = ndk_sys::AKEYCODE_NUMPAD_2,
|
||||
Numpad3 = ndk_sys::AKEYCODE_NUMPAD_3,
|
||||
Numpad4 = ndk_sys::AKEYCODE_NUMPAD_4,
|
||||
Numpad5 = ndk_sys::AKEYCODE_NUMPAD_5,
|
||||
Numpad6 = ndk_sys::AKEYCODE_NUMPAD_6,
|
||||
Numpad7 = ndk_sys::AKEYCODE_NUMPAD_7,
|
||||
Numpad8 = ndk_sys::AKEYCODE_NUMPAD_8,
|
||||
Numpad9 = ndk_sys::AKEYCODE_NUMPAD_9,
|
||||
NumpadDivide = ndk_sys::AKEYCODE_NUMPAD_DIVIDE,
|
||||
NumpadMultiply = ndk_sys::AKEYCODE_NUMPAD_MULTIPLY,
|
||||
NumpadSubtract = ndk_sys::AKEYCODE_NUMPAD_SUBTRACT,
|
||||
NumpadAdd = ndk_sys::AKEYCODE_NUMPAD_ADD,
|
||||
NumpadDot = ndk_sys::AKEYCODE_NUMPAD_DOT,
|
||||
NumpadComma = ndk_sys::AKEYCODE_NUMPAD_COMMA,
|
||||
NumpadEnter = ndk_sys::AKEYCODE_NUMPAD_ENTER,
|
||||
NumpadEquals = ndk_sys::AKEYCODE_NUMPAD_EQUALS,
|
||||
NumpadLeftParen = ndk_sys::AKEYCODE_NUMPAD_LEFT_PAREN,
|
||||
NumpadRightParen = ndk_sys::AKEYCODE_NUMPAD_RIGHT_PAREN,
|
||||
VolumeMute = ndk_sys::AKEYCODE_VOLUME_MUTE,
|
||||
Info = ndk_sys::AKEYCODE_INFO,
|
||||
ChannelUp = ndk_sys::AKEYCODE_CHANNEL_UP,
|
||||
ChannelDown = ndk_sys::AKEYCODE_CHANNEL_DOWN,
|
||||
ZoomIn = ndk_sys::AKEYCODE_ZOOM_IN,
|
||||
ZoomOut = ndk_sys::AKEYCODE_ZOOM_OUT,
|
||||
Tv = ndk_sys::AKEYCODE_TV,
|
||||
Window = ndk_sys::AKEYCODE_WINDOW,
|
||||
Guide = ndk_sys::AKEYCODE_GUIDE,
|
||||
Dvr = ndk_sys::AKEYCODE_DVR,
|
||||
Bookmark = ndk_sys::AKEYCODE_BOOKMARK,
|
||||
Captions = ndk_sys::AKEYCODE_CAPTIONS,
|
||||
Settings = ndk_sys::AKEYCODE_SETTINGS,
|
||||
TvPower = ndk_sys::AKEYCODE_TV_POWER,
|
||||
TvInput = ndk_sys::AKEYCODE_TV_INPUT,
|
||||
StbPower = ndk_sys::AKEYCODE_STB_POWER,
|
||||
StbInput = ndk_sys::AKEYCODE_STB_INPUT,
|
||||
AvrPower = ndk_sys::AKEYCODE_AVR_POWER,
|
||||
AvrInput = ndk_sys::AKEYCODE_AVR_INPUT,
|
||||
ProgRed = ndk_sys::AKEYCODE_PROG_RED,
|
||||
ProgGreen = ndk_sys::AKEYCODE_PROG_GREEN,
|
||||
ProgYellow = ndk_sys::AKEYCODE_PROG_YELLOW,
|
||||
ProgBlue = ndk_sys::AKEYCODE_PROG_BLUE,
|
||||
AppSwitch = ndk_sys::AKEYCODE_APP_SWITCH,
|
||||
Button1 = ndk_sys::AKEYCODE_BUTTON_1,
|
||||
Button2 = ndk_sys::AKEYCODE_BUTTON_2,
|
||||
Button3 = ndk_sys::AKEYCODE_BUTTON_3,
|
||||
Button4 = ndk_sys::AKEYCODE_BUTTON_4,
|
||||
Button5 = ndk_sys::AKEYCODE_BUTTON_5,
|
||||
Button6 = ndk_sys::AKEYCODE_BUTTON_6,
|
||||
Button7 = ndk_sys::AKEYCODE_BUTTON_7,
|
||||
Button8 = ndk_sys::AKEYCODE_BUTTON_8,
|
||||
Button9 = ndk_sys::AKEYCODE_BUTTON_9,
|
||||
Button10 = ndk_sys::AKEYCODE_BUTTON_10,
|
||||
Button11 = ndk_sys::AKEYCODE_BUTTON_11,
|
||||
Button12 = ndk_sys::AKEYCODE_BUTTON_12,
|
||||
Button13 = ndk_sys::AKEYCODE_BUTTON_13,
|
||||
Button14 = ndk_sys::AKEYCODE_BUTTON_14,
|
||||
Button15 = ndk_sys::AKEYCODE_BUTTON_15,
|
||||
Button16 = ndk_sys::AKEYCODE_BUTTON_16,
|
||||
LanguageSwitch = ndk_sys::AKEYCODE_LANGUAGE_SWITCH,
|
||||
MannerMode = ndk_sys::AKEYCODE_MANNER_MODE,
|
||||
Keycode3dMode = ndk_sys::AKEYCODE_3D_MODE,
|
||||
Contacts = ndk_sys::AKEYCODE_CONTACTS,
|
||||
Calendar = ndk_sys::AKEYCODE_CALENDAR,
|
||||
Music = ndk_sys::AKEYCODE_MUSIC,
|
||||
Calculator = ndk_sys::AKEYCODE_CALCULATOR,
|
||||
ZenkakuHankaku = ndk_sys::AKEYCODE_ZENKAKU_HANKAKU,
|
||||
Eisu = ndk_sys::AKEYCODE_EISU,
|
||||
Muhenkan = ndk_sys::AKEYCODE_MUHENKAN,
|
||||
Henkan = ndk_sys::AKEYCODE_HENKAN,
|
||||
KatakanaHiragana = ndk_sys::AKEYCODE_KATAKANA_HIRAGANA,
|
||||
Yen = ndk_sys::AKEYCODE_YEN,
|
||||
Ro = ndk_sys::AKEYCODE_RO,
|
||||
Kana = ndk_sys::AKEYCODE_KANA,
|
||||
Assist = ndk_sys::AKEYCODE_ASSIST,
|
||||
BrightnessDown = ndk_sys::AKEYCODE_BRIGHTNESS_DOWN,
|
||||
BrightnessUp = ndk_sys::AKEYCODE_BRIGHTNESS_UP,
|
||||
MediaAudioTrack = ndk_sys::AKEYCODE_MEDIA_AUDIO_TRACK,
|
||||
Sleep = ndk_sys::AKEYCODE_SLEEP,
|
||||
Wakeup = ndk_sys::AKEYCODE_WAKEUP,
|
||||
Pairing = ndk_sys::AKEYCODE_PAIRING,
|
||||
MediaTopMenu = ndk_sys::AKEYCODE_MEDIA_TOP_MENU,
|
||||
Keycode11 = ndk_sys::AKEYCODE_11,
|
||||
Keycode12 = ndk_sys::AKEYCODE_12,
|
||||
LastChannel = ndk_sys::AKEYCODE_LAST_CHANNEL,
|
||||
TvDataService = ndk_sys::AKEYCODE_TV_DATA_SERVICE,
|
||||
VoiceAssist = ndk_sys::AKEYCODE_VOICE_ASSIST,
|
||||
TvRadioService = ndk_sys::AKEYCODE_TV_RADIO_SERVICE,
|
||||
TvTeletext = ndk_sys::AKEYCODE_TV_TELETEXT,
|
||||
TvNumberEntry = ndk_sys::AKEYCODE_TV_NUMBER_ENTRY,
|
||||
TvTerrestrialAnalog = ndk_sys::AKEYCODE_TV_TERRESTRIAL_ANALOG,
|
||||
TvTerrestrialDigital = ndk_sys::AKEYCODE_TV_TERRESTRIAL_DIGITAL,
|
||||
TvSatellite = ndk_sys::AKEYCODE_TV_SATELLITE,
|
||||
TvSatelliteBs = ndk_sys::AKEYCODE_TV_SATELLITE_BS,
|
||||
TvSatelliteCs = ndk_sys::AKEYCODE_TV_SATELLITE_CS,
|
||||
TvSatelliteService = ndk_sys::AKEYCODE_TV_SATELLITE_SERVICE,
|
||||
TvNetwork = ndk_sys::AKEYCODE_TV_NETWORK,
|
||||
TvAntennaCable = ndk_sys::AKEYCODE_TV_ANTENNA_CABLE,
|
||||
TvInputHdmi1 = ndk_sys::AKEYCODE_TV_INPUT_HDMI_1,
|
||||
TvInputHdmi2 = ndk_sys::AKEYCODE_TV_INPUT_HDMI_2,
|
||||
TvInputHdmi3 = ndk_sys::AKEYCODE_TV_INPUT_HDMI_3,
|
||||
TvInputHdmi4 = ndk_sys::AKEYCODE_TV_INPUT_HDMI_4,
|
||||
TvInputComposite1 = ndk_sys::AKEYCODE_TV_INPUT_COMPOSITE_1,
|
||||
TvInputComposite2 = ndk_sys::AKEYCODE_TV_INPUT_COMPOSITE_2,
|
||||
TvInputComponent1 = ndk_sys::AKEYCODE_TV_INPUT_COMPONENT_1,
|
||||
TvInputComponent2 = ndk_sys::AKEYCODE_TV_INPUT_COMPONENT_2,
|
||||
TvInputVga1 = ndk_sys::AKEYCODE_TV_INPUT_VGA_1,
|
||||
TvAudioDescription = ndk_sys::AKEYCODE_TV_AUDIO_DESCRIPTION,
|
||||
TvAudioDescriptionMixUp = ndk_sys::AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP,
|
||||
TvAudioDescriptionMixDown = ndk_sys::AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN,
|
||||
TvZoomMode = ndk_sys::AKEYCODE_TV_ZOOM_MODE,
|
||||
TvContentsMenu = ndk_sys::AKEYCODE_TV_CONTENTS_MENU,
|
||||
TvMediaContextMenu = ndk_sys::AKEYCODE_TV_MEDIA_CONTEXT_MENU,
|
||||
TvTimerProgramming = ndk_sys::AKEYCODE_TV_TIMER_PROGRAMMING,
|
||||
Help = ndk_sys::AKEYCODE_HELP,
|
||||
NavigatePrevious = ndk_sys::AKEYCODE_NAVIGATE_PREVIOUS,
|
||||
NavigateNext = ndk_sys::AKEYCODE_NAVIGATE_NEXT,
|
||||
NavigateIn = ndk_sys::AKEYCODE_NAVIGATE_IN,
|
||||
NavigateOut = ndk_sys::AKEYCODE_NAVIGATE_OUT,
|
||||
StemPrimary = ndk_sys::AKEYCODE_STEM_PRIMARY,
|
||||
Stem1 = ndk_sys::AKEYCODE_STEM_1,
|
||||
Stem2 = ndk_sys::AKEYCODE_STEM_2,
|
||||
Stem3 = ndk_sys::AKEYCODE_STEM_3,
|
||||
DpadUpLeft = ndk_sys::AKEYCODE_DPAD_UP_LEFT,
|
||||
DpadDownLeft = ndk_sys::AKEYCODE_DPAD_DOWN_LEFT,
|
||||
DpadUpRight = ndk_sys::AKEYCODE_DPAD_UP_RIGHT,
|
||||
DpadDownRight = ndk_sys::AKEYCODE_DPAD_DOWN_RIGHT,
|
||||
MediaSkipForward = ndk_sys::AKEYCODE_MEDIA_SKIP_FORWARD,
|
||||
MediaSkipBackward = ndk_sys::AKEYCODE_MEDIA_SKIP_BACKWARD,
|
||||
MediaStepForward = ndk_sys::AKEYCODE_MEDIA_STEP_FORWARD,
|
||||
MediaStepBackward = ndk_sys::AKEYCODE_MEDIA_STEP_BACKWARD,
|
||||
SoftSleep = ndk_sys::AKEYCODE_SOFT_SLEEP,
|
||||
Cut = ndk_sys::AKEYCODE_CUT,
|
||||
Copy = ndk_sys::AKEYCODE_COPY,
|
||||
Paste = ndk_sys::AKEYCODE_PASTE,
|
||||
SystemNavigationUp = ndk_sys::AKEYCODE_SYSTEM_NAVIGATION_UP,
|
||||
SystemNavigationDown = ndk_sys::AKEYCODE_SYSTEM_NAVIGATION_DOWN,
|
||||
SystemNavigationLeft = ndk_sys::AKEYCODE_SYSTEM_NAVIGATION_LEFT,
|
||||
SystemNavigationRight = ndk_sys::AKEYCODE_SYSTEM_NAVIGATION_RIGHT,
|
||||
AllApps = ndk_sys::AKEYCODE_ALL_APPS,
|
||||
Refresh = ndk_sys::AKEYCODE_REFRESH,
|
||||
ThumbsUp = ndk_sys::AKEYCODE_THUMBS_UP,
|
||||
ThumbsDown = ndk_sys::AKEYCODE_THUMBS_DOWN,
|
||||
ProfileSwitch = ndk_sys::AKEYCODE_PROFILE_SWITCH,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(u32),
|
||||
}
|
||||
|
||||
/// Flags associated with [`KeyEvent`].
|
||||
///
|
||||
/// See [the NDK docs](https://developer.android.com/ndk/reference/group/input#anonymous-enum-28)
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct KeyEventFlags(pub u32);
|
||||
|
||||
impl KeyEventFlags {
|
||||
#[inline]
|
||||
pub fn cancelled(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_CANCELED != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn cancelled_long_press(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_CANCELED_LONG_PRESS != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn editor_action(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_EDITOR_ACTION != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn fallback(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_FALLBACK != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_system(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_FROM_SYSTEM != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn keep_touch_mode(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_KEEP_TOUCH_MODE != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn long_press(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_LONG_PRESS != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn soft_keyboard(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_SOFT_KEYBOARD != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn tracking(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_TRACKING != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn virtual_hard_key(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY != 0
|
||||
}
|
||||
#[inline]
|
||||
pub fn woke_here(&self) -> bool {
|
||||
self.0 & ndk_sys::AKEY_EVENT_FLAG_WOKE_HERE != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ndk::event::KeyEventFlags> for KeyEventFlags {
|
||||
fn from(value: ndk::event::KeyEventFlags) -> Self {
|
||||
Self(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct holds a span within a region of text from `start` to `end`.
|
||||
///
|
||||
/// The `start` index may be greater than the `end` index (swapping `start` and `end` will represent the same span)
|
||||
@@ -78,6 +907,33 @@ pub struct TextInputState {
|
||||
pub compose_region: Option<TextSpan>,
|
||||
}
|
||||
|
||||
// Represents the action button on a soft keyboard.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive, num_enum::IntoPrimitive)]
|
||||
#[non_exhaustive]
|
||||
#[repr(i32)]
|
||||
pub enum TextInputAction {
|
||||
/// Let receiver decide what logical action to perform
|
||||
Unspecified = 0,
|
||||
/// No action - receiver could instead interpret as an "enter" key that inserts a newline character
|
||||
None = 1,
|
||||
/// Navigate to the input location (such as a URL)
|
||||
Go = 2,
|
||||
/// Search based on the input text
|
||||
Search = 3,
|
||||
/// Send the input to the target
|
||||
Send = 4,
|
||||
/// Move to the next input field
|
||||
Next = 5,
|
||||
/// Indicate that input is done
|
||||
Done = 6,
|
||||
/// Move to the previous input field
|
||||
Previous = 7,
|
||||
|
||||
#[doc(hidden)]
|
||||
#[num_enum(catch_all)]
|
||||
__Unknown(i32),
|
||||
}
|
||||
|
||||
/// An exclusive, lending iterator for input events
|
||||
pub struct InputIterator<'a> {
|
||||
pub(crate) inner: crate::activity_impl::InputIteratorInner<'a>,
|
||||
|
||||
+149
-209
@@ -1,21 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
use jni::sys::jint;
|
||||
use jni::{objects::Global, JavaVM};
|
||||
|
||||
use jni::{
|
||||
objects::{GlobalRef, JClass, JMethodID, JObject, JStaticMethodID, JValue},
|
||||
signature::{Primitive, ReturnType},
|
||||
JNIEnv,
|
||||
};
|
||||
use jni_sys::jint;
|
||||
|
||||
use crate::{
|
||||
input::{Keycode, MetaState},
|
||||
jni_utils::CloneJavaVM,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{AppError, InternalAppError},
|
||||
jni_utils,
|
||||
};
|
||||
use crate::error::{AppError, InternalAppError, InternalResult};
|
||||
use crate::input::{Keycode, MetaState};
|
||||
use crate::jni_utils;
|
||||
|
||||
/// An enum representing the types of keyboards that may generate key events
|
||||
///
|
||||
@@ -80,157 +68,86 @@ pub enum KeyMapChar {
|
||||
CombiningAccent(char),
|
||||
}
|
||||
|
||||
// I've also tried to think here about how to we could potentially automatically
|
||||
// generate a binding struct like `KeyCharacterMapBinding` with a procmacro and
|
||||
// so have intentionally limited the `Binding` being a very thin, un-opinionated
|
||||
// wrapper based on basic JNI types.
|
||||
|
||||
/// Lower-level JNI binding for `KeyCharacterMap` class only holds 'static state
|
||||
/// and can be shared with an `Arc` ref count.
|
||||
///
|
||||
/// The separation here also neatly helps us separate `InternalAppError` from
|
||||
/// `AppError` for mapping JNI errors without exposing any `jni-rs` types in the
|
||||
/// public API.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct KeyCharacterMapBinding {
|
||||
//vm: JavaVM,
|
||||
klass: GlobalRef,
|
||||
get_method_id: JMethodID,
|
||||
get_dead_char_method_id: JStaticMethodID,
|
||||
get_keyboard_type_method_id: JMethodID,
|
||||
jni::bind_java_type! {
|
||||
pub(crate) AKeyCharacterMap => "android.view.KeyCharacterMap",
|
||||
methods {
|
||||
priv fn _get(key_code: jint, meta_state: jint) -> jint,
|
||||
priv static fn _get_dead_char(accent_char: jint, base_char: jint) -> jint,
|
||||
priv fn _get_keyboard_type() -> jint,
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyCharacterMapBinding {
|
||||
pub(crate) fn new(env: &mut JNIEnv) -> Result<Self, InternalAppError> {
|
||||
let binding = env.with_local_frame::<_, _, InternalAppError>(10, |env| {
|
||||
let klass = env.find_class("android/view/KeyCharacterMap")?; // Creates a local ref
|
||||
Ok(Self {
|
||||
get_method_id: env.get_method_id(&klass, "get", "(II)I")?,
|
||||
get_dead_char_method_id: env.get_static_method_id(
|
||||
&klass,
|
||||
"getDeadChar",
|
||||
"(II)I",
|
||||
)?,
|
||||
get_keyboard_type_method_id: env.get_method_id(&klass, "getKeyboardType", "()I")?,
|
||||
klass: env.new_global_ref(&klass)?,
|
||||
})
|
||||
})?;
|
||||
Ok(binding)
|
||||
}
|
||||
|
||||
pub fn get<'local>(
|
||||
impl AKeyCharacterMap<'_> {
|
||||
pub(crate) fn get<'local>(
|
||||
&self,
|
||||
env: &'local mut JNIEnv,
|
||||
key_map: impl AsRef<JObject<'local>>,
|
||||
env: &'local mut jni::Env,
|
||||
key_code: jint,
|
||||
meta_state: jint,
|
||||
) -> Result<jint, InternalAppError> {
|
||||
let key_map = key_map.as_ref();
|
||||
|
||||
// Safety:
|
||||
// - we know our global `key_map` reference is non-null and valid.
|
||||
// - we know `get_method_id` remains valid
|
||||
// - we know that the signature of KeyCharacterMap::get is `(int, int) -> int`
|
||||
// - we know this won't leak any local references as a side effect
|
||||
//
|
||||
// We know it's ok to unwrap the `.i()` value since we explicitly
|
||||
// specify the return type as `Int`
|
||||
let unicode = unsafe {
|
||||
env.call_method_unchecked(
|
||||
key_map,
|
||||
self.get_method_id,
|
||||
ReturnType::Primitive(Primitive::Int),
|
||||
&[
|
||||
JValue::Int(key_code).as_jni(),
|
||||
JValue::Int(meta_state).as_jni(),
|
||||
],
|
||||
)
|
||||
}
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?;
|
||||
Ok(unicode.i().unwrap())
|
||||
self._get(env, key_code, meta_state)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
}
|
||||
|
||||
pub fn get_dead_char(
|
||||
&self,
|
||||
env: &mut JNIEnv,
|
||||
pub(crate) fn get_dead_char(
|
||||
env: &mut jni::Env,
|
||||
accent_char: jint,
|
||||
base_char: jint,
|
||||
) -> Result<jint, InternalAppError> {
|
||||
// Safety:
|
||||
// - we know `get_dead_char_method_id` remains valid
|
||||
// - we know that KeyCharacterMap::getDeadKey is a static method
|
||||
// - we know that the signature of KeyCharacterMap::getDeadKey is `(int, int) -> int`
|
||||
// - we know this won't leak any local references as a side effect
|
||||
//
|
||||
// We know it's ok to unwrap the `.i()` value since we explicitly
|
||||
// specify the return type as `Int`
|
||||
|
||||
// Urgh, it's pretty terrible that there's no ergonomic/safe way to get a JClass reference from a GlobalRef
|
||||
// Safety: we don't do anything that would try to delete the JClass as if it were a real local reference
|
||||
let klass = unsafe { JClass::from_raw(self.klass.as_obj().as_raw()) };
|
||||
let unicode = unsafe {
|
||||
env.call_static_method_unchecked(
|
||||
&klass,
|
||||
self.get_dead_char_method_id,
|
||||
ReturnType::Primitive(Primitive::Int),
|
||||
&[
|
||||
JValue::Int(accent_char).as_jni(),
|
||||
JValue::Int(base_char).as_jni(),
|
||||
],
|
||||
)
|
||||
}
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?;
|
||||
Ok(unicode.i().unwrap())
|
||||
Self::_get_dead_char(env, accent_char, base_char)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
}
|
||||
|
||||
pub fn get_keyboard_type<'local>(
|
||||
pub(crate) fn get_keyboard_type<'local>(
|
||||
&self,
|
||||
env: &'local mut JNIEnv,
|
||||
key_map: impl AsRef<JObject<'local>>,
|
||||
env: &'local mut jni::Env,
|
||||
) -> Result<jint, InternalAppError> {
|
||||
let key_map = key_map.as_ref();
|
||||
|
||||
// Safety:
|
||||
// - we know our global `key_map` reference is non-null and valid.
|
||||
// - we know `get_keyboard_type_method_id` remains valid
|
||||
// - we know that the signature of KeyCharacterMap::getKeyboardType is `() -> int`
|
||||
// - we know this won't leak any local references as a side effect
|
||||
//
|
||||
// We know it's ok to unwrap the `.i()` value since we explicitly
|
||||
// specify the return type as `Int`
|
||||
Ok(unsafe {
|
||||
env.call_method_unchecked(
|
||||
key_map,
|
||||
self.get_keyboard_type_method_id,
|
||||
ReturnType::Primitive(Primitive::Int),
|
||||
&[],
|
||||
)
|
||||
}
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))?
|
||||
.i()
|
||||
.unwrap())
|
||||
self._get_keyboard_type(env)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
}
|
||||
}
|
||||
|
||||
jni::bind_java_type! {
|
||||
rust_type = AInputDevice,
|
||||
java_type = "android.view.InputDevice",
|
||||
type_map {
|
||||
AKeyCharacterMap => "android.view.KeyCharacterMap",
|
||||
},
|
||||
methods {
|
||||
static fn get_device(id: jint) -> AInputDevice,
|
||||
fn get_key_character_map() -> AKeyCharacterMap,
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly initialize the JNI bindings so we can get and early, upfront,
|
||||
// error if something is wrong.
|
||||
pub fn jni_init(env: &jni::Env) -> jni::errors::Result<()> {
|
||||
let _ = AKeyCharacterMapAPI::get(env, &Default::default())?;
|
||||
let _ = AInputDeviceAPI::get(env, &Default::default())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Describes the keys provided by a keyboard device and their associated labels.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct KeyCharacterMap {
|
||||
jvm: CloneJavaVM,
|
||||
binding: Arc<KeyCharacterMapBinding>,
|
||||
key_map: GlobalRef,
|
||||
jvm: JavaVM,
|
||||
key_map: Global<AKeyCharacterMap<'static>>,
|
||||
}
|
||||
impl Clone for KeyCharacterMap {
|
||||
fn clone(&self) -> Self {
|
||||
let jvm = self.jvm.clone();
|
||||
jvm.attach_current_thread(|env| -> jni::errors::Result<_> {
|
||||
Ok(Self {
|
||||
jvm: jvm.clone(),
|
||||
key_map: env.new_global_ref(&self.key_map)?,
|
||||
})
|
||||
})
|
||||
.expect("Failed to attach thread to JVM and clone key map")
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyCharacterMap {
|
||||
pub(crate) fn new(
|
||||
jvm: CloneJavaVM,
|
||||
binding: Arc<KeyCharacterMapBinding>,
|
||||
key_map: GlobalRef,
|
||||
) -> Self {
|
||||
Self {
|
||||
jvm,
|
||||
binding,
|
||||
key_map,
|
||||
}
|
||||
pub(crate) fn new(jvm: JavaVM, key_map: Global<AKeyCharacterMap<'static>>) -> Self {
|
||||
Self { jvm, key_map }
|
||||
}
|
||||
|
||||
/// Gets the Unicode character generated by the specified [`Keycode`] and [`MetaState`] combination.
|
||||
@@ -246,40 +163,37 @@ impl KeyCharacterMap {
|
||||
/// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception
|
||||
/// is caught.
|
||||
pub fn get(&self, key_code: Keycode, meta_state: MetaState) -> Result<KeyMapChar, AppError> {
|
||||
let key_code = key_code.into();
|
||||
let key_code: u32 = key_code.into();
|
||||
let key_code = key_code as i32;
|
||||
let meta_state = meta_state.0 as i32;
|
||||
|
||||
// Since we expect this API to be called from the `main` thread then we expect to already be
|
||||
// attached to the JVM
|
||||
//
|
||||
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
|
||||
// borrow rules that ensure we can only add local references to the top JNI frame.
|
||||
let mut env = self.jvm.get_env().map_err(|err| {
|
||||
let vm = self.jvm.clone();
|
||||
vm.attach_current_thread(|env| -> InternalResult<_> {
|
||||
let unicode = self.key_map.get(env, key_code, meta_state)?;
|
||||
let unicode = unicode as u32;
|
||||
|
||||
const COMBINING_ACCENT: u32 = 0x80000000;
|
||||
const COMBINING_ACCENT_MASK: u32 = !COMBINING_ACCENT;
|
||||
|
||||
if unicode == 0 {
|
||||
Ok(KeyMapChar::None)
|
||||
} else if unicode & COMBINING_ACCENT == COMBINING_ACCENT {
|
||||
let accent = unicode & COMBINING_ACCENT_MASK;
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(KeyMapChar::CombiningAccent(unsafe {
|
||||
char::from_u32_unchecked(accent)
|
||||
}))
|
||||
} else {
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(KeyMapChar::Unicode(unsafe {
|
||||
char::from_u32_unchecked(unicode)
|
||||
}))
|
||||
}
|
||||
})
|
||||
.map_err(|err| {
|
||||
let err: InternalAppError = err.into();
|
||||
err
|
||||
})?;
|
||||
let unicode = self
|
||||
.binding
|
||||
.get(&mut env, self.key_map.as_obj(), key_code, meta_state)?;
|
||||
let unicode = unicode as u32;
|
||||
|
||||
const COMBINING_ACCENT: u32 = 0x80000000;
|
||||
const COMBINING_ACCENT_MASK: u32 = !COMBINING_ACCENT;
|
||||
|
||||
if unicode == 0 {
|
||||
Ok(KeyMapChar::None)
|
||||
} else if unicode & COMBINING_ACCENT == COMBINING_ACCENT {
|
||||
let accent = unicode & COMBINING_ACCENT_MASK;
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(KeyMapChar::CombiningAccent(unsafe {
|
||||
char::from_u32_unchecked(accent)
|
||||
}))
|
||||
} else {
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(KeyMapChar::Unicode(unsafe {
|
||||
char::from_u32_unchecked(unicode)
|
||||
}))
|
||||
}
|
||||
err.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the character that is produced by combining the dead key producing accent with the key producing character c.
|
||||
@@ -295,28 +209,24 @@ impl KeyCharacterMap {
|
||||
accent_char: char,
|
||||
base_char: char,
|
||||
) -> Result<Option<char>, AppError> {
|
||||
let accent_char = accent_char as jni_sys::jint;
|
||||
let base_char = base_char as jni_sys::jint;
|
||||
let accent_char = accent_char as jni::sys::jint;
|
||||
let base_char = base_char as jni::sys::jint;
|
||||
|
||||
// Since we expect this API to be called from the `main` thread then we expect to already be
|
||||
// attached to the JVM
|
||||
//
|
||||
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
|
||||
// borrow rules that ensure we can only add local references to the top JNI frame.
|
||||
let mut env = self.jvm.get_env().map_err(|err| {
|
||||
let vm = self.jvm.clone();
|
||||
vm.attach_current_thread(|env| -> InternalResult<_> {
|
||||
let unicode = AKeyCharacterMap::get_dead_char(env, accent_char, base_char)?;
|
||||
let unicode = unicode as u32;
|
||||
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(if unicode == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { char::from_u32_unchecked(unicode) })
|
||||
})
|
||||
})
|
||||
.map_err(|err| {
|
||||
let err: InternalAppError = err.into();
|
||||
err
|
||||
})?;
|
||||
let unicode = self
|
||||
.binding
|
||||
.get_dead_char(&mut env, accent_char, base_char)?;
|
||||
let unicode = unicode as u32;
|
||||
|
||||
// Safety: assumes Android key maps don't contain invalid unicode characters
|
||||
Ok(if unicode == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { char::from_u32_unchecked(unicode) })
|
||||
err.into()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -330,19 +240,49 @@ impl KeyCharacterMap {
|
||||
/// a [`AppError::JavaError`] in case there is a spurious JNI error or an exception
|
||||
/// is caught.
|
||||
pub fn get_keyboard_type(&self) -> Result<KeyboardType, AppError> {
|
||||
// Since we expect this API to be called from the `main` thread then we expect to already be
|
||||
// attached to the JVM
|
||||
//
|
||||
// Safety: there's no other JNIEnv in scope so this env can't be used to subvert the mutable
|
||||
// borrow rules that ensure we can only add local references to the top JNI frame.
|
||||
let mut env = self.jvm.get_env().map_err(|err| {
|
||||
let vm = self.jvm.clone();
|
||||
vm.attach_current_thread(|env| -> InternalResult<_> {
|
||||
let keyboard_type = self.key_map.get_keyboard_type(env)?;
|
||||
let keyboard_type = keyboard_type as u32;
|
||||
Ok(keyboard_type.into())
|
||||
})
|
||||
.map_err(|err| {
|
||||
let err: InternalAppError = err.into();
|
||||
err
|
||||
})?;
|
||||
let keyboard_type = self
|
||||
.binding
|
||||
.get_keyboard_type(&mut env, self.key_map.as_obj())?;
|
||||
let keyboard_type = keyboard_type as u32;
|
||||
Ok(keyboard_type.into())
|
||||
err.into()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn device_key_character_map_with_env(
|
||||
env: &mut jni::Env<'_>,
|
||||
device_id: i32,
|
||||
) -> jni::errors::Result<KeyCharacterMap> {
|
||||
let device = AInputDevice::get_device(env, device_id)?;
|
||||
if device.is_null() {
|
||||
// This isn't really an error from a JNI perspective but we would only expect
|
||||
// this to return null for a device ID of zero or an invalid device ID.
|
||||
log::error!("No input device with id {}", device_id);
|
||||
return Err(jni::errors::Error::WrongObjectType);
|
||||
}
|
||||
let character_map = device.get_key_character_map(env)?;
|
||||
let character_map = env.new_global_ref(character_map)?;
|
||||
Ok(KeyCharacterMap::new(
|
||||
env.get_java_vm().clone(),
|
||||
character_map,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn device_key_character_map(
|
||||
jvm: JavaVM,
|
||||
device_id: i32,
|
||||
) -> InternalResult<KeyCharacterMap> {
|
||||
jvm.attach_current_thread(|env| {
|
||||
if device_id == 0 {
|
||||
return Err(InternalAppError::JniBadArgument(
|
||||
"Can't get key character map for non-physical device_id 0".into(),
|
||||
));
|
||||
}
|
||||
device_key_character_map_with_env(env, device_id)
|
||||
.map_err(|err| jni_utils::clear_and_map_exception_to_err(env, err))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,46 +5,21 @@
|
||||
//!
|
||||
//! These utilities help us check + clear exceptions and map them into Rust Errors.
|
||||
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
use crate::error::InternalAppError;
|
||||
|
||||
use jni::{
|
||||
objects::{JObject, JString},
|
||||
JavaVM,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{InternalAppError, InternalResult},
|
||||
input::{KeyCharacterMap, KeyCharacterMapBinding},
|
||||
};
|
||||
|
||||
// TODO: JavaVM should implement Clone
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CloneJavaVM {
|
||||
pub jvm: JavaVM,
|
||||
}
|
||||
impl Clone for CloneJavaVM {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
jvm: unsafe { JavaVM::from_raw(self.jvm.get_java_vm_pointer()).unwrap() },
|
||||
}
|
||||
}
|
||||
}
|
||||
impl CloneJavaVM {
|
||||
pub unsafe fn from_raw(jvm: *mut jni_sys::JavaVM) -> InternalResult<Self> {
|
||||
Ok(Self {
|
||||
jvm: JavaVM::from_raw(jvm)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
unsafe impl Send for CloneJavaVM {}
|
||||
unsafe impl Sync for CloneJavaVM {}
|
||||
|
||||
impl Deref for CloneJavaVM {
|
||||
type Target = JavaVM;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.jvm
|
||||
fn try_get_stack_trace(
|
||||
env: &mut jni::Env<'_>,
|
||||
throwable: &jni::objects::JThrowable,
|
||||
) -> jni::errors::Result<String> {
|
||||
let stack_trace = throwable.get_stack_trace(env)?;
|
||||
let len = stack_trace.len(env)?;
|
||||
let mut trace = String::new();
|
||||
for i in 0..len {
|
||||
let element = stack_trace.get_element(env, i)?;
|
||||
let element_jstr = element.try_to_string(env)?;
|
||||
trace.push_str(&format!("{i}: {element_jstr}\n"));
|
||||
}
|
||||
Ok(trace)
|
||||
}
|
||||
|
||||
/// Use with `.map_err()` to map `jni::errors::Error::JavaException` into a
|
||||
@@ -55,41 +30,28 @@ impl Deref for CloneJavaVM {
|
||||
///
|
||||
/// This will also clear the exception
|
||||
pub(crate) fn clear_and_map_exception_to_err(
|
||||
env: &mut jni::JNIEnv<'_>,
|
||||
env: &mut jni::Env<'_>,
|
||||
err: jni::errors::Error,
|
||||
) -> InternalAppError {
|
||||
if matches!(err, jni::errors::Error::JavaException) {
|
||||
let result = env.with_local_frame::<_, _, InternalAppError>(5, |env| {
|
||||
let e = env.exception_occurred()?;
|
||||
assert!(!e.is_null()); // should only be called after receiving a JavaException Result
|
||||
env.exception_clear()?;
|
||||
|
||||
let class = env.get_object_class(&e)?;
|
||||
//let get_stack_trace_method = env.get_method_id(&class, "getStackTrace", "()[Ljava/lang/StackTraceElement;")?;
|
||||
let get_message_method =
|
||||
env.get_method_id(&class, "getMessage", "()Ljava/lang/String;")?;
|
||||
|
||||
let msg = unsafe {
|
||||
env.call_method_unchecked(
|
||||
&e,
|
||||
get_message_method,
|
||||
jni::signature::ReturnType::Object,
|
||||
&[],
|
||||
)?
|
||||
.l()
|
||||
.unwrap()
|
||||
let Some(e) = env.exception_occurred() else {
|
||||
// should only be called after receiving a JavaException Result
|
||||
unreachable!("JNI Error was JavaException but no exception was set");
|
||||
};
|
||||
let msg = unsafe { JString::from_raw(JObject::into_raw(msg)) };
|
||||
let msg = env.get_string(&msg)?;
|
||||
let msg: String = msg.into();
|
||||
|
||||
// TODO: get Java backtrace:
|
||||
/*
|
||||
if let JValue::Object(elements) = env.call_method_unchecked(&e, get_stack_trace_method, jni::signature::ReturnType::Array, &[])? {
|
||||
let elements = env.auto_local(elements);
|
||||
env.exception_clear();
|
||||
|
||||
let msg = e.get_message(env)?;
|
||||
let mut msg: String = msg.to_string();
|
||||
match try_get_stack_trace(env, &e) {
|
||||
Ok(stack_trace) => {
|
||||
msg.push_str("stack trace:\n");
|
||||
msg.push_str(&stack_trace);
|
||||
}
|
||||
Err(err) => {
|
||||
msg.push_str(&format!("\nfailed to get stack trace: {err:?}"));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
Ok(msg)
|
||||
});
|
||||
@@ -104,48 +66,3 @@ pub(crate) fn clear_and_map_exception_to_err(
|
||||
err.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn device_key_character_map(
|
||||
jvm: CloneJavaVM,
|
||||
key_map_binding: Arc<KeyCharacterMapBinding>,
|
||||
device_id: i32,
|
||||
) -> InternalResult<KeyCharacterMap> {
|
||||
// Don't really need to 'attach' since this should be called from the app's main thread that
|
||||
// should already be attached, but the redundancy should be fine
|
||||
//
|
||||
// Attach 'permanently' to avoid any chance of detaching the thread from the VM
|
||||
let mut env = jvm.attach_current_thread_permanently()?;
|
||||
|
||||
// We don't want to accidentally leak any local references while we
|
||||
// aren't going to be returning from here back to the JVM, to unwind, so
|
||||
// we make a local frame
|
||||
let character_map = env.with_local_frame::<_, _, jni::errors::Error>(10, |env| {
|
||||
let input_device_class = env.find_class("android/view/InputDevice")?; // Creates a local ref
|
||||
let device = env
|
||||
.call_static_method(
|
||||
input_device_class,
|
||||
"getDevice",
|
||||
"(I)Landroid/view/InputDevice;",
|
||||
&[device_id.into()],
|
||||
)?
|
||||
.l()?; // Creates a local ref
|
||||
|
||||
let character_map = env
|
||||
.call_method(
|
||||
&device,
|
||||
"getKeyCharacterMap",
|
||||
"()Landroid/view/KeyCharacterMap;",
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
let character_map = env.new_global_ref(character_map)?;
|
||||
|
||||
Ok(character_map)
|
||||
})?;
|
||||
|
||||
Ok(KeyCharacterMap::new(
|
||||
jvm.clone(),
|
||||
key_map_binding,
|
||||
character_map,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -566,7 +566,7 @@ impl AndroidApp {
|
||||
/// 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:
|
||||
/// If you use the [`jni`] crate you can could this as a [`JavaVM`] via:
|
||||
/// ```no_run
|
||||
/// # use jni::JavaVM;
|
||||
/// # let app: android_activity::AndroidApp = todo!();
|
||||
@@ -581,24 +581,28 @@ impl AndroidApp {
|
||||
|
||||
/// 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:
|
||||
/// If you use the [`jni`] crate you can cast this as a `JObject` reference via:
|
||||
/// ```no_run
|
||||
/// # use jni::objects::JObject;
|
||||
/// # let app: android_activity::AndroidApp = todo!();
|
||||
/// let activity = unsafe { JObject::from_raw(app.activity_as_ptr().cast()) };
|
||||
/// # use jni::refs::Global;
|
||||
/// # fn use_jni(env: &jni::Env, app: &android_activity::AndroidApp) -> jni::errors::Result<()> {
|
||||
/// let raw_activity_global = app.activity_as_ptr() as jni::sys::jobject;
|
||||
/// // SAFETY: The pointer is valid as long as `app` is valid.
|
||||
/// let activity = unsafe { env.as_cast_raw::<Global<JObject>>(&raw_activity_global)? };
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
///
|
||||
/// # 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`]
|
||||
/// in an [`Auto`] which would try to explicitly delete the reference
|
||||
/// when dropped. Similarly, don't wrap the reference as a [`Global`]
|
||||
/// 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
|
||||
/// [`Auto`]: https://docs.rs/jni/latest/jni/refs/struct.Auto.html
|
||||
/// [`Global`]: https://docs.rs/jni/latest/jni/refs/struct.Global.html
|
||||
pub fn activity_as_ptr(&self) -> *mut c_void {
|
||||
self.inner.read().unwrap().activity_as_ptr()
|
||||
}
|
||||
@@ -855,6 +859,9 @@ impl AndroidApp {
|
||||
/// Since this API needs to use JNI internally to call into the Android JVM it may return
|
||||
/// a [`error::AppError::JavaError`] in case there is a spurious JNI error or an exception
|
||||
/// is caught.
|
||||
///
|
||||
/// This API should not be called with a `device_id` of `0`, since that indicates a non-physical
|
||||
/// device and will result in a [`error::AppError::JavaError`].
|
||||
pub fn device_key_character_map(&self, device_id: i32) -> Result<KeyCharacterMap> {
|
||||
Ok(self
|
||||
.inner
|
||||
|
||||
@@ -9,11 +9,11 @@ use std::{
|
||||
sync::{Arc, Condvar, Mutex, Weak},
|
||||
};
|
||||
|
||||
use jni::{objects::JObject, refs::Global, vm::AttachConfig};
|
||||
use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow};
|
||||
|
||||
use crate::{
|
||||
jni_utils::CloneJavaVM,
|
||||
util::{abort_on_panic, forward_stdio_to_logcat, log_panic},
|
||||
util::{abort_on_panic, forward_stdio_to_logcat, init_android_main_thread, log_panic},
|
||||
ConfigurationRef,
|
||||
};
|
||||
|
||||
@@ -840,11 +840,9 @@ extern "C" fn ANativeActivity_onCreate(
|
||||
abort_on_panic(|| {
|
||||
let _join_log_forwarder = forward_stdio_to_logcat();
|
||||
|
||||
log::trace!(
|
||||
eprintln!(
|
||||
"Creating: {:p}, saved_state = {:p}, save_state_size = {}",
|
||||
activity,
|
||||
saved_state,
|
||||
saved_state_size
|
||||
activity, saved_state, saved_state_size
|
||||
);
|
||||
|
||||
// Conceptually we associate a glue reference with the JVM main thread, and another
|
||||
@@ -858,60 +856,7 @@ extern "C" fn ANativeActivity_onCreate(
|
||||
// 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 = abort_on_panic(|| unsafe {
|
||||
let na = activity;
|
||||
let jvm: *mut jni_sys::JavaVM = (*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());
|
||||
|
||||
let jvm = CloneJavaVM::from_raw(jvm).unwrap();
|
||||
// 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
|
||||
jvm.attach_current_thread_permanently().unwrap();
|
||||
jvm
|
||||
});
|
||||
|
||||
let app = AndroidApp::new(rust_glue.clone(), jvm.clone());
|
||||
|
||||
rust_glue.notify_main_thread_running();
|
||||
|
||||
unsafe {
|
||||
// Name thread - this needs to happen here after attaching to a JVM thread,
|
||||
// since that changes the thread name to something like "Thread-2".
|
||||
let thread_name = std::ffi::CStr::from_bytes_with_nul(b"android_main\0").unwrap();
|
||||
libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
|
||||
|
||||
// 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(log_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);
|
||||
|
||||
// This should detach automatically but lets detach explicitly to avoid depending
|
||||
// on the TLS trickery in `jni-rs`
|
||||
jvm.detach_current_thread();
|
||||
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
|
||||
rust_glue.notify_main_thread_stopped_running();
|
||||
rust_glue_entry(rust_glue, activity);
|
||||
});
|
||||
|
||||
// Wait for thread to start.
|
||||
@@ -924,3 +869,69 @@ extern "C" fn ANativeActivity_onCreate(
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn rust_glue_entry(rust_glue: NativeActivityGlue, activity: *mut ndk_sys::ANativeActivity) {
|
||||
abort_on_panic(|| {
|
||||
let (jvm, jni_activity) = unsafe {
|
||||
let jvm: *mut jni::sys::JavaVM = (*activity).vm.cast();
|
||||
let jni_activity: jni::sys::jobject = (*activity).clazz as _; // Completely bogus name; this is the _instance_ not class pointer
|
||||
ndk_context::initialize_android_context(jvm.cast(), jni_activity.cast());
|
||||
(jni::JavaVM::from_raw(jvm), jni_activity)
|
||||
};
|
||||
// Note: At this point we can assume jni::JavaVM::singleton is initialized
|
||||
|
||||
// Since this is a newly spawned thread then the JVM hasn't been attached to the
|
||||
// thread yet.
|
||||
//
|
||||
// For compatibility we attach before calling the applications main function to
|
||||
// allow it to assume the thread is attached before making JNI calls.
|
||||
jvm.attach_current_thread_with_config(
|
||||
|| AttachConfig::new().name("android_main"),
|
||||
Some(16),
|
||||
|env| -> jni::errors::Result<()> {
|
||||
// SAFETY: We know jni_activity is a valid JNI global ref to an Activity instance
|
||||
let jni_activity = unsafe { env.as_cast_raw::<Global<JObject>>(&jni_activity)? };
|
||||
|
||||
if let Err(err) = init_android_main_thread(&jvm, &jni_activity) {
|
||||
eprintln!(
|
||||
"Failed to name Java thread and set thread context class loader: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
let app = AndroidApp::new(rust_glue.clone(), jvm.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(log_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);
|
||||
|
||||
ndk_context::release_android_context();
|
||||
}
|
||||
|
||||
rust_glue.notify_main_thread_stopped_running();
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.expect("Failed to attach thread to JVM");
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use ndk::event::ButtonState;
|
||||
|
||||
use crate::input::{
|
||||
Axis, EdgeFlags, KeyAction, Keycode, MetaState, MotionAction, MotionEventFlags, Pointer,
|
||||
PointersIter, Source, ToolType,
|
||||
Axis, Button, ButtonState, EdgeFlags, KeyAction, Keycode, MetaState, MotionAction,
|
||||
MotionEventFlags, Pointer, PointersIter, Source, ToolType,
|
||||
};
|
||||
|
||||
/// A motion event
|
||||
@@ -32,7 +30,13 @@ impl MotionEvent<'_> {
|
||||
///
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source {
|
||||
self.ndk_event.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 also capture unknown variants
|
||||
// added in new versions of Android.
|
||||
let source =
|
||||
unsafe { ndk_sys::AInputEvent_getSource(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
source.into()
|
||||
}
|
||||
|
||||
/// Get the device id associated with the event.
|
||||
@@ -47,7 +51,13 @@ impl MotionEvent<'_> {
|
||||
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionMasked())
|
||||
#[inline]
|
||||
pub fn action(&self) -> MotionAction {
|
||||
self.ndk_event.action()
|
||||
// XXX: we use `AMotionEvent_getAction` directly since we have our own
|
||||
// `MotionAction` enum that we share between backends, which may also
|
||||
// capture unknown variants added in new versions of Android.
|
||||
let action =
|
||||
unsafe { ndk_sys::AMotionEvent_getAction(self.ndk_event.ptr().as_ptr()) as u32 }
|
||||
& ndk_sys::AMOTION_EVENT_ACTION_MASK;
|
||||
action.into()
|
||||
}
|
||||
|
||||
/// Returns which button has been modified during a press or release action.
|
||||
@@ -57,11 +67,10 @@ impl MotionEvent<'_> {
|
||||
///
|
||||
/// See [the MotionEvent docs](https://developer.android.com/reference/android/view/MotionEvent#getActionButton())
|
||||
#[inline]
|
||||
#[cfg(feature = "api-level-33")]
|
||||
#[doc(alias = "AMotionEvent_getActionButton")]
|
||||
// TODO: Button enum to signify only one bitflag can be set?
|
||||
pub fn action_button(&self) -> ButtonState {
|
||||
self.ndk_event.action_button()
|
||||
pub fn action_button(&self) -> Button {
|
||||
let action_button =
|
||||
unsafe { ndk_sys::AMotionEvent_getActionButton(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
action_button.into()
|
||||
}
|
||||
|
||||
/// Returns the pointer index of an `Up` or `Down` event.
|
||||
@@ -145,7 +154,7 @@ impl MotionEvent<'_> {
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getmetastate)
|
||||
#[inline]
|
||||
pub fn meta_state(&self) -> MetaState {
|
||||
self.ndk_event.meta_state()
|
||||
self.ndk_event.meta_state().into()
|
||||
}
|
||||
|
||||
/// Returns the button state during this event, as a bitfield.
|
||||
@@ -154,7 +163,7 @@ impl MotionEvent<'_> {
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getbuttonstate)
|
||||
#[inline]
|
||||
pub fn button_state(&self) -> ButtonState {
|
||||
self.ndk_event.button_state()
|
||||
self.ndk_event.button_state().into()
|
||||
}
|
||||
|
||||
/// Returns the time of the start of this gesture, in the `java.lang.System.nanoTime()` time
|
||||
@@ -173,7 +182,7 @@ impl MotionEvent<'_> {
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getedgeflags)
|
||||
#[inline]
|
||||
pub fn edge_flags(&self) -> EdgeFlags {
|
||||
self.ndk_event.edge_flags()
|
||||
self.ndk_event.edge_flags().into()
|
||||
}
|
||||
|
||||
/// Returns the time of this event, in the `java.lang.System.nanoTime()` time base
|
||||
@@ -191,7 +200,7 @@ impl MotionEvent<'_> {
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#amotionevent_getflags)
|
||||
#[inline]
|
||||
pub fn flags(&self) -> MotionEventFlags {
|
||||
self.ndk_event.flags()
|
||||
self.ndk_event.flags().into()
|
||||
}
|
||||
|
||||
/* Missing from GameActivity currently...
|
||||
@@ -252,7 +261,8 @@ impl PointerImpl<'_> {
|
||||
|
||||
#[inline]
|
||||
pub fn axis_value(&self, axis: Axis) -> f32 {
|
||||
let value: i32 = axis.into();
|
||||
let value: u32 = axis.into();
|
||||
let value = value as i32;
|
||||
self.ndk_pointer.axis_value(value.into())
|
||||
}
|
||||
|
||||
@@ -269,6 +279,7 @@ impl PointerImpl<'_> {
|
||||
#[inline]
|
||||
pub fn tool_type(&self) -> ToolType {
|
||||
let value: i32 = self.ndk_pointer.tool_type().into();
|
||||
let value = value as u32;
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
@@ -320,9 +331,16 @@ impl KeyEvent<'_> {
|
||||
}
|
||||
|
||||
/// Get the source of the event.
|
||||
///
|
||||
#[inline]
|
||||
pub fn source(&self) -> Source {
|
||||
self.ndk_event.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 also capture unknown variants
|
||||
// added in new versions of Android.
|
||||
let source =
|
||||
unsafe { ndk_sys::AInputEvent_getSource(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
source.into()
|
||||
}
|
||||
|
||||
/// Get the device id associated with the event.
|
||||
@@ -337,7 +355,11 @@ impl KeyEvent<'_> {
|
||||
/// See [the KeyEvent docs](https://developer.android.com/reference/android/view/KeyEvent#getAction())
|
||||
#[inline]
|
||||
pub fn action(&self) -> KeyAction {
|
||||
self.ndk_event.action()
|
||||
// XXX: we use `AInputEvent_getAction` directly since we have our own
|
||||
// `KeyAction` enum that we share between backends, which may also
|
||||
// capture unknown variants added in new versions of Android.
|
||||
let action = unsafe { ndk_sys::AKeyEvent_getAction(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
action.into()
|
||||
}
|
||||
|
||||
/// Returns the last time the key was pressed. This is on the scale of
|
||||
@@ -366,7 +388,12 @@ impl KeyEvent<'_> {
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getkeycode)
|
||||
#[inline]
|
||||
pub fn key_code(&self) -> Keycode {
|
||||
self.ndk_event.key_code()
|
||||
// XXX: we use `AInputEvent_getKeyCode` directly since we have our own
|
||||
// `Keycode` enum that we share between backends, which may also
|
||||
// capture unknown variants added in new versions of Android.
|
||||
let keycode =
|
||||
unsafe { ndk_sys::AKeyEvent_getKeyCode(self.ndk_event.ptr().as_ptr()) as u32 };
|
||||
keycode.into()
|
||||
}
|
||||
|
||||
/// Returns the number of repeats of a key.
|
||||
@@ -393,12 +420,13 @@ impl KeyEvent<'_> {
|
||||
/// docs](https://developer.android.com/ndk/reference/group/input#akeyevent_getmetastate)
|
||||
#[inline]
|
||||
pub fn meta_state(&self) -> MetaState {
|
||||
self.ndk_event.meta_state()
|
||||
self.ndk_event.meta_state().into()
|
||||
}
|
||||
}
|
||||
|
||||
// We use our own wrapper type for input events to have better consistency
|
||||
// with GameActivity
|
||||
// with GameActivity and ensure the enum can be extended without needing a
|
||||
// semver bump
|
||||
/// Enum of possible input events
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
@@ -406,4 +434,5 @@ pub enum InputEvent<'a> {
|
||||
MotionEvent(self::MotionEvent<'a>),
|
||||
KeyEvent(self::KeyEvent<'a>),
|
||||
TextEvent(crate::input::TextInputState),
|
||||
TextAction(crate::input::TextInputAction),
|
||||
}
|
||||
|
||||
@@ -6,15 +6,15 @@ use std::ptr::NonNull;
|
||||
use std::sync::{Arc, Mutex, RwLock, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use jni::JavaVM;
|
||||
use libc::c_void;
|
||||
use log::{error, trace};
|
||||
use ndk::input_queue::InputQueue;
|
||||
use ndk::{asset::AssetManager, native_window::NativeWindow};
|
||||
|
||||
use crate::error::InternalResult;
|
||||
use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding};
|
||||
use crate::input::{device_key_character_map, Axis, KeyCharacterMap};
|
||||
use crate::input::{TextInputState, TextSpan};
|
||||
use crate::jni_utils::{self, CloneJavaVM};
|
||||
use crate::{
|
||||
util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
|
||||
};
|
||||
@@ -84,50 +84,47 @@ impl AndroidAppWaker {
|
||||
}
|
||||
|
||||
impl AndroidApp {
|
||||
pub(crate) fn new(native_activity: NativeActivityGlue, jvm: CloneJavaVM) -> Self {
|
||||
let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp
|
||||
pub(crate) fn new(native_activity: NativeActivityGlue, jvm: JavaVM) -> Self {
|
||||
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
|
||||
if let Err(err) = crate::input::jni_init(env) {
|
||||
panic!("Failed to init JNI bindings: {err:?}");
|
||||
};
|
||||
|
||||
let key_map_binding = match KeyCharacterMapBinding::new(&mut env) {
|
||||
Ok(b) => b,
|
||||
Err(err) => {
|
||||
panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}");
|
||||
let app = Self {
|
||||
inner: Arc::new(RwLock::new(AndroidAppInner {
|
||||
jvm: jvm.clone(),
|
||||
native_activity,
|
||||
looper: Looper {
|
||||
ptr: ptr::null_mut(),
|
||||
},
|
||||
key_maps: Mutex::new(HashMap::new()),
|
||||
input_receiver: Mutex::new(None),
|
||||
})),
|
||||
};
|
||||
|
||||
{
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let app = Self {
|
||||
inner: Arc::new(RwLock::new(AndroidAppInner {
|
||||
jvm,
|
||||
native_activity,
|
||||
looper: Looper {
|
||||
ptr: ptr::null_mut(),
|
||||
},
|
||||
key_map_binding: Arc::new(key_map_binding),
|
||||
key_maps: Mutex::new(HashMap::new()),
|
||||
input_receiver: Mutex::new(None),
|
||||
})),
|
||||
};
|
||||
|
||||
{
|
||||
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
|
||||
Ok(app)
|
||||
})
|
||||
.expect("Failed to create AndroidApp instance")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,14 +137,11 @@ unsafe impl Sync for Looper {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AndroidAppInner {
|
||||
pub(crate) jvm: CloneJavaVM,
|
||||
pub(crate) jvm: JavaVM,
|
||||
|
||||
pub(crate) native_activity: NativeActivityGlue,
|
||||
looper: Looper,
|
||||
|
||||
/// Shared JNI bindings for the `KeyCharacterMap` class
|
||||
key_map_binding: Arc<KeyCharacterMapBinding>,
|
||||
|
||||
/// A table of `KeyCharacterMap`s per `InputDevice` ID
|
||||
/// these are used to be able to map key presses to unicode
|
||||
/// characters
|
||||
@@ -396,11 +390,7 @@ impl AndroidAppInner {
|
||||
let key_map = match guard.entry(device_id) {
|
||||
std::collections::hash_map::Entry::Occupied(occupied) => occupied.get().clone(),
|
||||
std::collections::hash_map::Entry::Vacant(vacant) => {
|
||||
let character_map = jni_utils::device_key_character_map(
|
||||
self.jvm.clone(),
|
||||
self.key_map_binding.clone(),
|
||||
device_id,
|
||||
)?;
|
||||
let character_map = device_key_character_map(self.jvm.clone(), device_id)?;
|
||||
vacant.insert(character_map.clone());
|
||||
character_map
|
||||
}
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
use jni::{
|
||||
jni_str,
|
||||
objects::{JObject, JString, JThread},
|
||||
vm::JavaVM,
|
||||
};
|
||||
use log::{error, Level};
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
@@ -111,3 +116,29 @@ pub(crate) fn abort_on_panic<R>(f: impl FnOnce() -> R) -> R {
|
||||
std::process::abort();
|
||||
})
|
||||
}
|
||||
|
||||
/// Name the Java Thread + native thread "android_main" and set the Java Thread context class loader
|
||||
/// so that jni code can more-easily find non-system Java classes.
|
||||
pub(crate) fn init_android_main_thread(
|
||||
vm: &JavaVM,
|
||||
jni_activity: &JObject,
|
||||
) -> jni::errors::Result<()> {
|
||||
vm.with_local_frame(10, |env| -> jni::errors::Result<()> {
|
||||
let activity_class = env.get_object_class(jni_activity)?;
|
||||
let class_loader = activity_class.get_class_loader(env)?;
|
||||
|
||||
let thread = JThread::current_thread(env)?;
|
||||
thread.set_context_class_loader(env, &class_loader)?;
|
||||
let thread_name = JString::from_jni_str(env, jni_str!("android_main"))?;
|
||||
thread.set_name(env, &thread_name)?;
|
||||
|
||||
// Also name native thread - this needs to happen here after attaching to a JVM thread,
|
||||
// since that changes the thread name to something like "Thread-2".
|
||||
unsafe {
|
||||
let thread_name = std::ffi::CStr::from_bytes_with_nul(b"android_main\0").unwrap();
|
||||
let _ = libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,3 +15,6 @@ ndk = "0.9.0"
|
||||
[lib]
|
||||
name="main"
|
||||
crate_type=["cdylib"]
|
||||
|
||||
[patch.crates-io]
|
||||
jni = { git = "https://github.com/jni-rs/jni-rs.git", branch = "release-0.22" }
|
||||
@@ -182,3 +182,6 @@ label = "Application Name"
|
||||
#path = "/rust-windowing/android-ndk-rs/tree/master/cargo-apk"
|
||||
#path_prefix = "/rust-windowing/"
|
||||
#mime_type = "image/jpeg"
|
||||
|
||||
[patch.crates-io]
|
||||
jni = { git = "https://github.com/jni-rs/jni-rs.git", branch = "release-0.22" }
|
||||
Reference in New Issue
Block a user