mod.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use miniquad::native::android::{self, ndk_sys, ndk_utils};
  19. use parking_lot::Mutex as SyncMutex;
  20. use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
  21. use crate::AndroidSuggestEvent;
  22. pub mod vid;
  23. macro_rules! call_mainactivity_int_method {
  24. ($method:expr, $sig:expr $(, $args:expr)*) => {{
  25. unsafe {
  26. let env = android::attach_jni_env();
  27. ndk_utils::call_int_method!(env, android::ACTIVITY, $method, $sig $(, $args)*)
  28. }
  29. }};
  30. }
  31. macro_rules! call_mainactivity_str_method {
  32. ($method:expr) => {{
  33. unsafe {
  34. let env = android::attach_jni_env();
  35. let text = ndk_utils::call_object_method!(
  36. env,
  37. android::ACTIVITY,
  38. $method,
  39. "()Ljava/lang/String;"
  40. );
  41. ndk_utils::get_utf_str!(env, text)
  42. }
  43. }};
  44. }
  45. macro_rules! call_mainactivity_float_method {
  46. ($method:expr) => {{
  47. unsafe {
  48. let env = android::attach_jni_env();
  49. ndk_utils::call_method!(CallFloatMethod, env, android::ACTIVITY, $method, "()F")
  50. }
  51. }};
  52. }
  53. macro_rules! call_mainactivity_bool_method {
  54. ($method:expr) => {{
  55. unsafe {
  56. let env = android::attach_jni_env();
  57. ndk_utils::call_method!(CallBooleanMethod, env, android::ACTIVITY, $method, "()Z") !=
  58. 0u8
  59. }
  60. }};
  61. }
  62. struct GlobalData {
  63. senders: HashMap<usize, async_channel::Sender<AndroidSuggestEvent>>,
  64. next_id: usize,
  65. }
  66. fn send(id: usize, ev: AndroidSuggestEvent) {
  67. let globals = &GLOBALS.lock();
  68. let Some(sender) = globals.senders.get(&id) else {
  69. warn!(target: "android", "Unknown composer_id={id} discard ev: {ev:?}");
  70. return
  71. };
  72. let _ = sender.try_send(ev);
  73. }
  74. unsafe impl Send for GlobalData {}
  75. unsafe impl Sync for GlobalData {}
  76. static GLOBALS: LazyLock<SyncMutex<GlobalData>> =
  77. LazyLock::new(|| SyncMutex::new(GlobalData { senders: HashMap::new(), next_id: 0 }));
  78. #[no_mangle]
  79. pub unsafe extern "C" fn Java_darkfi_darkfi_1app_MainActivity_onInitEdit(
  80. _env: *mut ndk_sys::JNIEnv,
  81. _: ndk_sys::jobject,
  82. id: ndk_sys::jint,
  83. ) {
  84. assert!(id >= 0);
  85. let id = id as usize;
  86. send(id, AndroidSuggestEvent::Init);
  87. }
  88. #[no_mangle]
  89. pub unsafe extern "C" fn Java_autosuggest_InvisibleInputView_onCreateInputConnect(
  90. _env: *mut ndk_sys::JNIEnv,
  91. _: ndk_sys::jobject,
  92. id: ndk_sys::jint,
  93. ) {
  94. assert!(id >= 0);
  95. let id = id as usize;
  96. send(id, AndroidSuggestEvent::CreateInputConnect);
  97. }
  98. #[no_mangle]
  99. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onCompose(
  100. env: *mut ndk_sys::JNIEnv,
  101. _: ndk_sys::jobject,
  102. id: ndk_sys::jint,
  103. text: ndk_sys::jobject,
  104. cursor_pos: ndk_sys::jint,
  105. is_commit: ndk_sys::jboolean,
  106. ) {
  107. assert!(id >= 0);
  108. let id = id as usize;
  109. let text = ndk_utils::get_utf_str!(env, text);
  110. send(
  111. id,
  112. AndroidSuggestEvent::Compose {
  113. text: text.to_string(),
  114. cursor_pos,
  115. is_commit: is_commit == 1,
  116. },
  117. );
  118. }
  119. #[no_mangle]
  120. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onSetComposeRegion(
  121. _env: *mut ndk_sys::JNIEnv,
  122. _: ndk_sys::jobject,
  123. id: ndk_sys::jint,
  124. start: ndk_sys::jint,
  125. end: ndk_sys::jint,
  126. ) {
  127. assert!(id >= 0);
  128. let id = id as usize;
  129. send(id, AndroidSuggestEvent::ComposeRegion { start: start as usize, end: end as usize });
  130. }
  131. #[no_mangle]
  132. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onFinishCompose(
  133. _env: *mut ndk_sys::JNIEnv,
  134. _: ndk_sys::jobject,
  135. id: ndk_sys::jint,
  136. ) {
  137. assert!(id >= 0);
  138. let id = id as usize;
  139. send(id, AndroidSuggestEvent::FinishCompose);
  140. }
  141. #[no_mangle]
  142. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onDeleteSurroundingText(
  143. _env: *mut ndk_sys::JNIEnv,
  144. _: ndk_sys::jobject,
  145. id: ndk_sys::jint,
  146. left: ndk_sys::jint,
  147. right: ndk_sys::jint,
  148. ) {
  149. assert!(id >= 0);
  150. let id = id as usize;
  151. send(
  152. id,
  153. AndroidSuggestEvent::DeleteSurroundingText { left: left as usize, right: right as usize },
  154. );
  155. }
  156. pub fn create_composer(sender: async_channel::Sender<AndroidSuggestEvent>) -> usize {
  157. let composer_id = {
  158. let mut globals = GLOBALS.lock();
  159. let id = globals.next_id;
  160. globals.next_id += 1;
  161. globals.senders.insert(id, sender);
  162. id
  163. };
  164. unsafe {
  165. let env = android::attach_jni_env();
  166. ndk_utils::call_void_method!(env, android::ACTIVITY, "createComposer", "(I)V", composer_id);
  167. }
  168. composer_id
  169. }
  170. pub fn focus(id: usize) -> Option<()> {
  171. let is_success = unsafe {
  172. let env = android::attach_jni_env();
  173. ndk_utils::call_bool_method!(env, android::ACTIVITY, "focus", "(I)Z", id as i32)
  174. };
  175. if is_success == 0u8 {
  176. None
  177. } else {
  178. Some(())
  179. }
  180. }
  181. pub fn unfocus(id: usize) -> Option<()> {
  182. let is_success = unsafe {
  183. let env = android::attach_jni_env();
  184. ndk_utils::call_bool_method!(env, android::ACTIVITY, "unfocus", "(I)Z", id as i32)
  185. };
  186. if is_success == 0u8 {
  187. None
  188. } else {
  189. Some(())
  190. }
  191. }
  192. pub fn set_text(id: usize, text: &str) -> Option<()> {
  193. let ctext = std::ffi::CString::new(text).unwrap();
  194. let is_success = unsafe {
  195. let env = android::attach_jni_env();
  196. let new_string_utf = (**env).NewStringUTF.unwrap();
  197. let jtext = new_string_utf(env, ctext.as_ptr());
  198. let delete_local_ref = (**env).DeleteLocalRef.unwrap();
  199. let res = ndk_utils::call_bool_method!(
  200. env,
  201. android::ACTIVITY,
  202. "setText",
  203. "(ILjava/lang/String;)Z",
  204. id as i32,
  205. jtext
  206. );
  207. delete_local_ref(env, jtext);
  208. res
  209. };
  210. if is_success == 0u8 {
  211. None
  212. } else {
  213. Some(())
  214. }
  215. }
  216. pub fn set_selection(id: usize, select_start: usize, select_end: usize) -> Option<()> {
  217. //trace!(target: "android", "set_selection({id}, {select_start}, {select_end})");
  218. let is_success = unsafe {
  219. let env = android::attach_jni_env();
  220. ndk_utils::call_bool_method!(
  221. env,
  222. android::ACTIVITY,
  223. "setSelection",
  224. "(III)Z",
  225. id as i32,
  226. select_start as i32,
  227. select_end as i32
  228. )
  229. };
  230. if is_success == 0u8 {
  231. None
  232. } else {
  233. Some(())
  234. }
  235. }
  236. pub fn commit_text(id: usize, text: &str) -> Option<()> {
  237. let ctext = std::ffi::CString::new(text).unwrap();
  238. let is_success = unsafe {
  239. let env = android::attach_jni_env();
  240. let new_string_utf = (**env).NewStringUTF.unwrap();
  241. let delete_local_ref = (**env).DeleteLocalRef.unwrap();
  242. let jtext = new_string_utf(env, ctext.as_ptr());
  243. let res = ndk_utils::call_bool_method!(
  244. env,
  245. android::ACTIVITY,
  246. "commitText",
  247. "(ILjava/lang/String;)Z",
  248. id as i32,
  249. jtext
  250. );
  251. delete_local_ref(env, jtext);
  252. res
  253. };
  254. if is_success == 0u8 {
  255. None
  256. } else {
  257. Some(())
  258. }
  259. }
  260. pub struct Editable {
  261. pub buffer: String,
  262. pub select_start: usize,
  263. pub select_end: usize,
  264. pub compose_start: Option<usize>,
  265. pub compose_end: Option<usize>,
  266. }
  267. pub fn get_editable(id: usize) -> Option<Editable> {
  268. //trace!(target: "android", "get_editable({id})");
  269. unsafe {
  270. let env = android::attach_jni_env();
  271. let input_view = ndk_utils::call_object_method!(
  272. env,
  273. android::ACTIVITY,
  274. "getInputView",
  275. "(I)Lautosuggest/InvisibleInputView;",
  276. id as i32
  277. );
  278. if input_view.is_null() {
  279. return None
  280. }
  281. let buffer =
  282. ndk_utils::call_object_method!(env, input_view, "rawText", "()Ljava/lang/String;");
  283. assert!(!buffer.is_null());
  284. let buffer = ndk_utils::get_utf_str!(env, buffer).to_string();
  285. let select_start = ndk_utils::call_int_method!(env, input_view, "getSelectionStart", "()I");
  286. let select_end = ndk_utils::call_int_method!(env, input_view, "getSelectionEnd", "()I");
  287. let compose_start = ndk_utils::call_int_method!(env, input_view, "getComposeStart", "()I");
  288. let compose_end = ndk_utils::call_int_method!(env, input_view, "getComposeEnd", "()I");
  289. assert!(select_start >= 0);
  290. assert!(select_end >= 0);
  291. assert!(compose_start >= 0 || compose_start == compose_end);
  292. assert!(compose_start <= compose_end);
  293. Some(Editable {
  294. buffer,
  295. select_start: select_start as usize,
  296. select_end: select_end as usize,
  297. compose_start: if compose_start < 0 { None } else { Some(compose_start as usize) },
  298. compose_end: if compose_end < 0 { None } else { Some(compose_end as usize) },
  299. })
  300. }
  301. }
  302. pub fn get_appdata_path() -> PathBuf {
  303. call_mainactivity_str_method!("getAppDataPath").into()
  304. }
  305. pub fn get_external_storage_path() -> PathBuf {
  306. call_mainactivity_str_method!("getExternalStoragePath").into()
  307. }
  308. pub fn get_keyboard_height() -> usize {
  309. call_mainactivity_int_method!("getKeyboardHeight", "()I") as usize
  310. }
  311. pub fn get_screen_density() -> f32 {
  312. call_mainactivity_float_method!("getScreenDensity")
  313. }
  314. pub fn is_ime_visible() -> bool {
  315. call_mainactivity_bool_method!("isImeVisible")
  316. }