Skip to content

Commit b4cf0ee

Browse files
authored
Merge pull request #102 from rust-mobile/rib/pr/input-api-rework-with-key-character-maps
Rework `input_events` API and expose `KeyCharacterMap` bindings
2 parents 6f72dde + af331e3 commit b4cf0ee

File tree

15 files changed

+1555
-231
lines changed

15 files changed

+1555
-231
lines changed

android-activity/CHANGELOG.md

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,108 @@
1-
<!-- markdownlint-disable MD022 MD024 MD032 -->
1+
<!-- markdownlint-disable MD022 MD024 MD032 MD033 -->
22

33
# Changelog
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
9+
### Added
10+
- Added `KeyEvent::meta_state()` for being able to query the state of meta keys, needed for character mapping ([#102](https://github.com/rust-mobile/android-activity/pull/102))
11+
- Added `KeyCharacterMap` JNI bindings to the corresponding Android SDK API ([#102](https://github.com/rust-mobile/android-activity/pull/102))
12+
- Added `AndroidApp::device_key_character_map()` for being able to get a `KeyCharacterMap` for a given `device_id` for unicode character mapping ([#102](https://github.com/rust-mobile/android-activity/pull/102))
13+
14+
<details>
15+
<summary>Click here for an example of how to handle unicode character mapping:</summary>
16+
17+
```rust
18+
let mut combining_accent = None;
19+
// Snip
20+
21+
22+
let combined_key_char = if let Ok(map) = app.device_key_character_map(device_id) {
23+
match map.get(key_event.key_code(), key_event.meta_state()) {
24+
Ok(KeyMapChar::Unicode(unicode)) => {
25+
let combined_unicode = if let Some(accent) = combining_accent {
26+
match map.get_dead_char(accent, unicode) {
27+
Ok(Some(key)) => {
28+
info!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{key}'");
29+
Some(key)
30+
}
31+
Ok(None) => None,
32+
Err(err) => {
33+
log::error!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
34+
None
35+
}
36+
}
37+
} else {
38+
info!("KeyEvent: Pressed '{unicode}'");
39+
Some(unicode)
40+
};
41+
combining_accent = None;
42+
combined_unicode.map(|unicode| KeyMapChar::Unicode(unicode))
43+
}
44+
Ok(KeyMapChar::CombiningAccent(accent)) => {
45+
info!("KeyEvent: Pressed 'dead key' combining accent '{accent}'");
46+
combining_accent = Some(accent);
47+
Some(KeyMapChar::CombiningAccent(accent))
48+
}
49+
Ok(KeyMapChar::None) => {
50+
info!("KeyEvent: Pressed non-unicode key");
51+
combining_accent = None;
52+
None
53+
}
54+
Err(err) => {
55+
log::error!("KeyEvent: Failed to get key map character: {err:?}");
56+
combining_accent = None;
57+
None
58+
}
59+
}
60+
} else {
61+
None
62+
};
63+
```
64+
65+
</details>
66+
867
### Changed
968
- GameActivity updated to 2.0.2 (requires the corresponding 2.0.2 `.aar` release from Google) ([#88](https://github.com/rust-mobile/android-activity/pull/88))
69+
- `AndroidApp::input_events()` is replaced by `AndroidApp::input_events_iter()` ([#102](https://github.com/rust-mobile/android-activity/pull/102))
70+
71+
<details>
72+
<summary>Click here for an example of how to use `input_events_iter()`:</summary>
73+
74+
```rust
75+
match app.input_events_iter() {
76+
Ok(mut iter) => {
77+
loop {
78+
let read_input = iter.next(|event| {
79+
let handled = match event {
80+
InputEvent::KeyEvent(key_event) => {
81+
// Snip
82+
}
83+
InputEvent::MotionEvent(motion_event) => {
84+
// Snip
85+
}
86+
event => {
87+
// Snip
88+
}
89+
};
90+
91+
handled
92+
});
93+
94+
if !read_input {
95+
break;
96+
}
97+
}
98+
}
99+
Err(err) => {
100+
log::error!("Failed to get input events iterator: {err:?}");
101+
}
102+
}
103+
```
104+
105+
</details>
10106

11107
## [0.4.3] - 2022-07-30
12108
### Fixed

android-activity/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ native-activity = []
3838
log = "0.4"
3939
jni-sys = "0.3"
4040
cesu8 = "1"
41+
jni = "0.21"
4142
ndk = "0.7"
4243
ndk-sys = "0.4"
4344
ndk-context = "0.1"
4445
android-properties = "0.2"
4546
num_enum = "0.6"
4647
bitflags = "2.0"
4748
libc = "0.2"
49+
thiserror = "1"
4850

4951
[build-dependencies]
5052
cc = { version = "1.0", features = ["parallel"] }

android-activity/LICENSE

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1-
The third-party glue code, under the native-activity-csrc/ and game-activity-csrc/ directories
2-
is covered by the Apache 2.0 license only:
1+
# License
32

4-
Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
3+
## GameActivity
54

5+
The third-party glue code, under the game-activity-csrc/ directory is covered by
6+
the Apache 2.0 license only:
7+
8+
Apache License, Version 2.0 (docs/LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
9+
10+
## SDK Documentation
11+
12+
Documentation for APIs that are direct bindings of Android platform APIs are covered
13+
by the Apache 2.0 license only:
14+
15+
Apache License, Version 2.0 (docs/LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
16+
17+
## android-activity
618

719
All other code is dual-licensed under either
820

9-
* MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT)
10-
* Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
21+
- MIT License (docs/LICENSE-MIT or <http://opensource.org/licenses/MIT>)
22+
- Apache License, Version 2.0 (docs/LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
1123

12-
at your option.
24+
at your option.

android-activity/src/error.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use thiserror::Error;
2+
3+
#[derive(Error, Debug)]
4+
pub enum AppError {
5+
#[error("Operation only supported from the android_main() thread: {0}")]
6+
NonMainThread(String),
7+
8+
#[error("Java VM or JNI error, including Java exceptions")]
9+
JavaError(String),
10+
11+
#[error("Input unavailable")]
12+
InputUnavailable,
13+
}
14+
15+
pub type Result<T> = std::result::Result<T, AppError>;
16+
17+
// XXX: we don't want to expose jni-rs in the public API
18+
// so we have an internal error type that we can generally
19+
// use in the backends and then we can strip the error
20+
// in the frontend of the API.
21+
//
22+
// This way we avoid exposing a public trait implementation for
23+
// `From<jni::errors::Error>`
24+
#[derive(Error, Debug)]
25+
pub(crate) enum InternalAppError {
26+
#[error("A JNI error")]
27+
JniError(jni::errors::JniError),
28+
#[error("A Java Exception was thrown via a JNI method call")]
29+
JniException(String),
30+
#[error("A Java VM error")]
31+
JvmError(jni::errors::Error),
32+
#[error("Input unavailable")]
33+
InputUnavailable,
34+
}
35+
36+
pub(crate) type InternalResult<T> = std::result::Result<T, InternalAppError>;
37+
38+
impl From<jni::errors::Error> for InternalAppError {
39+
fn from(value: jni::errors::Error) -> Self {
40+
InternalAppError::JvmError(value)
41+
}
42+
}
43+
impl From<jni::errors::JniError> for InternalAppError {
44+
fn from(value: jni::errors::JniError) -> Self {
45+
InternalAppError::JniError(value)
46+
}
47+
}
48+
49+
impl From<InternalAppError> for AppError {
50+
fn from(value: InternalAppError) -> Self {
51+
match value {
52+
InternalAppError::JniError(err) => AppError::JavaError(err.to_string()),
53+
InternalAppError::JniException(msg) => AppError::JavaError(msg),
54+
InternalAppError::JvmError(err) => AppError::JavaError(err.to_string()),
55+
InternalAppError::InputUnavailable => AppError::InputUnavailable,
56+
}
57+
}
58+
}

android-activity/src/game_activity/input.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use num_enum::{IntoPrimitive, TryFromPrimitive};
1717
use std::convert::TryInto;
1818

19-
use crate::game_activity::ffi::{GameActivityKeyEvent, GameActivityMotionEvent};
19+
use crate::activity_impl::ffi::{GameActivityKeyEvent, GameActivityMotionEvent};
2020
use crate::input::{Class, Source};
2121

2222
// Note: try to keep this wrapper API compatible with the AInputEvent API if possible
@@ -1274,6 +1274,12 @@ impl<'a> KeyEvent<'a> {
12741274
action.try_into().unwrap()
12751275
}
12761276

1277+
#[inline]
1278+
pub fn action_button(&self) -> KeyAction {
1279+
let action = self.ga_event.action as u32;
1280+
action.try_into().unwrap()
1281+
}
1282+
12771283
/// Returns the last time the key was pressed. This is on the scale of
12781284
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
12791285
///

0 commit comments

Comments
 (0)