From 0c2792b1eb8b5b449bc9f27acfbadb30eef9ca1e Mon Sep 17 00:00:00 2001 From: ljlvink Date: Mon, 9 Mar 2026 17:51:00 +0800 Subject: [PATCH 1/9] feat: add initial ohos support --- Cargo.toml | 7 ++ src/backend/mod.rs | 7 ++ src/backend/ohos.rs | 236 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 src/backend/ohos.rs diff --git a/Cargo.toml b/Cargo.toml index 7edf159..e7df4b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,10 @@ objc2 = "0.6.4" objc2-core-foundation = "0.3.2" objc2-foundation = "0.3.2" objc2-ui-kit = "0.3.2" + +[target.'cfg(target_env = "ohos")'.dependencies] +napi-derive-ohos = { version = "1.1.3" } +napi-ohos = { version = "1.1.3", default-features = false, features = [ + "napi8", + "async", +] } \ No newline at end of file diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 5a475cd..83349c8 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -54,6 +54,11 @@ mod ios; #[cfg(target_os = "ios")] pub use ios::IOS; +#[cfg(target_env = "ohos")] +mod ohos; +#[cfg(target_env = "ohos")] +pub use ohos::OHOS; + /// Trait for platform-specific input box backends. /// /// Implement this trait to add support for different dialog implementations. @@ -162,6 +167,8 @@ pub fn default_backend() -> Box { Box::new(Android::default()) } else if #[cfg(target_os = "ios")] { Box::new(IOS::default()) + } else if #[cfg(target_env = "ohos")] { + Box::new(OHOS::default()) } else { Box::new(Zenity::default()) } diff --git a/src/backend/ohos.rs b/src/backend/ohos.rs new file mode 100644 index 0000000..29af6c5 --- /dev/null +++ b/src/backend/ohos.rs @@ -0,0 +1,236 @@ +//! OHOS (OpenHarmony) backend for InputBox. +//! +//! This backend uses NAPI to communicate with ArkTS layer for showing native dialogs. + +use std::collections::HashMap; +use std::io; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Mutex, OnceLock}; + +use napi_derive_ohos::napi; +use napi_ohos::{ + bindgen_prelude::*, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, +}; + +use super::Backend; +use crate::{DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE, InputBox, InputMode}; + +static REQUEST_ID_COUNTER: AtomicU64 = AtomicU64::new(1); + +static PENDING_CALLBACKS: OnceLock< + Mutex>) + Send>>>, +> = OnceLock::new(); + +static REQUEST_CALLBACK: OnceLock< + ThreadsafeFunction, +> = OnceLock::new(); + +fn get_pending_callbacks() +-> &'static Mutex>) + Send>>> { + PENDING_CALLBACKS.get_or_init(|| Mutex::new(HashMap::new())) +} + +#[napi(object)] +#[derive(Clone)] +pub struct InputBoxRequest { + pub request_id: i64, + pub title: String, + pub prompt: Option, + pub default_value: String, + pub mode: String, + pub ok_label: String, + pub cancel_label: String, + pub width: Option, + pub height: Option, + pub auto_wrap: bool, + pub scroll_to_end: bool, +} + +#[napi(object)] +pub struct InputBoxResponse { + pub request_id: i64, + pub text: Option, + pub error: Option, +} + +/// OHOS backend for InputBox. +/// +/// This backend uses NAPI to call into ArkTS layer for showing native dialogs. +/// +/// # Setup +/// +/// To use this backend, you need to: +/// +/// 1. Import this native library in your ArkTS code. +/// 2. Call [`register_inputbox_callback`] to register the request handler. +/// 3. Implement the dialog display logic in ArkTS. +/// +/// # ArkTS Integration Example +/// +/// ```typescript +/// import inputbox from 'libinputbox.so'; +/// +/// // Register the callback handler +/// inputbox.registerInputboxCallback((request: InputBoxRequest) => { +/// // Show your custom dialog using request.title, request.prompt, etc. +/// // When user confirms or cancels, call: +/// inputbox.onInputboxResponse({ +/// requestId: request.requestId, +/// text: userInput, // or null if cancelled +/// error: null +/// }); +/// }); +/// ``` +/// +/// # Limitations +/// +/// - `width` and `height` are hints only and may be ignored. +/// +/// # Defaults +/// +/// - `title`: `DEFAULT_TITLE` +/// - `prompt`: empty +/// - `cancel_label`: `DEFAULT_CANCEL_LABEL` +/// - `ok_label`: `DEFAULT_OK_LABEL` +#[derive(Default, Debug, Clone)] +pub struct OHOS { + _priv: (), +} + +impl OHOS { + pub fn new() -> Self { + Self::default() + } +} + +impl Backend for OHOS { + fn execute_async( + &self, + input: &InputBox, + callback: Box>) + Send>, + ) -> io::Result<()> { + let tsfn = REQUEST_CALLBACK.get().ok_or_else(|| { + io::Error::other( + "OHOS callback not registered. Call registerInputboxCallback from ArkTS first.", + ) + })?; + let request_id = REQUEST_ID_COUNTER.fetch_add(1, Ordering::SeqCst); + let mut callbacks = get_pending_callbacks().lock().unwrap(); + callbacks.insert(request_id, callback); + let request = InputBoxRequest { + request_id: request_id as i64, + title: input.title.as_deref().unwrap_or(DEFAULT_TITLE).to_string(), + prompt: input.prompt.as_deref().map(|s| s.to_string()), + default_value: input.default.to_string(), + mode: match input.mode { + InputMode::Text => "text", + InputMode::Password => "password", + InputMode::Multiline => "multiline", + } + .to_string(), + ok_label: input + .ok_label + .as_deref() + .unwrap_or(DEFAULT_OK_LABEL) + .to_string(), + cancel_label: input + .cancel_label + .as_deref() + .unwrap_or(DEFAULT_CANCEL_LABEL) + .to_string(), + width: input.width, + height: input.height, + auto_wrap: input.auto_wrap, + scroll_to_end: input.scroll_to_end, + }; + + // Send request to ArkTS layer + let status = tsfn.call(request, ThreadsafeFunctionCallMode::NonBlocking); + if status != napi_ohos::Status::Ok { + // Remove callback if send failed + let mut callbacks = get_pending_callbacks().lock().unwrap(); + if let Some(cb) = callbacks.remove(&request_id) { + cb(Err(io::Error::other(format!( + "Failed to send request to ArkTS: {:?}", + status + )))); + } + } + + Ok(()) + } +} + +/// Register the ArkTS callback handler for input box requests. +/// +/// This function must be called from ArkTS before using the InputBox API. +/// The callback will receive [`InputBoxRequest`] objects when `show()` is called. +/// +/// # Example +/// +/// ```typescript +/// import inputbox from 'libinputbox.so'; +/// +/// inputbox.registerInputboxCallback((request) => { +/// // Display dialog and handle user input +/// }); +/// ``` +#[napi] +pub fn register_inputbox_callback( + callback: Function, +) -> napi_ohos::Result<()> { + let tsfn = callback + .build_threadsafe_function() + .max_queue_size::<16>() + .build()?; + + REQUEST_CALLBACK + .set(tsfn) + .map_err(|_| napi_ohos::Error::from_reason("Callback already registered"))?; + + Ok(()) +} + +/// Handle response from ArkTS layer. +/// +/// This function should be called from ArkTS when the user completes or cancels +/// the input dialog. +/// +/// # Example +/// +/// ```typescript +/// import inputbox from 'libinputbox.so'; +/// +/// // When user clicks OK: +/// inputbox.onInputboxResponse({ +/// requestId: request.requestId, +/// text: userInputText, +/// error: null +/// }); +/// +/// // When user clicks Cancel: +/// inputbox.onInputboxResponse({ +/// requestId: request.requestId, +/// text: null, +/// error: null +/// }); +/// ``` +#[napi] +pub fn on_inputbox_response(response: InputBoxResponse) { + let request_id = response.request_id as u64; + + // Retrieve and remove the pending callback + let callback = { + let mut callbacks = get_pending_callbacks().lock().unwrap(); + callbacks.remove(&request_id) + }; + + if let Some(cb) = callback { + if let Some(error) = response.error { + cb(Err(io::Error::other(error))); + } else { + cb(Ok(response.text)); + } + } +} From 6e7bafce52f955d93ac4ff1200a76a9df2ca508d Mon Sep 17 00:00:00 2001 From: ljlvink Date: Mon, 9 Mar 2026 23:18:17 +0800 Subject: [PATCH 2/9] fix: pr review --- Cargo.toml | 2 +- src/backend/ohos.rs | 79 +++++++++++++++++++++------------------------ 2 files changed, 37 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e7df4b9..0513bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,4 +43,4 @@ napi-derive-ohos = { version = "1.1.3" } napi-ohos = { version = "1.1.3", default-features = false, features = [ "napi8", "async", -] } \ No newline at end of file +] } diff --git a/src/backend/ohos.rs b/src/backend/ohos.rs index 29af6c5..c69f4ad 100644 --- a/src/backend/ohos.rs +++ b/src/backend/ohos.rs @@ -2,10 +2,8 @@ //! //! This backend uses NAPI to communicate with ArkTS layer for showing native dialogs. -use std::collections::HashMap; use std::io; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::sync::{Mutex, OnceLock}; +use std::sync::OnceLock; use napi_derive_ohos::napi; use napi_ohos::{ @@ -16,25 +14,15 @@ use napi_ohos::{ use super::Backend; use crate::{DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE, InputBox, InputMode}; -static REQUEST_ID_COUNTER: AtomicU64 = AtomicU64::new(1); - -static PENDING_CALLBACKS: OnceLock< - Mutex>) + Send>>>, -> = OnceLock::new(); - static REQUEST_CALLBACK: OnceLock< ThreadsafeFunction, > = OnceLock::new(); -fn get_pending_callbacks() --> &'static Mutex>) + Send>>> { - PENDING_CALLBACKS.get_or_init(|| Mutex::new(HashMap::new())) -} - #[napi(object)] #[derive(Clone)] pub struct InputBoxRequest { - pub request_id: i64, + /// Callback pointer as i64 (leaked Box) + pub callback_ptr: i64, pub title: String, pub prompt: Option, pub default_value: String, @@ -47,9 +35,11 @@ pub struct InputBoxRequest { pub scroll_to_end: bool, } +#[allow(dead_code)] #[napi(object)] pub struct InputBoxResponse { - pub request_id: i64, + /// Callback pointer as i64 (to recover the leaked Box) + pub callback_ptr: i64, pub text: Option, pub error: Option, } @@ -76,7 +66,7 @@ pub struct InputBoxResponse { /// // Show your custom dialog using request.title, request.prompt, etc. /// // When user confirms or cancels, call: /// inputbox.onInputboxResponse({ -/// requestId: request.requestId, +/// callbackPtr: request.callbackPtr, /// text: userInput, // or null if cancelled /// error: null /// }); @@ -115,11 +105,13 @@ impl Backend for OHOS { "OHOS callback not registered. Call registerInputboxCallback from ArkTS first.", ) })?; - let request_id = REQUEST_ID_COUNTER.fetch_add(1, Ordering::SeqCst); - let mut callbacks = get_pending_callbacks().lock().unwrap(); - callbacks.insert(request_id, callback); + + // Leak the callback and pass its pointer to ArkTS + // ArkTS will call on_inputbox_response with this pointer to invoke the callback + let callback_ptr = Box::into_raw(Box::new(callback)) as i64; + let request = InputBoxRequest { - request_id: request_id as i64, + callback_ptr, title: input.title.as_deref().unwrap_or(DEFAULT_TITLE).to_string(), prompt: input.prompt.as_deref().map(|s| s.to_string()), default_value: input.default.to_string(), @@ -148,14 +140,16 @@ impl Backend for OHOS { // Send request to ArkTS layer let status = tsfn.call(request, ThreadsafeFunctionCallMode::NonBlocking); if status != napi_ohos::Status::Ok { - // Remove callback if send failed - let mut callbacks = get_pending_callbacks().lock().unwrap(); - if let Some(cb) = callbacks.remove(&request_id) { - cb(Err(io::Error::other(format!( - "Failed to send request to ArkTS: {:?}", - status - )))); - } + // Recover and call the callback with error if send failed + let callback = unsafe { + Box::from_raw( + callback_ptr as *mut Box>) + Send>, + ) + }; + callback(Err(io::Error::other(format!( + "Failed to send request to ArkTS: {:?}", + status + )))); } Ok(()) @@ -176,6 +170,7 @@ impl Backend for OHOS { /// // Display dialog and handle user input /// }); /// ``` +#[allow(dead_code)] #[napi] pub fn register_inputbox_callback( callback: Function, @@ -204,33 +199,31 @@ pub fn register_inputbox_callback( /// /// // When user clicks OK: /// inputbox.onInputboxResponse({ -/// requestId: request.requestId, +/// callbackPtr: request.callbackPtr, /// text: userInputText, /// error: null /// }); /// /// // When user clicks Cancel: /// inputbox.onInputboxResponse({ -/// requestId: request.requestId, +/// callbackPtr: request.callbackPtr, /// text: null, /// error: null /// }); /// ``` +#[allow(dead_code)] #[napi] pub fn on_inputbox_response(response: InputBoxResponse) { - let request_id = response.request_id as u64; - - // Retrieve and remove the pending callback - let callback = { - let mut callbacks = get_pending_callbacks().lock().unwrap(); - callbacks.remove(&request_id) + // Recover the leaked callback from the pointer passed by ArkTS + let callback = unsafe { + Box::from_raw( + response.callback_ptr as *mut Box>) + Send>, + ) }; - if let Some(cb) = callback { - if let Some(error) = response.error { - cb(Err(io::Error::other(error))); - } else { - cb(Ok(response.text)); - } + if let Some(error) = response.error { + callback(Err(io::Error::other(error))); + } else { + callback(Ok(response.text)); } } From 2cede1b8caad763b7d257908af23607e7e6eb7af Mon Sep 17 00:00:00 2001 From: mivik Date: Mon, 9 Mar 2026 23:35:46 +0800 Subject: [PATCH 3/9] doc: adjust text wrap --- src/backend/ohos.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/ohos.rs b/src/backend/ohos.rs index c69f4ad..fe43189 100644 --- a/src/backend/ohos.rs +++ b/src/backend/ohos.rs @@ -158,8 +158,8 @@ impl Backend for OHOS { /// Register the ArkTS callback handler for input box requests. /// -/// This function must be called from ArkTS before using the InputBox API. -/// The callback will receive [`InputBoxRequest`] objects when `show()` is called. +/// This function must be called from ArkTS before using the InputBox API. The +/// callback will receive [`InputBoxRequest`] objects when `show()` is called. /// /// # Example /// From 40114a8cd8396656751a6bf1e548124eb40eede2 Mon Sep 17 00:00:00 2001 From: mivik Date: Mon, 9 Mar 2026 23:36:36 +0800 Subject: [PATCH 4/9] fix: msrv compatibility --- src/backend/ohos.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/backend/ohos.rs b/src/backend/ohos.rs index fe43189..7afa29f 100644 --- a/src/backend/ohos.rs +++ b/src/backend/ohos.rs @@ -12,7 +12,7 @@ use napi_ohos::{ }; use super::Backend; -use crate::{DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE, InputBox, InputMode}; +use crate::{InputBox, InputMode, DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE}; static REQUEST_CALLBACK: OnceLock< ThreadsafeFunction, @@ -101,7 +101,8 @@ impl Backend for OHOS { callback: Box>) + Send>, ) -> io::Result<()> { let tsfn = REQUEST_CALLBACK.get().ok_or_else(|| { - io::Error::other( + io::Error::new( + io::ErrorKind::Other, "OHOS callback not registered. Call registerInputboxCallback from ArkTS first.", ) })?; @@ -146,10 +147,10 @@ impl Backend for OHOS { callback_ptr as *mut Box>) + Send>, ) }; - callback(Err(io::Error::other(format!( - "Failed to send request to ArkTS: {:?}", - status - )))); + callback(Err(io::Error::new( + io::ErrorKind::Other, + format!("Failed to send request to ArkTS: {:?}", status), + ))); } Ok(()) @@ -222,7 +223,7 @@ pub fn on_inputbox_response(response: InputBoxResponse) { }; if let Some(error) = response.error { - callback(Err(io::Error::other(error))); + callback(Err(io::Error::new(io::ErrorKind::Other, error))); } else { callback(Ok(response.text)); } From 99ef786194c1cb7084256645e3e1f82912218577 Mon Sep 17 00:00:00 2001 From: mivik Date: Mon, 9 Mar 2026 23:36:59 +0800 Subject: [PATCH 5/9] style: use as_str --- src/backend/ohos.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/backend/ohos.rs b/src/backend/ohos.rs index 7afa29f..85f46ce 100644 --- a/src/backend/ohos.rs +++ b/src/backend/ohos.rs @@ -116,12 +116,7 @@ impl Backend for OHOS { title: input.title.as_deref().unwrap_or(DEFAULT_TITLE).to_string(), prompt: input.prompt.as_deref().map(|s| s.to_string()), default_value: input.default.to_string(), - mode: match input.mode { - InputMode::Text => "text", - InputMode::Password => "password", - InputMode::Multiline => "multiline", - } - .to_string(), + mode: input.mode.as_str().to_owned(), ok_label: input .ok_label .as_deref() From 7eacb73938306e58efce5944bc25dd004f351855 Mon Sep 17 00:00:00 2001 From: ljlvink Date: Mon, 9 Mar 2026 23:58:50 +0800 Subject: [PATCH 6/9] fix: ohos fix --- src/backend/android.rs | 6 ++-- src/backend/general.rs | 2 +- src/backend/ios.rs | 6 ++-- src/backend/macos/mod.rs | 2 +- src/backend/ohos.rs | 61 ++++++++++++++++---------------------- src/backend/windows/mod.rs | 2 +- src/lib.rs | 2 +- 7 files changed, 36 insertions(+), 45 deletions(-) diff --git a/src/backend/android.rs b/src/backend/android.rs index 6825fd7..6730f15 100644 --- a/src/backend/android.rs +++ b/src/backend/android.rs @@ -1,17 +1,17 @@ use std::io; use jni::{ - Env, EnvUnowned, JavaVM, Outcome, errors::ThrowRuntimeExAndDefault, jni_sig, jni_str, objects::{JClass, JObject, JString, JValue}, refs::Global, signature::MethodSignature, - sys::{JNIEnv, jlong}, + sys::{jlong, JNIEnv}, + Env, EnvUnowned, JavaVM, Outcome, }; use once_cell::sync::OnceCell; -use crate::{DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE, InputBox}; +use crate::{InputBox, DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE}; use super::Backend; diff --git a/src/backend/general.rs b/src/backend/general.rs index c59128b..ef26558 100644 --- a/src/backend/general.rs +++ b/src/backend/general.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, path::Path, process::Command}; -use crate::{InputBox, InputMode, backend::CommandBackend}; +use crate::{backend::CommandBackend, InputBox, InputMode}; /// Zenity backend. /// diff --git a/src/backend/ios.rs b/src/backend/ios.rs index 28f44bd..65d89ee 100644 --- a/src/backend/ios.rs +++ b/src/backend/ios.rs @@ -6,16 +6,16 @@ use std::{ }; use block2::StackBlock; -use objc2::{MainThreadMarker, rc::Retained}; +use objc2::{rc::Retained, MainThreadMarker}; use objc2_core_foundation::{CGFloat, CGRect, CGSize}; -use objc2_foundation::{NSArray, NSObjectNSKeyValueCoding, NSRange, NSString, ns_string}; +use objc2_foundation::{ns_string, NSArray, NSObjectNSKeyValueCoding, NSRange, NSString}; use objc2_ui_kit::{ NSLayoutConstraint, UIAlertAction, UIAlertActionStyle, UIAlertController, UIAlertControllerStyle, UIApplication, UIFont, UITextField, UITextInputTraits, UITextView, UIViewController, UIWindowScene, }; -use crate::{DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE, InputMode, backend::Backend}; +use crate::{backend::Backend, InputMode, DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE}; /// iOS backend for InputBox using `UIAlertController`. /// diff --git a/src/backend/macos/mod.rs b/src/backend/macos/mod.rs index cd56bcf..1067f52 100644 --- a/src/backend/macos/mod.rs +++ b/src/backend/macos/mod.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, path::Path, process::Command}; use crate::{ - DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE, InputBox, backend::CommandBackend, + backend::CommandBackend, InputBox, DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE, }; const JXA_SCRIPT: &str = include_str!("inputbox.jxa.js"); diff --git a/src/backend/ohos.rs b/src/backend/ohos.rs index 85f46ce..bde974a 100644 --- a/src/backend/ohos.rs +++ b/src/backend/ohos.rs @@ -14,6 +14,8 @@ use napi_ohos::{ use super::Backend; use crate::{InputBox, InputMode, DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE}; +type Callback = Box>) + Send>; + static REQUEST_CALLBACK: OnceLock< ThreadsafeFunction, > = OnceLock::new(); @@ -21,8 +23,7 @@ static REQUEST_CALLBACK: OnceLock< #[napi(object)] #[derive(Clone)] pub struct InputBoxRequest { - /// Callback pointer as i64 (leaked Box) - pub callback_ptr: i64, + pub callback: i64, pub title: String, pub prompt: Option, pub default_value: String, @@ -38,8 +39,7 @@ pub struct InputBoxRequest { #[allow(dead_code)] #[napi(object)] pub struct InputBoxResponse { - /// Callback pointer as i64 (to recover the leaked Box) - pub callback_ptr: i64, + pub callback: i64, pub text: Option, pub error: Option, } @@ -66,7 +66,7 @@ pub struct InputBoxResponse { /// // Show your custom dialog using request.title, request.prompt, etc. /// // When user confirms or cancels, call: /// inputbox.onInputboxResponse({ -/// callbackPtr: request.callbackPtr, +/// callback: request.callback, /// text: userInput, // or null if cancelled /// error: null /// }); @@ -101,22 +101,22 @@ impl Backend for OHOS { callback: Box>) + Send>, ) -> io::Result<()> { let tsfn = REQUEST_CALLBACK.get().ok_or_else(|| { - io::Error::new( - io::ErrorKind::Other, + io::Error::other( "OHOS callback not registered. Call registerInputboxCallback from ArkTS first.", ) })?; - - // Leak the callback and pass its pointer to ArkTS - // ArkTS will call on_inputbox_response with this pointer to invoke the callback - let callback_ptr = Box::into_raw(Box::new(callback)) as i64; - + let callback_ptr = Box::into_raw(Box::new(callback)); let request = InputBoxRequest { - callback_ptr, + callback: callback_ptr as i64, title: input.title.as_deref().unwrap_or(DEFAULT_TITLE).to_string(), prompt: input.prompt.as_deref().map(|s| s.to_string()), default_value: input.default.to_string(), - mode: input.mode.as_str().to_owned(), + mode: match input.mode { + InputMode::Text => "text", + InputMode::Password => "password", + InputMode::Multiline => "multiline", + } + .to_string(), ok_label: input .ok_label .as_deref() @@ -136,16 +136,12 @@ impl Backend for OHOS { // Send request to ArkTS layer let status = tsfn.call(request, ThreadsafeFunctionCallMode::NonBlocking); if status != napi_ohos::Status::Ok { - // Recover and call the callback with error if send failed - let callback = unsafe { - Box::from_raw( - callback_ptr as *mut Box>) + Send>, - ) - }; - callback(Err(io::Error::new( - io::ErrorKind::Other, - format!("Failed to send request to ArkTS: {:?}", status), - ))); + // Recover and invoke callback if send failed + let callback = unsafe { Box::from_raw(callback_ptr) }; + callback(Err(io::Error::other(format!( + "Failed to send request to ArkTS: {:?}", + status + )))); } Ok(()) @@ -154,8 +150,8 @@ impl Backend for OHOS { /// Register the ArkTS callback handler for input box requests. /// -/// This function must be called from ArkTS before using the InputBox API. The -/// callback will receive [`InputBoxRequest`] objects when `show()` is called. +/// This function must be called from ArkTS before using the InputBox API. +/// The callback will receive [`InputBoxRequest`] objects when `show()` is called. /// /// # Example /// @@ -195,14 +191,14 @@ pub fn register_inputbox_callback( /// /// // When user clicks OK: /// inputbox.onInputboxResponse({ -/// callbackPtr: request.callbackPtr, +/// callback: request.callback, /// text: userInputText, /// error: null /// }); /// /// // When user clicks Cancel: /// inputbox.onInputboxResponse({ -/// callbackPtr: request.callbackPtr, +/// callback: request.callback, /// text: null, /// error: null /// }); @@ -210,15 +206,10 @@ pub fn register_inputbox_callback( #[allow(dead_code)] #[napi] pub fn on_inputbox_response(response: InputBoxResponse) { - // Recover the leaked callback from the pointer passed by ArkTS - let callback = unsafe { - Box::from_raw( - response.callback_ptr as *mut Box>) + Send>, - ) - }; + let callback = unsafe { Box::from_raw(response.callback as *mut Callback) }; if let Some(error) = response.error { - callback(Err(io::Error::new(io::ErrorKind::Other, error))); + callback(Err(io::Error::other(error))); } else { callback(Ok(response.text)); } diff --git a/src/backend/windows/mod.rs b/src/backend/windows/mod.rs index 7ffb45e..8e7b8ef 100644 --- a/src/backend/windows/mod.rs +++ b/src/backend/windows/mod.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, path::Path, process::Command}; use serde_json::json; -use crate::{DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, InputBox, backend::CommandBackend}; +use crate::{backend::CommandBackend, InputBox, DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL}; const PS_SCRIPT: &str = include_str!("inputbox.ps1"); diff --git a/src/lib.rs b/src/lib.rs index 59ff500..d87c10f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,7 +34,7 @@ pub mod backend; use std::{borrow::Cow, io}; -use crate::backend::{Backend, default_backend}; +use crate::backend::{default_backend, Backend}; /// Default title for the input box dialog. pub const DEFAULT_TITLE: &str = "Input"; From 288f1dc36f6c7f453dd155b217f9eb840bf34c81 Mon Sep 17 00:00:00 2001 From: mivik Date: Tue, 10 Mar 2026 00:01:14 +0800 Subject: [PATCH 7/9] style: various issues --- src/backend/ohos.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/backend/ohos.rs b/src/backend/ohos.rs index bde974a..2fb2be6 100644 --- a/src/backend/ohos.rs +++ b/src/backend/ohos.rs @@ -1,6 +1,7 @@ //! OHOS (OpenHarmony) backend for InputBox. //! -//! This backend uses NAPI to communicate with ArkTS layer for showing native dialogs. +//! This backend uses NAPI to communicate with ArkTS layer for showing native +//! dialogs. use std::io; use std::sync::OnceLock; @@ -12,7 +13,7 @@ use napi_ohos::{ }; use super::Backend; -use crate::{InputBox, InputMode, DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE}; +use crate::{InputBox, DEFAULT_CANCEL_LABEL, DEFAULT_OK_LABEL, DEFAULT_TITLE}; type Callback = Box>) + Send>; @@ -101,7 +102,8 @@ impl Backend for OHOS { callback: Box>) + Send>, ) -> io::Result<()> { let tsfn = REQUEST_CALLBACK.get().ok_or_else(|| { - io::Error::other( + io::Error::new( + io::ErrorKind::Other, "OHOS callback not registered. Call registerInputboxCallback from ArkTS first.", ) })?; @@ -111,12 +113,7 @@ impl Backend for OHOS { title: input.title.as_deref().unwrap_or(DEFAULT_TITLE).to_string(), prompt: input.prompt.as_deref().map(|s| s.to_string()), default_value: input.default.to_string(), - mode: match input.mode { - InputMode::Text => "text", - InputMode::Password => "password", - InputMode::Multiline => "multiline", - } - .to_string(), + mode: input.mode.as_str().to_owned(), ok_label: input .ok_label .as_deref() @@ -138,10 +135,10 @@ impl Backend for OHOS { if status != napi_ohos::Status::Ok { // Recover and invoke callback if send failed let callback = unsafe { Box::from_raw(callback_ptr) }; - callback(Err(io::Error::other(format!( - "Failed to send request to ArkTS: {:?}", - status - )))); + callback(Err(io::Error::new( + io::ErrorKind::Other, + format!("Failed to send request to ArkTS: {:?}", status), + ))); } Ok(()) @@ -150,8 +147,8 @@ impl Backend for OHOS { /// Register the ArkTS callback handler for input box requests. /// -/// This function must be called from ArkTS before using the InputBox API. -/// The callback will receive [`InputBoxRequest`] objects when `show()` is called. +/// This function must be called from ArkTS before using the InputBox API. The +/// callback will receive [`InputBoxRequest`] objects when `show()` is called. /// /// # Example /// @@ -209,7 +206,7 @@ pub fn on_inputbox_response(response: InputBoxResponse) { let callback = unsafe { Box::from_raw(response.callback as *mut Callback) }; if let Some(error) = response.error { - callback(Err(io::Error::other(error))); + callback(Err(io::Error::new(io::ErrorKind::Other, error))); } else { callback(Ok(response.text)); } From 1e92cc793b45800a2a38f9c81ad4c051788f4dac Mon Sep 17 00:00:00 2001 From: mivik Date: Tue, 10 Mar 2026 00:03:05 +0800 Subject: [PATCH 8/9] style: organize import --- src/backend/ohos.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/ohos.rs b/src/backend/ohos.rs index 2fb2be6..a8eccd3 100644 --- a/src/backend/ohos.rs +++ b/src/backend/ohos.rs @@ -3,8 +3,7 @@ //! This backend uses NAPI to communicate with ArkTS layer for showing native //! dialogs. -use std::io; -use std::sync::OnceLock; +use std::{io, sync::OnceLock}; use napi_derive_ohos::napi; use napi_ohos::{ From 0442714ecf668cdc8053985d7629de30eebf0ad4 Mon Sep 17 00:00:00 2001 From: mivik Date: Tue, 10 Mar 2026 00:04:18 +0800 Subject: [PATCH 9/9] doc: add ohos --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 61e676c..6777149 100644 --- a/README.md +++ b/README.md @@ -106,20 +106,21 @@ InputBox::new() - **Multiple input modes** — text, password, or multiline - **Highly customizable** — title, prompt, button labels, dimensions, and more -- **Works on most platforms** — Windows, macOS, Linux, Android, and iOS +- **Works on most platforms** — Windows, macOS, Linux, Android, iOS nad OpenHarmony - **Pluggable backends** — use a specific backend or let the library pick - **Synchronous and asynchronous** — safe sync on most platforms, async required on iOS ## Backends -| Backend | Platform | How it works | -| ----------- | -------- | ----------------------------------------------- | -| `PSScript` | Windows | PowerShell + WinForms, no extra install needed | -| `JXAScript` | macOS | `osascript` JXA, built into the OS | -| `Android` | Android | AAR + JNI to show an Android AlertDialog | -| `IOS` | iOS | UIKit alert | -| `Yad` | Linux | [`yad`](https://github.com/v1cont/yad) | -| `Zenity` | Linux | `zenity` — fallback on GNOME systems | +| Backend | Platform | How it works | +| ----------- | ----------- | ----------------------------------------------- | +| `PSScript` | Windows | PowerShell + WinForms, no extra install needed | +| `JXAScript` | macOS | `osascript` JXA, built into the OS | +| `Android` | Android | AAR + JNI to show an Android AlertDialog | +| `IOS` | iOS | UIKit alert | +| `OHOS` | OpenHarmony | NAPI + ArkTS dialog | +| `Yad` | Linux | [`yad`](https://github.com/v1cont/yad) | +| `Zenity` | Linux | `zenity` — fallback on GNOME systems | ### Linux Installation