Skip to content

Commit

Permalink
Merge pull request #102 from rust-mobile/rib/pr/input-api-rework-with…
Browse files Browse the repository at this point in the history
…-key-character-maps

Rework `input_events` API and expose `KeyCharacterMap` bindings
  • Loading branch information
rib authored Aug 7, 2023
2 parents 6f72dde + af331e3 commit b4cf0ee
Show file tree
Hide file tree
Showing 15 changed files with 1,555 additions and 231 deletions.
98 changes: 97 additions & 1 deletion android-activity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,108 @@
<!-- markdownlint-disable MD022 MD024 MD032 -->
<!-- markdownlint-disable MD022 MD024 MD032 MD033 -->

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

## [Unreleased]

### Added
- 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))
- Added `KeyCharacterMap` JNI bindings to the corresponding Android SDK API ([#102](https://github.com/rust-mobile/android-activity/pull/102))
- 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))

<details>
<summary>Click here for an example of how to handle unicode character mapping:</summary>

```rust
let mut combining_accent = None;
// Snip


let combined_key_char = if let Ok(map) = app.device_key_character_map(device_id) {
match map.get(key_event.key_code(), key_event.meta_state()) {
Ok(KeyMapChar::Unicode(unicode)) => {
let combined_unicode = if let Some(accent) = combining_accent {
match map.get_dead_char(accent, unicode) {
Ok(Some(key)) => {
info!("KeyEvent: Combined '{unicode}' with accent '{accent}' to give '{key}'");
Some(key)
}
Ok(None) => None,
Err(err) => {
log::error!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}");
None
}
}
} else {
info!("KeyEvent: Pressed '{unicode}'");
Some(unicode)
};
combining_accent = None;
combined_unicode.map(|unicode| KeyMapChar::Unicode(unicode))
}
Ok(KeyMapChar::CombiningAccent(accent)) => {
info!("KeyEvent: Pressed 'dead key' combining accent '{accent}'");
combining_accent = Some(accent);
Some(KeyMapChar::CombiningAccent(accent))
}
Ok(KeyMapChar::None) => {
info!("KeyEvent: Pressed non-unicode key");
combining_accent = None;
None
}
Err(err) => {
log::error!("KeyEvent: Failed to get key map character: {err:?}");
combining_accent = None;
None
}
}
} else {
None
};
```

</details>

### Changed
- 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))
- `AndroidApp::input_events()` is replaced by `AndroidApp::input_events_iter()` ([#102](https://github.com/rust-mobile/android-activity/pull/102))

<details>
<summary>Click here for an example of how to use `input_events_iter()`:</summary>

```rust
match app.input_events_iter() {
Ok(mut iter) => {
loop {
let read_input = iter.next(|event| {
let handled = match event {
InputEvent::KeyEvent(key_event) => {
// Snip
}
InputEvent::MotionEvent(motion_event) => {
// Snip
}
event => {
// Snip
}
};

handled
});

if !read_input {
break;
}
}
}
Err(err) => {
log::error!("Failed to get input events iterator: {err:?}");
}
}
```

</details>

## [0.4.3] - 2022-07-30
### Fixed
Expand Down
2 changes: 2 additions & 0 deletions android-activity/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ native-activity = []
log = "0.4"
jni-sys = "0.3"
cesu8 = "1"
jni = "0.21"
ndk = "0.7"
ndk-sys = "0.4"
ndk-context = "0.1"
android-properties = "0.2"
num_enum = "0.6"
bitflags = "2.0"
libc = "0.2"
thiserror = "1"

[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
Expand Down
24 changes: 18 additions & 6 deletions android-activity/LICENSE
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
The third-party glue code, under the native-activity-csrc/ and game-activity-csrc/ directories
is covered by the Apache 2.0 license only:
# License

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

The third-party glue code, under the game-activity-csrc/ directory is covered by
the Apache 2.0 license only:

Apache License, Version 2.0 (docs/LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)

## SDK Documentation

Documentation for APIs that are direct bindings of Android platform APIs are covered
by the Apache 2.0 license only:

Apache License, Version 2.0 (docs/LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)

## android-activity

All other code is dual-licensed under either

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

at your option.
at your option.
58 changes: 58 additions & 0 deletions android-activity/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
#[error("Operation only supported from the android_main() thread: {0}")]
NonMainThread(String),

#[error("Java VM or JNI error, including Java exceptions")]
JavaError(String),

#[error("Input unavailable")]
InputUnavailable,
}

pub type Result<T> = std::result::Result<T, AppError>;

// XXX: we don't want to expose jni-rs in the public API
// so we have an internal error type that we can generally
// use in the backends and then we can strip the error
// in the frontend of the API.
//
// This way we avoid exposing a public trait implementation for
// `From<jni::errors::Error>`
#[derive(Error, Debug)]
pub(crate) enum InternalAppError {
#[error("A JNI error")]
JniError(jni::errors::JniError),
#[error("A Java Exception was thrown via a JNI method call")]
JniException(String),
#[error("A Java VM error")]
JvmError(jni::errors::Error),
#[error("Input unavailable")]
InputUnavailable,
}

pub(crate) type InternalResult<T> = std::result::Result<T, InternalAppError>;

impl From<jni::errors::Error> for InternalAppError {
fn from(value: jni::errors::Error) -> Self {
InternalAppError::JvmError(value)
}
}
impl From<jni::errors::JniError> for InternalAppError {
fn from(value: jni::errors::JniError) -> Self {
InternalAppError::JniError(value)
}
}

impl From<InternalAppError> for AppError {
fn from(value: InternalAppError) -> Self {
match value {
InternalAppError::JniError(err) => AppError::JavaError(err.to_string()),
InternalAppError::JniException(msg) => AppError::JavaError(msg),
InternalAppError::JvmError(err) => AppError::JavaError(err.to_string()),
InternalAppError::InputUnavailable => AppError::InputUnavailable,
}
}
}
8 changes: 7 additions & 1 deletion android-activity/src/game_activity/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::convert::TryInto;

use crate::game_activity::ffi::{GameActivityKeyEvent, GameActivityMotionEvent};
use crate::activity_impl::ffi::{GameActivityKeyEvent, GameActivityMotionEvent};
use crate::input::{Class, Source};

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

#[inline]
pub fn action_button(&self) -> KeyAction {
let action = self.ga_event.action as u32;
action.try_into().unwrap()
}

/// Returns the last time the key was pressed. This is on the scale of
/// `java.lang.System.nanoTime()`, which has nanosecond precision, but no defined start time.
///
Expand Down
Loading

0 comments on commit b4cf0ee

Please sign in to comment.