Flutter Engine
The Flutter Engine
Public Member Functions | List of all members
flutter_runner::TextDelegate Class Reference

#include <text_delegate.h>

Inheritance diagram for flutter_runner::TextDelegate:

Public Member Functions

 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)
 
void OnKeyEvent (fuchsia::ui::input3::KeyEvent key_event, fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback callback) override
 
void DidUpdateState (fuchsia::ui::input::TextInputState state, std::unique_ptr< fuchsia::ui::input::InputEvent > event) override
 
void OnAction (fuchsia::ui::input::InputMethodAction action) override
 
void ActivateIme ()
 
void DeactivateIme ()
 
bool HandleFlutterTextInputChannelPlatformMessage (std::unique_ptr< flutter::PlatformMessage > message)
 Channel handler for kTextInputChannel. More...
 
bool HasTextState ()
 

Detailed Description

TextDelegate handles keyboard inpout and text editing.

It mediates between Fuchsia's input and Flutter's platform messages. When it is initialized, it contacts fuchsia.ui.input.Keyboard to register itself as listener of key events.

Whenever a text editing request comes from the Flutter app, it will activate Fuchsia's input method editor, and will send text edit actions coming from the Fuchsia platform over to the Flutter app, by converting FIDL messages (fuchsia.ui.input.InputMethodEditorClient calls) to appropriate text editing Flutter platform messages.

For details refer to:

Definition at line 48 of file text_delegate.h.

Constructor & Destructor Documentation

◆ TextDelegate()

flutter_runner::TextDelegate::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 
)

Creates a new TextDelegate.

Args: view_ref: the reference to the app's view. Required for registration with Fuchsia. ime_service: a handle to Fuchsia's input method service. keyboard: the keyboard listener, gets notified of key presses and releases. dispatch_callback: a function used to send a Flutter platform message.

Definition at line 115 of file text_delegate.cc.

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}
void SetInterfaceErrorHandler(fidl::InterfacePtr< T > &interface, std::string name)

Member Function Documentation

◆ ActivateIme()

void flutter_runner::TextDelegate::ActivateIme ( )

Gets a new input method editor from the input connection. Run when both Scenic has focus and Flutter has requested input with setClient.

Definition at line 138 of file text_delegate.cc.

138 {
139 ActivateIme(requested_text_action_.value_or(
140 fuchsia::ui::input::InputMethodAction::DONE));
141}

◆ DeactivateIme()

void flutter_runner::TextDelegate::DeactivateIme ( )

Detaches the input method editor connection, ending the edit session and closing the onscreen keyboard. Call when input is no longer desired, either because Scenic says we lost focus or when Flutter no longer has a text field focused.

Definition at line 156 of file text_delegate.cc.

156 {
157 if (ime_) {
158 text_sync_service_->HideKeyboard();
159 ime_ = nullptr;
160 }
161 if (ime_client_.is_bound()) {
162 ime_client_.Unbind();
163 }
164}

◆ DidUpdateState()

void flutter_runner::TextDelegate::DidUpdateState ( fuchsia::ui::input::TextInputState  state,
std::unique_ptr< fuchsia::ui::input::InputEvent >  event 
)
override

|fuchsia::ui::input::InputMethodEditorClient| Called by the embedder every time the edit state is updated.

Definition at line 167 of file text_delegate.cc.

169 {
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}
static MallocMapping Copy(const T *begin, const T *end)
Definition: mapping.h:162
AtkStateType state
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
constexpr char kTextInputChannel[]
The channel name used for text editing platofrm messages.
Definition: text_delegate.h:27
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63

◆ HandleFlutterTextInputChannelPlatformMessage()

bool flutter_runner::TextDelegate::HandleFlutterTextInputChannelPlatformMessage ( std::unique_ptr< flutter::PlatformMessage message)

Channel handler for kTextInputChannel.

Definition at line 246 of file text_delegate.cc.

247 {
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}
#define FML_LOG(severity)
Definition: logging.h:82
#define FML_DCHECK(condition)
Definition: logging.h:103
std::u16string text
Win32Message message
static constexpr char kInputActionKey[]
static fuchsia::ui::input::InputMethodAction IntoInputMethodAction(const std::string action_string)
string root
Definition: scale_cpu.py:20
#define ERROR(message)
Definition: elf_loader.cc:260

◆ HasTextState()

bool flutter_runner::TextDelegate::HasTextState ( )
inline

Returns true if there is a text state (i.e. if some text editing is in progress).

Definition at line 99 of file text_delegate.h.

99{ return last_text_state_.has_value(); }

◆ OnAction()

void flutter_runner::TextDelegate::OnAction ( fuchsia::ui::input::InputMethodAction  action)
override

|fuchsia::ui::input::InputMethodEditorClient| Called by the embedder when the action key is pressed, and the requested action is supplied to Flutter.

Definition at line 216 of file text_delegate.cc.

216 {
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}
static const std::string IntoTextInputAction(fuchsia::ui::input::InputMethodAction action)

◆ OnKeyEvent()

void flutter_runner::TextDelegate::OnKeyEvent ( fuchsia::ui::input3::KeyEvent  key_event,
fuchsia::ui::input3::KeyboardListener::OnKeyEventCallback  callback 
)
override

|fuchsia.ui.input3.KeyboardListener| Called by the embedder every time there is a key event to process.

Definition at line 397 of file text_delegate.cc.

399 {
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}
GLenum type
uint32_t LastCodePoint()
Definition: keyboard.cc:307
bool ConsumeEvent(fuchsia::ui::input3::KeyEvent event)
Definition: keyboard.cc:174
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
#define FML_VLOG(verbose_level)
Definition: logging.h:98
FlutterKeyEvent key_event
constexpr char kKeyEventChannel[]
The channel name used for key event platform messages.
Definition: text_delegate.h:30
FlutterKeyEventType type
The event kind.
Definition: embedder.h:1118

The documentation for this class was generated from the following files: