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>
This commit is contained in:
Jonathan Schwender 2024-11-15 16:04:48 +01:00 committed by GitHub
parent c64d5e9d30
commit 538ac61a82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 320 additions and 63 deletions

View file

@ -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

22
Cargo.lock generated
View file

@ -5089,6 +5089,22 @@ dependencies = [
"memchr",
]
[[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"
@ -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"

View file

@ -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"),

View file

@ -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();
},
@ -4235,6 +4238,42 @@ where
}
}
#[cfg_attr(
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))

View file

@ -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"),

View file

@ -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) {

View file

@ -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",

View file

@ -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"

View file

@ -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"),

View file

@ -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.

View file

@ -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> {

View file

@ -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 |

View file

@ -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}")

View file

@ -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"
}
],