Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
text_delegate.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "text_delegate.h"
6
7#include <fuchsia/ui/input/cpp/fidl.h>
8#include <fuchsia/ui/input3/cpp/fidl.h>
9#include <fuchsia/ui/views/cpp/fidl.h>
10#include <lib/fidl/cpp/binding.h>
11
12#include "flutter/fml/logging.h"
13#include "flutter/fml/mapping.h"
14#include "flutter/lib/ui/window/platform_message.h"
15#include "flutter/shell/platform/fuchsia/flutter/keyboard.h"
16#include "flutter/shell/platform/fuchsia/runtime/dart/utils/inlines.h"
17#include "third_party/rapidjson/include/rapidjson/document.h"
18#include "third_party/rapidjson/include/rapidjson/stringbuffer.h"
19#include "third_party/rapidjson/include/rapidjson/writer.h"
20
21#include "logging.h"
22
23namespace flutter_runner {
24
25static constexpr char kInputActionKey[] = "inputAction";
26
27// See: https://api.flutter.dev/flutter/services/TextInputAction.html
28// Only the actions relevant for Fuchsia are listed here.
29static constexpr char kTextInputActionDone[] = "TextInputAction.done";
30static constexpr char kTextInputActionNewline[] = "TextInputAction.newline";
31static constexpr char kTextInputActionGo[] = "TextInputAction.go";
32static constexpr char kTextInputActionNext[] = "TextInputAction.next";
33static constexpr char kTextInputActionPrevious[] = "TextInputAction.previous";
34static constexpr char kTextInputActionNone[] = "TextInputAction.none";
35static constexpr char kTextInputActionSearch[] = "TextInputAction.search";
36static constexpr char kTextInputActionSend[] = "TextInputAction.send";
37static constexpr char kTextInputActionUnspecified[] =
38 "TextInputAction.unspecified";
39
40// Converts Flutter TextInputAction to Fuchsia action enum.
41static fuchsia::ui::input::InputMethodAction IntoInputMethodAction(
42 const std::string action_string) {
43 if (action_string == kTextInputActionNewline) {
44 return fuchsia::ui::input::InputMethodAction::NEWLINE;
45 } else if (action_string == kTextInputActionDone) {
46 return fuchsia::ui::input::InputMethodAction::DONE;
47 } else if (action_string == kTextInputActionGo) {
48 return fuchsia::ui::input::InputMethodAction::GO;
49 } else if (action_string == kTextInputActionNext) {
50 return fuchsia::ui::input::InputMethodAction::NEXT;
51 } else if (action_string == kTextInputActionPrevious) {
52 return fuchsia::ui::input::InputMethodAction::PREVIOUS;
53 } else if (action_string == kTextInputActionNone) {
54 return fuchsia::ui::input::InputMethodAction::NONE;
55 } else if (action_string == kTextInputActionSearch) {
56 return fuchsia::ui::input::InputMethodAction::SEARCH;
57 } else if (action_string == kTextInputActionSend) {
58 return fuchsia::ui::input::InputMethodAction::SEND;
59 } else if (action_string == kTextInputActionUnspecified) {
60 return fuchsia::ui::input::InputMethodAction::UNSPECIFIED;
61 }
62 // If this message comes along it means we should really add the missing 'if'
63 // above.
64 FML_VLOG(1) << "unexpected action_string: " << action_string;
65 // Substituting DONE for an unexpected action string will probably be OK.
66 return fuchsia::ui::input::InputMethodAction::DONE;
67}
68
69// Converts the Fuchsia action enum into Flutter TextInputAction.
70static const std::string IntoTextInputAction(
71 fuchsia::ui::input::InputMethodAction action) {
72 if (action == fuchsia::ui::input::InputMethodAction::NEWLINE) {
74 } else if (action == fuchsia::ui::input::InputMethodAction::DONE) {
76 } else if (action == fuchsia::ui::input::InputMethodAction::GO) {
77 return kTextInputActionGo;
78 } else if (action == fuchsia::ui::input::InputMethodAction::NEXT) {
80 } else if (action == fuchsia::ui::input::InputMethodAction::PREVIOUS) {
82 } else if (action == fuchsia::ui::input::InputMethodAction::NONE) {
84 } else if (action == fuchsia::ui::input::InputMethodAction::SEARCH) {
86 } else if (action == fuchsia::ui::input::InputMethodAction::SEND) {
88 } else if (action == fuchsia::ui::input::InputMethodAction::UNSPECIFIED) {
90 }
91 // If this message comes along it means we should really add the missing 'if'
92 // above.
93 FML_VLOG(1) << "unexpected action: " << static_cast<uint32_t>(action);
94 // Substituting "done" for an unexpected text input action will probably
95 // be OK.
97}
98
99// TODO(fxbug.dev/8868): Terminate engine if Fuchsia system FIDL connections
100// have error.
101template <class T>
102void SetInterfaceErrorHandler(fidl::InterfacePtr<T>& interface,
103 std::string name) {
104 interface.set_error_handler([name](zx_status_t status) {
105 FML_LOG(ERROR) << "Interface error on: " << name << ", status: " << status;
106 });
107}
108template <class T>
109void SetInterfaceErrorHandler(fidl::Binding<T>& binding, std::string name) {
110 binding.set_error_handler([name](zx_status_t status) {
111 FML_LOG(ERROR) << "Binding error on: " << name << ", status: " << status;
112 });
113}
114
116 fuchsia::ui::views::ViewRef view_ref,
117 fuchsia::ui::input::ImeServiceHandle ime_service,
118 fuchsia::ui::input3::KeyboardHandle keyboard,
119 std::function<void(std::unique_ptr<flutter::PlatformMessage>)>
120 dispatch_callback)
121 : dispatch_callback_(dispatch_callback),
122 ime_client_(this),
123 text_sync_service_(ime_service.Bind()),
124 keyboard_listener_binding_(this),
125 keyboard_(keyboard.Bind()) {
126 // Register all error handlers.
127 SetInterfaceErrorHandler(ime_, "Input Method Editor");
128 SetInterfaceErrorHandler(ime_client_, "IME Client");
129 SetInterfaceErrorHandler(text_sync_service_, "Text Sync Service");
130 SetInterfaceErrorHandler(keyboard_listener_binding_, "Keyboard Listener");
131 SetInterfaceErrorHandler(keyboard_, "Keyboard");
132
133 // Configure keyboard listener.
134 keyboard_->AddListener(std::move(view_ref),
135 keyboard_listener_binding_.NewBinding(), [] {});
136}
137
139 ActivateIme(requested_text_action_.value_or(
140 fuchsia::ui::input::InputMethodAction::DONE));
141}
142
143void TextDelegate::ActivateIme(fuchsia::ui::input::InputMethodAction action) {
144 FML_DCHECK(last_text_state_.has_value());
145
146 requested_text_action_ = action;
147 text_sync_service_->GetInputMethodEditor(
148 fuchsia::ui::input::KeyboardType::TEXT, // keyboard type
149 action, // input method action
150 last_text_state_.value(), // initial state
151 ime_client_.NewBinding(), // client
152 ime_.NewRequest() // editor
153 );
154}
155
157 if (ime_) {
158 text_sync_service_->HideKeyboard();
159 ime_ = nullptr;
160 }
161 if (ime_client_.is_bound()) {
162 ime_client_.Unbind();
163 }
164}
165
166// |fuchsia::ui::input::InputMethodEditorClient|
168 fuchsia::ui::input::TextInputState state,
169 std::unique_ptr<fuchsia::ui::input::InputEvent> input_event) {
170 rapidjson::Document document;
171 auto& allocator = document.GetAllocator();
172 rapidjson::Value encoded_state(rapidjson::kObjectType);
173 encoded_state.AddMember("text", state.text, allocator);
174 encoded_state.AddMember("selectionBase", state.selection.base, allocator);
175 encoded_state.AddMember("selectionExtent", state.selection.extent, allocator);
176 switch (state.selection.affinity) {
177 case fuchsia::ui::input::TextAffinity::UPSTREAM:
178 encoded_state.AddMember("selectionAffinity",
179 rapidjson::Value("TextAffinity.upstream"),
180 allocator);
181 break;
182 case fuchsia::ui::input::TextAffinity::DOWNSTREAM:
183 encoded_state.AddMember("selectionAffinity",
184 rapidjson::Value("TextAffinity.downstream"),
185 allocator);
186 break;
187 }
188 encoded_state.AddMember("selectionIsDirectional", true, allocator);
189 encoded_state.AddMember("composingBase", state.composing.start, allocator);
190 encoded_state.AddMember("composingExtent", state.composing.end, allocator);
191
192 rapidjson::Value args(rapidjson::kArrayType);
193 args.PushBack(current_text_input_client_, allocator);
194 args.PushBack(encoded_state, allocator);
195
196 document.SetObject();
197 document.AddMember("method",
198 rapidjson::Value("TextInputClient.updateEditingState"),
199 allocator);
200 document.AddMember("args", args, allocator);
201
202 rapidjson::StringBuffer buffer;
203 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
204 document.Accept(writer);
205
206 const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
207 dispatch_callback_(std::make_unique<flutter::PlatformMessage>(
208 kTextInputChannel, // channel
209 fml::MallocMapping::Copy(data, buffer.GetSize()), // message
210 nullptr) // response
211 );
212 last_text_state_ = std::move(state);
213}
214
215// |fuchsia::ui::input::InputMethodEditorClient|
216void TextDelegate::OnAction(fuchsia::ui::input::InputMethodAction action) {
217 rapidjson::Document document;
218 auto& allocator = document.GetAllocator();
219
220 rapidjson::Value args(rapidjson::kArrayType);
221 args.PushBack(current_text_input_client_, allocator);
222
223 const std::string action_string = IntoTextInputAction(action);
224 args.PushBack(rapidjson::Value{}.SetString(action_string.c_str(),
225 action_string.length()),
226 allocator);
227
228 document.SetObject();
229 document.AddMember(
230 "method", rapidjson::Value("TextInputClient.performAction"), allocator);
231 document.AddMember("args", args, allocator);
232
233 rapidjson::StringBuffer buffer;
234 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
235 document.Accept(writer);
236
237 const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
238 dispatch_callback_(std::make_unique<flutter::PlatformMessage>(
239 kTextInputChannel, // channel
240 fml::MallocMapping::Copy(data, buffer.GetSize()), // message
241 nullptr) // response
242 );
243}
244
245// Channel handler for kTextInputChannel
247 std::unique_ptr<flutter::PlatformMessage> message) {
248 FML_DCHECK(message->channel() == kTextInputChannel);
249 const auto& data = message->data();
250
251 rapidjson::Document document;
252 document.Parse(reinterpret_cast<const char*>(data.GetMapping()),
253 data.GetSize());
254 if (document.HasParseError() || !document.IsObject()) {
255 return false;
256 }
257 auto root = document.GetObject();
258 auto method = root.FindMember("method");
259 if (method == root.MemberEnd() || !method->value.IsString()) {
260 return false;
261 }
262
263 if (method->value == "TextInput.show") {
264 if (ime_) {
265 text_sync_service_->ShowKeyboard();
266 }
267 } else if (method->value == "TextInput.hide") {
268 if (ime_) {
269 text_sync_service_->HideKeyboard();
270 }
271 } else if (method->value == "TextInput.setClient") {
272 // Sample "setClient" message:
273 //
274 // {
275 // "method": "TextInput.setClient",
276 // "args": [
277 // 7,
278 // {
279 // "inputType": {
280 // "name": "TextInputType.multiline",
281 // "signed":null,
282 // "decimal":null
283 // },
284 // "readOnly": false,
285 // "obscureText": false,
286 // "autocorrect":true,
287 // "smartDashesType":"1",
288 // "smartQuotesType":"1",
289 // "enableSuggestions":true,
290 // "enableInteractiveSelection":true,
291 // "actionLabel":null,
292 // "inputAction":"TextInputAction.newline",
293 // "textCapitalization":"TextCapitalization.none",
294 // "keyboardAppearance":"Brightness.dark",
295 // "enableIMEPersonalizedLearning":true,
296 // "enableDeltaModel":false
297 // }
298 // ]
299 // }
300
301 current_text_input_client_ = 0;
303 auto args = root.FindMember("args");
304 if (args == root.MemberEnd() || !args->value.IsArray() ||
305 args->value.Size() != 2)
306 return false;
307 const auto& configuration = args->value[1];
308 if (!configuration.IsObject()) {
309 return false;
310 }
311 // TODO(abarth): Read the keyboard type from the configuration.
312 current_text_input_client_ = args->value[0].GetInt();
313
314 auto initial_text_input_state = fuchsia::ui::input::TextInputState{};
315 initial_text_input_state.text = "";
316 last_text_state_ = std::move(initial_text_input_state);
317
318 const auto configuration_object = configuration.GetObject();
319 if (!configuration_object.HasMember(kInputActionKey)) {
320 return false;
321 }
322 const auto& action_object = configuration_object[kInputActionKey];
323 if (!action_object.IsString()) {
324 return false;
325 }
326 const auto action_string =
327 std::string(action_object.GetString(), action_object.GetStringLength());
328 ActivateIme(IntoInputMethodAction(std::move(action_string)));
329 } else if (method->value == "TextInput.setEditingState") {
330 if (ime_) {
331 auto args_it = root.FindMember("args");
332 if (args_it == root.MemberEnd() || !args_it->value.IsObject()) {
333 return false;
334 }
335 const auto& args = args_it->value;
336 fuchsia::ui::input::TextInputState state;
337 state.text = "";
338 // TODO(abarth): Deserialize state.
339 auto text = args.FindMember("text");
340 if (text != args.MemberEnd() && text->value.IsString()) {
341 state.text = text->value.GetString();
342 }
343 auto selection_base = args.FindMember("selectionBase");
344 if (selection_base != args.MemberEnd() && selection_base->value.IsInt()) {
345 state.selection.base = selection_base->value.GetInt();
346 }
347 auto selection_extent = args.FindMember("selectionExtent");
348 if (selection_extent != args.MemberEnd() &&
349 selection_extent->value.IsInt()) {
350 state.selection.extent = selection_extent->value.GetInt();
351 }
352 auto selection_affinity = args.FindMember("selectionAffinity");
353 if (selection_affinity != args.MemberEnd() &&
354 selection_affinity->value.IsString() &&
355 selection_affinity->value == "TextAffinity.upstream") {
356 state.selection.affinity = fuchsia::ui::input::TextAffinity::UPSTREAM;
357 } else {
358 state.selection.affinity = fuchsia::ui::input::TextAffinity::DOWNSTREAM;
359 }
360 // We ignore selectionIsDirectional because that concept doesn't exist on
361 // Fuchsia.
362 auto composing_base = args.FindMember("composingBase");
363 if (composing_base != args.MemberEnd() && composing_base->value.IsInt()) {
364 state.composing.start = composing_base->value.GetInt();
365 }
366 auto composing_extent = args.FindMember("composingExtent");
367 if (composing_extent != args.MemberEnd() &&
368 composing_extent->value.IsInt()) {
369 state.composing.end = composing_extent->value.GetInt();
370 }
371 ime_->SetState(std::move(state));
372 }
373 } else if (method->value == "TextInput.clearClient") {
374 current_text_input_client_ = 0;
375 last_text_state_ = std::nullopt;
376 requested_text_action_ = std::nullopt;
378 } else if (method->value == "TextInput.setCaretRect" ||
379 method->value == "TextInput.setEditableSizeAndTransform" ||
380 method->value == "TextInput.setMarkedTextRect" ||
381 method->value == "TextInput.setStyle") {
382 // We don't have these methods implemented and they get
383 // sent a lot during text input, so we create an empty case for them
384 // here to avoid "Unknown flutter/textinput method TextInput.*"
385 // log spam.
386 //
387 // TODO(fxb/101619): We should implement these.
388 } else {
389 FML_LOG(ERROR) << "Unknown " << message->channel() << " method "
390 << method->value.GetString();
391 }
392 // Complete with an empty response.
393 return false;
394}
395
396// |fuchsia::ui:input3::KeyboardListener|
398 fuchsia::ui::input3::KeyEvent key_event,
399 fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback callback) {
400 const char* type = nullptr;
401 switch (key_event.type()) {
402 case fuchsia::ui::input3::KeyEventType::PRESSED:
403 type = "keydown";
404 break;
405 case fuchsia::ui::input3::KeyEventType::RELEASED:
406 type = "keyup";
407 break;
408 case fuchsia::ui::input3::KeyEventType::SYNC:
409 // SYNC means the key was pressed while focus was not on this application.
410 // This should possibly behave like PRESSED in the future, though it
411 // doesn't hurt to ignore it today.
412 case fuchsia::ui::input3::KeyEventType::CANCEL:
413 // CANCEL means the key was released while focus was not on this
414 // application.
415 // This should possibly behave like RELEASED in the future to ensure that
416 // a key is not repeated forever if it is pressed while focus is lost.
417 default:
418 break;
419 }
420 if (type == nullptr) {
421 FML_VLOG(1) << "Unknown key event phase.";
422 callback(fuchsia::ui::input3::KeyEventStatus::NOT_HANDLED);
423 return;
424 }
425 keyboard_translator_.ConsumeEvent(std::move(key_event));
426
427 rapidjson::Document document;
428 auto& allocator = document.GetAllocator();
429 document.SetObject();
430 document.AddMember("type", rapidjson::Value(type, strlen(type)), allocator);
431 document.AddMember("keymap", rapidjson::Value("fuchsia"), allocator);
432 document.AddMember("hidUsage", keyboard_translator_.LastHIDUsage(),
433 allocator);
434 document.AddMember("codePoint", keyboard_translator_.LastCodePoint(),
435 allocator);
436 document.AddMember("modifiers", keyboard_translator_.Modifiers(), allocator);
437 rapidjson::StringBuffer buffer;
438 rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
439 document.Accept(writer);
440
441 const uint8_t* data = reinterpret_cast<const uint8_t*>(buffer.GetString());
442 dispatch_callback_(std::make_unique<flutter::PlatformMessage>(
443 kKeyEventChannel, // channel
444 fml::MallocMapping::Copy(data, buffer.GetSize()), // data
445 nullptr) // response
446 );
447 callback(fuchsia::ui::input3::KeyEventStatus::HANDLED);
448}
449} // namespace flutter_runner
bool ConsumeEvent(fuchsia::ui::input3::KeyEvent event)
Definition keyboard.cc:174
void DidUpdateState(fuchsia::ui::input::TextInputState state, std::unique_ptr< fuchsia::ui::input::InputEvent > event) override
void OnKeyEvent(fuchsia::ui::input3::KeyEvent key_event, fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback callback) override
void OnAction(fuchsia::ui::input::InputMethodAction action) override
TextDelegate(fuchsia::ui::views::ViewRef view_ref, fuchsia::ui::input::ImeServiceHandle ime_service, fuchsia::ui::input3::KeyboardHandle keyboard, std::function< void(std::unique_ptr< flutter::PlatformMessage >)> dispatch_callback)
bool HandleFlutterTextInputChannelPlatformMessage(std::unique_ptr< flutter::PlatformMessage > message)
Channel handler for kTextInputChannel.
static MallocMapping Copy(const T *begin, const T *end)
Definition mapping.h:162
AtkStateType state
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
static const uint8_t buffer[]
#define FML_VLOG(verbose_level)
Definition logging.h:98
#define FML_LOG(severity)
Definition logging.h:82
#define FML_DCHECK(condition)
Definition logging.h:103
const char * name
Definition fuchsia.cc:50
FlutterKeyEvent key_event
std::u16string text
Win32Message message
static const std::string IntoTextInputAction(fuchsia::ui::input::InputMethodAction action)
static constexpr char kTextInputActionPrevious[]
static constexpr char kTextInputActionUnspecified[]
static constexpr char kTextInputActionGo[]
static constexpr char kTextInputActionDone[]
static constexpr char kTextInputActionNewline[]
static constexpr char kTextInputActionNone[]
static constexpr char kInputActionKey[]
constexpr char kTextInputChannel[]
The channel name used for text editing platofrm messages.
static constexpr char kTextInputActionSearch[]
void SetInterfaceErrorHandler(fidl::InterfacePtr< T > &interface, std::string name)
static fuchsia::ui::input::InputMethodAction IntoInputMethodAction(const std::string action_string)
constexpr char kKeyEventChannel[]
The channel name used for key event platform messages.
static constexpr char kTextInputActionNext[]
static constexpr char kTextInputActionSend[]
fidl::Binding< fuchsia::ui::input3::Keyboard > keyboard_
FlutterKeyEventType type
The event kind.
Definition embedder.h:1116
#define ERROR(message)