| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/embedder/text_delegate.h" |
| |
| #include <fuchsia/ui/input/cpp/fidl.h> |
| #include <fuchsia/ui/input3/cpp/fidl.h> |
| #include <fuchsia/ui/views/cpp/fidl.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/syslog/global.h> |
| #include <zircon/status.h> |
| |
| #include "logging.h" |
| #include "src/embedder/engine/embedder.h" |
| #include "src/embedder/fuchsia_logger.h" |
| #include "src/embedder/keyboard.h" |
| #include "src/embedder/logging.h" |
| |
| namespace embedder { |
| |
| // static constexpr char kInputActionKey[] = "inputAction"; |
| |
| // See: https://api.flutter.dev/flutter/services/TextInputAction.html |
| // Only the actions relevant for Fuchsia are listed here. |
| static constexpr char kTextInputActionDone[] = "TextInputAction.done"; |
| static constexpr char kTextInputActionNewline[] = "TextInputAction.newline"; |
| static constexpr char kTextInputActionGo[] = "TextInputAction.go"; |
| static constexpr char kTextInputActionNext[] = "TextInputAction.next"; |
| static constexpr char kTextInputActionPrevious[] = "TextInputAction.previous"; |
| static constexpr char kTextInputActionNone[] = "TextInputAction.none"; |
| static constexpr char kTextInputActionSearch[] = "TextInputAction.search"; |
| static constexpr char kTextInputActionSend[] = "TextInputAction.send"; |
| static constexpr char kTextInputActionUnspecified[] = "TextInputAction.unspecified"; |
| |
| [[maybe_unused]] |
| // Converts Flutter TextInputAction to Fuchsia action enum. |
| static fuchsia::ui::input::InputMethodAction |
| IntoInputMethodAction(const std::string action_string) { |
| if (action_string == kTextInputActionNewline) { |
| return fuchsia::ui::input::InputMethodAction::NEWLINE; |
| } else if (action_string == kTextInputActionDone) { |
| return fuchsia::ui::input::InputMethodAction::DONE; |
| } else if (action_string == kTextInputActionGo) { |
| return fuchsia::ui::input::InputMethodAction::GO; |
| } else if (action_string == kTextInputActionNext) { |
| return fuchsia::ui::input::InputMethodAction::NEXT; |
| } else if (action_string == kTextInputActionPrevious) { |
| return fuchsia::ui::input::InputMethodAction::PREVIOUS; |
| } else if (action_string == kTextInputActionNone) { |
| return fuchsia::ui::input::InputMethodAction::NONE; |
| } else if (action_string == kTextInputActionSearch) { |
| return fuchsia::ui::input::InputMethodAction::SEARCH; |
| } else if (action_string == kTextInputActionSend) { |
| return fuchsia::ui::input::InputMethodAction::SEND; |
| } else if (action_string == kTextInputActionUnspecified) { |
| return fuchsia::ui::input::InputMethodAction::UNSPECIFIED; |
| } |
| // If this message comes along it means we should really add the missing 'if' |
| // above. |
| FX_LOGF(INFO, kLogTag, "unexpected action_string: %s", action_string.c_str()); |
| // Substituting DONE for an unexpected action string will probably be OK. |
| return fuchsia::ui::input::InputMethodAction::DONE; |
| } |
| |
| [[maybe_unused]] |
| // Converts the Fuchsia action enum into Flutter TextInputAction. |
| static const std::string |
| IntoTextInputAction(fuchsia::ui::input::InputMethodAction action) { |
| if (action == fuchsia::ui::input::InputMethodAction::NEWLINE) { |
| return kTextInputActionNewline; |
| } else if (action == fuchsia::ui::input::InputMethodAction::DONE) { |
| return kTextInputActionDone; |
| } else if (action == fuchsia::ui::input::InputMethodAction::GO) { |
| return kTextInputActionGo; |
| } else if (action == fuchsia::ui::input::InputMethodAction::NEXT) { |
| return kTextInputActionNext; |
| } else if (action == fuchsia::ui::input::InputMethodAction::PREVIOUS) { |
| return kTextInputActionPrevious; |
| } else if (action == fuchsia::ui::input::InputMethodAction::NONE) { |
| return kTextInputActionNone; |
| } else if (action == fuchsia::ui::input::InputMethodAction::SEARCH) { |
| return kTextInputActionSearch; |
| } else if (action == fuchsia::ui::input::InputMethodAction::SEND) { |
| return kTextInputActionSend; |
| } else if (action == fuchsia::ui::input::InputMethodAction::UNSPECIFIED) { |
| return kTextInputActionUnspecified; |
| } |
| // If this message comes along it means we should really add the missing 'if' |
| // above. |
| FX_LOGF(INFO, kLogTag, "unexpected action: %d", static_cast<uint32_t>(action)); |
| |
| // Substituting "done" for an unexpected text input action will probably |
| // be OK. |
| return kTextInputActionDone; |
| } |
| |
| // TODO(fxbug.dev/8868): Terminate embedder if Fuchsia system FIDL connections |
| // have error. |
| template <class T> |
| void SetInterfaceErrorHandler(fidl::InterfacePtr<T>& interface, std::string name) { |
| interface.set_error_handler([name](zx_status_t status) { |
| FX_LOGF(ERROR, kLogTag, "Interface error on: %s, status: %s", name.c_str(), |
| zx_status_get_string(status)); |
| }); |
| } |
| |
| template <class T> |
| void SetInterfaceErrorHandler(fidl::Binding<T>& binding, std::string name) { |
| binding.set_error_handler([name](zx_status_t status) { |
| FX_LOGF(ERROR, kLogTag, "Binding error on: %s, status: %s", name.c_str(), |
| zx_status_get_string(status)); |
| }); |
| } |
| |
| TextDelegate::TextDelegate(fuchsia::ui::views::ViewRef view_ref, |
| fuchsia::ui::input::ImeServiceHandle ime_service, |
| fuchsia::ui::input3::KeyboardHandle keyboard, |
| std::function<void(const FlutterKeyEvent* event)> dispatch_callback) |
| : dispatch_callback_(dispatch_callback), |
| ime_client_(this), |
| text_sync_service_(ime_service.Bind()), |
| keyboard_listener_binding_(this), |
| keyboard_(keyboard.Bind()) { |
| // Register all error handlers. |
| SetInterfaceErrorHandler(ime_, "Input Method Editor"); |
| SetInterfaceErrorHandler(ime_client_, "IME Client"); |
| SetInterfaceErrorHandler(text_sync_service_, "Text Sync Service"); |
| SetInterfaceErrorHandler(keyboard_listener_binding_, "Keyboard Listener"); |
| SetInterfaceErrorHandler(keyboard_, "Keyboard"); |
| |
| // Configure keyboard listener. |
| keyboard_->AddListener(std::move(view_ref), keyboard_listener_binding_.NewBinding(), [] {}); |
| } |
| |
| void TextDelegate::ActivateIme() { |
| ActivateIme(requested_text_action_.value_or(fuchsia::ui::input::InputMethodAction::DONE)); |
| } |
| |
| void TextDelegate::ActivateIme(fuchsia::ui::input::InputMethodAction action) { |
| FX_DCHECK(last_text_state_.has_value()); |
| |
| requested_text_action_ = action; |
| text_sync_service_->GetInputMethodEditor(fuchsia::ui::input::KeyboardType::TEXT, // keyboard type |
| action, // input method action |
| last_text_state_.value(), // initial state |
| ime_client_.NewBinding(), // client |
| ime_.NewRequest() // editor |
| ); |
| } |
| |
| void TextDelegate::DeactivateIme() { |
| if (ime_) { |
| text_sync_service_->HideKeyboard(); |
| ime_ = nullptr; |
| } |
| if (ime_client_.is_bound()) { |
| ime_client_.Unbind(); |
| } |
| } |
| |
| // |fuchsia::ui::input::InputMethodEditorClient| |
| void TextDelegate::DidUpdateState(fuchsia::ui::input::TextInputState state, |
| std::unique_ptr<fuchsia::ui::input::InputEvent> input_event) { |
| FX_LOG(ERROR, kLogTag, "TextDelegate::DidUpdateState not implemented."); |
| } |
| |
| // |fuchsia::ui::input::InputMethodEditorClient| |
| void TextDelegate::OnAction(fuchsia::ui::input::InputMethodAction action) { |
| FX_LOG(ERROR, kLogTag, "TextDelegate::OnAction not implemented."); |
| } |
| |
| // |fuchsia::ui:input3::KeyboardListener| |
| void TextDelegate::OnKeyEvent( |
| fuchsia::ui::input3::KeyEvent key_event, |
| fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback on_key_event_callback) { |
| const char* type = nullptr; |
| FlutterKeyEventType flutter_key_type; |
| switch (key_event.type()) { |
| case fuchsia::ui::input3::KeyEventType::PRESSED: |
| type = "keydown"; |
| flutter_key_type = FlutterKeyEventType::kFlutterKeyEventTypeDown; |
| break; |
| case fuchsia::ui::input3::KeyEventType::RELEASED: |
| type = "keyup"; |
| flutter_key_type = FlutterKeyEventType::kFlutterKeyEventTypeUp; |
| break; |
| case fuchsia::ui::input3::KeyEventType::SYNC: |
| // SYNC means the key was pressed while focus was not on this application. |
| // This should possibly behave like PRESSED in the future, though it |
| // doesn't hurt to ignore it today. |
| case fuchsia::ui::input3::KeyEventType::CANCEL: |
| // CANCEL means the key was released while focus was not on this |
| // application. |
| // This should possibly behave like RELEASED in the future to ensure that |
| // a key is not repeated forever if it is pressed while focus is lost. |
| default: |
| break; |
| } |
| |
| if (type == nullptr) { |
| FX_LOGF(INFO, kLogTag, "Unknown key event phase."); |
| |
| // Notify the key event wasn't handled by this keyboard listener. |
| on_key_event_callback(fuchsia::ui::input3::KeyEventStatus::NOT_HANDLED); |
| return; |
| } |
| |
| keyboard_translator_.ConsumeEvent(std::move(key_event)); |
| |
| // Encourage the Engine to vsync for a 60Hz display. |
| const uint64_t now_nanos = FlutterEngineGetCurrentTime(); |
| const uint64_t now_micro = now_nanos * 1e3; |
| |
| FlutterKeyEvent flutter_key_event = { |
| .struct_size = sizeof(FlutterKeyEvent), |
| .timestamp = static_cast<double>(now_micro), |
| .type = flutter_key_type, |
| .physical = static_cast<uint64_t>(keyboard_translator_.LastHIDUsage()), |
| .logical = static_cast<uint64_t>(keyboard_translator_.LastCodePoint()), |
| .character = strcmp(type, "keydown") == 0 |
| ? keyboard_translator_.LastCodePointAsChar() |
| : nullptr, // Only assign the character on keydown strokes. |
| .synthesized = |
| true}; // For now we want the hardware keyboard to handle this event, and therefore the |
| // synthesized field is set to true and needs to take this codepath: |
| // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/hardware_keyboard.dart#L873 |
| |
| // Send key event to engine. |
| dispatch_callback_(&flutter_key_event); |
| |
| // Notify the key event was handled by this keyboard listener. |
| on_key_event_callback(fuchsia::ui::input3::KeyEventStatus::HANDLED); |
| } |
| } // namespace embedder |