blob: e211b5bb3de343aeedaec0b88b40d33f931265b8 [file] [log] [blame]
// 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