aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonathan Schwender <55576758+jschwe@users.noreply.github.com>2024-11-15 16:04:48 +0100
committerGitHub <noreply@github.com>2024-11-15 15:04:48 +0000
commit538ac61a82ebd4f6bd02062c23c654cf83fb18ec (patch)
tree9a564a95711114324fb1650e60aac622a1d85383
parentc64d5e9d30c48f59e61439947c63a2b97a45512f (diff)
downloadservo-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.yml2
-rw-r--r--Cargo.lock22
-rw-r--r--components/compositing/windowing.rs5
-rw-r--r--components/constellation/constellation.rs41
-rw-r--r--components/constellation/tracing.rs1
-rw-r--r--components/servo/lib.rs10
-rw-r--r--components/shared/compositing/constellation_msg.rs5
-rw-r--r--ports/servoshell/Cargo.toml5
-rw-r--r--ports/servoshell/desktop/tracing.rs1
-rw-r--r--ports/servoshell/egl/log.rs7
-rw-r--r--ports/servoshell/egl/ohos.rs233
-rw-r--r--ports/servoshell/egl/servo_glue.rs29
-rw-r--r--python/servo/platform/build_target.py12
-rw-r--r--support/openharmony/build-profile.json510
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"
}
],