| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2025 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use miniquad::native::android::{self, ndk_sys, ndk_utils};
- use parking_lot::Mutex as SyncMutex;
- use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
- use crate::AndroidSuggestEvent;
- pub mod vid;
- macro_rules! call_mainactivity_int_method {
- ($method:expr, $sig:expr $(, $args:expr)*) => {{
- unsafe {
- let env = android::attach_jni_env();
- ndk_utils::call_int_method!(env, android::ACTIVITY, $method, $sig $(, $args)*)
- }
- }};
- }
- macro_rules! call_mainactivity_str_method {
- ($method:expr) => {{
- unsafe {
- let env = android::attach_jni_env();
- let text = ndk_utils::call_object_method!(
- env,
- android::ACTIVITY,
- $method,
- "()Ljava/lang/String;"
- );
- ndk_utils::get_utf_str!(env, text)
- }
- }};
- }
- macro_rules! call_mainactivity_float_method {
- ($method:expr) => {{
- unsafe {
- let env = android::attach_jni_env();
- ndk_utils::call_method!(CallFloatMethod, env, android::ACTIVITY, $method, "()F")
- }
- }};
- }
- macro_rules! call_mainactivity_bool_method {
- ($method:expr) => {{
- unsafe {
- let env = android::attach_jni_env();
- ndk_utils::call_method!(CallBooleanMethod, env, android::ACTIVITY, $method, "()Z") !=
- 0u8
- }
- }};
- }
- struct GlobalData {
- senders: HashMap<usize, async_channel::Sender<AndroidSuggestEvent>>,
- next_id: usize,
- }
- fn send(id: usize, ev: AndroidSuggestEvent) {
- let globals = &GLOBALS.lock();
- let Some(sender) = globals.senders.get(&id) else {
- warn!(target: "android", "Unknown composer_id={id} discard ev: {ev:?}");
- return
- };
- let _ = sender.try_send(ev);
- }
- unsafe impl Send for GlobalData {}
- unsafe impl Sync for GlobalData {}
- static GLOBALS: LazyLock<SyncMutex<GlobalData>> =
- LazyLock::new(|| SyncMutex::new(GlobalData { senders: HashMap::new(), next_id: 0 }));
- #[no_mangle]
- pub unsafe extern "C" fn Java_darkfi_darkfi_1app_MainActivity_onInitEdit(
- _env: *mut ndk_sys::JNIEnv,
- _: ndk_sys::jobject,
- id: ndk_sys::jint,
- ) {
- assert!(id >= 0);
- let id = id as usize;
- send(id, AndroidSuggestEvent::Init);
- }
- #[no_mangle]
- pub unsafe extern "C" fn Java_autosuggest_InvisibleInputView_onCreateInputConnect(
- _env: *mut ndk_sys::JNIEnv,
- _: ndk_sys::jobject,
- id: ndk_sys::jint,
- ) {
- assert!(id >= 0);
- let id = id as usize;
- send(id, AndroidSuggestEvent::CreateInputConnect);
- }
- #[no_mangle]
- pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onCompose(
- env: *mut ndk_sys::JNIEnv,
- _: ndk_sys::jobject,
- id: ndk_sys::jint,
- text: ndk_sys::jobject,
- cursor_pos: ndk_sys::jint,
- is_commit: ndk_sys::jboolean,
- ) {
- assert!(id >= 0);
- let id = id as usize;
- let text = ndk_utils::get_utf_str!(env, text);
- send(
- id,
- AndroidSuggestEvent::Compose {
- text: text.to_string(),
- cursor_pos,
- is_commit: is_commit == 1,
- },
- );
- }
- #[no_mangle]
- pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onSetComposeRegion(
- _env: *mut ndk_sys::JNIEnv,
- _: ndk_sys::jobject,
- id: ndk_sys::jint,
- start: ndk_sys::jint,
- end: ndk_sys::jint,
- ) {
- assert!(id >= 0);
- let id = id as usize;
- send(id, AndroidSuggestEvent::ComposeRegion { start: start as usize, end: end as usize });
- }
- #[no_mangle]
- pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onFinishCompose(
- _env: *mut ndk_sys::JNIEnv,
- _: ndk_sys::jobject,
- id: ndk_sys::jint,
- ) {
- assert!(id >= 0);
- let id = id as usize;
- send(id, AndroidSuggestEvent::FinishCompose);
- }
- #[no_mangle]
- pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onDeleteSurroundingText(
- _env: *mut ndk_sys::JNIEnv,
- _: ndk_sys::jobject,
- id: ndk_sys::jint,
- left: ndk_sys::jint,
- right: ndk_sys::jint,
- ) {
- assert!(id >= 0);
- let id = id as usize;
- send(
- id,
- AndroidSuggestEvent::DeleteSurroundingText { left: left as usize, right: right as usize },
- );
- }
- pub fn create_composer(sender: async_channel::Sender<AndroidSuggestEvent>) -> usize {
- let composer_id = {
- let mut globals = GLOBALS.lock();
- let id = globals.next_id;
- globals.next_id += 1;
- globals.senders.insert(id, sender);
- id
- };
- unsafe {
- let env = android::attach_jni_env();
- ndk_utils::call_void_method!(env, android::ACTIVITY, "createComposer", "(I)V", composer_id);
- }
- composer_id
- }
- pub fn focus(id: usize) -> Option<()> {
- let is_success = unsafe {
- let env = android::attach_jni_env();
- ndk_utils::call_bool_method!(env, android::ACTIVITY, "focus", "(I)Z", id as i32)
- };
- if is_success == 0u8 {
- None
- } else {
- Some(())
- }
- }
- pub fn unfocus(id: usize) -> Option<()> {
- let is_success = unsafe {
- let env = android::attach_jni_env();
- ndk_utils::call_bool_method!(env, android::ACTIVITY, "unfocus", "(I)Z", id as i32)
- };
- if is_success == 0u8 {
- None
- } else {
- Some(())
- }
- }
- pub fn set_text(id: usize, text: &str) -> Option<()> {
- let ctext = std::ffi::CString::new(text).unwrap();
- let is_success = unsafe {
- let env = android::attach_jni_env();
- let new_string_utf = (**env).NewStringUTF.unwrap();
- let jtext = new_string_utf(env, ctext.as_ptr());
- let delete_local_ref = (**env).DeleteLocalRef.unwrap();
- let res = ndk_utils::call_bool_method!(
- env,
- android::ACTIVITY,
- "setText",
- "(ILjava/lang/String;)Z",
- id as i32,
- jtext
- );
- delete_local_ref(env, jtext);
- res
- };
- if is_success == 0u8 {
- None
- } else {
- Some(())
- }
- }
- pub fn set_selection(id: usize, select_start: usize, select_end: usize) -> Option<()> {
- //trace!(target: "android", "set_selection({id}, {select_start}, {select_end})");
- let is_success = unsafe {
- let env = android::attach_jni_env();
- ndk_utils::call_bool_method!(
- env,
- android::ACTIVITY,
- "setSelection",
- "(III)Z",
- id as i32,
- select_start as i32,
- select_end as i32
- )
- };
- if is_success == 0u8 {
- None
- } else {
- Some(())
- }
- }
- pub fn commit_text(id: usize, text: &str) -> Option<()> {
- let ctext = std::ffi::CString::new(text).unwrap();
- let is_success = unsafe {
- let env = android::attach_jni_env();
- let new_string_utf = (**env).NewStringUTF.unwrap();
- let delete_local_ref = (**env).DeleteLocalRef.unwrap();
- let jtext = new_string_utf(env, ctext.as_ptr());
- let res = ndk_utils::call_bool_method!(
- env,
- android::ACTIVITY,
- "commitText",
- "(ILjava/lang/String;)Z",
- id as i32,
- jtext
- );
- delete_local_ref(env, jtext);
- res
- };
- if is_success == 0u8 {
- None
- } else {
- Some(())
- }
- }
- pub struct Editable {
- pub buffer: String,
- pub select_start: usize,
- pub select_end: usize,
- pub compose_start: Option<usize>,
- pub compose_end: Option<usize>,
- }
- pub fn get_editable(id: usize) -> Option<Editable> {
- //trace!(target: "android", "get_editable({id})");
- unsafe {
- let env = android::attach_jni_env();
- let input_view = ndk_utils::call_object_method!(
- env,
- android::ACTIVITY,
- "getInputView",
- "(I)Lautosuggest/InvisibleInputView;",
- id as i32
- );
- if input_view.is_null() {
- return None
- }
- let buffer =
- ndk_utils::call_object_method!(env, input_view, "rawText", "()Ljava/lang/String;");
- assert!(!buffer.is_null());
- let buffer = ndk_utils::get_utf_str!(env, buffer).to_string();
- let select_start = ndk_utils::call_int_method!(env, input_view, "getSelectionStart", "()I");
- let select_end = ndk_utils::call_int_method!(env, input_view, "getSelectionEnd", "()I");
- let compose_start = ndk_utils::call_int_method!(env, input_view, "getComposeStart", "()I");
- let compose_end = ndk_utils::call_int_method!(env, input_view, "getComposeEnd", "()I");
- assert!(select_start >= 0);
- assert!(select_end >= 0);
- assert!(compose_start >= 0 || compose_start == compose_end);
- assert!(compose_start <= compose_end);
- Some(Editable {
- buffer,
- select_start: select_start as usize,
- select_end: select_end as usize,
- compose_start: if compose_start < 0 { None } else { Some(compose_start as usize) },
- compose_end: if compose_end < 0 { None } else { Some(compose_end as usize) },
- })
- }
- }
- pub fn get_appdata_path() -> PathBuf {
- call_mainactivity_str_method!("getAppDataPath").into()
- }
- pub fn get_external_storage_path() -> PathBuf {
- call_mainactivity_str_method!("getExternalStoragePath").into()
- }
- pub fn get_keyboard_height() -> usize {
- call_mainactivity_int_method!("getKeyboardHeight", "()I") as usize
- }
- pub fn get_screen_density() -> f32 {
- call_mainactivity_float_method!("getScreenDensity")
- }
- pub fn is_ime_visible() -> bool {
- call_mainactivity_bool_method!("isImeVisible")
- }
|