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}