[flutter/keyevent, flutter/textinput] Add additional wiring to handle
flutter/keyevent and flutter/textinput channels.
- This should be at the same state for keyboard input as flutter runner
impl in the engine repo.
- May need more investigating in the future, currently cursor doesn't
move to end of text in TextField, but text is inputted and displays.
Change-Id: I90f324286acc7dee82f8dd67cbb16fe69ec06649
Reviewed-on: https://fuchsia-review.googlesource.com/c/flutter-embedder/+/761164
Reviewed-by: Ben Bergkamp <benbergkamp@google.com>
Reviewed-by: Alexander Biggs <akbiggs@google.com>
diff --git a/src/embedder/BUILD.bazel b/src/embedder/BUILD.bazel
index 7dc2973..5217c52 100644
--- a/src/embedder/BUILD.bazel
+++ b/src/embedder/BUILD.bazel
@@ -28,6 +28,7 @@
"main.cc",
"mouse_delegate.cc",
"mouse_delegate.h",
+ "platform_message_channels.h",
"pointer_utility.h",
"software_surface.cc",
"software_surface.h",
diff --git a/src/embedder/embedder_state.h b/src/embedder/embedder_state.h
index d08ee83..76cfa47 100644
--- a/src/embedder/embedder_state.h
+++ b/src/embedder/embedder_state.h
@@ -16,6 +16,7 @@
#include "src/embedder/engine/embedder.h"
#include "src/embedder/flatland_connection.h"
#include "src/embedder/software_surface.h"
+#include "src/embedder/text_delegate.h"
namespace embedder {
@@ -81,6 +82,10 @@
/// The accessibility bridge responsible for intermediating
/// accessibility-related calls between Fuchsia and Flutter
std::unique_ptr<AccessibilityBridge> accessibility_bridge_;
+
+ /// The text delegate is responsible for handling keyboard input and text editing,
+ /// mediating between Fuchsia's input and Flutter's platform messages.
+ std::unique_ptr<TextDelegate> text_delegate_;
};
} // namespace embedder
diff --git a/src/embedder/keyboard.cc b/src/embedder/keyboard.cc
index e2edd61..0a1e3e8 100644
--- a/src/embedder/keyboard.cc
+++ b/src/embedder/keyboard.cc
@@ -30,6 +30,14 @@
namespace {
+/// The plane value for the private keys defined by the Fuchsia embedding.
+/// This is used by platform-specific code to generate Flutter key codes.
+///
+/// Make sure to match with the fuchsiaPlane defined in keyboard_key.g.dart
+/// which is a generated file.
+/// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_key.g.dart#L304
+static const uint64_t fuchsiaPlane = 0x01200000000;
+
// A simple keymap from a QWERTY keyboard to code points. A value 0 means no
// code point has been assigned for the respective keypress. Column 0 is the
// code point without a level modifier active, and Column 1 is the code point
@@ -345,6 +353,8 @@
uint32_t Keyboard::LastHIDUsage() { return GetLastKey() & 0xFFFFFFFF; }
+uint64_t Keyboard::LastLogicalKey() { return GetLastKey() | fuchsiaPlane; }
+
uint16_t Keyboard::LastHIDUsageID() { return GetLastKey() & 0xFFFF; }
uint16_t Keyboard::LastHIDUsagePage() { return (GetLastKey() >> 16) & 0xFFFF; }
diff --git a/src/embedder/keyboard.h b/src/embedder/keyboard.h
index 9c60a25..578f928 100644
--- a/src/embedder/keyboard.h
+++ b/src/embedder/keyboard.h
@@ -28,6 +28,7 @@
/// the state of the modifier keys.
uint32_t LastCodePoint();
+ /// Returns the value of the last key as a char.
char* LastCodePointAsChar();
/// Gets the last encountered HID usage. This is a 32-bit number, with the
@@ -36,8 +37,21 @@
///
/// The key corresponding to A will have the usage 0x7004. This function will
/// return 0x7004 in that case.
+ ///
+ /// These values also act as Flutter IDs that map to physical key values according
+ /// to
+ /// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_maps.g.dart#L778
uint32_t LastHIDUsage();
+ /// Gets the last encountered HID usage and assigns the Flutter ID for its logical value.
+ ///
+ /// This is done by applying the fuchsiaPlane value, which has higher order bits according to
+ /// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_key.g.dart#L304
+ ///
+ /// and whose values maps to the logical keys assigned according
+ /// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/keyboard_maps.g.dart#L541
+ uint64_t LastLogicalKey();
+
/// Gets the last encountered HID usage page.
///
/// The key corresponding to A will have the usage 0x7004. This function will
diff --git a/src/embedder/main.cc b/src/embedder/main.cc
index 548bafb..0f8785d 100644
--- a/src/embedder/main.cc
+++ b/src/embedder/main.cc
@@ -26,6 +26,7 @@
#include <string>
+#include "platform_message_channels.h"
#include "src/embedder/accessibility_bridge.h"
#include "src/embedder/embedder_state.h"
#include "src/embedder/engine/embedder.h"
@@ -34,6 +35,7 @@
#include "src/embedder/fuchsia_logger.h"
#include "src/embedder/logging.h"
#include "src/embedder/mouse_delegate.h"
+#include "src/embedder/platform_message_channels.h"
#include "src/embedder/root_inspect_node.h"
#include "src/embedder/software_surface.h"
#include "src/embedder/text_delegate.h"
@@ -42,11 +44,6 @@
namespace embedder {
namespace {
-/// Platform messages sent from the flutter engine include a
-/// channel to indicate what type of message they are. This is
-/// the name for the accessibility channel
-constexpr char kAccessibilityChannel[] = "flutter/accessibility";
-
// TODO(akbiggs): Don't hardcode this screen size, get it from
// an event instead.
constexpr int kScreenWidth = 1280;
@@ -94,6 +91,11 @@
if (!strcmp(message->channel, kAccessibilityChannel)) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
embedder->accessibility_bridge_->HandlePlatformMessage(message);
+ } else if (!strcmp(message->channel, kTextInputChannel)) {
+ EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
+ embedder->text_delegate_->HandleFlutterTextInputChannelPlatformMessage(message);
+ } else {
+ FX_LOGF(INFO, embedder::kLogTag, "FuchsiaHandlePlatformMessage invoked: %s", message->channel);
}
}
@@ -120,9 +122,12 @@
/// Callback for the embedder's FlutterEngineSendKeyEvent function.
void FuchsiaFlutterKeyEventCallback(bool handled, void* user_data) {
- // TODO(naudzghebre): Currently this callback isn't being fired. Do something meaningful
- // here.
- FX_LOG(ERROR, embedder::kLogTag, "FlutterKeyEventCallback invoked!");
+ FX_LOG(INFO, embedder::kLogTag, "FlutterKeyEventCallback invoked!");
+}
+
+void FuchsiaFlutterDataCallback(const uint8_t* data, size_t size, void* user_data) {
+ auto message_data = static_cast<const unsigned char*>(data);
+ FX_LOGF(INFO, embedder::kLogTag, "FuchsiaFlutterDataCallback invoked, data: %s", message_data);
}
/// Fuchsia implementation of the Flutter Engine's software rendering
@@ -131,8 +136,8 @@
uint8_t** allocation, size_t* stride) {
EmbedderState* embedder = static_cast<EmbedderState*>(user_data);
- // TODO(akbiggs): Anything we need to do to clean up the old software surface
- // explicitly before reassigning here?
+ // TODO(akbiggs): Anything we need to do to clean up the old
+ // software surface explicitly before reassigning here?
embedder->software_surface = std::make_unique<SoftwareSurface>(
embedder->sysmem_allocator, embedder->flatland_allocator,
Size{.width = static_cast<uint32_t>(width), .height = static_cast<uint32_t>(height)});
@@ -142,8 +147,8 @@
return false;
}
- // If we receive an unitialized surface, we need to create a Flatland
- // image and associate it with the surface.
+ // If we receive an unitialized surface, we need to create a
+ // Flatland image and associate it with the surface.
if (embedder->software_surface->GetImageId() == 0) {
auto image_id = embedder->flatland_connection->NextContentId().value;
const auto surface_size = embedder->software_surface->GetSize();
@@ -196,9 +201,11 @@
embedder->flatland_connection->Present();
// Signal to the rendered surface that we've finished writing.
- embedder->software_surface->SignalWritesFinished(/* on_read_finished = */ []() {
- // TODO(akbiggs): Anything we need to do after the read finishes?
- });
+ embedder->software_surface->SignalWritesFinished(
+ /* on_read_finished = */ []() {
+ // TODO(akbiggs): Anything we need to do after the read
+ // finishes?
+ });
return true;
}
@@ -261,14 +268,15 @@
embedder::EmbedderState embedder = {
// This must be initialized before calling RunFlutterApp, as
- // this hack https://github.com/flutter/engine/pull/33472 relies
+ // this hack https://github.com/flutter/engine/pull/33472
+ // relies
// on it.
.component_context = sys::ComponentContext::Create(),
};
// Serving the component context must happen after RunFlutterApp
- // because the embedder platform has a hack that adds inspect data into the
- // Dart VM using the component context
+ // because the embedder platform has a hack that adds inspect data
+ // into the Dart VM using the component context
// (https://github.com/flutter/engine/pull/33472).
if (!embedder::RunFlutterApp(assets_path, &embedder)) {
return EXIT_FAILURE;
@@ -326,11 +334,11 @@
zx_status_get_string(status));
return EXIT_FAILURE;
}
- // TODO(akbiggs): Create this on the raster thread per FlatlandConnection's
- // documentation.
+ // TODO(akbiggs): Create this on the raster thread per
+ // FlatlandConnection's documentation.
embedder.flatland_connection = std::make_unique<embedder::FlatlandConnection>(
- // TODO(akbiggs): Pick a more appropriate debug name based on the Flutter
- // app name.
+ // TODO(akbiggs): Pick a more appropriate debug name based
+ // on the Flutter app name.
"FlutterEmbedder" /* debug_name */, std::move(flatland_handle),
/* on_error_callback = */
[]() {
@@ -338,11 +346,18 @@
},
/* on_frame_presented_callback = */
[](fuchsia::scenic::scheduling::FramePresentedInfo info) {
- // TODO(akbiggs): What should we do after a frame finishes presenting?
+ // TODO(akbiggs): What should we do after a frame
+ // finishes presenting?
});
+ // Focuser for programatically transfering View focus.
+ fuchsia::ui::views::FocuserHandle focuser;
+ fuchsia::ui::views::ViewRefFocusedHandle view_ref_focused;
+
// The protocol endpoints bound to a Flatland ViewCreationToken.
fuchsia::ui::composition::ViewBoundProtocols flatland_view_protocols;
+ flatland_view_protocols.set_view_focuser(focuser.NewRequest());
+ flatland_view_protocols.set_view_ref_focused(view_ref_focused.NewRequest());
// Mouse & Touch source
fuchsia::ui::pointer::MouseSourceHandle mouse_source;
@@ -357,21 +372,36 @@
fuchsia::ui::views::ViewRef accessibility_view_ref;
view_ref_pair.view_ref.Clone(&accessibility_view_ref);
+ // Response handle attached to a FlutterPlatformMessage to be called in the engine.
+ FlutterPlatformMessageResponseHandle* response_handle;
+ FlutterPlatformMessageCreateResponseHandle(embedder.engine, embedder::FuchsiaFlutterDataCallback,
+ &embedder, &response_handle);
+
// Keyboard/Text input.
- auto text_delegate = std::make_unique<embedder::TextDelegate>(
+ embedder.text_delegate_ = std::make_unique<embedder::TextDelegate>(
std::move(platform_view_ref), std::move(ime_service), std::move(keyboard),
+ /* key_event_dispatch_callback: [flutter/keydata] */
[&embedder](const FlutterKeyEvent* event) {
// Send key event to the engine.
FlutterEngineResult result = FlutterEngineSendKeyEvent(
embedder.engine, event, embedder::FuchsiaFlutterKeyEventCallback, &embedder);
- // This is here for logging success/failure. Eventually remove.
- if (result == kSuccess) {
- FX_LOG(INFO, embedder::kLogTag, "FlutterEngineResult: SUCCESS");
- } else {
- FX_LOG(INFO, embedder::kLogTag, "FlutterEngineResult: FAIL");
+ if (result != kSuccess) {
+ FX_LOGF(INFO, embedder::kLogTag, "FlutterEngineSendKeyEvent failed for the %s channel.",
+ embedder::kKeyDataChannel);
}
- });
+ },
+ /*platform_dispatch_callback: [flutter/keyevent, flutter/textinput] */
+ [&embedder](const FlutterPlatformMessage* message) {
+ // Send platform message to the engine.
+ FlutterEngineResult result = FlutterEngineSendPlatformMessage(embedder.engine, message);
+
+ if (result != kSuccess) {
+ FX_LOGF(INFO, embedder::kLogTag,
+ "FlutterEngineSendPlatformMessage failed for the %s channel.", message->channel);
+ }
+ },
+ response_handle);
embedder::FlatlandViewProvider view_provider(embedder.flatland_connection.get(),
std::move(view_ref_pair),
@@ -401,8 +431,9 @@
return EXIT_FAILURE;
}
- // Our run loop's dispatcher must be used to serve the component context in
- // order for requests' handlers to get called while we're looping.
+ // Our run loop's dispatcher must be used to serve the component
+ // context in order for requests' handlers to get called while
+ // we're looping.
status = embedder.component_context->outgoing()->ServeFromStartupInfo();
if (status != ZX_OK) {
FX_LOGF(ERROR, embedder::kLogTag, "Failed to serve component context: %s",
diff --git a/src/embedder/platform_message_channels.h b/src/embedder/platform_message_channels.h
new file mode 100644
index 0000000..b183a53
--- /dev/null
+++ b/src/embedder/platform_message_channels.h
@@ -0,0 +1,30 @@
+// Copyright 2022 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SRC_EMBEDDER_PLATFORM_MESSAGE_CHANNELS_H_
+#define SRC_EMBEDDER_PLATFORM_MESSAGE_CHANNELS_H_
+
+namespace embedder {
+
+/// Platform messages sent from the flutter engine include a
+/// channel to indicate what type of message they are.
+
+// Accessibility channel.
+constexpr char kAccessibilityChannel[] = "flutter/accessibility";
+
+// Key data channel for sending key input events. This is the preferred
+// channel for sending these events in the future and flutter/keyevent will
+// be deprecated. For now, we implement both.
+constexpr char kKeyDataChannel[] = "flutter/keydata";
+
+/// The channel name used for key event platform messages. This chanel is used for sending raw
+/// native events and will be deprecated in the future.
+constexpr char kKeyEventChannel[] = "flutter/keyevent";
+
+/// The channel name used for text editing platform messages.
+constexpr char kTextInputChannel[] = "flutter/textinput";
+
+} // namespace embedder
+
+#endif // SRC_EMBEDDER_PLATFORM_MESSAGE_CHANNELS_H_
diff --git a/src/embedder/text_delegate.cc b/src/embedder/text_delegate.cc
index e211b5b..a2e3c55 100644
--- a/src/embedder/text_delegate.cc
+++ b/src/embedder/text_delegate.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#define RAPIDJSON_HAS_STDSTRING 1
+
#include "src/embedder/text_delegate.h"
#include <fuchsia/ui/input/cpp/fidl.h>
@@ -11,15 +13,20 @@
#include <lib/syslog/global.h>
#include <zircon/status.h>
+#include "include/rapidjson/document.h"
+#include "include/rapidjson/rapidjson.h"
+#include "include/rapidjson/stringbuffer.h"
+#include "include/rapidjson/writer.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"
+#include "src/embedder/platform_message_channels.h"
namespace embedder {
-// static constexpr char kInputActionKey[] = "inputAction";
+static constexpr char kInputActionKey[] = "inputAction";
// See: https://api.flutter.dev/flutter/services/TextInputAction.html
// Only the actions relevant for Fuchsia are listed here.
@@ -113,11 +120,15 @@
});
}
-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),
+TextDelegate::TextDelegate(
+ fuchsia::ui::views::ViewRef view_ref, fuchsia::ui::input::ImeServiceHandle ime_service,
+ fuchsia::ui::input3::KeyboardHandle keyboard,
+ std::function<void(const FlutterKeyEvent*)> key_event_dispatch_callback,
+ std::function<void(const FlutterPlatformMessage*)> platform_dispatch_callback,
+ FlutterPlatformMessageResponseHandle* response_handle)
+ : key_event_dispatch_callback_(key_event_dispatch_callback),
+ platform_dispatch_callback_(platform_dispatch_callback),
+ response_handle_(response_handle),
ime_client_(this),
text_sync_service_(ime_service.Bind()),
keyboard_listener_binding_(this),
@@ -159,15 +170,227 @@
}
}
-// |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.");
+ rapidjson::Document document;
+ auto& allocator = document.GetAllocator();
+ rapidjson::Value encoded_state(rapidjson::kObjectType);
+ encoded_state.AddMember("text", state.text, allocator);
+ encoded_state.AddMember("selectionBase", state.selection.base, allocator);
+ encoded_state.AddMember("selectionExtent", state.selection.extent, allocator);
+ switch (state.selection.affinity) {
+ case fuchsia::ui::input::TextAffinity::UPSTREAM:
+ encoded_state.AddMember("selectionAffinity", rapidjson::Value("TextAffinity.upstream"),
+ allocator);
+ break;
+ case fuchsia::ui::input::TextAffinity::DOWNSTREAM:
+ encoded_state.AddMember("selectionAffinity", rapidjson::Value("TextAffinity.downstream"),
+ allocator);
+ break;
+ }
+ encoded_state.AddMember("selectionIsDirectional", true, allocator);
+ encoded_state.AddMember("composingBase", state.composing.start, allocator);
+ encoded_state.AddMember("composingExtent", state.composing.end, allocator);
+
+ rapidjson::Value args(rapidjson::kArrayType);
+ args.PushBack(current_text_input_client_, allocator);
+ args.PushBack(encoded_state, allocator);
+
+ document.SetObject();
+ document.AddMember("method", rapidjson::Value("TextInputClient.updateEditingState"), allocator);
+ document.AddMember("args", args, allocator);
+
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ document.Accept(writer);
+
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
+ FlutterPlatformMessage flutter_platform_message = {.struct_size = sizeof(FlutterPlatformMessage),
+ .channel = kTextInputChannel,
+ .message = data,
+ .message_size = buffer.GetSize(),
+ .response_handle = response_handle_};
+ platform_dispatch_callback_(&flutter_platform_message);
+ last_text_state_ = std::move(state);
}
-// |fuchsia::ui::input::InputMethodEditorClient|
void TextDelegate::OnAction(fuchsia::ui::input::InputMethodAction action) {
- FX_LOG(ERROR, kLogTag, "TextDelegate::OnAction not implemented.");
+ rapidjson::Document document;
+ auto& allocator = document.GetAllocator();
+
+ rapidjson::Value args(rapidjson::kArrayType);
+ args.PushBack(current_text_input_client_, allocator);
+
+ const std::string action_string = IntoTextInputAction(action);
+ args.PushBack(rapidjson::Value{}.SetString(action_string.c_str(), action_string.length()),
+ allocator);
+
+ document.SetObject();
+ document.AddMember("method", rapidjson::Value("TextInputClient.performAction"), allocator);
+ document.AddMember("args", args, allocator);
+
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ document.Accept(writer);
+
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
+ FlutterPlatformMessage flutter_platform_message = {.struct_size = sizeof(FlutterPlatformMessage),
+ .channel = kTextInputChannel,
+ .message = data,
+ .message_size = buffer.GetSize(),
+ .response_handle = response_handle_};
+ platform_dispatch_callback_(&flutter_platform_message);
+}
+
+// Channel handler for kTextInputChannel
+bool TextDelegate::HandleFlutterTextInputChannelPlatformMessage(
+ const FlutterPlatformMessage* message) {
+ FX_DCHECK(!strcmp(message->channel, kTextInputChannel));
+
+ const auto& data = message->message;
+
+ rapidjson::Document document;
+ document.Parse(reinterpret_cast<const char*>(data), message->message_size);
+ if (document.HasParseError()) {
+ return false;
+ }
+
+ if (!document.IsObject()) {
+ return false;
+ }
+
+ auto root = document.GetObject();
+ auto method = root.FindMember("method");
+ if (method == root.MemberEnd() || !method->value.IsString()) {
+ return false;
+ }
+
+ if (method->value == "TextInput.show") {
+ if (ime_) {
+ text_sync_service_->ShowKeyboard();
+ }
+ } else if (method->value == "TextInput.hide") {
+ if (ime_) {
+ text_sync_service_->HideKeyboard();
+ }
+ } else if (method->value == "TextInput.setClient") {
+ // Sample "setClient" message:
+ //
+ // {
+ // "method": "TextInput.setClient",
+ // "args": [
+ // 7,
+ // {
+ // "inputType": {
+ // "name": "TextInputType.multiline",
+ // "signed":null,
+ // "decimal":null
+ // },
+ // "readOnly": false,
+ // "obscureText": false,
+ // "autocorrect":true,
+ // "smartDashesType":"1",
+ // "smartQuotesType":"1",
+ // "enableSuggestions":true,
+ // "enableInteractiveSelection":true,
+ // "actionLabel":null,
+ // "inputAction":"TextInputAction.newline",
+ // "textCapitalization":"TextCapitalization.none",
+ // "keyboardAppearance":"Brightness.dark",
+ // "enableIMEPersonalizedLearning":true,
+ // "enableDeltaModel":false
+ // }
+ // ]
+ // }
+
+ current_text_input_client_ = 0;
+ DeactivateIme();
+ auto args = root.FindMember("args");
+ if (args == root.MemberEnd() || !args->value.IsArray() || args->value.Size() != 2)
+ return false;
+ const auto& configuration = args->value[1];
+ if (!configuration.IsObject()) {
+ return false;
+ }
+ // TODO(abarth): Read the keyboard type from the configuration.
+ current_text_input_client_ = args->value[0].GetInt();
+
+ auto initial_text_input_state = fuchsia::ui::input::TextInputState{};
+ initial_text_input_state.text = "";
+ last_text_state_ = std::move(initial_text_input_state);
+
+ const auto configuration_object = configuration.GetObject();
+ if (!configuration_object.HasMember(kInputActionKey)) {
+ return false;
+ }
+ const auto& action_object = configuration_object[kInputActionKey];
+ if (!action_object.IsString()) {
+ return false;
+ }
+ const auto action_string =
+ std::string(action_object.GetString(), action_object.GetStringLength());
+ ActivateIme(IntoInputMethodAction(std::move(action_string)));
+ } else if (method->value == "TextInput.setEditingState") {
+ if (ime_) {
+ auto args_it = root.FindMember("args");
+ if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
+ return false;
+ }
+ const auto& args = args_it->value;
+ fuchsia::ui::input::TextInputState state;
+ state.text = "";
+ // TODO(abarth): Deserialize state.
+ auto text = args.FindMember("text");
+ if (text != args.MemberEnd() && text->value.IsString()) {
+ state.text = text->value.GetString();
+ }
+ auto selection_base = args.FindMember("selectionBase");
+ if (selection_base != args.MemberEnd() && selection_base->value.IsInt()) {
+ state.selection.base = selection_base->value.GetInt();
+ }
+ auto selection_extent = args.FindMember("selectionExtent");
+ if (selection_extent != args.MemberEnd() && selection_extent->value.IsInt()) {
+ state.selection.extent = selection_extent->value.GetInt();
+ }
+ auto selection_affinity = args.FindMember("selectionAffinity");
+ if (selection_affinity != args.MemberEnd() && selection_affinity->value.IsString() &&
+ selection_affinity->value == "TextAffinity.upstream") {
+ state.selection.affinity = fuchsia::ui::input::TextAffinity::UPSTREAM;
+ } else {
+ state.selection.affinity = fuchsia::ui::input::TextAffinity::DOWNSTREAM;
+ }
+ // We ignore selectionIsDirectional because that concept doesn't exist on
+ // Fuchsia.
+ auto composing_base = args.FindMember("composingBase");
+ if (composing_base != args.MemberEnd() && composing_base->value.IsInt()) {
+ state.composing.start = composing_base->value.GetInt();
+ }
+ auto composing_extent = args.FindMember("composingExtent");
+ if (composing_extent != args.MemberEnd() && composing_extent->value.IsInt()) {
+ state.composing.end = composing_extent->value.GetInt();
+ }
+ ime_->SetState(std::move(state));
+ }
+ } else if (method->value == "TextInput.clearClient") {
+ current_text_input_client_ = 0;
+ last_text_state_ = std::nullopt;
+ requested_text_action_ = std::nullopt;
+ DeactivateIme();
+ } else if (method->value == "TextInput.setCaretRect" ||
+ method->value == "TextInput.setEditableSizeAndTransform" ||
+ method->value == "TextInput.setMarkedTextRect" ||
+ method->value == "TextInput.setStyle") {
+ // We don't have these methods implemented and they get
+ // sent a lot during text input, so we create an empty case for them
+ // here to avoid "Unknown flutter/textinput method TextInput.*"
+ // log spam.
+ //
+ // TODO(fxb/101619): We should implement these.
+ } else {
+ FX_LOGF(ERROR, kLogTag, "Unknown %s method %s", message->channel, method->value.GetString());
+ }
+ // Complete with an empty response.
+ return false;
}
// |fuchsia::ui:input3::KeyboardListener|
@@ -212,24 +435,49 @@
const uint64_t now_nanos = FlutterEngineGetCurrentTime();
const uint64_t now_micro = now_nanos * 1e3;
- FlutterKeyEvent flutter_key_event = {
+ // The character representation for key-up strokes should be null as the
+ // true character is already sent to the engine on the key-down stroke.
+ static char keyUp[1] = {'\0'};
+
+ // Key message to be passed via flutter's flutter/keydata channel.
+ FlutterKeyEvent flutter_key_data_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()),
+ .logical = static_cast<uint64_t>(keyboard_translator_.LastLogicalKey()),
.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
+ : keyUp, // Only assign the character on keydown strokes and
+ .synthesized = false};
- // Send key event to engine.
- dispatch_callback_(&flutter_key_event);
+ // Send key data event to engine.
+ key_event_dispatch_callback_(&flutter_key_data_event);
- // Notify the key event was handled by this keyboard listener.
+ // Key message to be passed via flutter's flutter/keyevent channel. To be deprecated in the
+ // future in favor of flutter/keydata channel.
+ rapidjson::Document document;
+ auto& allocator = document.GetAllocator();
+ document.SetObject();
+ document.AddMember("type", rapidjson::Value(type, strlen(type)), allocator);
+ document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator);
+ document.AddMember("hidUsage", keyboard_translator_.LastHIDUsage(), allocator);
+ document.AddMember("codePoint", keyboard_translator_.LastCodePoint(), allocator);
+ document.AddMember("modifiers", keyboard_translator_.Modifiers(), allocator);
+ rapidjson::StringBuffer buffer;
+ rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
+ document.Accept(writer);
+
+ const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
+ FlutterPlatformMessage flutter_raw_event = {.struct_size = sizeof(FlutterPlatformMessage),
+ .channel = kKeyEventChannel,
+ .message = data,
+ .message_size = buffer.GetSize(),
+ .response_handle = response_handle_};
+
+ // Send raw key event to engine.
+ platform_dispatch_callback_(&flutter_raw_event);
+
on_key_event_callback(fuchsia::ui::input3::KeyEventStatus::HANDLED);
}
} // namespace embedder
diff --git a/src/embedder/text_delegate.h b/src/embedder/text_delegate.h
index d688e1c..345693c 100644
--- a/src/embedder/text_delegate.h
+++ b/src/embedder/text_delegate.h
@@ -18,12 +18,6 @@
namespace embedder {
-/// The channel name used for text editing platofrm messages.
-constexpr char kTextInputChannel[] = "flutter/textinput";
-
-/// The channel name used for key event platform messages.
-constexpr char kKeyEventChannel[] = "flutter/keyevent";
-
/// TextDelegate handles keyboard input and text editing.
///
/// It mediates between Fuchsia's input and Flutter's platform messages. When it
@@ -40,9 +34,6 @@
/// * Flutter side:
/// https://api.flutter.dev/javadoc/io/flutter/embedding/engine/systemchannels/TextInputChannel.html
/// * Fuchsia side: https://fuchsia.dev/reference/fidl/fuchsia.ui.input
-/// TODO(naudzghebre):
-/// 1. Handle 'flutter/keyevent' platform messages
-/// 2. Handle 'flutter/textinput' platform messages
class TextDelegate : public fuchsia::ui::input3::KeyboardListener,
public fuchsia::ui::input::InputMethodEditorClient {
public:
@@ -58,7 +49,9 @@
TextDelegate(fuchsia::ui::views::ViewRef view_ref,
fuchsia::ui::input::ImeServiceHandle ime_service,
fuchsia::ui::input3::KeyboardHandle keyboard,
- std::function<void(const FlutterKeyEvent*)> dispatch_callback);
+ std::function<void(const FlutterKeyEvent*)> key_event_dispatch_callback,
+ std::function<void(const FlutterPlatformMessage*)> platform_dispatch_callback,
+ FlutterPlatformMessageResponseHandle* response_handle);
TextDelegate(const TextDelegate&) = delete;
TextDelegate& operator=(const TextDelegate&) = delete;
@@ -77,6 +70,8 @@
/// action is supplied to Flutter.
void OnAction(fuchsia::ui::input::InputMethodAction action) override;
+ bool HandleFlutterTextInputChannelPlatformMessage(const FlutterPlatformMessage* message);
+
/// Gets a new input method editor from the input connection. Run when both
/// Scenic has focus and Flutter has requested input with setClient.
void ActivateIme();
@@ -102,8 +97,10 @@
/// Converts Fuchsia platform key codes into Flutter key codes.
Keyboard keyboard_translator_;
- /// A callback for sending a single Flutter platform message.
- std::function<void(const FlutterKeyEvent* event)> dispatch_callback_;
+ std::function<void(const FlutterKeyEvent*)> key_event_dispatch_callback_;
+ std::function<void(const FlutterPlatformMessage*)> platform_dispatch_callback_;
+
+ FlutterPlatformMessageResponseHandle* response_handle_;
/// TextDelegate server-side binding. Methods called when the text edit state
/// is updated.
@@ -117,7 +114,7 @@
/// The locally-unique identifier of the text input currently in use. Flutter
/// usually uses only one at a time.
- /// int current_text_input_client_ = 0;
+ int current_text_input_client_ = 0;
/// TextDelegate server side binding. Methods called when a key is pressed.
fidl::Binding<fuchsia::ui::input3::KeyboardListener> keyboard_listener_binding_;
diff --git a/src/examples/hello_flutter/lib/main.dart b/src/examples/hello_flutter/lib/main.dart
index e66ea45..69c6870 100644
--- a/src/examples/hello_flutter/lib/main.dart
+++ b/src/examples/hello_flutter/lib/main.dart
@@ -58,12 +58,6 @@
void initState() {
super.initState();
_controller = TextEditingController();
-
- // Temporary hack to display key events to the textfield.
- HardwareKeyboard.instance.addHandler((event) {
- _controller.text = _controller.text + (event.character ?? "");
- return true;
- });
}
@override
diff --git a/src/examples/hello_flutter/pubspec.lock b/src/examples/hello_flutter/pubspec.lock
index 913be73..bc4f11c 100644
--- a/src/examples/hello_flutter/pubspec.lock
+++ b/src/examples/hello_flutter/pubspec.lock
@@ -156,10 +156,10 @@
dependency: transitive
description:
name: string_scanner
- sha256: "862015c5db1f3f3c4ea3b94dc2490363a84262994b88902315ed74be1155612f"
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
- version: "1.1.1"
+ version: "1.2.0"
term_glyph:
dependency: transitive
description:
@@ -172,10 +172,10 @@
dependency: transitive
description:
name: test_api
- sha256: c9aba3b3dbfe8878845dfab5fa096eb8de7b62231baeeb1cea8e3ee81ca8c6d8
+ sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
url: "https://pub.dev"
source: hosted
- version: "0.4.15"
+ version: "0.4.16"
vector_math:
dependency: transitive
description:
diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel
index 04f2c62..b998032 100644
--- a/third_party/BUILD.bazel
+++ b/third_party/BUILD.bazel
@@ -4,6 +4,6 @@
cc_library(
name = "rapidjson",
- srcs = glob(["rapidjson/include/**/*.h"]),
+ srcs = glob(["**/*.h"]),
visibility = ["//visibility:public"],
)