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