From 24f343f290eb641427d92026409dd99078eba2b2 Mon Sep 17 00:00:00 2001 From: Gajendra N Date: Mon, 19 Dec 2022 10:55:04 +0530 Subject: [PATCH] Enable IME support for Ozone Efl Functionalities: 1) IME show/hide panel on text input client's focus/blur respectively 2) IME focus/unfocus based on aura window's focus state 3) IME key filtering for HW KB input 4) Migrates text composition related base code Change-Id: I9ad84780c70ac6a9aedb6d52e9d1cd1dce5db3a3 Signed-off-by: Gajendra N --- content/browser/web_contents/web_contents_impl.h | 3 + .../rwhv_aura_offscreen_helper_efl.cc | 34 +- .../renderer_host/rwhv_aura_offscreen_helper_efl.h | 10 + .../chromium_impl/ui/ozone/platform/efl/BUILD.gn | 4 + .../ui/ozone/platform/efl/efl_event_handler.cc | 120 ++- .../ui/ozone/platform/efl/efl_event_handler.h | 11 + .../ozone/platform/efl/efl_input_method_context.cc | 59 ++ .../ozone/platform/efl/efl_input_method_context.h | 58 ++ .../ui/ozone/platform/efl/im_context_efl.cc | 1007 ++++++++++++++++++++ .../ui/ozone/platform/efl/im_context_efl.h | 221 +++++ tizen_src/ewk/efl_integration/eweb_view.cc | 2 + ui/base/ime/linux/input_method_auralinux.cc | 8 + ui/events/event.h | 4 + 13 files changed, 1537 insertions(+), 4 deletions(-) create mode 100644 tizen_src/chromium_impl/ui/ozone/platform/efl/efl_input_method_context.cc create mode 100644 tizen_src/chromium_impl/ui/ozone/platform/efl/efl_input_method_context.h create mode 100644 tizen_src/chromium_impl/ui/ozone/platform/efl/im_context_efl.cc create mode 100644 tizen_src/chromium_impl/ui/ozone/platform/efl/im_context_efl.h diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h index 738061d..f601711 100644 --- a/content/browser/web_contents/web_contents_impl.h +++ b/content/browser/web_contents/web_contents_impl.h @@ -226,6 +226,8 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents, #if defined(USE_EFL) void CreateEflNativeView(); Evas_Object* GetEflNativeView() const { return efl_native_view_; } + void set_ewk_view(void* ewk_view) { ewk_view_ = ewk_view; } + void* ewk_view() const { return ewk_view_; } #endif // Returns the SavePackage which manages the page saving job. May be NULL. @@ -2386,6 +2388,7 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents, #if defined(USE_EFL) Evas_Object* efl_native_view_ = nullptr; + void* ewk_view_ = nullptr; #endif base::WeakPtrFactory loading_weak_factory_{this}; diff --git a/tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.cc b/tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.cc index 1f7a5e7..4ea4a0e 100644 --- a/tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.cc +++ b/tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.cc @@ -24,6 +24,8 @@ #include "ui/gfx/skbitmap_operations.h" #include "ui/gl/gl_share_group.h" #include "ui/gl/gl_shared_context_efl.h" +#include "ui/ozone/platform/efl/efl_event_handler.h" +#include "ui/ozone/platform/efl/im_context_efl.h" #include "ui/platform_window/platform_window.h" #define MAX_SURFACE_WIDTH_EGL 4096 // max supported Framebuffer width @@ -442,10 +444,8 @@ void RWHVAuraOffscreenHelperEfl::OnFocusOut(void* data, aura::Window* focused_window = aura::client::GetFocusClient(window_host->window())->GetFocusedWindow(); -#if 0 if (focused_window) focused_window->LostFocus(); -#endif } void RWHVAuraOffscreenHelperEfl::OnHostFocusIn(void* data, @@ -486,6 +486,14 @@ void RWHVAuraOffscreenHelperEfl::EvasToBlinkCords(int x, } } +Evas_Object* RWHVAuraOffscreenHelperEfl::ewk_view() const { + auto wci = static_cast(web_contents_); + if (!wci) + return nullptr; + + return static_cast(wci->ewk_view()); +} + void RWHVAuraOffscreenHelperEfl::FocusRWHVA() { if (!rwhv_aura_->HasFocus()) rwhv_aura_->Focus(); @@ -533,6 +541,28 @@ RenderWidgetHostImpl* RWHVAuraOffscreenHelperEfl::GetRenderWidgetHostImpl() { return rwhv_aura_->host(); } +ui::EflEventHandler* RWHVAuraOffscreenHelperEfl::GetEventHandler() { + aura::WindowTreeHost* window_host = rwhv_aura_->window()->GetHost(); + if (!window_host) + return nullptr; + + return static_cast(window_host) + ->platform_window() + ->GetEventHandler(); +} + +ui::IMContextEfl* RWHVAuraOffscreenHelperEfl::GetIMContextEfl() { + if (!im_context_efl_) { + if (GetEventHandler() && GetEventHandler()->GetIMContextEfl()) { + im_context_efl_ = GetEventHandler()->GetIMContextEfl(); + im_context_efl_->SetRWHVHelper(this); + return im_context_efl_; + } + LOG(ERROR) << "im_context_efl_ is not set"; + } + return im_context_efl_; +} + gfx::NativeView RWHVAuraOffscreenHelperEfl::GetNativeView() { return rwhv_aura_->GetNativeView(); } diff --git a/tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.h b/tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.h index 3eec19a..6c49df0 100644 --- a/tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.h +++ b/tizen_src/chromium_impl/content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.h @@ -25,6 +25,11 @@ #include "ui/gfx/geometry/size_f.h" #include "ui/gfx/native_widget_types.h" +namespace ui { +class EflEventHandler; +class IMContextEfl; +} // namespace ui + namespace content { class RenderWidgetHostImpl; @@ -60,6 +65,7 @@ class CONTENT_EXPORT RWHVAuraOffscreenHelperEfl { void OnMouseOrTouchEvent(ui::Event* event); void EvasToBlinkCords(int x, int y, int* view_x, int* view_y); + Evas_Object* ewk_view() const; Evas_Object* content_image() const { return content_image_; } Evas_Object* content_image_elm_host() const { return content_image_elm_host_; @@ -88,6 +94,9 @@ class CONTENT_EXPORT RWHVAuraOffscreenHelperEfl { bool MakeCurrent(); void ClearBrowserFrame(); + ui::EflEventHandler* GetEventHandler(); + ui::IMContextEfl* GetIMContextEfl(); + Evas* evas_ = nullptr; Evas_GL* evas_gl_ = nullptr; Evas_GL_API* evas_gl_api_ = nullptr; @@ -113,6 +122,7 @@ class CONTENT_EXPORT RWHVAuraOffscreenHelperEfl { gfx::SizeF scaled_contents_size_; gfx::Size custom_viewport_size_; + ui::IMContextEfl* im_context_efl_ = nullptr; RenderWidgetHostViewAura* rwhv_aura_ = nullptr; WebContents* web_contents_ = nullptr; }; diff --git a/tizen_src/chromium_impl/ui/ozone/platform/efl/BUILD.gn b/tizen_src/chromium_impl/ui/ozone/platform/efl/BUILD.gn index 30f4eeb..6bfe089 100644 --- a/tizen_src/chromium_impl/ui/ozone/platform/efl/BUILD.gn +++ b/tizen_src/chromium_impl/ui/ozone/platform/efl/BUILD.gn @@ -12,6 +12,8 @@ source_set("efl") { "client_native_pixmap_factory_efl.h", "efl_event_handler.cc", "efl_event_handler.h", + "efl_input_method_context.cc", + "efl_input_method_context.h", "efl_platform_event_source.cc", "efl_platform_event_source.h", "efl_screen.cc", @@ -21,6 +23,8 @@ source_set("efl") { "efl_window.cc", "efl_window.h", "efl_window_manager.h", + "im_context_efl.cc", + "im_context_efl.h", "ozone_platform_efl.cc", "ozone_platform_efl.h", ] diff --git a/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_event_handler.cc b/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_event_handler.cc index 060ffa0..b471f6c 100644 --- a/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_event_handler.cc +++ b/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_event_handler.cc @@ -18,6 +18,7 @@ #include "ui/ozone/platform/efl/efl_keycode_map.h" #include "ui/ozone/platform/efl/efl_platform_event_source.h" #include "ui/ozone/platform/efl/efl_window.h" +#include "ui/ozone/platform/efl/im_context_efl.h" #if defined(OS_TIZEN) #include "ui/events/keycodes/dom/dom_code.h" @@ -563,8 +564,25 @@ void EflEventHandler::OnKeyDown(void* data, LOG(INFO) << "Before consumed check (Key Name: " << key_down->key << ", Key State: Down)"; +#if defined(OS_TIZEN) + if (IsMobileProfile() || IsTvProfile()) { + if (thiz->FilterIMEKeyDownEvent(key_down)) { + LOG(INFO) << "OnKeyDown, IME filtered the key : " << key_down->key; + return; + } + } +#endif + KeyEvent event = MakeWebKeyEvent(true, key_down); - EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event); + auto im_context_efl = thiz->GetIMContextEfl(); + if (!im_context_efl || + im_context_efl->ShouldHandleImmediately(key_down->key)) { + EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event); + return; + } + + LOG(INFO) << "OnKeyUp,PushToKeyDownEventQueue,key:" << key_down->key; + im_context_efl->PushToKeyDownEventQueue(event); } // static @@ -581,8 +599,106 @@ void EflEventHandler::OnKeyUp(void* data, LOG(INFO) << "Before consumed check (Key Name: " << key_up->key << ", Key State: Up)"; +#if defined(OS_TIZEN) + if (IsMobileProfile() || IsTvProfile()) { + if (thiz->FilterIMEKeyUpEvent(key_up)) { + LOG(INFO) << "OnKeyUp, IME filtered the key : " << key_up->key; + return; + } + } +#endif + + if (IsTvProfile()) { + // For TV IME "Select" and "Cancel" key + if (thiz->im_context_efl_ && thiz->im_context_efl_->IsVisible()) { + if (!strcmp(key_up->key, "Select") || !strcmp(key_up->key, "Cancel")) + thiz->im_context_efl_->HidePanel(); + } + } + KeyEvent event = MakeWebKeyEvent(false, key_up); - EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event); + auto im_context_efl = thiz->GetIMContextEfl(); + if (!im_context_efl || im_context_efl->ShouldHandleImmediately(key_up->key)) { + EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event); + return; + } + + LOG(INFO) << "OnKeyUp,PushToKeyUpEventQueue,key:" << key_up->key; + im_context_efl->PushToKeyUpEventQueue(event.key_code()); +} + +bool EflEventHandler::IsIMEHandleKeyEventEnabled() { + if (!GetIMContextEfl()) + return false; + +#if defined(OS_TIZEN_TV_PRODUCT) + return GetIMContextEfl()->ImeHandleKeyEventEnabled(); +#endif + return true; +} + +bool EflEventHandler::FilterIMEKeyDownEvent(Evas_Event_Key_Down* key_down) { + if (!IsIMEHandleKeyEventEnabled()) + return false; + + bool was_filtered = false; + im_context_efl_->HandleKeyDownEvent(key_down, &was_filtered); + + // It is for checking whether key is filtered or not about keyUp event. + // Because the process related with key is processed at + // EVAS_CALLBACK_KEY_DOWN although key is processed by Ecore_IMF, it isn't + // filtered by ecore_imf_context_filter_event about EVAS_CALLBACK_KEY_UP. + was_keydown_filtered_by_platform_ = was_filtered; + + if (IsTvProfile() && !strcmp(key_down->key, "Return")) + was_return_keydown_filtered_by_platform_ = was_filtered; + if (was_filtered) + LOG(INFO) << "OnKeyDown,IME filtered the key:" << key_down->key; + + return was_filtered; +} + +bool EflEventHandler::FilterIMEKeyUpEvent(Evas_Event_Key_Up* key_up) { + if (!IsIMEHandleKeyEventEnabled()) + return false; + + // IME will change language mode when receive "Alt" keyup event. + // For avoiding IME change language when "Alt + Enter" scene, + // don't send the "Alt" keyup event when "Alt + Enter" keydown. + if (IsTvProfile() && + (!strcmp(key_up->key, "Alt_L") || !strcmp(key_up->key, "Alt_R")) && + was_alt_enter_key_down_) { + was_alt_enter_key_down_ = false; + } else { + bool was_filtered = false; + im_context_efl_->HandleKeyUpEvent(key_up, &was_filtered); + } + + // Both selected key and retrun key events are emitted together + // while typing a single key on OSK by using remote controller. + // The redundant retrun key event needs to be ignored here. + if (IsTvProfile() && evas_device_name_get(key_up->dev)) { + if (!strstr(evas_device_name_get(key_up->dev), "ime") && + im_context_efl_->IsVisible() && !strcmp(key_up->key, "Return")) { + return true; + } + } + + // When IME was focused out in keydown event handler, + // 'was_filtered' will not give the right value. + // Refer to 'was_return_keydown_filtered_by_platform_' in this case. + if (IsTvProfile() && !strcmp(key_up->key, "Return") && + was_return_keydown_filtered_by_platform_) { + was_return_keydown_filtered_by_platform_ = false; + return true; + } + + if (was_keydown_filtered_by_platform_) { + was_keydown_filtered_by_platform_ = false; + return true; + } + + return false; } void EflEventHandler::OnMultiTouchDownEvent(void* data, diff --git a/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_event_handler.h b/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_event_handler.h index 72e3347..102269a 100644 --- a/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_event_handler.h +++ b/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_event_handler.h @@ -17,6 +17,7 @@ namespace ui { class EflWindow; class EflPlatformEventSource; +class IMContextEfl; class KeyEvent; class TouchEvent; @@ -49,6 +50,11 @@ class EflEventHandler { void SendMouseWheel(bool y_direction, int step, int x, int y); void SendMouseOut(); + void SetIMContextEfl(IMContextEfl* im_context) { + im_context_efl_ = im_context; + } + IMContextEfl* GetIMContextEfl() { return im_context_efl_; } + private: void RegisterCallbacks(); void AddTouchCallbacks(); @@ -73,6 +79,10 @@ class EflEventHandler { template bool GetTouchEventsEnabled(const EVT* evas_evt); + bool FilterIMEKeyDownEvent(Evas_Event_Key_Down* key_down); + bool FilterIMEKeyUpEvent(Evas_Event_Key_Up* key_up); + bool IsIMEHandleKeyEventEnabled(); + int key_modifiers_ = 0; bool touch_events_enabled_ = false; bool key_events_enabled_ = true; @@ -84,6 +94,7 @@ class EflEventHandler { EflWindow* window_ = nullptr; Evas_Object* native_view_ = nullptr; + IMContextEfl* im_context_efl_ = nullptr; }; } // namespace ui diff --git a/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_input_method_context.cc b/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_input_method_context.cc new file mode 100644 index 0000000..34162ad --- /dev/null +++ b/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_input_method_context.cc @@ -0,0 +1,59 @@ +// Copyright 2021 Samsung Electronics. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/ozone/platform/efl/efl_input_method_context.h" + +#include "ui/aura/window_tree_host_platform.h" +#include "ui/base/ime/linux/input_method_auralinux.h" +#include "ui/ozone/platform/efl/efl_event_handler.h" +#include "ui/ozone/platform/efl/efl_window.h" + +namespace ui { + +std::unique_ptr CreateEflInputMethodContext( + ImeKeyEventDispatcher* ime_key_event_dispatcher) { + return std::make_unique(ime_key_event_dispatcher); +} + +EflInputMethodContext::EflInputMethodContext( + ImeKeyEventDispatcher* ime_key_event_dispatcher) { + EflWindow* window = static_cast( + static_cast(ime_key_event_dispatcher) + ->platform_window()); + + im_context_ = IMContextEfl::Create(window); + if (im_context_ && window->GetEventHandler()) + window->GetEventHandler()->SetIMContextEfl(im_context_.get()); +} + +void EflInputMethodContext::UpdateFocus(bool has_client, + TextInputType old_type, + TextInputType new_type) { + if (old_type != TEXT_INPUT_TYPE_NONE) + Blur(); + if (new_type != TEXT_INPUT_TYPE_NONE) + Focus(); +} + +void EflInputMethodContext::Reset() { + if (im_context_) + im_context_->ResetIMFContext(); +} + +void EflInputMethodContext::Focus() { + if (im_context_ && !im_context_->IsVisible()) + im_context_->ShowPanel(); +} + +void EflInputMethodContext::Blur() { + if (im_context_ && im_context_->IsVisible()) + im_context_->HidePanel(); +} + +void EflInputMethodContext::SetCursorLocation(const gfx::Rect& rect) { + if (im_context_) + im_context_->UpdateCaretBounds(rect); +} + +} // namespace ui diff --git a/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_input_method_context.h b/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_input_method_context.h new file mode 100644 index 0000000..3cb4a2e --- /dev/null +++ b/tizen_src/chromium_impl/ui/ozone/platform/efl/efl_input_method_context.h @@ -0,0 +1,58 @@ +// Copyright 2021 Samsung Electronics. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_OZONE_PLATFORM_EFL_EFL_INPUT_METHOD_CONTEXT_H_ +#define UI_OZONE_PLATFORM_EFL_EFL_INPUT_METHOD_CONTEXT_H_ + +#include "ui/base/ime/linux/linux_input_method_context.h" +#include "ui/ozone/platform/efl/im_context_efl.h" + +namespace ui { + +class EflWindow; +class ImeKeyEventDispatcher; + +class EflInputMethodContext : public LinuxInputMethodContext { + public: + EflInputMethodContext(ImeKeyEventDispatcher* ime_key_event_dispatcher); + ~EflInputMethodContext() override {} + + // LinuxInputMethodContext overrides: + void SetCursorLocation(const gfx::Rect& rect) override; + void SetSurroundingText(const std::u16string& text, + const gfx::Range& selection_range) override {} + void SetContentType(TextInputType type, + TextInputMode mode, + uint32_t flags, + bool should_do_learning) override {} + void SetGrammarFragmentAtCursor(const GrammarFragment& fragment) override {} + void SetAutocorrectInfo(const gfx::Range& autocorrect_range, + const gfx::Rect& autocorrect_bounds) override {} + void UpdateFocus(bool has_client, + TextInputType old_type, + TextInputType new_type) override; + void Reset() override; + VirtualKeyboardController* GetVirtualKeyboardController() override { + return nullptr; + } + bool DispatchKeyEvent(const ui::KeyEvent& key_event) override { + return false; + } + bool IsPeekKeyEvent(const ui::KeyEvent& key_event) override { return false; } + + void Focus(); + void Blur(); + + IMContextEfl* GetIMContextEfl() { return im_context_.get(); } + + private: + std::unique_ptr im_context_; +}; + +std::unique_ptr CreateEflInputMethodContext( + ImeKeyEventDispatcher* ime_key_event_dispatcher); + +} // namespace ui + +#endif // UI_OZONE_PLATFORM_EFL_EFL_INPUT_METHOD_CONTEXT_H_ diff --git a/tizen_src/chromium_impl/ui/ozone/platform/efl/im_context_efl.cc b/tizen_src/chromium_impl/ui/ozone/platform/efl/im_context_efl.cc new file mode 100644 index 0000000..35ce817 --- /dev/null +++ b/tizen_src/chromium_impl/ui/ozone/platform/efl/im_context_efl.cc @@ -0,0 +1,1007 @@ +// Copyright 2014 Samsung Electronics. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/ozone/platform/efl/im_context_efl.h" + +#include +#include + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/browser/renderer_host/rwhv_aura_offscreen_helper_efl.h" +#include "content/public/browser/web_contents_delegate.h" +#include "tizen/system_info.h" +#include "ui/base/ime/ime_text_span.h" +#include "ui/ozone/platform/efl/efl_event_handler.h" +#include "ui/ozone/platform/efl/efl_platform_event_source.h" +#include "ui/ozone/platform/efl/efl_window.h" + +#ifdef IM_CTX_DEBUG +#define IM_CTX_LOG_CHANNEL LOG(ERROR) +#else +#define IM_CTX_LOG_CHANNEL LOG(INFO) +#endif + +#define IM_CTX_LOG IM_CTX_LOG_CHANNEL << "## IMCTX ## " << __FUNCTION__ << " " + +namespace { + +Ecore_IMF_Context* CreateIMFContext(Evas* evas) { + IM_CTX_LOG; + const char* default_context_id = ecore_imf_context_default_id_get(); + if (!default_context_id) { + LOG(ERROR) << "no default context id"; + return NULL; + } + Ecore_IMF_Context* context = ecore_imf_context_add(default_context_id); + if (!context) { + LOG(ERROR) << "cannot create context"; + return NULL; + } + + Ecore_Window window = ecore_evas_window_get(ecore_evas_ecore_evas_get(evas)); + ecore_imf_context_client_window_set(context, reinterpret_cast(window)); + ecore_imf_context_client_canvas_set(context, evas); + + return context; +} + +Eina_Bool IsIMFVisible(Ecore_IMF_Context* context_) { + return ecore_imf_context_input_panel_state_get(context_) == + ECORE_IMF_INPUT_PANEL_STATE_SHOW; +} + +} // namespace + +namespace ui { + +// static +std::unique_ptr IMContextEfl::Create(EflWindow* window) { + Ecore_IMF_Context* context = CreateIMFContext(window->evas()); + if (!context) + return nullptr; + + return std::make_unique(context); +} + +IMContextEfl::IMContextEfl(Ecore_IMF_Context* context) : context_(context) { + IM_CTX_LOG; + InitializeIMFContext(); +} + +void IMContextEfl::InitializeIMFContext() { + if (IsTvProfile()) { + const char* custom_conformant_enabled = "conformant:custom,enabled"; + ecore_imf_context_input_panel_imdata_set( + context_, custom_conformant_enabled, strlen(custom_conformant_enabled)); + } + + ecore_imf_context_input_panel_enabled_set(context_, false); + ecore_imf_context_use_preedit_set(context_, false); + ecore_imf_context_event_callback_add(context_, ECORE_IMF_CALLBACK_COMMIT, + &IMFCommitCallback, this); + ecore_imf_context_event_callback_add(context_, + ECORE_IMF_CALLBACK_DELETE_SURROUNDING, + &IMFDeleteSurroundingCallback, this); + ecore_imf_context_event_callback_add(context_, + ECORE_IMF_CALLBACK_PREEDIT_CHANGED, + &IMFPreeditChangedCallback, this); + ecore_imf_context_event_callback_add( + context_, ECORE_IMF_CALLBACK_PRIVATE_COMMAND_SEND, + &IMFTransactionPrivateCommandSendCallback, this); + ecore_imf_context_input_panel_event_callback_add( + context_, ECORE_IMF_INPUT_PANEL_STATE_EVENT, + &IMFInputPanelStateChangedCallback, this); + ecore_imf_context_input_panel_event_callback_add( + context_, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT, + &IMFInputPanelGeometryChangedCallback, this); + ecore_imf_context_input_panel_event_callback_add( + context_, ECORE_IMF_CANDIDATE_PANEL_STATE_EVENT, + &IMFCandidatePanelStateChangedCallback, this); + ecore_imf_context_input_panel_event_callback_add( + context_, ECORE_IMF_CANDIDATE_PANEL_GEOMETRY_EVENT, + &IMFCandidatePanelGeometryChangedCallback, this); + ecore_imf_context_input_panel_event_callback_add( + context_, ECORE_IMF_INPUT_PANEL_LANGUAGE_EVENT, + &IMFCandidatePanelLanguageChangedCallback, this); + ecore_imf_context_retrieve_surrounding_callback_set( + context_, &IMFRetrieveSurroundingCallback, this); +} + +IMContextEfl::~IMContextEfl() { + ecore_imf_context_event_callback_del(context_, ECORE_IMF_CALLBACK_COMMIT, + &IMFCommitCallback); + ecore_imf_context_event_callback_del(context_, + ECORE_IMF_CALLBACK_DELETE_SURROUNDING, + &IMFDeleteSurroundingCallback); + ecore_imf_context_event_callback_del( + context_, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, &IMFPreeditChangedCallback); + ecore_imf_context_event_callback_del( + context_, ECORE_IMF_CALLBACK_PRIVATE_COMMAND_SEND, + &IMFTransactionPrivateCommandSendCallback); + ecore_imf_context_input_panel_event_callback_del( + context_, ECORE_IMF_INPUT_PANEL_STATE_EVENT, + &IMFInputPanelStateChangedCallback); + ecore_imf_context_input_panel_event_callback_del( + context_, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT, + &IMFInputPanelGeometryChangedCallback); + ecore_imf_context_input_panel_event_callback_del( + context_, ECORE_IMF_CANDIDATE_PANEL_STATE_EVENT, + &IMFCandidatePanelStateChangedCallback); + ecore_imf_context_input_panel_event_callback_del( + context_, ECORE_IMF_CANDIDATE_PANEL_GEOMETRY_EVENT, + &IMFCandidatePanelGeometryChangedCallback); + ecore_imf_context_input_panel_event_callback_del( + context_, ECORE_IMF_INPUT_PANEL_LANGUAGE_EVENT, + &IMFCandidatePanelLanguageChangedCallback); + ecore_imf_context_del(context_); +} + +void IMContextEfl::HandleKeyDownEvent(const Evas_Event_Key_Down* event, + bool* was_filtered) { + Ecore_IMF_Event im_event; + ecore_imf_evas_event_key_down_wrap(const_cast(event), + &im_event.key_down); + *was_filtered = ecore_imf_context_filter_event( + context_, ECORE_IMF_EVENT_KEY_DOWN, &im_event); + is_keyevent_processing_ = true; +} + +void IMContextEfl::HandleKeyUpEvent(const Evas_Event_Key_Up* event, + bool* was_filtered) { + Ecore_IMF_Event im_event; + ecore_imf_evas_event_key_up_wrap(const_cast(event), + &im_event.key_up); + *was_filtered = ecore_imf_context_filter_event( + context_, ECORE_IMF_EVENT_KEY_UP, &im_event); +} + +void IMContextEfl::UpdateInputMethodType(TextInputType type, + TextInputMode mode, + bool can_compose_inline +#if defined(OS_TIZEN_TV_PRODUCT) + , + int password_input_minlength, + int input_maxlength +#endif +) { + if (current_type_ == type && current_mode_ == mode && + can_compose_inline_ == can_compose_inline +#if defined(OS_TIZEN_TV_PRODUCT) + && password_input_minlength_ == password_input_minlength && + input_maxlength_ == input_maxlength +#endif + ) { + return; + } + +#if defined(OS_TIZEN_TV_PRODUCT) + password_input_minlength_ = password_input_minlength; + input_maxlength_ = input_maxlength; +#endif + + Ecore_IMF_Input_Panel_Layout layout; + Ecore_IMF_Input_Panel_Return_Key_Type return_key_type = + ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT; + Ecore_IMF_Autocapital_Type cap_type = ECORE_IMF_AUTOCAPITAL_TYPE_NONE; + bool allow_prediction = true; + bool is_multi_line __attribute__((unused)) = false; + bool disable_done_key = false; + + return_key_type = is_in_form_tag_ + ? ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_GO + : ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DONE; + + switch (type) { + case TEXT_INPUT_TYPE_TEXT: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; + break; + case TEXT_INPUT_TYPE_PASSWORD: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD; + allow_prediction = false; + break; + case TEXT_INPUT_TYPE_SEARCH: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; + return_key_type = ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SEARCH; + break; + case TEXT_INPUT_TYPE_EMAIL: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_EMAIL; + break; + case TEXT_INPUT_TYPE_NUMBER: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBER; + break; + case TEXT_INPUT_TYPE_TELEPHONE: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER; + break; + case TEXT_INPUT_TYPE_URL: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_URL; + break; + case TEXT_INPUT_TYPE_MONTH: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_MONTH; + break; + case TEXT_INPUT_TYPE_TEXT_AREA: + case TEXT_INPUT_TYPE_CONTENT_EDITABLE: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; + cap_type = ECORE_IMF_AUTOCAPITAL_TYPE_SENTENCE; + return_key_type = ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT; + is_multi_line = true; + break; + + // No direct mapping to Ecore_IMF API, use simple text layout. + case TEXT_INPUT_TYPE_DATE: + case TEXT_INPUT_TYPE_DATE_TIME: + case TEXT_INPUT_TYPE_DATE_TIME_LOCAL: + case TEXT_INPUT_TYPE_TIME: + case TEXT_INPUT_TYPE_WEEK: + case TEXT_INPUT_TYPE_DATE_TIME_FIELD: + layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; + break; + + case TEXT_INPUT_TYPE_NONE: + return; + + default: + NOTREACHED(); + return; + } + +#if defined(OS_TIZEN_TV_PRODUCT) + // Always enable "Up" and "Down" key + // Set IMEUp=2, IMEDown=2 will always enable the 'up' and 'down' arrow key + std::string im_data("IMEUp=2&IMEDown=2"); + + if (allow_prediction && is_get_lookup_table_from_app_) + im_data.append("&layouttype=1"); + + // set input max length by entrylimit + if (input_maxlength_ != DEFAULT_MAX_LENGTH && input_maxlength_ != -1) + im_data.append("&entrylimit=" + std::to_string(input_maxlength_)); + + ecore_imf_context_input_panel_imdata_set(context_, im_data.c_str(), + im_data.length()); + + // For password input type,disable "Done" key when inputed text not meet + // minlength + if (TEXT_INPUT_TYPE_PASSWORD == type && + surrounding_text_length_ < password_input_minlength_) { + LOG(INFO) << "surrounding_text_length_:" << surrounding_text_length_ + << ",password_input_minlength_:" << password_input_minlength_ + << ",disable done key"; + disable_done_key = true; + } +#endif + + Ecore_IMF_Input_Mode imf_mode; + switch (mode) { + case TEXT_INPUT_MODE_NUMERIC: + imf_mode = ECORE_IMF_INPUT_MODE_NUMERIC; + break; + default: + imf_mode = ECORE_IMF_INPUT_MODE_ALPHA; + } + + ecore_imf_context_input_panel_layout_set(context_, layout); + ecore_imf_context_input_mode_set(context_, imf_mode); + ecore_imf_context_input_panel_return_key_type_set(context_, return_key_type); + ecore_imf_context_autocapital_type_set(context_, cap_type); + ecore_imf_context_prediction_allow_set(context_, allow_prediction); + ecore_imf_context_input_panel_return_key_disabled_set(context_, + disable_done_key); + +#if defined(OS_TIZEN) + Ecore_IMF_Input_Hints hints; + if (is_multi_line) { + hints = static_cast( + ecore_imf_context_input_hint_get(context_) | + ECORE_IMF_INPUT_HINT_MULTILINE); + } else { + hints = static_cast( + ecore_imf_context_input_hint_get(context_) & + ~ECORE_IMF_INPUT_HINT_MULTILINE); + } + ecore_imf_context_input_hint_set(context_, hints); +#endif + + // If the focused element supports inline rendering of composition text, + // we receive and send related events to it. Otherwise, the events related + // to the updates of composition text are directed to the candidate window. + ecore_imf_context_use_preedit_set(context_, can_compose_inline); + + current_mode_ = mode; +} + +bool IMContextEfl::ShouldHandleImmediately(const char* key) { + // Do not forward keyevent now if there is fake key event + // handling at the moment to preserve orders of events as in Webkit. + // Some keys should be processed after composition text is + // released. + // So some keys are pushed to KeyDownEventQueue first + // then it will be porcessed after composition text is processed. + return ( + (IsPreeditQueueEmpty() || IsKeyUpQueueEmpty()) && + !(!IsCommitQueueEmpty() && + ((IsTvProfile() && + (!strcmp(key, "Select") || !strcmp(key, "Cancel"))) || + !strcmp(key, "Down") || !strcmp(key, "Up") || !strcmp(key, "Right") || + !strcmp(key, "Left") || !strcmp(key, "space") || !strcmp(key, "Tab") || + !strcmp(key, "Return")))); +} + +void IMContextEfl::UpdateInputMethodState(TextInputType type, + bool can_compose_inline, + bool show_if_needed +#if defined(OS_TIZEN_TV_PRODUCT) + , + int password_input_minlength, + int input_maxlength +#endif +) { +#if defined(OS_TIZEN_TV_PRODUCT) + if (current_type_ != type || can_compose_inline_ != can_compose_inline || + password_input_minlength_ != password_input_minlength || + input_maxlength_ != input_maxlength) { + UpdateInputMethodType(type, TEXT_INPUT_MODE_DEFAULT, can_compose_inline, + password_input_minlength, input_maxlength); +#else + if (current_type_ != type || can_compose_inline_ != can_compose_inline) { + UpdateInputMethodType(type, TEXT_INPUT_MODE_DEFAULT, can_compose_inline); +#endif + + // Workaround on platform issue: + // http://107.108.218.239/bugzilla/show_bug.cgi?id=11494 + // Keyboard layout doesn't update after change. + if (IsVisible() && type != TEXT_INPUT_TYPE_NONE) + HidePanel(); + } + + current_type_ = type; + can_compose_inline_ = can_compose_inline; + + bool focus_in = type != TEXT_INPUT_TYPE_NONE; + if (focus_in == is_focused_ && (!show_if_needed || IsVisible())) + return; + + if (focus_in && show_if_needed) { + ShowPanel(); + } else if (focus_in) + OnFocusIn(); + else if (IsVisible()) { + HidePanel(); + } else { + OnFocusOut(); + } +} + +void IMContextEfl::ShowPanel() { + LOG(INFO) << "Show Input Panel!"; + is_showing_ = is_focused_ = true; + ecore_imf_context_focus_in(context_); + ecore_imf_context_input_panel_show(context_); +} + +void IMContextEfl::HidePanel() { + LOG(INFO) << "Hide Input Panel!"; + is_showing_ = is_focused_ = false; + ecore_imf_context_focus_out(context_); + ecore_imf_context_input_panel_hide(context_); +} + +void IMContextEfl::OnFocusIn() { + if (current_type_ == ui::TEXT_INPUT_TYPE_NONE) + return; + + LOG(INFO) << "IME Focus In"; + ecore_imf_context_focus_in(context_); + is_focused_ = true; +} + +void IMContextEfl::OnFocusOut() { + LOG(INFO) << "IME Focus Out"; + is_focused_ = false; + + CancelComposition(); + ecore_imf_context_focus_out(context_); +} + +void IMContextEfl::ResetIMFContext() { + is_ime_ctx_reset_ = true; + ecore_imf_context_reset(context_); + is_ime_ctx_reset_ = false; +} + +void IMContextEfl::CancelComposition() { + ClearQueues(); + ResetIMFContext(); + + if (composition_.text.length() > 0) { + std::u16string empty; + ConfirmComposition(empty); + composition_.text = u""; + } +} + +void IMContextEfl::SetIsInFormTag(bool is_in_form_tag) { + is_in_form_tag_ = is_in_form_tag; + // TODO: workaround on tizen v3.0 platform issue + // Even if virtual keyboard is shown, + // the API 'ecore_imf_context_input_panel_state_get()' + // returns '1' which means the keyboard is not shown. + // It makes the 'HidePanel()' unreachable. + if (!IsVisible()) + return; + + if (ecore_imf_context_input_panel_return_key_type_get(context_) == + ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SEARCH || + current_type_ == TEXT_INPUT_TYPE_TEXT_AREA) { + return; + } + + if (is_in_form_tag_) { + ecore_imf_context_input_panel_return_key_type_set( + context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_GO); + } else { + if (current_type_ == TEXT_INPUT_TYPE_TEXT || + current_type_ == TEXT_INPUT_TYPE_NUMBER || + current_type_ == TEXT_INPUT_TYPE_TELEPHONE || + current_type_ == TEXT_INPUT_TYPE_PASSWORD || + current_type_ == TEXT_INPUT_TYPE_EMAIL) + ecore_imf_context_input_panel_return_key_type_set( + context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DONE); + else { + ecore_imf_context_input_panel_return_key_type_set( + context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT); + } + } +} + +void IMContextEfl::SetCaretPosition(int position) { + if (!context_) + return; + + caret_position_ = position; + if (!is_keyevent_processing_) + ecore_imf_context_cursor_position_set(context_, position); +} + +void IMContextEfl::OnCommit(void* event_info) { + if (is_ime_ctx_reset_) + return; + + composition_ = CompositionText(); + char* text = static_cast(event_info); + std::u16string text16 = base::UTF8ToUTF16(text); + LOG(INFO) << "OnCommit, text: " << text16; + // Only add commit to queue, till we dont know if key event + // should be handled. It can be default prevented for exactly. + commit_queue_.push(text16); + +#if defined(OS_TIZEN_TV_PRODUCT) + // Fix IMFCommitCallback come but previous preedit_queue_ haven't handle + // cause show double text issue. + if (!preedit_queue_.empty()) + preedit_queue_ = PreeditQueue(); +#endif + + is_surrounding_text_change_in_progress_ = true; + + // sending fake key event if hardware key is not handled as it is + // in Webkit. + SendFakeCompositionKeyEvent(text16); +} + +void IMContextEfl::SendFakeCompositionKeyEvent(const std::u16string& buf) { + std::string str = base::UTF16ToUTF8(buf); + Evas_Event_Key_Down downEvent; + memset(&downEvent, 0, sizeof(Evas_Event_Key_Down)); + downEvent.key = str.c_str(); + downEvent.string = str.c_str(); + + KeyEvent event = MakeWebKeyEvent(true, &downEvent); + event.is_system_key = true; + + // Key code should be '229' except ASCII key event about key down/up event. + // It is according to Web spec about key code [1]. + // On TV WebAPPs case, key code should be '229' including ASCII key event + // about key down/up event. + // [1] https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/ + // keyCode-spec.html + // [2] http://developer.samsung.com/tv/develop/tutorials/user-input/ + // text-input-ime-external-keyboard + if (event.key_code() == 0) + event.set_key_code(static_cast(229)); + + is_keyevent_processing_ = true; + PushToKeyUpEventQueue(event.key_code()); + EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event); +} + +void IMContextEfl::SetComposition(const char* buffer) { + std::u16string text16 = base::UTF8ToUTF16(buffer); + if (!text16.empty()) + SendFakeCompositionKeyEvent(text16.substr(text16.length() - 1)); + else + SendFakeCompositionKeyEvent(text16); + + composition_ = CompositionText(); + composition_.text = text16; + composition_.ime_text_spans.push_back( + ImeTextSpan(ImeTextSpan::Type::kComposition, 0, + composition_.text.length(), ImeTextSpan::Thickness::kThin, + ImeTextSpan::UnderlineStyle::kSolid, SK_ColorTRANSPARENT)); + composition_.selection = gfx::Range(composition_.text.length()); + + is_surrounding_text_change_in_progress_ = true; + + // Only add preedit to queue, till we dont know if key event + // should be handled. It can be default prevented for exactly. + preedit_queue_.push(composition_); +} + +void IMContextEfl::OnPreeditChanged(void* data, + Ecore_IMF_Context* context, + void* event_info) { + if (is_ime_ctx_reset_) + return; + + char* buffer = NULL; + ecore_imf_context_preedit_string_get(context, &buffer, 0); + + if (!buffer) + return; + + LOG(INFO) << "OnPreeditChanged,text:" << buffer; + // 'buffer' is a null teminated utf-8 string, + // it should be OK to find '\n' via strchr + char* new_line = std::strchr(buffer, '\n'); + + // SetComposition does not handle '\n' properly for + // 1. Single line input() + // 2. Multiple line input(