diff options
author | Jonathan Schwender <55576758+jschwe@users.noreply.github.com> | 2024-11-15 16:04:48 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-15 15:04:48 +0000 |
commit | 538ac61a82ebd4f6bd02062c23c654cf83fb18ec (patch) | |
tree | 9a564a95711114324fb1650e60aac622a1d85383 | |
parent | c64d5e9d30c48f59e61439947c63a2b97a45512f (diff) | |
download | servo-538ac61a82ebd4f6bd02062c23c654cf83fb18ec.tar.gz servo-538ac61a82ebd4f6bd02062c23c654cf83fb18ec.zip |
ohos: Add basic IME and keyboard support (#34188)
* ohos: Add basic IME and keyboard support
- Add extremely basic support for keyboard events
- Add basic IME support
- Showing and hiding the IME
- inserting text
- deleting characters
- very basic configuration of the IME
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
* Apply suggestions from code review
Improve the log message
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
Signed-off-by: Jonathan Schwender <55576758+jschwe@users.noreply.github.com>
* Update ports/servoshell/egl/ohos.rs
Co-authored-by: Mukilan Thiyagarajan <mukilanthiagarajan@gmail.com>
Signed-off-by: Jonathan Schwender <55576758+jschwe@users.noreply.github.com>
* ohos: Bump the minimum required SDK version to 5.0
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
* ohos: Remove pub from callbacks
The callbacks don't need to be public, as we will be registering them.
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
* Rename composition event
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
* ohos: clippy in log
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
* ohos: address some clippy warnings
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
* ohos: Raise Error in mach if unsupported SDK version is used.
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
* Add keyboard-types dependency for android
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
---------
Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com>
Signed-off-by: Jonathan Schwender <55576758+jschwe@users.noreply.github.com>
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
Co-authored-by: Mukilan Thiyagarajan <mukilanthiagarajan@gmail.com>
-rw-r--r-- | .github/workflows/ohos.yml | 2 | ||||
-rw-r--r-- | Cargo.lock | 22 | ||||
-rw-r--r-- | components/compositing/windowing.rs | 5 | ||||
-rw-r--r-- | components/constellation/constellation.rs | 41 | ||||
-rw-r--r-- | components/constellation/tracing.rs | 1 | ||||
-rw-r--r-- | components/servo/lib.rs | 10 | ||||
-rw-r--r-- | components/shared/compositing/constellation_msg.rs | 5 | ||||
-rw-r--r-- | ports/servoshell/Cargo.toml | 5 | ||||
-rw-r--r-- | ports/servoshell/desktop/tracing.rs | 1 | ||||
-rw-r--r-- | ports/servoshell/egl/log.rs | 7 | ||||
-rw-r--r-- | ports/servoshell/egl/ohos.rs | 233 | ||||
-rw-r--r-- | ports/servoshell/egl/servo_glue.rs | 29 | ||||
-rw-r--r-- | python/servo/platform/build_target.py | 12 | ||||
-rw-r--r-- | support/openharmony/build-profile.json5 | 10 |
14 files changed, 320 insertions, 63 deletions
diff --git a/.github/workflows/ohos.yml b/.github/workflows/ohos.yml index ff12ffa00aa..79e36c1eea3 100644 --- a/.github/workflows/ohos.yml +++ b/.github/workflows/ohos.yml @@ -61,7 +61,7 @@ jobs: id: setup_sdk uses: openharmony-rs/setup-ohos-sdk@v0.1 with: - version: "4.1" + version: "5.0" fixup-path: true - name: Install node for hvigor uses: actions/setup-node@v4 diff --git a/Cargo.lock b/Cargo.lock index 8c9020ba527..724e30ecf92 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5090,6 +5090,22 @@ dependencies = [ ] [[package]] +name = "ohos-ime" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48107e68ed8451c17c2ff95938e1ba86003fb290a04f7a0213ce2d16ce4b3ee6" +dependencies = [ + "log", + "ohos-ime-sys", +] + +[[package]] +name = "ohos-ime-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12be156a401d3a97d3b2e5de3a864f585a71098bbc830130a5c876c6ba38f572" + +[[package]] name = "ohos-sys" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -6608,6 +6624,8 @@ dependencies = [ "net", "net_traits", "nix", + "ohos-ime", + "ohos-ime-sys", "ohos-sys", "ohos-vsync", "raw-window-handle", @@ -6628,6 +6646,7 @@ dependencies = [ "windows-sys 0.59.0", "winit", "winres", + "xcomponent-sys", ] [[package]] @@ -8855,6 +8874,9 @@ name = "xcomponent-sys" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "663e26cce4f574daf506a01f4e5cfedd5355c60afebc30daeeeea95f38f6b600" +dependencies = [ + "keyboard-types", +] [[package]] name = "xcursor" diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs index 5d7a45bcd10..d294f084376 100644 --- a/components/compositing/windowing.rs +++ b/components/compositing/windowing.rs @@ -10,7 +10,7 @@ use std::time::Duration; use base::id::{PipelineId, TopLevelBrowsingContextId}; use embedder_traits::EventLoopWaker; use euclid::Scale; -use keyboard_types::KeyboardEvent; +use keyboard_types::{CompositionEvent, KeyboardEvent}; use libc::c_void; use net::protocols::ProtocolRegistry; use script_traits::{ @@ -83,6 +83,8 @@ pub enum EmbedderEvent { ExitFullScreen(TopLevelBrowsingContextId), /// Sent when a key input state changes Keyboard(KeyboardEvent), + /// Sent for IME composition updates + IMEComposition(CompositionEvent), /// Sent when Ctr+R/Apple+R is called to reload the current page. Reload(TopLevelBrowsingContextId), /// Create a new top-level browsing context. @@ -139,6 +141,7 @@ impl Debug for EmbedderEvent { EmbedderEvent::Refresh => write!(f, "Refresh"), EmbedderEvent::WindowResize => write!(f, "Resize"), EmbedderEvent::Keyboard(..) => write!(f, "Keyboard"), + EmbedderEvent::IMEComposition(..) => write!(f, "IMEComposition"), EmbedderEvent::AllowNavigationResponse(..) => write!(f, "AllowNavigationResponse"), EmbedderEvent::LoadUrl(..) => write!(f, "LoadUrl"), EmbedderEvent::MouseWindowEventClass(..) => write!(f, "Mouse"), diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index ff959d6e35d..b915828b497 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -125,7 +125,7 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use ipc_channel::Error as IpcError; use keyboard_types::webdriver::Event as WebDriverInputEvent; -use keyboard_types::KeyboardEvent; +use keyboard_types::{CompositionEvent, KeyboardEvent}; use log::{debug, error, info, trace, warn}; use media::{GLPlayerThreads, WindowGLContext}; use net_traits::pub_domains::reg_host; @@ -1334,6 +1334,9 @@ where FromCompositorMsg::Keyboard(key_event) => { self.handle_key_msg(key_event); }, + FromCompositorMsg::IMECompositionEvent(ime_event) => { + self.handle_ime_msg(ime_event); + }, FromCompositorMsg::IMEDismissed => { self.handle_ime_dismissed(); }, @@ -4239,6 +4242,42 @@ where feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true)) )] + fn handle_ime_msg(&mut self, event: CompositionEvent) { + // Send to the focused browsing contexts' current pipeline. + let Some(focused_browsing_context_id) = self + .webviews + .focused_webview() + .map(|(_, webview)| webview.focused_browsing_context_id) + else { + warn!("No focused browsing context! Dropping IME event {event:?}"); + return; + }; + let event = CompositorEvent::CompositionEvent(event); + let pipeline_id = match self.browsing_contexts.get(&focused_browsing_context_id) { + Some(ctx) => ctx.pipeline_id, + None => { + return warn!( + "{}: Got composition event for nonexistent browsing context", + focused_browsing_context_id, + ); + }, + }; + let msg = ConstellationControlMsg::SendEvent(pipeline_id, event); + let result = match self.pipelines.get(&pipeline_id) { + Some(pipeline) => pipeline.event_loop.send(msg), + None => { + return debug!("{}: Got composition event after closure", pipeline_id); + }, + }; + if let Err(e) = result { + self.handle_send_error(pipeline_id, e); + } + } + + #[cfg_attr( + feature = "tracing", + tracing::instrument(skip_all, fields(servo_profiling = true)) + )] fn handle_key_msg(&mut self, event: KeyboardEvent) { // Send to the focused browsing contexts' current pipeline. If it // doesn't exist, fall back to sending to the compositor. diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index c3e87104457..d20522342fa 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -66,6 +66,7 @@ mod from_compositor { }, Self::IsReadyToSaveImage(..) => target!("IsReadyToSaveImage"), Self::Keyboard(..) => target!("Keyboard"), + Self::IMECompositionEvent(..) => target!("IMECompositionEvent"), Self::AllowNavigationResponse(..) => target!("AllowNavigationResponse"), Self::LoadUrl(..) => target!("LoadUrl"), Self::ClearCache => target!("ClearCache"), diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 4de6cecf501..d400dc64a9a 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -727,6 +727,16 @@ where } }, + EmbedderEvent::IMEComposition(ime_event) => { + let msg = ConstellationMsg::IMECompositionEvent(ime_event); + if let Err(e) = self.constellation_chan.send(msg) { + warn!( + "Sending composition event to constellation failed ({:?}).", + e + ); + } + }, + EmbedderEvent::IMEDismissed => { let msg = ConstellationMsg::IMEDismissed; if let Err(e) = self.constellation_chan.send(msg) { diff --git a/components/shared/compositing/constellation_msg.rs b/components/shared/compositing/constellation_msg.rs index 2536a6cfc05..dc1d3c7a13f 100644 --- a/components/shared/compositing/constellation_msg.rs +++ b/components/shared/compositing/constellation_msg.rs @@ -10,7 +10,7 @@ use base::id::{BrowsingContextId, PipelineId, TopLevelBrowsingContextId, WebView use base::Epoch; use embedder_traits::Cursor; use ipc_channel::ipc::IpcSender; -use keyboard_types::KeyboardEvent; +use keyboard_types::{CompositionEvent, KeyboardEvent}; use script_traits::{ AnimationTickType, CompositorEvent, GamepadEvent, LogEntry, MediaSessionActionType, TraversalDirection, WebDriverCommandMsg, WindowSizeData, WindowSizeType, @@ -34,6 +34,8 @@ pub enum ConstellationMsg { IsReadyToSaveImage(HashMap<PipelineId, Epoch>), /// Inform the constellation of a key event. Keyboard(KeyboardEvent), + /// Inform the constellation of a composition event (IME). + IMECompositionEvent(CompositionEvent), /// Whether to allow script to navigate. AllowNavigationResponse(PipelineId, bool), /// Request to load a page. @@ -103,6 +105,7 @@ impl ConstellationMsg { GetFocusTopLevelBrowsingContext(..) => "GetFocusTopLevelBrowsingContext", IsReadyToSaveImage(..) => "IsReadyToSaveImage", Keyboard(..) => "Keyboard", + IMECompositionEvent(..) => "IMECompositionEvent", AllowNavigationResponse(..) => "AllowNavigationResponse", LoadUrl(..) => "LoadUrl", TraverseHistory(..) => "TraverseHistory", diff --git a/ports/servoshell/Cargo.toml b/ports/servoshell/Cargo.toml index 3622d325c6e..f944b988ae2 100644 --- a/ports/servoshell/Cargo.toml +++ b/ports/servoshell/Cargo.toml @@ -60,6 +60,7 @@ webxr = ["dep:webxr", "libservo/webxr"] libc = { workspace = true } libservo = { path = "../../components/servo" } cfg-if = { workspace = true } +keyboard-types = { workspace = true } log = { workspace = true } getopts = { workspace = true } hitrace = { workspace = true, optional = true } @@ -91,6 +92,9 @@ napi-derive-ohos = "1.0.1" napi-ohos = "1.0.1" ohos-sys = { version = "0.4.0", features = ["xcomponent"] } ohos-vsync = "0.1.2" +ohos-ime = { version = "0.2" } +ohos-ime-sys = "0.1.1" +xcomponent-sys = { version = "0.1.1", features = ["api-12", "keyboard-types"] } [target.'cfg(any(target_os = "android", target_env = "ohos"))'.dependencies] nix = { workspace = true, features = ["fs"] } @@ -112,7 +116,6 @@ gleam = { workspace = true } glow = "0.14.2" headers = { workspace = true } http = { workspace = true } -keyboard-types = { workspace = true } net = { path = "../../components/net" } net_traits = { workspace = true } raw-window-handle = "0.6" diff --git a/ports/servoshell/desktop/tracing.rs b/ports/servoshell/desktop/tracing.rs index 5feabb7afe2..ec6e27d4959 100644 --- a/ports/servoshell/desktop/tracing.rs +++ b/ports/servoshell/desktop/tracing.rs @@ -229,6 +229,7 @@ mod to_servo { Self::ToggleSamplingProfiler(..) => target!("ToggleSamplingProfiler"), Self::MediaSessionAction(..) => target!("MediaSessionAction"), Self::SetWebViewThrottled(..) => target!("SetWebViewThrottled"), + Self::IMEComposition(..) => target!("IMEComposition"), Self::IMEDismissed => target!("IMEDismissed"), Self::InvalidateNativeSurface => target!("InvalidateNativeSurface"), Self::ReplaceNativeSurface(..) => target!("ReplaceNativeSurface"), diff --git a/ports/servoshell/egl/log.rs b/ports/servoshell/egl/log.rs index 4be07e0393f..db21de6d14c 100644 --- a/ports/servoshell/egl/log.rs +++ b/ports/servoshell/egl/log.rs @@ -33,14 +33,13 @@ pub(crate) fn redirect_stdout_and_stderr() -> Result<(), LogRedirectError> { // The first step is to redirect stdout and stderr to the logs. // We redirect stdout and stderr to a custom descriptor. - let (readerfd, writerfd) = - nix::unistd::pipe().map_err(|e| LogRedirectError::CreatePipeFailed(e))?; + let (readerfd, writerfd) = nix::unistd::pipe().map_err(LogRedirectError::CreatePipeFailed)?; // Leaks the writer fd. We want to log for the whole program lifetime. let raw_writerfd = writerfd.into_raw_fd(); let _fd = nix::unistd::dup2(raw_writerfd, RawFd::from(1)) - .map_err(|e| LogRedirectError::RedirectToPipeFailed(e))?; + .map_err(LogRedirectError::RedirectToPipeFailed)?; let _fd = nix::unistd::dup2(raw_writerfd, RawFd::from(2)) - .map_err(|e| LogRedirectError::RedirectToPipeFailed(e))?; + .map_err(LogRedirectError::RedirectToPipeFailed)?; // Then we spawn a thread whose only job is to read from the other side of the // pipe and redirect to the logs. diff --git a/ports/servoshell/egl/ohos.rs b/ports/servoshell/egl/ohos.rs index f5e76f0cf49..e4586c90f8f 100644 --- a/ports/servoshell/egl/ohos.rs +++ b/ports/servoshell/egl/ohos.rs @@ -3,6 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![allow(non_snake_case)] +use std::cell::RefCell; use std::mem::MaybeUninit; use std::os::raw::c_void; use std::sync::mpsc::{Receiver, Sender}; @@ -11,21 +12,30 @@ use std::thread; use std::thread::sleep; use std::time::Duration; +use keyboard_types::Key; use log::{debug, error, info, trace, warn, LevelFilter}; use napi_derive_ohos::{module_exports, napi}; use napi_ohos::bindgen_prelude::Function; use napi_ohos::threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}; use napi_ohos::{Env, JsObject, JsString, NapiRaw}; +use ohos_ime::{AttachOptions, Ime, ImeProxy, RawTextEditorProxy}; +use ohos_ime_sys::types::InputMethod_EnterKeyType; use ohos_sys::xcomponent::{ - OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetTouchEvent, - OH_NativeXComponent_RegisterCallback, OH_NativeXComponent_TouchEvent, + OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetKeyEvent, + OH_NativeXComponent_GetKeyEventAction, OH_NativeXComponent_GetTouchEvent, + OH_NativeXComponent_KeyEvent, OH_NativeXComponent_RegisterCallback, + OH_NativeXComponent_RegisterKeyEventCallback, OH_NativeXComponent_TouchEvent, OH_NativeXComponent_TouchEventType, }; use servo::compositing::windowing::EmbedderEvent; -use servo::embedder_traits::PromptResult; +use servo::embedder_traits; +use servo::embedder_traits::{InputMethodType, PromptResult}; use servo::euclid::Point2D; use servo::style::Zero; use simpleservo::EventLoopWaker; +use xcomponent_sys::{ + OH_NativeXComponent_GetKeyEventCode, OH_NativeXComponent_KeyAction, OH_NativeXComponent_KeyCode, +}; use super::gl_glue; use super::host_trait::HostTrait; @@ -92,6 +102,12 @@ enum ServoAction { y: f32, pointer_id: i32, }, + KeyUp(Key), + KeyDown(Key), + InsertText(String), + ImeDeleteForward(usize), + ImeDeleteBackward(usize), + ImeSendEnter, Initialize(Box<InitOpts>), Vsync, } @@ -130,6 +146,7 @@ impl ServoAction { } } + // todo: consider making this take `self`, so we don't need to needlessly clone. fn do_action(&self, servo: &mut ServoGlue) { use ServoAction::*; let res = match self { @@ -143,14 +160,34 @@ impl ServoAction { y, pointer_id, } => Self::dispatch_touch_event(servo, *kind, *x, *y, *pointer_id), + KeyUp(k) => servo.key_up(k.clone()), + KeyDown(k) => servo.key_down(k.clone()), + InsertText(text) => servo.ime_insert_text(text.clone()), + ImeDeleteForward(len) => { + for _ in 0..*len { + let _ = servo.key_down(Key::Delete); + let _ = servo.key_up(Key::Delete); + } + Ok(()) + }, + ImeDeleteBackward(len) => { + for _ in 0..*len { + let _ = servo.key_down(Key::Backspace); + let _ = servo.key_up(Key::Backspace); + } + Ok(()) + }, + ImeSendEnter => servo + .key_down(Key::Enter) + .and_then(|()| servo.key_up(Key::Enter)), + Initialize(_init_opts) => { panic!("Received Initialize event, even though servo is already initialized") }, - Vsync => servo .process_event(EmbedderEvent::Vsync) .and_then(|()| servo.perform_updates()) - .and_then(|()| Ok(servo.present_if_needed())), + .map(|()| servo.present_if_needed()), }; if let Err(e) = res { error!("Failed to do {self:?} with error {e}"); @@ -186,7 +223,7 @@ unsafe extern "C" fn on_vsync_cb( static SERVO_CHANNEL: OnceLock<Sender<ServoAction>> = OnceLock::new(); #[no_mangle] -pub extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) { +extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) { info!("on_surface_created_cb"); let xc_wrapper = XComponentWrapper(xcomponent); @@ -248,24 +285,15 @@ pub extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, wi } // Todo: Probably we need to block here, until the main thread has processed the change. -pub extern "C" fn on_surface_changed_cb( - _component: *mut OH_NativeXComponent, - _window: *mut c_void, -) { +extern "C" fn on_surface_changed_cb(_component: *mut OH_NativeXComponent, _window: *mut c_void) { error!("on_surface_changed_cb is currently not implemented!"); } -pub extern "C" fn on_surface_destroyed_cb( - _component: *mut OH_NativeXComponent, - _window: *mut c_void, -) { +extern "C" fn on_surface_destroyed_cb(_component: *mut OH_NativeXComponent, _window: *mut c_void) { error!("on_surface_destroyed_cb is currently not implemented"); } -pub extern "C" fn on_dispatch_touch_event_cb( - component: *mut OH_NativeXComponent, - window: *mut c_void, -) { +extern "C" fn on_dispatch_touch_event_cb(component: *mut OH_NativeXComponent, window: *mut c_void) { info!("DispatchTouchEvent"); let mut touch_event: MaybeUninit<OH_NativeXComponent_TouchEvent> = MaybeUninit::uninit(); let res = @@ -298,6 +326,39 @@ pub extern "C" fn on_dispatch_touch_event_cb( } } +extern "C" fn on_dispatch_key_event(xc: *mut OH_NativeXComponent, _window: *mut c_void) { + info!("DispatchKeyEvent"); + let mut event: *mut OH_NativeXComponent_KeyEvent = core::ptr::null_mut(); + let res = unsafe { OH_NativeXComponent_GetKeyEvent(xc, &mut event as *mut *mut _) }; + assert_eq!(res, 0); + + let mut action = OH_NativeXComponent_KeyAction::OH_NATIVEXCOMPONENT_KEY_ACTION_UNKNOWN; + let res = unsafe { OH_NativeXComponent_GetKeyEventAction(event, &mut action as *mut _) }; + assert_eq!(res, 0); + + let mut keycode = OH_NativeXComponent_KeyCode::KEY_UNKNOWN; + let res = unsafe { OH_NativeXComponent_GetKeyEventCode(event, &mut keycode as *mut _) }; + assert_eq!(res, 0); + + // Simplest possible impl, just for testing purposes + let code: keyboard_types::Code = keycode.into(); + // There currently doesn't seem to be an API to query keymap / keyboard layout, so + // we don't even bother implementing modifier support for now, since we expect to be using the + // IME most of the time anyway. We can revisit this when someone has an OH device with a + // physical keyboard. + let char = code.to_string(); + let key = Key::Character(char); + match action { + OH_NativeXComponent_KeyAction::OH_NATIVEXCOMPONENT_KEY_ACTION_UP => { + call(ServoAction::KeyUp(key)).expect("Call failed") + }, + OH_NativeXComponent_KeyAction::OH_NATIVEXCOMPONENT_KEY_ACTION_DOWN => { + call(ServoAction::KeyDown(key)).expect("Call failed") + }, + _ => error!("Unknown key action {:?}", action), + } +} + fn initialize_logging_once() { static ONCE: Once = Once::new(); ONCE.call_once(|| { @@ -316,6 +377,7 @@ fn initialize_logging_once() { "compositing::compositor", "compositing::touch", "constellation::constellation", + "ohos_ime", ]; for &module in &filters { builder.filter_module(module, log::LevelFilter::Debug); @@ -338,7 +400,7 @@ fn initialize_logging_once() { let current_thread = thread::current(); let name = current_thread.name().unwrap_or("<unnamed>"); if let Some(location) = info.location() { - let _ = error!( + error!( "{} (thread {}, at {}:{})", msg, name, @@ -346,10 +408,10 @@ fn initialize_logging_once() { location.line() ); } else { - let _ = error!("{} (thread {})", msg, name); + error!("{} (thread {})", msg, name); } - let _ = crate::backtrace::print_ohos(); + crate::backtrace::print_ohos(); })); // We only redirect stdout and stderr for non-production builds, since it is @@ -386,7 +448,16 @@ fn register_xcomponent_callbacks(env: &Env, xcomponent: &JsObject) -> napi_ohos: if res != 0 { error!("Failed to register callbacks"); } else { - info!("Registerd callbacks successfully"); + info!("Registered callbacks successfully"); + } + + let res = unsafe { + OH_NativeXComponent_RegisterKeyEventCallback(nativeXComponent, Some(on_dispatch_key_event)) + }; + if res != 0 { + error!("Failed to register key event callbacks"); + } else { + debug!("Registered key event callbacks successfully"); } Ok(()) } @@ -491,6 +562,54 @@ pub fn init_servo(init_opts: InitOpts) -> napi_ohos::Result<()> { Ok(()) } +struct OhosImeOptions { + input_type: ohos_ime_sys::types::InputMethod_TextInputType, + enterkey_type: InputMethod_EnterKeyType, +} + +/// TODO: This needs some more consideration and perhaps both more information from +/// servos side as well as clarification on the meaning of some of the openharmony variants. +/// For now for example we just ignore the `multiline` parameter in all the cases where it +/// seems like it wouldn't make sense, but this needs a closer look. +fn convert_ime_options(input_method_type: InputMethodType, multiline: bool) -> OhosImeOptions { + use ohos_ime_sys::types::InputMethod_TextInputType as IME_TextInputType; + // There are a couple of cases when the mapping is not quite clear to me, + // so we clearly mark them with `input_fallback` and come back to this later. + let input_fallback = IME_TextInputType::IME_TEXT_INPUT_TYPE_TEXT; + let input_type = match input_method_type { + InputMethodType::Color => input_fallback, + InputMethodType::Date => input_fallback, + InputMethodType::DatetimeLocal => IME_TextInputType::IME_TEXT_INPUT_TYPE_DATETIME, + InputMethodType::Email => IME_TextInputType::IME_TEXT_INPUT_TYPE_EMAIL_ADDRESS, + InputMethodType::Month => input_fallback, + InputMethodType::Number => IME_TextInputType::IME_TEXT_INPUT_TYPE_NUMBER, + // There is no type "password", but "new password" seems closest. + InputMethodType::Password => IME_TextInputType::IME_TEXT_INPUT_TYPE_NEW_PASSWORD, + InputMethodType::Search => IME_TextInputType::IME_TEXT_INPUT_TYPE_TEXT, + InputMethodType::Tel => IME_TextInputType::IME_TEXT_INPUT_TYPE_PHONE, + InputMethodType::Text => { + if multiline { + IME_TextInputType::IME_TEXT_INPUT_TYPE_MULTILINE + } else { + IME_TextInputType::IME_TEXT_INPUT_TYPE_TEXT + } + }, + InputMethodType::Time => input_fallback, + InputMethodType::Url => IME_TextInputType::IME_TEXT_INPUT_TYPE_URL, + InputMethodType::Week => input_fallback, + }; + let enterkey_type = match (input_method_type, multiline) { + (InputMethodType::Text, true) => InputMethod_EnterKeyType::IME_ENTER_KEY_NEWLINE, + (InputMethodType::Text, false) => InputMethod_EnterKeyType::IME_ENTER_KEY_DONE, + (InputMethodType::Search, false) => InputMethod_EnterKeyType::IME_ENTER_KEY_SEARCH, + _ => InputMethod_EnterKeyType::IME_ENTER_KEY_UNSPECIFIED, + }; + OhosImeOptions { + input_type, + enterkey_type, + } +} + #[derive(Clone)] pub struct WakeupCallback { chan: Sender<ServoAction>, @@ -515,11 +634,38 @@ impl EventLoopWaker for WakeupCallback { } } -struct HostCallbacks {} +struct HostCallbacks { + ime_proxy: RefCell<Option<ohos_ime::ImeProxy>>, +} impl HostCallbacks { pub fn new() -> Self { - HostCallbacks {} + HostCallbacks { + ime_proxy: RefCell::new(None), + } + } +} + +struct ServoIme { + text_config: ohos_ime::TextConfig, +} +impl Ime for ServoIme { + fn insert_text(&self, text: String) { + call(ServoAction::InsertText(text)).unwrap() + } + fn delete_forward(&self, len: usize) { + call(ServoAction::ImeDeleteForward(len)).unwrap() + } + fn delete_backward(&self, len: usize) { + call(ServoAction::ImeDeleteBackward(len)).unwrap() + } + + fn get_text_config(&self) -> &ohos_ime::TextConfig { + &self.text_config + } + + fn send_enter_key(&self, _enter_key: InputMethod_EnterKeyType) { + call(ServoAction::ImeSendEnter).unwrap() } } @@ -595,18 +741,47 @@ impl HostTrait for HostCallbacks { fn on_shutdown_complete(&self) {} + /// Shows the Inputmethod + /// + /// Most basic implementation for now, which just ignores all the input parameters + /// and shows the soft keyboard with default settings. fn on_ime_show( &self, - input_type: servo::embedder_traits::InputMethodType, - text: Option<(String, i32)>, + input_type: embedder_traits::InputMethodType, + _text: Option<(String, i32)>, multiline: bool, - bounds: servo::webrender_api::units::DeviceIntRect, + _bounds: servo::webrender_api::units::DeviceIntRect, ) { - warn!("on_title_changed not implemented") + debug!("IME show!"); + let mut ime_proxy = self.ime_proxy.borrow_mut(); + let ime = ime_proxy.get_or_insert_with(|| { + let attach_options = AttachOptions::new(true); + let editor = RawTextEditorProxy::new(); + let configbuilder = ohos_ime::TextConfigBuilder::new(); + let options = convert_ime_options(input_type, multiline); + let text_config = configbuilder + .input_type(options.input_type) + .enterkey_type(options.enterkey_type) + .build(); + ImeProxy::new(editor, attach_options, Box::new(ServoIme { text_config })) + }); + match ime.show_keyboard() { + Ok(()) => debug!("IME show keyboard - success"), + Err(_e) => error!("IME show keyboard error"), + } } fn on_ime_hide(&self) { - warn!("on_title_changed not implemented") + debug!("IME hide!"); + let mut ime_proxy = self.ime_proxy.take(); + if let Some(ime) = ime_proxy { + match ime.hide_keyboard() { + Ok(()) => debug!("IME hide keyboard - success"), + Err(_e) => error!("IME hide keyboard error"), + } + } else { + warn!("IME hide called, but no active IME found!") + } } fn get_clipboard_contents(&self) -> Option<String> { diff --git a/ports/servoshell/egl/servo_glue.rs b/ports/servoshell/egl/servo_glue.rs index 6526b7eba3a..ab16a04277b 100644 --- a/ports/servoshell/egl/servo_glue.rs +++ b/ports/servoshell/egl/servo_glue.rs @@ -8,7 +8,8 @@ use std::os::raw::c_void; use std::rc::Rc; use ipc_channel::ipc::IpcSender; -use log::{debug, info, warn}; +use keyboard_types::{CompositionEvent, CompositionState}; +use log::{debug, error, info, warn}; use servo::base::id::WebViewId; use servo::compositing::windowing::{ AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent, @@ -155,7 +156,7 @@ impl ServoGlue { /// to act on its pending events. pub fn perform_updates(&mut self) -> Result<(), &'static str> { debug!("perform_updates"); - let events = mem::replace(&mut self.events, Vec::new()); + let events = mem::take(&mut self.events); self.servo.handle_events(events); let r = self.handle_servo_events(); debug!("done perform_updates"); @@ -274,7 +275,7 @@ impl ServoGlue { let event = EmbedderEvent::Touch( TouchEventType::Down, TouchId(pointer_id), - Point2D::new(x as f32, y as f32), + Point2D::new(x, y), ); self.process_event(event) } @@ -284,18 +285,15 @@ impl ServoGlue { let event = EmbedderEvent::Touch( TouchEventType::Move, TouchId(pointer_id), - Point2D::new(x as f32, y as f32), + Point2D::new(x, y), ); self.process_event(event) } /// Touch event: Lift touching finger pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> { - let event = EmbedderEvent::Touch( - TouchEventType::Up, - TouchId(pointer_id), - Point2D::new(x as f32, y as f32), - ); + let event = + EmbedderEvent::Touch(TouchEventType::Up, TouchId(pointer_id), Point2D::new(x, y)); self.process_event(event) } @@ -304,7 +302,7 @@ impl ServoGlue { let event = EmbedderEvent::Touch( TouchEventType::Cancel, TouchId(pointer_id), - Point2D::new(x as f32, y as f32), + Point2D::new(x, y), ); self.process_event(event) } @@ -374,6 +372,13 @@ impl ServoGlue { self.process_event(EmbedderEvent::Keyboard(key_event)) } + pub fn ime_insert_text(&mut self, text: String) -> Result<(), &'static str> { + self.process_event(EmbedderEvent::IMEComposition(CompositionEvent { + state: CompositionState::End, + data: text, + })) + } + pub fn pause_compositor(&mut self) -> Result<(), &'static str> { self.process_event(EmbedderEvent::InvalidateNativeSurface) } @@ -619,11 +624,13 @@ impl ServoGlue { EmbedderMsg::ReadyToPresent(_webview_ids) => { self.need_present = true; }, + EmbedderMsg::Keyboard(..) => { + error!("Received unexpected keyboard event"); + }, EmbedderMsg::Status(..) | EmbedderMsg::SelectFiles(..) | EmbedderMsg::MoveTo(..) | EmbedderMsg::ResizeTo(..) | - EmbedderMsg::Keyboard(..) | EmbedderMsg::SetCursor(..) | EmbedderMsg::NewFavicon(..) | EmbedderMsg::HeadParsed | diff --git a/python/servo/platform/build_target.py b/python/servo/platform/build_target.py index 701ed5f7ce4..cdf7755df27 100644 --- a/python/servo/platform/build_target.py +++ b/python/servo/platform/build_target.py @@ -275,16 +275,10 @@ class OpenHarmonyTarget(CrossBuildTarget): meta = json.load(meta_file) ohos_api_version = int(meta['apiVersion']) ohos_sdk_version = parse_version(meta['version']) - if ohos_sdk_version < parse_version('4.0'): - print("Warning: mach build currently assumes at least the OpenHarmony 4.0 SDK is used.") + if ohos_sdk_version < parse_version('5.0') or ohos_api_version < 12: + raise RuntimeError("Building servo for OpenHarmony requires SDK version 5.0 (API-12) or newer.") print(f"Info: The OpenHarmony SDK {ohos_sdk_version} is targeting API-level {ohos_api_version}") - os_type = platform.system().lower() - if os_type == "windows" and ohos_sdk_version < parse_version('5.0'): - # The OpenHarmony SDK for Windows hosts currently before OH 5.0 did not contain a - # libclang shared library, which is required by `bindgen`. - raise Exception("Building servo for OpenHarmony on windows requires SDK version 5.0 or newer.") - - except Exception as e: + except (OSError, json.JSONDecodeError) as e: print(f"Failed to read metadata information from {package_info}") print(f"Exception: {e}") diff --git a/support/openharmony/build-profile.json5 b/support/openharmony/build-profile.json5 index 46ab10aae86..7ca1da47865 100644 --- a/support/openharmony/build-profile.json5 +++ b/support/openharmony/build-profile.json5 @@ -4,16 +4,16 @@ { "name": "default", "signingConfig": "default", - "compileSdkVersion": 11, - "compatibleSdkVersion": 11, - "targetSdkVersion": 11, + "compileSdkVersion": 12, + "compatibleSdkVersion": 12, + "targetSdkVersion": 12, "runtimeOS": "OpenHarmony" }, { "name": "harmonyos", "signingConfig": "hos", - "compatibleSdkVersion": "4.1.0(11)", - "targetSdkVersion": "4.1.0(11)", + "compatibleSdkVersion": "5.0.0(12)", + "targetSdkVersion": "5.0.0(12)", "runtimeOS": "HarmonyOS" } ], |