From c996cff8a3187b57ca55adc9afa5d7130f93dbd8 Mon Sep 17 00:00:00 2001 From: Boram Bae Date: Tue, 13 Jul 2021 19:09:36 +0900 Subject: [PATCH 1/6] Refactor TextInputChannel * Introduce TizenInputMethodContext * Do not handle key event for PlatformView in TextInputChannel Each PlatformView must Implement to handle key event. After this patch, the input panel of webview_flutter does not work properly. but it have to handle every key event on itself like webview_flutter_ewk does. Signed-off-by: Boram Bae --- shell/platform/tizen/BUILD.gn | 1 + .../tizen/channels/platform_view_channel.cc | 14 +- .../tizen/channels/platform_view_channel.h | 4 +- .../tizen/channels/text_input_channel.cc | 563 ++++-------------- .../tizen/channels/text_input_channel.h | 83 +-- shell/platform/tizen/flutter_tizen_engine.cc | 5 +- shell/platform/tizen/key_event_handler.cc | 8 +- .../tizen/tizen_input_method_context.cc | 312 ++++++++++ .../tizen/tizen_input_method_context.h | 81 +++ 9 files changed, 554 insertions(+), 517 deletions(-) create mode 100644 shell/platform/tizen/tizen_input_method_context.cc create mode 100644 shell/platform/tizen/tizen_input_method_context.h diff --git a/shell/platform/tizen/BUILD.gn b/shell/platform/tizen/BUILD.gn index ea2fbf946f90e..1e2b8e2f100cf 100644 --- a/shell/platform/tizen/BUILD.gn +++ b/shell/platform/tizen/BUILD.gn @@ -38,6 +38,7 @@ _flutter_tizen_source = [ "flutter_tizen_texture_registrar.cc", "key_event_handler.cc", "tizen_event_loop.cc", + "tizen_input_method_context.cc", "tizen_renderer.cc", "touch_event_handler.cc", ] diff --git a/shell/platform/tizen/channels/platform_view_channel.cc b/shell/platform/tizen/channels/platform_view_channel.cc index 71bf2f906c646..70cbd9d618227 100644 --- a/shell/platform/tizen/channels/platform_view_channel.cc +++ b/shell/platform/tizen/channels/platform_view_channel.cc @@ -8,7 +8,6 @@ #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_message_codec.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/standard_method_codec.h" #include "flutter/shell/platform/common/json_method_codec.h" -#include "flutter/shell/platform/tizen/channels/text_input_channel.h" #include "flutter/shell/platform/tizen/flutter_tizen_engine.h" #include "flutter/shell/platform/tizen/public/flutter_platform_view.h" #include "flutter/shell/platform/tizen/tizen_log.h" @@ -33,10 +32,8 @@ bool GetValueFromEncodableMap(const EncodableValue& arguments, return false; } -PlatformViewChannel::PlatformViewChannel(BinaryMessenger* messenger, - FlutterTizenEngine* engine) - : engine_(engine), - channel_(std::make_unique>( +PlatformViewChannel::PlatformViewChannel(BinaryMessenger* messenger) + : channel_(std::make_unique>( messenger, kChannelName, &StandardMethodCodec::GetInstance())) { @@ -162,12 +159,6 @@ void PlatformViewChannel::HandleMethodCall( if (view_instance) { view_instances_.insert( std::pair(view_id, view_instance)); - - if (engine_ && engine_->text_input_channel) { - Ecore_IMF_Context* context = - engine_->text_input_channel->GetImfContext(); - view_instance->SetSoftwareKeyboardContext(context); - } result->Success(EncodableValue(view_instance->GetTextureId())); } else { result->Error("Can't create a webview instance!!"); @@ -264,5 +255,4 @@ void PlatformViewChannel::HandleMethodCall( } } } - } // namespace flutter diff --git a/shell/platform/tizen/channels/platform_view_channel.h b/shell/platform/tizen/channels/platform_view_channel.h index de322a0aad3f1..0e5e8e1971406 100644 --- a/shell/platform/tizen/channels/platform_view_channel.h +++ b/shell/platform/tizen/channels/platform_view_channel.h @@ -24,8 +24,7 @@ class FlutterTizenEngine; class PlatformViewChannel { public: - explicit PlatformViewChannel(BinaryMessenger* messenger, - FlutterTizenEngine* engine); + explicit PlatformViewChannel(BinaryMessenger* messenger); virtual ~PlatformViewChannel(); void Dispose(); @@ -48,7 +47,6 @@ class PlatformViewChannel { void HandleMethodCall(const MethodCall& call, std::unique_ptr> result); - FlutterTizenEngine* engine_; std::unique_ptr> channel_; std::map> view_factories_; std::map view_instances_; diff --git a/shell/platform/tizen/channels/text_input_channel.cc b/shell/platform/tizen/channels/text_input_channel.cc index fb7064ee42507..a6d5711ba4030 100644 --- a/shell/platform/tizen/channels/text_input_channel.cc +++ b/shell/platform/tizen/channels/text_input_channel.cc @@ -40,209 +40,13 @@ constexpr char kTextKey[] = "text"; constexpr char kBadArgumentError[] = "Bad Arguments"; constexpr char kInternalConsistencyError[] = "Internal Consistency Error"; -} // namespace - -static const char* GetImfMethod() { - Eina_List* modules; - - modules = ecore_imf_context_available_ids_get(); - if (!modules) { - return nullptr; - } - - void* module; - EINA_LIST_FREE(modules, module) { return static_cast(module); } - - return nullptr; -} - -static bool IsASCIIPrintableKey(char c) { +bool IsASCIIPrintableKey(char c) { if (c >= 32 && c <= 126) { return true; } return false; } - -static bool TextInputTypeToEcoreIMFInputPanelLayout( - std::string text_input_type, - Ecore_IMF_Input_Panel_Layout* panel_layout) { - if (text_input_type == "TextInputType.text" || - text_input_type == "TextInputType.multiline") { - *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; - } else if (text_input_type == "TextInputType.number") { - *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBERONLY; - } else if (text_input_type == "TextInputType.phone") { - *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER; - } else if (text_input_type == "TextInputType.datetime") { - *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_DATETIME; - } else if (text_input_type == "TextInputType.emailAddress") { - *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_EMAIL; - } else if (text_input_type == "TextInputType.url") { - *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_URL; - } else if (text_input_type == "TextInputType.visiblePassword") { - *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD; - } else if (text_input_type == "TextInputType.name" || - text_input_type == "TextInputType.address") { - FT_LOGW( - "Actual requested text input type is [%s], but select " - "TextInputType.text as fallback type", - text_input_type.c_str()); - *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; - } else { - return false; - } - return true; -} - -void TextInputChannel::CommitCallback(void* data, - Ecore_IMF_Context* ctx, - void* event_info) { - TextInputChannel* self = static_cast(data); - if (!self) { - return; - } - char* str = static_cast(event_info); - if (self->engine_ && self->engine_->platform_view_channel && - self->engine_->platform_view_channel->CurrentFocusedViewId() > -1) { - self->engine_->platform_view_channel->DispatchCompositionEndEvent(str); - return; - } - - self->OnCommit(str); -} - -void TextInputChannel::PreeditCallback(void* data, - Ecore_IMF_Context* ctx, - void* event_info) { - TextInputChannel* self = static_cast(data); - if (!self) { - return; - } - - char* str = nullptr; - int cursor_pos; - ecore_imf_context_preedit_string_get(ctx, &str, &cursor_pos); - if (str) { - if (self->engine_ && self->engine_->platform_view_channel && - self->engine_->platform_view_channel->CurrentFocusedViewId() > -1) { - self->OnPreeditForPlatformView(str, cursor_pos); - } else { - self->OnPreedit(str, cursor_pos); - } - free(str); - } -} - -void TextInputChannel::PrivateCommandCallback(void* data, - Ecore_IMF_Context* ctx, - void* event_info) { - FT_UNIMPLEMENTED(); -} - -void TextInputChannel::DeleteSurroundingCallback(void* data, - Ecore_IMF_Context* ctx, - void* event_info) { - FT_UNIMPLEMENTED(); -} - -void TextInputChannel::InputPanelStateChangedCallback( - void* data, - Ecore_IMF_Context* context, - int value) { - FT_LOGI("Change input panel state[%d]", value); - if (!data) { - FT_LOGW("No Data"); - return; - } - TextInputChannel* self = static_cast(data); - switch (value) { - case ECORE_IMF_INPUT_PANEL_STATE_SHOW: - self->SetSoftwareKeyboardShowing(); - break; - case ECORE_IMF_INPUT_PANEL_STATE_HIDE: - self->HideSoftwareKeyboard(); // FIXME: Fallback for HW back-key - break; - case ECORE_IMF_INPUT_PANEL_STATE_WILL_SHOW: - break; - default: - break; - } -} - -void TextInputChannel::InputPanelGeometryChangedCallback( - void* data, - Ecore_IMF_Context* context, - int value) { - if (!data) { - FT_LOGW("No Data"); - return; - } - TextInputChannel* self = static_cast(data); - ecore_imf_context_input_panel_geometry_get( - self->imf_context_, &self->current_keyboard_geometry_.x, - &self->current_keyboard_geometry_.y, &self->current_keyboard_geometry_.w, - &self->current_keyboard_geometry_.h); - - FT_LOGI( - "Current keyboard geometry x:[%d] y:[%d] w:[%d] h:[%d]", - self->current_keyboard_geometry_.x, self->current_keyboard_geometry_.y, - self->current_keyboard_geometry_.w, self->current_keyboard_geometry_.h); -} - -Eina_Bool TextInputChannel::RetrieveSurroundingCallback(void* data, - Ecore_IMF_Context* ctx, - char** text, - int* cursor_pos) { - FT_UNIMPLEMENTED(); - return EINA_TRUE; -} - -Ecore_IMF_Keyboard_Modifiers EcoreInputModifierToEcoreIMFModifier( - unsigned int ecoreModifier) { - unsigned int modifier(ECORE_IMF_KEYBOARD_MODIFIER_NONE); - - if (ecoreModifier & ECORE_EVENT_MODIFIER_SHIFT) { - modifier |= ECORE_IMF_KEYBOARD_MODIFIER_SHIFT; - } - - if (ecoreModifier & ECORE_EVENT_MODIFIER_ALT) { - modifier |= ECORE_IMF_KEYBOARD_MODIFIER_ALT; - } - - if (ecoreModifier & ECORE_EVENT_MODIFIER_CTRL) { - modifier |= ECORE_IMF_KEYBOARD_MODIFIER_CTRL; - } - - if (ecoreModifier & ECORE_EVENT_MODIFIER_WIN) { - modifier |= ECORE_IMF_KEYBOARD_MODIFIER_WIN; - } - - if (ecoreModifier & ECORE_EVENT_MODIFIER_ALTGR) { - modifier |= ECORE_IMF_KEYBOARD_MODIFIER_ALTGR; - } - - return static_cast(modifier); -} - -Ecore_IMF_Keyboard_Locks EcoreInputModifierToEcoreIMFLock( - unsigned int modifier) { - // If no other matches, returns NONE. - unsigned int lock(ECORE_IMF_KEYBOARD_LOCK_NONE); - - if (modifier & ECORE_EVENT_LOCK_NUM) { - lock |= ECORE_IMF_KEYBOARD_LOCK_NUM; - } - - if (modifier & ECORE_EVENT_LOCK_CAPS) { - lock |= ECORE_IMF_KEYBOARD_LOCK_CAPS; - } - - if (modifier & ECORE_EVENT_LOCK_SCROLL) { - lock |= ECORE_IMF_KEYBOARD_LOCK_SCROLL; - } - - return static_cast(lock); -} +} // namespace TextInputChannel::TextInputChannel(BinaryMessenger* messenger, FlutterTizenEngine* engine) @@ -250,41 +54,77 @@ TextInputChannel::TextInputChannel(BinaryMessenger* messenger, messenger, kChannelName, &JsonMethodCodec::GetInstance())), - engine_(engine) { + input_method_context_(std::make_unique(engine)) { channel_->SetMethodCallHandler( [this](const MethodCall& call, std::unique_ptr> result) { HandleMethodCall(call, std::move(result)); }); - ecore_imf_init(); - // Register IMF callbacks - if (ecore_imf_context_default_id_get()) { - imf_context_ = ecore_imf_context_add(ecore_imf_context_default_id_get()); - } else if (GetImfMethod() != nullptr) { - imf_context_ = ecore_imf_context_add(GetImfMethod()); - } - if (imf_context_) { - ecore_imf_context_client_window_set( - imf_context_, - reinterpret_cast(engine_->renderer->GetWindowId())); - RegisterIMFCallback(); - } else { - FT_LOGE("Failed to create imfContext"); - } -} + // Set input method callbacks + input_method_context_->SetOnCommitCallback([this](std::string str) -> void { + FT_LOGI("OnCommit str[%s]", str.data()); + text_editing_context_.edit_status_ = EditStatus::kCommit; + ConsumeLastPreedit(); + active_model_->AddText(str); + SendStateUpdate(*active_model_); + }); + + input_method_context_->SetOnPreeditCallback( + [this](std::string str, int cursor_pos) -> void { + text_editing_context_.edit_status_ = EditStatus::kPreeditStart; + if (str.compare("") == 0) { + text_editing_context_.edit_status_ = EditStatus::kPreeditEnd; + } + + if (text_editing_context_.edit_status_ == EditStatus::kPreeditStart || + (text_editing_context_.edit_status_ == EditStatus::kPreeditEnd && + // For tv, fix me + text_editing_context_.last_handled_ecore_event_keyname_.compare( + "Return") != 0)) { + text_editing_context_.last_handled_ecore_event_keyname_ = ""; + ConsumeLastPreedit(); + } + + text_editing_context_.has_preedit_ = false; + if (text_editing_context_.edit_status_ == EditStatus::kPreeditStart) { + text_editing_context_.preedit_start_pos_ = + active_model_->selection().base(); + active_model_->AddText(str); + text_editing_context_.preedit_end_pos_ = + active_model_->selection().base(); + text_editing_context_.has_preedit_ = true; + SendStateUpdate(*active_model_); + FT_LOGI("preedit start pos[%d], preedit end pos[%d]", + text_editing_context_.preedit_start_pos_, + text_editing_context_.preedit_end_pos_); + } + }); -TextInputChannel::~TextInputChannel() { - if (imf_context_) { - UnregisterIMFCallback(); - ecore_imf_context_del(imf_context_); - ecore_imf_shutdown(); - } + input_method_context_->SetOnInputPannelStateChangedCallback( + [this](int state) { + if (state == ECORE_IMF_INPUT_PANEL_STATE_HIDE) { + // Fallback for HW back-key + input_method_context_->HideInputPannel(); + input_method_context_->ResetInputMethodContext(); + ResetTextEditingContext(); + is_software_keyboard_showing_ = false; + } else { + is_software_keyboard_showing_ = true; + } + }); } +TextInputChannel::~TextInputChannel() {} + void TextInputChannel::OnKeyDown(Ecore_Event_Key* key) { - if (!FilterEvent(key) && !have_preedit_) { - NonIMFFallback(key); + if (active_model_ == nullptr) { + FT_LOGW("There is no active TextInputModel"); + return; + } + + if (!FilterEvent(key) && !text_editing_context_.has_preedit_) { + HandleUnfilteredEvent(key); } } @@ -296,9 +136,11 @@ void TextInputChannel::HandleMethodCall( FT_LOGI("Handle Method : %s", method.data()); if (method.compare(kShowMethod) == 0) { - ShowSoftwareKeyboard(); + input_method_context_->ShowInputPannel(); } else if (method.compare(kHideMethod) == 0) { - HideSoftwareKeyboard(); + input_method_context_->HideInputPannel(); + input_method_context_->ResetInputMethodContext(); + ResetTextEditingContext(); } else if (method.compare(kSetPlatformViewClient) == 0) { FT_UNIMPLEMENTED(); } else if (method.compare(kClearClientMethod) == 0) { @@ -318,19 +160,24 @@ void TextInputChannel::HandleMethodCall( result->Error(kBadArgumentError, "Could not set client, ID is null."); return; } + if (client_config.IsNull()) { result->Error(kBadArgumentError, "Could not set client, missing arguments."); } + client_id_ = client_id_json.GetInt(); input_action_ = ""; auto input_action_json = client_config.FindMember(kTextInputAction); + if (input_action_json != client_config.MemberEnd() && input_action_json->value.IsString()) { input_action_ = input_action_json->value.GetString(); } + input_type_ = ""; auto input_type_info_json = client_config.FindMember(kTextInputType); + if (input_type_info_json != client_config.MemberEnd() && input_type_info_json->value.IsObject()) { auto input_type_json = @@ -338,14 +185,17 @@ void TextInputChannel::HandleMethodCall( if (input_type_json != input_type_info_json->value.MemberEnd() && input_type_json->value.IsString()) { input_type_ = input_type_json->value.GetString(); + input_method_context_->SetInputPannelLayout(input_type_); } } + active_model_ = std::make_unique(); } else if (method.compare(kSetEditingStateMethod) == 0) { if (!method_call.arguments() || method_call.arguments()->IsNull()) { result->Error(kBadArgumentError, "Method invoked without args"); return; } + const rapidjson::Document& args = *method_call.arguments(); if (active_model_ == nullptr) { @@ -354,12 +204,14 @@ void TextInputChannel::HandleMethodCall( "Set editing state has been invoked, but no client is set."); return; } + auto text = args.FindMember(kTextKey); if (text == args.MemberEnd() || text->value.IsNull()) { result->Error(kBadArgumentError, "Set editing state has been invoked, but without text."); return; } + auto selection_base = args.FindMember(kSelectionBaseKey); auto selection_extent = args.FindMember(kSelectionExtentKey); if (selection_base == args.MemberEnd() || selection_base->value.IsNull() || @@ -369,6 +221,7 @@ void TextInputChannel::HandleMethodCall( "Selection base/extent values invalid."); return; } + active_model_->SetText(text->value.GetString()); active_model_->SetSelection(TextRange(selection_base->value.GetInt(), selection_extent->value.GetInt())); @@ -406,62 +259,37 @@ void TextInputChannel::SendStateUpdate(const TextInputModel& model) { bool TextInputChannel::FilterEvent(Ecore_Event_Key* event) { bool handled = false; -#ifdef WEARABLE_PROFILE +#ifdef __X64_SHELL__ + bool is_ime = false; +#elif WEARABLE_PROFILE // Hardware keyboard not supported on watches. bool is_ime = true; -#elif __X64_SHELL__ - bool is_ime = false; -#else - bool is_ime = strcmp(ecore_device_name_get(event->dev), "ime") == 0; -#endif - - Ecore_IMF_Event_Key_Down imf_event; - imf_event.keyname = event->keyname; - imf_event.key = event->key; - imf_event.string = event->string; - imf_event.compose = event->compose; - imf_event.timestamp = event->timestamp; - imf_event.modifiers = EcoreInputModifierToEcoreIMFModifier(event->modifiers); - imf_event.locks = EcoreInputModifierToEcoreIMFLock(event->modifiers); - imf_event.dev_name = is_ime ? "ime" : ""; - imf_event.keycode = event->keycode; - -#ifdef WEARABLE_PROFILE // FIXME: Only for wearable. if (is_ime && strcmp(event->key, "Select") == 0) { - in_select_mode_ = true; + text_editing_context_.is_in_select_mode_ = true; FT_LOGI("Set select mode[true]"); } +#else + bool is_ime = strcmp(ecore_device_name_get(event->dev), "ime") == 0; #endif - if (is_ime) { - if (!strcmp(event->key, "Left") || !strcmp(event->key, "Right") || - !strcmp(event->key, "Up") || !strcmp(event->key, "Down") || - !strcmp(event->key, "End") || !strcmp(event->key, "Home") || - !strcmp(event->key, "BackSpace") || !strcmp(event->key, "Delete") || - (!strcmp(event->key, "Select") && !in_select_mode_)) { - // Force redirect to fallback!(especially on TV) - // If you don't do this, it affects the input panel. - // For example, when the left key of the input panel is pressed, the focus - // of the input panel is shifted to left! - // What we want is to move only the cursor on the text editor. - ResetCurrentContext(); - FT_LOGW("Force redirect IME key-event[%s] to fallback", event->keyname); - return false; - } + if (ShouldNotUseFilterEvent(event->key, is_ime)) { + ResetTextEditingContext(); + input_method_context_->ResetInputMethodContext(); + FT_LOGW("Force redirect IME key-event[%s] to fallback", event->keyname); + return false; } - handled = ecore_imf_context_filter_event( - imf_context_, ECORE_IMF_EVENT_KEY_DOWN, - reinterpret_cast(&imf_event)); + handled = input_method_context_->FilterEvent(event, is_ime ? "ime" : ""); if (handled) { - last_handled_ecore_event_keyname_ = event->keyname; + text_editing_context_.last_handled_ecore_event_keyname_ = event->keyname; } #ifdef WEARABLE_PROFILE - if (!handled && !strcmp(event->key, "Return") && in_select_mode_) { - in_select_mode_ = false; + if (!handled && !strcmp(event->key, "Return") && + text_editing_context_.is_in_select_mode_) { + text_editing_context_.is_in_select_mode_ = false; handled = true; FT_LOGI("Set select mode[false]"); } @@ -470,12 +298,12 @@ bool TextInputChannel::FilterEvent(Ecore_Event_Key* event) { return handled; } -void TextInputChannel::NonIMFFallback(Ecore_Event_Key* event) { +void TextInputChannel::HandleUnfilteredEvent(Ecore_Event_Key* event) { #ifdef MOBILE_PROFILE // FIXME: Only for mobile. - if (edit_status_ == EditStatus::kPreeditEnd) { - SetEditStatus(EditStatus::kNone); + if (text_editing_context_.edit_status_ == EditStatus::kPreeditEnd) { FT_LOGW("Ignore key-event[%s]!", event->keyname); + ResetTextEditingContext(); return; } #endif @@ -483,11 +311,11 @@ void TextInputChannel::NonIMFFallback(Ecore_Event_Key* event) { bool select = !strcmp(event->key, "Select"); bool is_filtered = true; if (!strcmp(event->key, "Left")) { - if (active_model_ && active_model_->MoveCursorBack()) { + if (active_model_->MoveCursorBack()) { SendStateUpdate(*active_model_); } } else if (!strcmp(event->key, "Right")) { - if (active_model_ && active_model_->MoveCursorForward()) { + if (active_model_->MoveCursorForward()) { SendStateUpdate(*active_model_); } } else if (!strcmp(event->key, "End")) { @@ -501,30 +329,26 @@ void TextInputChannel::NonIMFFallback(Ecore_Event_Key* event) { SendStateUpdate(*active_model_); } } else if (!strcmp(event->key, "BackSpace")) { - if (active_model_ && active_model_->Backspace()) { + if (active_model_->Backspace()) { SendStateUpdate(*active_model_); } } else if (!strcmp(event->key, "Delete")) { - if (active_model_ && active_model_->Delete()) { + if (active_model_->Delete()) { SendStateUpdate(*active_model_); } - } else if (!strcmp(event->key, "Return") || (select && !in_select_mode_)) { - if (active_model_) { - EnterPressed(active_model_.get(), select); - } + } else if (!strcmp(event->key, "Return") || + (select && !text_editing_context_.is_in_select_mode_)) { + EnterPressed(active_model_.get(), select); + } else if (event->string && strlen(event->string) == 1 && IsASCIIPrintableKey(event->string[0])) { - if (active_model_) { - active_model_->AddCodePoint(event->string[0]); - SendStateUpdate(*active_model_); - } + active_model_->AddCodePoint(event->string[0]); + SendStateUpdate(*active_model_); } else { is_filtered = false; } - if (!active_model_ && is_filtered) { - engine_->platform_view_channel->SendKeyEvent(event, true); - } - SetEditStatus(EditStatus::kNone); + + text_editing_context_.edit_status_ = EditStatus::kNone; } void TextInputChannel::EnterPressed(TextInputModel* model, bool select) { @@ -540,165 +364,34 @@ void TextInputChannel::EnterPressed(TextInputModel* model, bool select) { channel_->InvokeMethod(kPerformActionMethod, std::move(args)); } -void TextInputChannel::OnCommit(std::string str) { - FT_LOGI("OnCommit str[%s]", str.data()); - SetEditStatus(EditStatus::kCommit); - - ConsumeLastPreedit(); - - active_model_->AddText(str); - - SendStateUpdate(*active_model_); - SetEditStatus(EditStatus::kNone); -} - -void TextInputChannel::OnPreedit(std::string str, int cursor_pos) { - FT_LOGI("OnPreedit str[%s], cursor_pos[%d]", str.data(), cursor_pos); - SetEditStatus(EditStatus::kPreeditStart); - if (str.compare("") == 0) { - SetEditStatus(EditStatus::kPreeditEnd); - } - - if (edit_status_ == EditStatus::kPreeditStart || - (edit_status_ == EditStatus::kPreeditEnd && - // For tv, fix me - last_handled_ecore_event_keyname_.compare("Return") != 0)) { - last_handled_ecore_event_keyname_ = ""; - ConsumeLastPreedit(); - } - - have_preedit_ = false; - if (edit_status_ == EditStatus::kPreeditStart) { - preedit_start_pos_ = active_model_->selection().base(); - active_model_->AddText(str); - preedit_end_pos_ = active_model_->selection().base(); - have_preedit_ = true; - SendStateUpdate(*active_model_); - FT_LOGI("preedit start pos[%d], preedit end pos[%d]", preedit_start_pos_, - preedit_end_pos_); - } -} -void TextInputChannel::OnPreeditForPlatformView(std::string str, - int cursor_pos) { - FT_LOGI("OnPreeditForPlatformView str[%s], cursor_pos[%d]", str.data(), - cursor_pos); - - SetEditStatus(EditStatus::kPreeditStart); - if (str.compare("") == 0) { - SetEditStatus(EditStatus::kPreeditEnd); - } - - if (edit_status_ == EditStatus::kPreeditStart || - (edit_status_ == EditStatus::kPreeditEnd && - // For tv, fix me - last_handled_ecore_event_keyname_.compare("Return") != 0)) { - last_handled_ecore_event_keyname_ = ""; - } - - have_preedit_ = false; - if (edit_status_ == EditStatus::kPreeditStart) { - engine_->platform_view_channel->DispatchCompositionUpdateEvent(str); - } -} - -void TextInputChannel::ShowSoftwareKeyboard() { - if (imf_context_ && !is_software_keyboard_showing_) { - is_software_keyboard_showing_ = true; - Ecore_IMF_Input_Panel_Layout panel_layout; - if (TextInputTypeToEcoreIMFInputPanelLayout(input_type_, &panel_layout)) { - ecore_imf_context_input_panel_layout_set(imf_context_, panel_layout); - } - ecore_imf_context_input_panel_show(imf_context_); - ecore_imf_context_focus_in(imf_context_); - } -} - -void TextInputChannel::HideSoftwareKeyboard() { - if (imf_context_ && is_software_keyboard_showing_) { - is_software_keyboard_showing_ = false; - ResetCurrentContext(); - ecore_imf_context_focus_out(imf_context_); - } -} - -void TextInputChannel::SetEditStatus(EditStatus edit_status) { - edit_status_ = edit_status; -} - -void TextInputChannel::RegisterIMFCallback() { - // ecore_imf_context_input_panel_enabled_set(imf_context_, false); - ecore_imf_context_event_callback_add(imf_context_, ECORE_IMF_CALLBACK_COMMIT, - CommitCallback, this); - ecore_imf_context_event_callback_add( - imf_context_, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, PreeditCallback, this); - ecore_imf_context_event_callback_add(imf_context_, - ECORE_IMF_CALLBACK_DELETE_SURROUNDING, - DeleteSurroundingCallback, this); - ecore_imf_context_event_callback_add(imf_context_, - ECORE_IMF_CALLBACK_PRIVATE_COMMAND_SEND, - PrivateCommandCallback, this); - ecore_imf_context_input_panel_event_callback_add( - imf_context_, ECORE_IMF_INPUT_PANEL_STATE_EVENT, - InputPanelStateChangedCallback, this); - ecore_imf_context_input_panel_event_callback_add( - imf_context_, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT, - InputPanelGeometryChangedCallback, this); - ecore_imf_context_retrieve_surrounding_callback_set( - imf_context_, RetrieveSurroundingCallback, this); - - // These APIs have to be set when IMF's setting status is changed. - ecore_imf_context_autocapital_type_set(imf_context_, - ECORE_IMF_AUTOCAPITAL_TYPE_NONE); - ecore_imf_context_prediction_allow_set(imf_context_, EINA_FALSE); - ecore_imf_context_input_panel_layout_set(imf_context_, - ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL); - ecore_imf_context_input_panel_return_key_type_set( - imf_context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT); - ecore_imf_context_input_panel_layout_variation_set(imf_context_, 0); - ecore_imf_context_input_panel_language_set( - imf_context_, ECORE_IMF_INPUT_PANEL_LANG_AUTOMATIC); -} - -void TextInputChannel::UnregisterIMFCallback() { - ecore_imf_context_event_callback_del(imf_context_, ECORE_IMF_CALLBACK_COMMIT, - CommitCallback); - ecore_imf_context_event_callback_del( - imf_context_, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, PreeditCallback); - ecore_imf_context_event_callback_del(imf_context_, - ECORE_IMF_CALLBACK_DELETE_SURROUNDING, - DeleteSurroundingCallback); - ecore_imf_context_event_callback_del(imf_context_, - ECORE_IMF_CALLBACK_PRIVATE_COMMAND_SEND, - PrivateCommandCallback); - ecore_imf_context_input_panel_event_callback_del( - imf_context_, ECORE_IMF_INPUT_PANEL_STATE_EVENT, - InputPanelStateChangedCallback); - ecore_imf_context_input_panel_event_callback_del( - imf_context_, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT, - InputPanelGeometryChangedCallback); -} - void TextInputChannel::ConsumeLastPreedit() { - if (have_preedit_) { + if (text_editing_context_.has_preedit_) { std::string before = active_model_->GetText(); - int count = preedit_end_pos_ - preedit_start_pos_; + int count = text_editing_context_.preedit_end_pos_ - + text_editing_context_.preedit_start_pos_; active_model_->DeleteSurrounding(-count, count); std::string after = active_model_->GetText(); FT_LOGI("Consume last preedit count:[%d] text:[%s] -> [%s]", count, before.data(), after.data()); SendStateUpdate(*active_model_); } - have_preedit_ = false; - preedit_end_pos_ = 0; - preedit_start_pos_ = 0; + text_editing_context_.has_preedit_ = false; + text_editing_context_.preedit_end_pos_ = 0; + text_editing_context_.preedit_start_pos_ = 0; } -void TextInputChannel::ResetCurrentContext() { - SetEditStatus(EditStatus::kNone); - ecore_imf_context_reset(imf_context_); - preedit_start_pos_ = 0; - preedit_end_pos_ = 0; - have_preedit_ = false; +bool TextInputChannel::ShouldNotUseFilterEvent(std::string key, bool is_ime) { + // Force redirect to HandleUnfilteredEvent(especially on TV) + // If you don't do this, it will affects the input panel. + // For example, when the left key of the input panel is pressed, the focus + // of the input panel is shifted to left! + // What we want is to move only the cursor on the text editor. + if (is_ime && !text_editing_context_.is_in_select_mode_ && + (key == "Left" || key == "Right" || key == "Up" || key == "Down" || + key == "End" || key == "Home" || key == "BackSpace" || key == "Delete" || + key == "Select")) { + return true; + } + return false; } - } // namespace flutter diff --git a/shell/platform/tizen/channels/text_input_channel.h b/shell/platform/tizen/channels/text_input_channel.h index 2edcdaf22f3b3..9c8b6eb03bcd5 100644 --- a/shell/platform/tizen/channels/text_input_channel.h +++ b/shell/platform/tizen/channels/text_input_channel.h @@ -16,42 +16,33 @@ #include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h" #include "flutter/shell/platform/common/text_input_model.h" +#include "flutter/shell/platform/tizen/tizen_input_method_context.h" #include "rapidjson/document.h" namespace flutter { class FlutterTizenEngine; -class TextInputChannel { - public: - struct SoftwareKeyboardGeometry { - int32_t x = 0, y = 0, w = 0, h = 0; - }; +enum class EditStatus { kNone, kPreeditStart, kPreeditEnd, kCommit }; - enum EditStatus { kNone, kPreeditStart, kPreeditEnd, kCommit }; +struct TextEditingContext { + EditStatus edit_status_ = EditStatus::kNone; + bool has_preedit_ = false; + bool is_in_select_mode_ = false; + std::string last_handled_ecore_event_keyname_ = ""; + int preedit_end_pos_ = 0; + int preedit_start_pos_ = 0; +}; +class TextInputChannel { + public: explicit TextInputChannel(BinaryMessenger* messenger, FlutterTizenEngine* engine); virtual ~TextInputChannel(); - void OnKeyDown(Ecore_Event_Key* key); - void OnCommit(std::string str); - void OnPreedit(std::string str, int cursor_pos); - void OnPreeditForPlatformView(std::string str, int cursor_pos); - - void ShowSoftwareKeyboard(); - void HideSoftwareKeyboard(); bool IsSoftwareKeyboardShowing() { return is_software_keyboard_showing_; } - void SetSoftwareKeyboardShowing() { is_software_keyboard_showing_ = true; } - void SetEditStatus(EditStatus edit_status); - SoftwareKeyboardGeometry GetCurrentKeyboardGeometry() { - return current_keyboard_geometry_; - } - - Ecore_IMF_Context* GetImfContext() { return imf_context_; } - - int32_t rotation = 0; + void OnKeyDown(Ecore_Event_Key* key); private: void HandleMethodCall( @@ -59,53 +50,23 @@ class TextInputChannel { std::unique_ptr> result); void SendStateUpdate(const TextInputModel& model); bool FilterEvent(Ecore_Event_Key* event); - void NonIMFFallback(Ecore_Event_Key* event); + void HandleUnfilteredEvent(Ecore_Event_Key* event); void EnterPressed(TextInputModel* model, bool select); - void RegisterIMFCallback(); - void UnregisterIMFCallback(); void ConsumeLastPreedit(); - void ResetCurrentContext(); + void ResetTextEditingContext() { + text_editing_context_ = TextEditingContext(); + } + bool ShouldNotUseFilterEvent(std::string key, bool is_ime); std::unique_ptr> channel_; std::unique_ptr active_model_; + std::unique_ptr input_method_context_; - static void CommitCallback(void* data, - Ecore_IMF_Context* ctx, - void* event_info); - static void PreeditCallback(void* data, - Ecore_IMF_Context* ctx, - void* event_info); - static void PrivateCommandCallback(void* data, - Ecore_IMF_Context* ctx, - void* event_info); - static void DeleteSurroundingCallback(void* data, - Ecore_IMF_Context* ctx, - void* event_info); - static void InputPanelStateChangedCallback(void* data, - Ecore_IMF_Context* context, - int value); - static void InputPanelGeometryChangedCallback(void* data, - Ecore_IMF_Context* context, - int value); - static Eina_Bool RetrieveSurroundingCallback(void* data, - Ecore_IMF_Context* ctx, - char** text, - int* cursor_pos); - - int client_id_{0}; - SoftwareKeyboardGeometry current_keyboard_geometry_; - bool is_software_keyboard_showing_{false}; + int client_id_ = 0; + bool is_software_keyboard_showing_ = false; std::string input_action_; std::string input_type_; - - EditStatus edit_status_{EditStatus::kNone}; - bool have_preedit_{false}; - bool in_select_mode_{false}; - int preedit_end_pos_{0}; - int preedit_start_pos_{0}; - std::string last_handled_ecore_event_keyname_; - FlutterTizenEngine* engine_{nullptr}; - Ecore_IMF_Context* imf_context_{nullptr}; + TextEditingContext text_editing_context_; }; } // namespace flutter diff --git a/shell/platform/tizen/flutter_tizen_engine.cc b/shell/platform/tizen/flutter_tizen_engine.cc index 9242b50bbf290..378f2769bec7d 100644 --- a/shell/platform/tizen/flutter_tizen_engine.cc +++ b/shell/platform/tizen/flutter_tizen_engine.cc @@ -129,7 +129,7 @@ bool FlutterTizenEngine::RunEngine() { switches.begin(), switches.end(), std::back_inserter(argv), [](const std::string& arg) -> const char* { return arg.c_str(); }); - if (std::find(switches.begin(), switches.end(), "verbose-logging") != + if (std::find(switches.begin(), switches.end(), "--verbose-logging") != switches.end()) { SetMinLoggingLevel(DLOG_INFO); } @@ -232,7 +232,7 @@ bool FlutterTizenEngine::RunEngine() { text_input_channel = std::make_unique( internal_plugin_registrar_->messenger(), this); platform_view_channel = std::make_unique( - internal_plugin_registrar_->messenger(), this); + internal_plugin_registrar_->messenger()); key_event_handler_ = std::make_unique(this); touch_event_handler_ = std::make_unique(this); @@ -368,7 +368,6 @@ void FlutterTizenEngine::SetWindowOrientation(int32_t degree) { 0.0, 0.0, 1.0 // perspective }; touch_event_handler_->rotation = degree; - text_input_channel->rotation = degree; if (degree == 90 || degree == 270) { renderer->ResizeWithRotation(geometry.x, geometry.y, height, width, degree); SendWindowMetrics(height, width, 0.0); diff --git a/shell/platform/tizen/key_event_handler.cc b/shell/platform/tizen/key_event_handler.cc index 0a1e34a2b282c..22734a6ea67b2 100644 --- a/shell/platform/tizen/key_event_handler.cc +++ b/shell/platform/tizen/key_event_handler.cc @@ -44,14 +44,16 @@ Eina_Bool KeyEventHandler::OnKey(void* data, int type, void* event) { key->keyname, key->modifiers, is_down); if (engine->text_input_channel) { - if (is_down) { + if (is_down && engine->text_input_channel->IsSoftwareKeyboardShowing()) { engine->text_input_channel->OnKeyDown(key); - } - if (engine->text_input_channel->IsSoftwareKeyboardShowing()) { return ECORE_CALLBACK_PASS_ON; } } + if (engine->platform_view_channel) { + engine->platform_view_channel->SendKeyEvent(key, is_down); + } + if (engine->key_event_channel) { engine->key_event_channel->SendKeyEvent( key, is_down, diff --git a/shell/platform/tizen/tizen_input_method_context.cc b/shell/platform/tizen/tizen_input_method_context.cc new file mode 100644 index 0000000000000..6bb201c4db3a6 --- /dev/null +++ b/shell/platform/tizen/tizen_input_method_context.cc @@ -0,0 +1,312 @@ +// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/tizen/tizen_input_method_context.h" + +#include "flutter/shell/platform/tizen/flutter_tizen_engine.h" +#include "flutter/shell/platform/tizen/tizen_log.h" + +namespace { +const char* GetEcoreImfContextAvailableID() { + Eina_List* modules; + + modules = ecore_imf_context_available_ids_get(); + if (!modules) { + return nullptr; + } + + void* module; + EINA_LIST_FREE(modules, module) { return static_cast(module); } + + return nullptr; +} + +bool TextInputTypeToEcoreIMFInputPanelLayout( + std::string text_input_type, + Ecore_IMF_Input_Panel_Layout* panel_layout) { + FT_ASSERT(panel_layout); + if (text_input_type == "TextInputType.text" || + text_input_type == "TextInputType.multiline") { + *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; + } else if (text_input_type == "TextInputType.number") { + *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBERONLY; + } else if (text_input_type == "TextInputType.phone") { + *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER; + } else if (text_input_type == "TextInputType.datetime") { + *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_DATETIME; + } else if (text_input_type == "TextInputType.emailAddress") { + *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_EMAIL; + } else if (text_input_type == "TextInputType.url") { + *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_URL; + } else if (text_input_type == "TextInputType.visiblePassword") { + *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD; + } else if (text_input_type == "TextInputType.name" || + text_input_type == "TextInputType.address") { + FT_LOGW( + "Actual requested text input type is [%s], but select " + "TextInputType.text as fallback type", + text_input_type.c_str()); + *panel_layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL; + } else { + return false; + } + return true; +} + +Ecore_IMF_Keyboard_Modifiers EcoreInputModifierToEcoreIMFModifier( + unsigned int ecoreModifier) { + unsigned int modifier(ECORE_IMF_KEYBOARD_MODIFIER_NONE); + + if (ecoreModifier & ECORE_EVENT_MODIFIER_SHIFT) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_SHIFT; + } + + if (ecoreModifier & ECORE_EVENT_MODIFIER_ALT) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_ALT; + } + + if (ecoreModifier & ECORE_EVENT_MODIFIER_CTRL) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_CTRL; + } + + if (ecoreModifier & ECORE_EVENT_MODIFIER_WIN) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_WIN; + } + + if (ecoreModifier & ECORE_EVENT_MODIFIER_ALTGR) { + modifier |= ECORE_IMF_KEYBOARD_MODIFIER_ALTGR; + } + + return static_cast(modifier); +} + +Ecore_IMF_Keyboard_Locks EcoreInputModifierToEcoreIMFLock( + unsigned int modifier) { + // If no other matches, returns NONE. + unsigned int lock(ECORE_IMF_KEYBOARD_LOCK_NONE); + + if (modifier & ECORE_EVENT_LOCK_NUM) { + lock |= ECORE_IMF_KEYBOARD_LOCK_NUM; + } + + if (modifier & ECORE_EVENT_LOCK_CAPS) { + lock |= ECORE_IMF_KEYBOARD_LOCK_CAPS; + } + + if (modifier & ECORE_EVENT_LOCK_SCROLL) { + lock |= ECORE_IMF_KEYBOARD_LOCK_SCROLL; + } + + return static_cast(lock); +} + +void CommitCallback(void* data, Ecore_IMF_Context* ctx, void* event_info) { + FT_ASSERT(data); + + flutter::TizenInputMethodContext* self = + static_cast(data); + + char* str = static_cast(event_info); + + self->OnCommit(str); +} + +void PreeditCallback(void* data, Ecore_IMF_Context* ctx, void* event_info) { + FT_ASSERT(data); + flutter::TizenInputMethodContext* self = + static_cast(data); + + char* str = nullptr; + int cursor_pos = 0; + + ecore_imf_context_preedit_string_get(ctx, &str, &cursor_pos); + + if (str) { + self->OnPreedit(str, cursor_pos); + free(str); + } +} + +void InputPanelStateChangedCallback(void* data, + Ecore_IMF_Context* context, + int value) { + FT_ASSERT(data); + flutter::TizenInputMethodContext* self = + static_cast(data); + + self->OnInputPannelStateChanged(value); +} +} // namespace + +namespace flutter { + +TizenInputMethodContext::TizenInputMethodContext(FlutterTizenEngine* engine) + : engine_(engine) { + FT_ASSERT(engine_); + Init(); +} + +TizenInputMethodContext::~TizenInputMethodContext() { + Deinit(); +} + +void TizenInputMethodContext::Init() { + FT_ASSERT(engine_); + ecore_imf_init(); + + const char* imf_id = ecore_imf_context_default_id_get(); + if (imf_id == nullptr) { + // Try to get fallback id + imf_id = GetEcoreImfContextAvailableID(); + } + + if (imf_id == nullptr) { + FT_LOGE("Failed to get imf context id"); + return; + } + + imf_context_ = ecore_imf_context_add(imf_id); + if (imf_context_ == nullptr) { + FT_LOGE("Failed to create Ecore_IMF_Context"); + return; + } + + ecore_imf_context_client_window_set( + imf_context_, reinterpret_cast(engine_->renderer->GetWindowId())); + SetContextOptions(); + SetInputPannelOptions(); + RegisterEventCallbacks(); +} + +void TizenInputMethodContext::Deinit() { + UnregisterEventCallbacks(); + + if (imf_context_) { + ecore_imf_context_del(imf_context_); + } + + ecore_imf_shutdown(); +} + +bool TizenInputMethodContext::FilterEvent(Ecore_Event_Key* event, + const char* dev_name) { + FT_ASSERT(imf_context_); + FT_ASSERT(event); + FT_ASSERT(dev_name); + Ecore_IMF_Event_Key_Down imf_event; + + imf_event.keyname = event->keyname; + imf_event.key = event->key; + imf_event.string = event->string; + imf_event.compose = event->compose; + imf_event.timestamp = event->timestamp; + imf_event.modifiers = EcoreInputModifierToEcoreIMFModifier(event->modifiers); + imf_event.locks = EcoreInputModifierToEcoreIMFLock(event->modifiers); + imf_event.dev_name = dev_name; + imf_event.keycode = event->keycode; + + bool ret = ecore_imf_context_filter_event( + imf_context_, ECORE_IMF_EVENT_KEY_DOWN, + reinterpret_cast(&imf_event)); + + return ret; +} + +InputPannelGeometry TizenInputMethodContext::GetInputPannelGeometry() { + FT_ASSERT(imf_context_); + InputPannelGeometry geometry; + ecore_imf_context_input_panel_geometry_get( + imf_context_, &geometry.x, &geometry.y, &geometry.w, &geometry.h); + return geometry; +} + +void TizenInputMethodContext::ResetInputMethodContext() { + FT_ASSERT(imf_context_); + ecore_imf_context_reset(imf_context_); +} + +void TizenInputMethodContext::ShowInputPannel() { + FT_ASSERT(imf_context_); + ecore_imf_context_input_panel_show(imf_context_); + ecore_imf_context_focus_in(imf_context_); +} + +void TizenInputMethodContext::HideInputPannel() { + FT_ASSERT(imf_context_); + ecore_imf_context_focus_out(imf_context_); + ecore_imf_context_input_panel_hide(imf_context_); +} + +void TizenInputMethodContext::OnCommit(std::string str) { + if (!on_commit_callback_) { + FT_LOGW("Please, set OnCommitCallback"); + return; + } + on_commit_callback_(str); +} + +void TizenInputMethodContext::OnPreedit(std::string str, int cursor_pos) { + if (!on_preedit_callback_) { + FT_LOGW("Please, set OnInputPannelStateChangedCallback"); + return; + } + on_preedit_callback_(str, cursor_pos); +} + +void TizenInputMethodContext::OnInputPannelStateChanged(int state) { + if (!on_input_pannel_state_changed_callback_) { + FT_LOGW("Please, set OnPreeditCallback"); + return; + } + on_input_pannel_state_changed_callback_(state); +} + +void TizenInputMethodContext::SetInputPannelLayout(std::string input_type) { + FT_ASSERT(imf_context_); + Ecore_IMF_Input_Panel_Layout panel_layout; + if (TextInputTypeToEcoreIMFInputPanelLayout(input_type, &panel_layout)) { + ecore_imf_context_input_panel_layout_set(imf_context_, panel_layout); + } +} + +void TizenInputMethodContext::RegisterEventCallbacks() { + FT_ASSERT(imf_context_); + ecore_imf_context_event_callback_add(imf_context_, ECORE_IMF_CALLBACK_COMMIT, + CommitCallback, this); + ecore_imf_context_event_callback_add( + imf_context_, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, PreeditCallback, this); + ecore_imf_context_input_panel_event_callback_add( + imf_context_, ECORE_IMF_INPUT_PANEL_STATE_EVENT, + InputPanelStateChangedCallback, this); +} + +void TizenInputMethodContext::UnregisterEventCallbacks() { + FT_ASSERT(imf_context_); + ecore_imf_context_event_callback_del(imf_context_, ECORE_IMF_CALLBACK_COMMIT, + CommitCallback); + ecore_imf_context_event_callback_del( + imf_context_, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, PreeditCallback); + ecore_imf_context_input_panel_event_callback_del( + imf_context_, ECORE_IMF_INPUT_PANEL_STATE_EVENT, + InputPanelStateChangedCallback); +} + +void TizenInputMethodContext::SetContextOptions() { + FT_ASSERT(imf_context_); + ecore_imf_context_autocapital_type_set(imf_context_, + ECORE_IMF_AUTOCAPITAL_TYPE_NONE); + ecore_imf_context_prediction_allow_set(imf_context_, EINA_FALSE); +} + +void TizenInputMethodContext::SetInputPannelOptions() { + FT_ASSERT(imf_context_); + ecore_imf_context_input_panel_layout_set(imf_context_, + ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL); + ecore_imf_context_input_panel_return_key_type_set( + imf_context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT); + ecore_imf_context_input_panel_layout_variation_set(imf_context_, 0); + ecore_imf_context_input_panel_language_set( + imf_context_, ECORE_IMF_INPUT_PANEL_LANG_AUTOMATIC); +} +} // namespace flutter diff --git a/shell/platform/tizen/tizen_input_method_context.h b/shell/platform/tizen/tizen_input_method_context.h new file mode 100644 index 0000000000000..3f2eed7dd0876 --- /dev/null +++ b/shell/platform/tizen/tizen_input_method_context.h @@ -0,0 +1,81 @@ +// Copyright 2021 Samsung Electronics Co., Ltd. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EMBEDDER_TIZEN_INPUT_METHOD_CONTEXT_H_ +#define EMBEDDER_TIZEN_INPUT_METHOD_CONTEXT_H_ + +#define EFL_BETA_API_SUPPORT +#include +#include +#include + +namespace flutter { + +using OnCommitCallback = std::function; +using OnPreeditCallback = std::function; +using OnInputPannelStateChangedCallback = std::function; + +class FlutterTizenEngine; + +struct InputPannelGeometry { + int32_t x = 0, y = 0, w = 0, h = 0; +}; + +class TizenInputMethodContext { + public: + TizenInputMethodContext(FlutterTizenEngine* engine); + ~TizenInputMethodContext(); + + bool FilterEvent(Ecore_Event_Key* event, const char* dev_name); + + InputPannelGeometry GetInputPannelGeometry(); + + void ResetInputMethodContext(); + + void ShowInputPannel(); + + void HideInputPannel(); + + void OnCommit(std::string str); + + void OnPreedit(std::string str, int cursor_pos); + + void OnInputPannelStateChanged(int state); + + void SetInputPannelLayout(std::string layout); + + void SetOnCommitCallback(OnCommitCallback callback) { + on_commit_callback_ = callback; + } + + void SetOnPreeditCallback(OnPreeditCallback callback) { + on_preedit_callback_ = callback; + } + + void SetOnInputPannelStateChangedCallback( + OnInputPannelStateChangedCallback callback) { + on_input_pannel_state_changed_callback_ = callback; + } + + private: + void Init(); + + void Deinit(); + + void RegisterEventCallbacks(); + + void UnregisterEventCallbacks(); + + void SetContextOptions(); + + void SetInputPannelOptions(); + + FlutterTizenEngine* engine_{nullptr}; + Ecore_IMF_Context* imf_context_{nullptr}; + OnCommitCallback on_commit_callback_; + OnPreeditCallback on_preedit_callback_; + OnInputPannelStateChangedCallback on_input_pannel_state_changed_callback_; +}; +} // namespace flutter +#endif From 3f5aace6055bd2edf9f7eb00ad78c442d3a42bee Mon Sep 17 00:00:00 2001 From: Boram Bae Date: Wed, 14 Jul 2021 14:20:05 +0900 Subject: [PATCH 2/6] Remove unnecessary PlatformView APIs Signed-off-by: Boram Bae --- .../tizen/channels/platform_view_channel.cc | 17 ----------------- .../tizen/channels/platform_view_channel.h | 3 --- .../tizen/public/flutter_platform_view.h | 4 ---- 3 files changed, 24 deletions(-) diff --git a/shell/platform/tizen/channels/platform_view_channel.cc b/shell/platform/tizen/channels/platform_view_channel.cc index 70cbd9d618227..462c372e23fc2 100644 --- a/shell/platform/tizen/channels/platform_view_channel.cc +++ b/shell/platform/tizen/channels/platform_view_channel.cc @@ -92,23 +92,6 @@ void PlatformViewChannel::SendKeyEvent(Ecore_Event_Key* key, bool is_down) { } } -void PlatformViewChannel::DispatchCompositionUpdateEvent( - const std::string& key) { - auto instances = ViewInstances(); - auto it = instances.find(CurrentFocusedViewId()); - if (it != instances.end()) { - it->second->DispatchCompositionUpdateEvent(key.c_str(), key.size()); - } -} - -void PlatformViewChannel::DispatchCompositionEndEvent(const std::string& key) { - auto instances = ViewInstances(); - auto it = instances.find(CurrentFocusedViewId()); - if (it != instances.end()) { - it->second->DispatchCompositionEndEvent(key.c_str(), key.size()); - } -} - int PlatformViewChannel::CurrentFocusedViewId() { for (auto it = view_instances_.begin(); it != view_instances_.end(); it++) { if (it->second->IsFocused()) { diff --git a/shell/platform/tizen/channels/platform_view_channel.h b/shell/platform/tizen/channels/platform_view_channel.h index 0e5e8e1971406..5b597a488a4fa 100644 --- a/shell/platform/tizen/channels/platform_view_channel.h +++ b/shell/platform/tizen/channels/platform_view_channel.h @@ -40,9 +40,6 @@ class PlatformViewChannel { void SendKeyEvent(Ecore_Event_Key* key, bool is_down); int CurrentFocusedViewId(); - void DispatchCompositionUpdateEvent(const std::string& key); - void DispatchCompositionEndEvent(const std::string& key); - private: void HandleMethodCall(const MethodCall& call, std::unique_ptr> result); diff --git a/shell/platform/tizen/public/flutter_platform_view.h b/shell/platform/tizen/public/flutter_platform_view.h index b4540cb59e938..36efa93b8e9e7 100644 --- a/shell/platform/tizen/public/flutter_platform_view.h +++ b/shell/platform/tizen/public/flutter_platform_view.h @@ -45,10 +45,6 @@ class PlatformView { // Key input event virtual void DispatchKeyDownEvent(Ecore_Event_Key* key) = 0; virtual void DispatchKeyUpEvent(Ecore_Event_Key* key) = 0; - virtual void DispatchCompositionUpdateEvent(const char* str, int size) = 0; - virtual void DispatchCompositionEndEvent(const char* str, int size) = 0; - - virtual void SetSoftwareKeyboardContext(Ecore_IMF_Context* context) = 0; protected: void* platform_window_; From a5fb117d6c892616b93aba294b0ae559d3907b5c Mon Sep 17 00:00:00 2001 From: Boram Bae Date: Wed, 14 Jul 2021 14:35:42 +0900 Subject: [PATCH 3/6] Tidy up based on review Signed-off-by: Boram Bae --- shell/platform/tizen/channels/platform_view_channel.h | 2 -- shell/platform/tizen/tizen_input_method_context.cc | 1 + shell/platform/tizen/tizen_input_method_context.h | 5 ++++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/shell/platform/tizen/channels/platform_view_channel.h b/shell/platform/tizen/channels/platform_view_channel.h index 5b597a488a4fa..01c07466d2f33 100644 --- a/shell/platform/tizen/channels/platform_view_channel.h +++ b/shell/platform/tizen/channels/platform_view_channel.h @@ -20,8 +20,6 @@ class PlatformViewFactory; namespace flutter { -class FlutterTizenEngine; - class PlatformViewChannel { public: explicit PlatformViewChannel(BinaryMessenger* messenger); diff --git a/shell/platform/tizen/tizen_input_method_context.cc b/shell/platform/tizen/tizen_input_method_context.cc index 6bb201c4db3a6..e476ca564ed47 100644 --- a/shell/platform/tizen/tizen_input_method_context.cc +++ b/shell/platform/tizen/tizen_input_method_context.cc @@ -8,6 +8,7 @@ #include "flutter/shell/platform/tizen/tizen_log.h" namespace { + const char* GetEcoreImfContextAvailableID() { Eina_List* modules; diff --git a/shell/platform/tizen/tizen_input_method_context.h b/shell/platform/tizen/tizen_input_method_context.h index 3f2eed7dd0876..0fe29c70f9b4b 100644 --- a/shell/platform/tizen/tizen_input_method_context.h +++ b/shell/platform/tizen/tizen_input_method_context.h @@ -8,6 +8,7 @@ #define EFL_BETA_API_SUPPORT #include #include + #include namespace flutter { @@ -77,5 +78,7 @@ class TizenInputMethodContext { OnPreeditCallback on_preedit_callback_; OnInputPannelStateChangedCallback on_input_pannel_state_changed_callback_; }; + } // namespace flutter -#endif + +#endif // EMBEDDER_TIZEN_INPUT_METHOD_CONTEXT_H_ From 07d2c1a484ade11159ada4598b60ce29cadfa6cd Mon Sep 17 00:00:00 2001 From: Boram Bae Date: Wed, 14 Jul 2021 14:46:43 +0900 Subject: [PATCH 4/6] Always Send key event to TextInputChannel * Use SendKeyEvent as the method name for handling key events like others. * SendKeyEvent returns true if the key event has been handled. Signed-off-by: Boram Bae --- shell/platform/tizen/channels/text_input_channel.cc | 9 +++++---- shell/platform/tizen/channels/text_input_channel.h | 2 +- shell/platform/tizen/key_event_handler.cc | 3 +-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/shell/platform/tizen/channels/text_input_channel.cc b/shell/platform/tizen/channels/text_input_channel.cc index a6d5711ba4030..bb7515523af71 100644 --- a/shell/platform/tizen/channels/text_input_channel.cc +++ b/shell/platform/tizen/channels/text_input_channel.cc @@ -117,15 +117,16 @@ TextInputChannel::TextInputChannel(BinaryMessenger* messenger, TextInputChannel::~TextInputChannel() {} -void TextInputChannel::OnKeyDown(Ecore_Event_Key* key) { - if (active_model_ == nullptr) { - FT_LOGW("There is no active TextInputModel"); - return; +bool TextInputChannel::SendKeyEvent(Ecore_Event_Key* key, bool is_down) { + if (!active_model_ || !is_down) { + return false; } if (!FilterEvent(key) && !text_editing_context_.has_preedit_) { HandleUnfilteredEvent(key); } + + return true; } void TextInputChannel::HandleMethodCall( diff --git a/shell/platform/tizen/channels/text_input_channel.h b/shell/platform/tizen/channels/text_input_channel.h index 9c8b6eb03bcd5..491363f6a771a 100644 --- a/shell/platform/tizen/channels/text_input_channel.h +++ b/shell/platform/tizen/channels/text_input_channel.h @@ -42,7 +42,7 @@ class TextInputChannel { bool IsSoftwareKeyboardShowing() { return is_software_keyboard_showing_; } - void OnKeyDown(Ecore_Event_Key* key); + bool SendKeyEvent(Ecore_Event_Key* key, bool is_down); private: void HandleMethodCall( diff --git a/shell/platform/tizen/key_event_handler.cc b/shell/platform/tizen/key_event_handler.cc index 22734a6ea67b2..1904485e2c1d7 100644 --- a/shell/platform/tizen/key_event_handler.cc +++ b/shell/platform/tizen/key_event_handler.cc @@ -44,8 +44,7 @@ Eina_Bool KeyEventHandler::OnKey(void* data, int type, void* event) { key->keyname, key->modifiers, is_down); if (engine->text_input_channel) { - if (is_down && engine->text_input_channel->IsSoftwareKeyboardShowing()) { - engine->text_input_channel->OnKeyDown(key); + if (engine->text_input_channel->SendKeyEvent(key, is_down)) { return ECORE_CALLBACK_PASS_ON; } } From d4328a869fa5595090802f4ad21bc1eeb9b04732 Mon Sep 17 00:00:00 2001 From: Boram Bae Date: Thu, 15 Jul 2021 12:58:43 +0900 Subject: [PATCH 5/6] Use #if defined() instead of #ifdef Signed-off-by: Boram Bae --- shell/platform/tizen/channels/text_input_channel.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/tizen/channels/text_input_channel.cc b/shell/platform/tizen/channels/text_input_channel.cc index bb7515523af71..9f0eebd5d788e 100644 --- a/shell/platform/tizen/channels/text_input_channel.cc +++ b/shell/platform/tizen/channels/text_input_channel.cc @@ -260,9 +260,9 @@ void TextInputChannel::SendStateUpdate(const TextInputModel& model) { bool TextInputChannel::FilterEvent(Ecore_Event_Key* event) { bool handled = false; -#ifdef __X64_SHELL__ +#if defined(__X64_SHELL__) bool is_ime = false; -#elif WEARABLE_PROFILE +#elif defined(WEARABLE_PROFILE) // Hardware keyboard not supported on watches. bool is_ime = true; // FIXME: Only for wearable. From 7350d159f15054781f35ffd4c05d64ec2afb62ce Mon Sep 17 00:00:00 2001 From: Boram Bae Date: Thu, 15 Jul 2021 13:01:32 +0900 Subject: [PATCH 6/6] Rename to ShouldNotFilterEvent Signed-off-by: Boram Bae --- shell/platform/tizen/channels/text_input_channel.cc | 4 ++-- shell/platform/tizen/channels/text_input_channel.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/tizen/channels/text_input_channel.cc b/shell/platform/tizen/channels/text_input_channel.cc index 9f0eebd5d788e..a885ab66197c2 100644 --- a/shell/platform/tizen/channels/text_input_channel.cc +++ b/shell/platform/tizen/channels/text_input_channel.cc @@ -274,7 +274,7 @@ bool TextInputChannel::FilterEvent(Ecore_Event_Key* event) { bool is_ime = strcmp(ecore_device_name_get(event->dev), "ime") == 0; #endif - if (ShouldNotUseFilterEvent(event->key, is_ime)) { + if (ShouldNotFilterEvent(event->key, is_ime)) { ResetTextEditingContext(); input_method_context_->ResetInputMethodContext(); FT_LOGW("Force redirect IME key-event[%s] to fallback", event->keyname); @@ -381,7 +381,7 @@ void TextInputChannel::ConsumeLastPreedit() { text_editing_context_.preedit_start_pos_ = 0; } -bool TextInputChannel::ShouldNotUseFilterEvent(std::string key, bool is_ime) { +bool TextInputChannel::ShouldNotFilterEvent(std::string key, bool is_ime) { // Force redirect to HandleUnfilteredEvent(especially on TV) // If you don't do this, it will affects the input panel. // For example, when the left key of the input panel is pressed, the focus diff --git a/shell/platform/tizen/channels/text_input_channel.h b/shell/platform/tizen/channels/text_input_channel.h index 491363f6a771a..3fccb4395e294 100644 --- a/shell/platform/tizen/channels/text_input_channel.h +++ b/shell/platform/tizen/channels/text_input_channel.h @@ -56,7 +56,7 @@ class TextInputChannel { void ResetTextEditingContext() { text_editing_context_ = TextEditingContext(); } - bool ShouldNotUseFilterEvent(std::string key, bool is_ime); + bool ShouldNotFilterEvent(std::string key, bool is_ime); std::unique_ptr> channel_; std::unique_ptr active_model_;