xcomponent/
lib.rs

1//! An abstraction over the OpenHarmony [XComponent]
2//!
3//! ## Example
4//! ```
5//! # use ohos_sys::ace::xcomponent::native_interface_xcomponent::OH_NativeXComponent;
6//! # use core::ffi::c_void;
7//! pub extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) {
8//!     let xc = xcomponent::XComponent::new(xcomponent, window).expect("Invalid XC");
9//!     let size = xc.size();
10//!     // do something with the xcomponent ...
11//! }
12//!
13//! pub extern "C" fn on_dispatch_touch_event_cb(
14//!     component: *mut OH_NativeXComponent,
15//!     window: *mut c_void,
16//! ) {
17//!      let xc = xcomponent::XComponent::new(component, window).unwrap();
18//!      let touch_event = xc.get_touch_event().unwrap();
19//!      // Handle the touch event ....
20//! }
21//! ```
22//!
23//! ## Features
24//!
25//! * log: Outputs error and diagnostic messages via the `log` crate if enabled.
26//! * register: Add `register_xcomponent_callbacks` function to register XComponent callbacks.
27//!
28//! [XComponent]: https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/ui/napi-xcomponent-guidelines.md
29
30use crate::log::error;
31use core::{ffi::c_void, marker::PhantomData, mem::MaybeUninit, ptr::NonNull};
32use ohos_sys::ace::xcomponent::native_interface_xcomponent::OH_NativeXComponent_GetXComponentSize;
33use ohos_sys::{
34    ace::xcomponent::native_interface_xcomponent::{
35        OH_NativeXComponent, OH_NativeXComponent_GetTouchEvent, OH_NativeXComponent_TouchEvent,
36    },
37    native_window::OHNativeWindow,
38};
39
40mod log;
41
42pub struct Size {
43    pub width: u64,
44    pub height: u64,
45    _opaque: [u64; 0],
46}
47
48pub struct XComponent<'a> {
49    xcomponent: NonNull<OH_NativeXComponent>,
50    window: NonNull<OHNativeWindow>,
51    phantom: PhantomData<&'a mut OH_NativeXComponent>,
52}
53
54impl<'a> XComponent<'a> {
55    pub fn new(
56        xcomponent: *mut OH_NativeXComponent,
57        window: *mut c_void,
58    ) -> Option<XComponent<'a>> {
59        Some(XComponent {
60            xcomponent: NonNull::new(xcomponent)?,
61            window: NonNull::new(window.cast())?,
62            phantom: PhantomData,
63        })
64    }
65
66    pub fn get_touch_event(&self) -> Result<OH_NativeXComponent_TouchEvent, i32> {
67        let touch_event = unsafe {
68            let mut touch_event: MaybeUninit<OH_NativeXComponent_TouchEvent> =
69                MaybeUninit::uninit();
70            let res = OH_NativeXComponent_GetTouchEvent(
71                self.xcomponent.as_ptr(),
72                self.window.as_ptr().cast(),
73                touch_event.as_mut_ptr(),
74            );
75            if res != 0 {
76                error!("OH_NativeXComponent_GetTouchEvent failed with {res}");
77                return Err(res);
78            }
79            touch_event.assume_init()
80        };
81
82        Ok(touch_event)
83    }
84
85    /// Returns the size of the XComponent
86    pub fn size(&self) -> Size {
87        let mut width: u64 = 0;
88        let mut height: u64 = 0;
89        let res = unsafe {
90            OH_NativeXComponent_GetXComponentSize(
91                self.xcomponent.as_ptr(),
92                self.window.as_ptr() as *const c_void,
93                &mut width as *mut _,
94                &mut height as *mut _,
95            )
96        };
97        assert_eq!(res, 0, "OH_NativeXComponent_GetXComponentSize failed");
98        Size {
99            width,
100            height,
101            _opaque: [],
102        }
103    }
104}
105
106#[cfg(feature = "register")]
107#[cfg_attr(docsrs, doc(cfg(feature = "register")))]
108#[derive(Debug)]
109pub enum RegisterCallbackError {
110    XcomponentPropertyMissing(String),
111    UnwrapXComponentFailed(i32),
112    RegisterCallbackFailed(i32),
113}
114
115#[cfg(feature = "register")]
116#[cfg_attr(docsrs, doc(cfg(feature = "register")))]
117impl Into<String> for RegisterCallbackError {
118    fn into(self) -> String {
119        format!("{:?}", self)
120    }
121}
122
123/// Register callbacks for the XComponent
124///
125/// This function is intended to be called from the module init function (See Example below).
126/// We currently require the `callbacks` parameter to have a static lifetime, since despite
127/// contrary documentation `OH_NativeXComponent_RegisterCallback` seems to use the address of
128/// `callback` after it has returned.
129///
130///
131///
132///
133/// ## Example:
134///
135/// ```
136/// # use core::ffi::c_void;
137/// # use log::info;
138/// # use ohos_sys::ace::xcomponent::native_interface_xcomponent::{OH_NativeXComponent, OH_NativeXComponent_Callback};
139/// // use napi_derive_ohos::module_exports;
140/// // #[module_exports]
141/// fn init(exports: napi_ohos::JsObject, env: napi_ohos::Env) -> napi_ohos::Result<()> {
142///     xcomponent::register_xcomponent_callbacks(&exports, &env, &XC_CALLBACKS)
143///         .expect("Registering Callback failed.");
144///     Ok(())
145/// }
146///
147/// static XC_CALLBACKS: OH_NativeXComponent_Callback = OH_NativeXComponent_Callback {
148///     OnSurfaceCreated: Some(on_surface_created_cb),
149///     OnSurfaceChanged: Some(on_surface_changed_cb),
150///     OnSurfaceDestroyed: Some(on_surface_destroyed_cb),
151///     DispatchTouchEvent: Some(on_dispatch_touch_event_cb),
152/// };
153///
154/// // Note: `pub` attribute or `#[no_mangle]` are NOT required, since we just register the function
155/// // pointer.
156/// extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) {
157///     info!("on_surface_created_cb");
158/// }
159/// extern "C" fn on_surface_changed_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) {
160///     info!("on_surface_changed_cb");
161/// }
162/// extern "C" fn on_surface_destroyed_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) {
163///     info!("on_surface_destroyed_cb");
164/// }
165/// extern "C" fn on_dispatch_touch_event_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) {
166///     info!("on_dispatch_touch_event_cb");
167/// }
168/// ```
169#[cfg(feature = "register")]
170#[cfg_attr(docsrs, doc(cfg(feature = "register")))]
171pub fn register_xcomponent_callbacks(
172    exports: &napi_ohos::JsObject,
173    env: &napi_ohos::Env,
174    callbacks: &'static ohos_sys::ace::xcomponent::native_interface_xcomponent::OH_NativeXComponent_Callback,
175) -> Result<(), RegisterCallbackError> {
176    use napi_ohos::NapiRaw;
177    use ohos_sys::ace::xcomponent::native_interface_xcomponent::OH_NativeXComponent_RegisterCallback;
178
179    let xcomponent_js_object = exports
180        .get_named_property::<napi_ohos::JsObject>("__NATIVE_XCOMPONENT_OBJ__")
181        .map_err(|e| RegisterCallbackError::XcomponentPropertyMissing(e.to_string()))?;
182    let raw = unsafe { xcomponent_js_object.raw() };
183    let raw_env = env.raw();
184    let mut native_xcomponent: *mut OH_NativeXComponent = core::ptr::null_mut();
185    let res = unsafe {
186        napi_ohos::sys::napi_unwrap(
187            raw_env,
188            raw,
189            &mut native_xcomponent as *mut *mut OH_NativeXComponent as *mut *mut c_void,
190        )
191    };
192    if res != 0 {
193        return Err(RegisterCallbackError::UnwrapXComponentFailed(res));
194    }
195    let res =
196        // Note: The register function seems to offload the work to some other thread and return early.
197        // so the CBs need to live longer than this function ....
198        // SAFETY: `OH_NativeXComponent_RegisterCallback` will not mutate `callbacks`.
199        unsafe { OH_NativeXComponent_RegisterCallback(native_xcomponent, callbacks as *const _ as *mut _) };
200    if res != 0 {
201        return Err(RegisterCallbackError::RegisterCallbackFailed(res));
202    }
203    Ok(())
204}