Flutter Engine
The Flutter Engine
text_input_plugin.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 "flutter/shell/platform/windows/text_input_plugin.h"
6
7#include <windows.h>
8
9#include <cstdint>
10
11#include "flutter/fml/string_conversion.h"
12#include "flutter/shell/platform/common/json_method_codec.h"
13#include "flutter/shell/platform/common/text_editing_delta.h"
14#include "flutter/shell/platform/windows/flutter_windows_engine.h"
15#include "flutter/shell/platform/windows/flutter_windows_view.h"
16
17static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState";
18static constexpr char kClearClientMethod[] = "TextInput.clearClient";
19static constexpr char kSetClientMethod[] = "TextInput.setClient";
20static constexpr char kShowMethod[] = "TextInput.show";
21static constexpr char kHideMethod[] = "TextInput.hide";
22static constexpr char kSetMarkedTextRect[] = "TextInput.setMarkedTextRect";
23static constexpr char kSetEditableSizeAndTransform[] =
24 "TextInput.setEditableSizeAndTransform";
25
26static constexpr char kMultilineInputType[] = "TextInputType.multiline";
27
28static constexpr char kUpdateEditingStateMethod[] =
29 "TextInputClient.updateEditingState";
30static constexpr char kUpdateEditingStateWithDeltasMethod[] =
31 "TextInputClient.updateEditingStateWithDeltas";
32static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
33
34static constexpr char kDeltaOldTextKey[] = "oldText";
35static constexpr char kDeltaTextKey[] = "deltaText";
36static constexpr char kDeltaStartKey[] = "deltaStart";
37static constexpr char kDeltaEndKey[] = "deltaEnd";
38static constexpr char kDeltasKey[] = "deltas";
39static constexpr char kEnableDeltaModel[] = "enableDeltaModel";
40static constexpr char kTextInputAction[] = "inputAction";
41static constexpr char kTextInputType[] = "inputType";
42static constexpr char kTextInputTypeName[] = "name";
43static constexpr char kComposingBaseKey[] = "composingBase";
44static constexpr char kComposingExtentKey[] = "composingExtent";
45static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
46static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
47static constexpr char kSelectionBaseKey[] = "selectionBase";
48static constexpr char kSelectionExtentKey[] = "selectionExtent";
49static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
50static constexpr char kTextKey[] = "text";
51static constexpr char kXKey[] = "x";
52static constexpr char kYKey[] = "y";
53static constexpr char kWidthKey[] = "width";
54static constexpr char kHeightKey[] = "height";
55static constexpr char kTransformKey[] = "transform";
56
57static constexpr char kChannelName[] = "flutter/textinput";
58
59static constexpr char kBadArgumentError[] = "Bad Arguments";
60static constexpr char kInternalConsistencyError[] =
61 "Internal Consistency Error";
62
63static constexpr char kInputActionNewline[] = "TextInputAction.newline";
64
65namespace flutter {
66
67void TextInputPlugin::TextHook(const std::u16string& text) {
68 if (active_model_ == nullptr) {
69 return;
70 }
71 std::u16string text_before_change =
72 fml::Utf8ToUtf16(active_model_->GetText());
73 TextRange selection_before_change = active_model_->selection();
74 active_model_->AddText(text);
75
76 if (enable_delta_model) {
78 TextEditingDelta(text_before_change, selection_before_change, text);
79 SendStateUpdateWithDelta(*active_model_, &delta);
80 } else {
81 SendStateUpdate(*active_model_);
82 }
83}
84
86 int scancode,
87 int action,
88 char32_t character,
89 bool extended,
90 bool was_down) {
91 if (active_model_ == nullptr) {
92 return;
93 }
94 if (action == WM_KEYDOWN || action == WM_SYSKEYDOWN) {
95 // Most editing keys (arrow keys, backspace, delete, etc.) are handled in
96 // the framework, so don't need to be handled at this layer.
97 switch (key) {
98 case VK_RETURN:
99 EnterPressed(active_model_.get());
100 break;
101 default:
102 break;
103 }
104 }
105}
106
109 : channel_(std::make_unique<flutter::MethodChannel<rapidjson::Document>>(
110 messenger,
112 &flutter::JsonMethodCodec::GetInstance())),
113 engine_(engine),
114 active_model_(nullptr) {
115 channel_->SetMethodCallHandler(
116 [this](
119 HandleMethodCall(call, std::move(result));
120 });
121}
122
124
126 if (active_model_ == nullptr) {
127 return;
128 }
129 active_model_->BeginComposing();
130 if (enable_delta_model) {
131 std::string text = active_model_->GetText();
132 TextRange selection = active_model_->selection();
134 SendStateUpdateWithDelta(*active_model_, &delta);
135 } else {
136 SendStateUpdate(*active_model_);
137 }
138}
139
141 if (active_model_ == nullptr) {
142 return;
143 }
144 std::string text_before_change = active_model_->GetText();
145 TextRange selection_before_change = active_model_->selection();
146 TextRange composing_before_change = active_model_->composing_range();
147 std::string composing_text_before_change = text_before_change.substr(
148 composing_before_change.start(), composing_before_change.length());
149 active_model_->CommitComposing();
150
151 // We do not trigger SendStateUpdate here.
152 //
153 // Until a WM_IME_ENDCOMPOSING event, the user is still composing from the OS
154 // point of view. Commit events are always immediately followed by another
155 // composing event or an end composing event. However, in the brief window
156 // between the commit event and the following event, the composing region is
157 // collapsed. Notifying the framework of this intermediate state will trigger
158 // any framework code designed to execute at the end of composing, such as
159 // input formatters, which may try to update the text and send a message back
160 // to the engine with changes.
161 //
162 // This is a particular problem with Korean IMEs, which build up one
163 // character at a time in their composing region until a keypress that makes
164 // no sense for the in-progress character. At that point, the result
165 // character is committed and a compose event is immedidately received with
166 // the new composing region.
167 //
168 // In the case where this event is immediately followed by a composing event,
169 // the state will be sent in ComposeChangeHook.
170 //
171 // In the case where this event is immediately followed by an end composing
172 // event, the state will be sent in ComposeEndHook.
173}
174
176 if (active_model_ == nullptr) {
177 return;
178 }
179 std::string text_before_change = active_model_->GetText();
180 TextRange selection_before_change = active_model_->selection();
181 active_model_->CommitComposing();
182 active_model_->EndComposing();
183 if (enable_delta_model) {
184 std::string text = active_model_->GetText();
186 SendStateUpdateWithDelta(*active_model_, &delta);
187 } else {
188 SendStateUpdate(*active_model_);
189 }
190}
191
192void TextInputPlugin::ComposeChangeHook(const std::u16string& text,
193 int cursor_pos) {
194 if (active_model_ == nullptr) {
195 return;
196 }
197 std::string text_before_change = active_model_->GetText();
198 TextRange composing_before_change = active_model_->composing_range();
199 active_model_->AddText(text);
200 active_model_->UpdateComposingText(text, TextRange(cursor_pos, cursor_pos));
201 std::string text_after_change = active_model_->GetText();
202 if (enable_delta_model) {
204 fml::Utf8ToUtf16(text_before_change), composing_before_change, text);
205 SendStateUpdateWithDelta(*active_model_, &delta);
206 } else {
207 SendStateUpdate(*active_model_);
208 }
209}
210
211void TextInputPlugin::HandleMethodCall(
214 const std::string& method = method_call.method_name();
215
216 if (method.compare(kShowMethod) == 0 || method.compare(kHideMethod) == 0) {
217 // These methods are no-ops.
218 } else if (method.compare(kClearClientMethod) == 0) {
219 // TODO(loicsharma): Remove implicit view assumption.
220 // https://github.com/flutter/flutter/issues/142845
221 FlutterWindowsView* view = engine_->view(kImplicitViewId);
222 if (view == nullptr) {
224 "Text input is not available in Windows headless mode");
225 return;
226 }
227 if (active_model_ != nullptr && active_model_->composing()) {
228 active_model_->CommitComposing();
229 active_model_->EndComposing();
230 SendStateUpdate(*active_model_);
231 }
232 view->OnResetImeComposing();
233 active_model_ = nullptr;
234 } else if (method.compare(kSetClientMethod) == 0) {
235 if (!method_call.arguments() || method_call.arguments()->IsNull()) {
236 result->Error(kBadArgumentError, "Method invoked without args");
237 return;
238 }
239 const rapidjson::Document& args = *method_call.arguments();
240
241 const rapidjson::Value& client_id_json = args[0];
242 const rapidjson::Value& client_config = args[1];
243 if (client_id_json.IsNull()) {
244 result->Error(kBadArgumentError, "Could not set client, ID is null.");
245 return;
246 }
247 if (client_config.IsNull()) {
249 "Could not set client, missing arguments.");
250 return;
251 }
252 client_id_ = client_id_json.GetInt();
253 auto enable_delta_model_json = client_config.FindMember(kEnableDeltaModel);
254 if (enable_delta_model_json != client_config.MemberEnd() &&
255 enable_delta_model_json->value.IsBool()) {
256 enable_delta_model = enable_delta_model_json->value.GetBool();
257 }
258 input_action_ = "";
259 auto input_action_json = client_config.FindMember(kTextInputAction);
260 if (input_action_json != client_config.MemberEnd() &&
261 input_action_json->value.IsString()) {
262 input_action_ = input_action_json->value.GetString();
263 }
264 input_type_ = "";
265 auto input_type_info_json = client_config.FindMember(kTextInputType);
266 if (input_type_info_json != client_config.MemberEnd() &&
267 input_type_info_json->value.IsObject()) {
268 auto input_type_json =
269 input_type_info_json->value.FindMember(kTextInputTypeName);
270 if (input_type_json != input_type_info_json->value.MemberEnd() &&
271 input_type_json->value.IsString()) {
272 input_type_ = input_type_json->value.GetString();
273 }
274 }
275 active_model_ = std::make_unique<TextInputModel>();
276 } else if (method.compare(kSetEditingStateMethod) == 0) {
277 if (!method_call.arguments() || method_call.arguments()->IsNull()) {
278 result->Error(kBadArgumentError, "Method invoked without args");
279 return;
280 }
281 const rapidjson::Document& args = *method_call.arguments();
282
283 if (active_model_ == nullptr) {
284 result->Error(
286 "Set editing state has been invoked, but no client is set.");
287 return;
288 }
289 auto text = args.FindMember(kTextKey);
290 if (text == args.MemberEnd() || text->value.IsNull()) {
292 "Set editing state has been invoked, but without text.");
293 return;
294 }
295 auto base = args.FindMember(kSelectionBaseKey);
296 auto extent = args.FindMember(kSelectionExtentKey);
297 if (base == args.MemberEnd() || base->value.IsNull() ||
298 extent == args.MemberEnd() || extent->value.IsNull()) {
300 "Selection base/extent values invalid.");
301 return;
302 }
303 // Flutter uses -1/-1 for invalid; translate that to 0/0 for the model.
304 int selection_base = base->value.GetInt();
305 int selection_extent = extent->value.GetInt();
306 if (selection_base == -1 && selection_extent == -1) {
307 selection_base = selection_extent = 0;
308 }
309 active_model_->SetText(text->value.GetString());
310 active_model_->SetSelection(TextRange(selection_base, selection_extent));
311
312 base = args.FindMember(kComposingBaseKey);
313 extent = args.FindMember(kComposingExtentKey);
314 if (base == args.MemberEnd() || base->value.IsNull() ||
315 extent == args.MemberEnd() || extent->value.IsNull()) {
317 "Composing base/extent values invalid.");
318 return;
319 }
320 int composing_base = base->value.GetInt();
321 int composing_extent = base->value.GetInt();
322 if (composing_base == -1 && composing_extent == -1) {
323 active_model_->EndComposing();
324 } else {
325 int composing_start = std::min(composing_base, composing_extent);
326 int cursor_offset = selection_base - composing_start;
327 active_model_->SetComposingRange(
328 TextRange(composing_base, composing_extent), cursor_offset);
329 }
330 } else if (method.compare(kSetMarkedTextRect) == 0) {
331 // TODO(loicsharma): Remove implicit view assumption.
332 // https://github.com/flutter/flutter/issues/142845
333 FlutterWindowsView* view = engine_->view(kImplicitViewId);
334 if (view == nullptr) {
336 "Text input is not available in Windows headless mode");
337 return;
338 }
339 if (!method_call.arguments() || method_call.arguments()->IsNull()) {
340 result->Error(kBadArgumentError, "Method invoked without args");
341 return;
342 }
343 const rapidjson::Document& args = *method_call.arguments();
344 auto x = args.FindMember(kXKey);
345 auto y = args.FindMember(kYKey);
346 auto width = args.FindMember(kWidthKey);
347 auto height = args.FindMember(kHeightKey);
348 if (x == args.MemberEnd() || x->value.IsNull() || //
349 y == args.MemberEnd() || y->value.IsNull() || //
350 width == args.MemberEnd() || width->value.IsNull() || //
351 height == args.MemberEnd() || height->value.IsNull()) {
353 "Composing rect values invalid.");
354 return;
355 }
356 composing_rect_ = {{x->value.GetDouble(), y->value.GetDouble()},
357 {width->value.GetDouble(), height->value.GetDouble()}};
358
359 Rect transformed_rect = GetCursorRect();
360 view->OnCursorRectUpdated(transformed_rect);
361 } else if (method.compare(kSetEditableSizeAndTransform) == 0) {
362 // TODO(loicsharma): Remove implicit view assumption.
363 // https://github.com/flutter/flutter/issues/142845
364 FlutterWindowsView* view = engine_->view(kImplicitViewId);
365 if (view == nullptr) {
367 "Text input is not available in Windows headless mode");
368 return;
369 }
370 if (!method_call.arguments() || method_call.arguments()->IsNull()) {
371 result->Error(kBadArgumentError, "Method invoked without args");
372 return;
373 }
374 const rapidjson::Document& args = *method_call.arguments();
375 auto transform = args.FindMember(kTransformKey);
376 if (transform == args.MemberEnd() || transform->value.IsNull() ||
377 !transform->value.IsArray() || transform->value.Size() != 16) {
379 "EditableText transform invalid.");
380 return;
381 }
382 size_t i = 0;
383 for (auto& entry : transform->value.GetArray()) {
384 if (entry.IsNull()) {
386 "EditableText transform contains null value.");
387 return;
388 }
389 editabletext_transform_[i / 4][i % 4] = entry.GetDouble();
390 ++i;
391 }
392 Rect transformed_rect = GetCursorRect();
393 view->OnCursorRectUpdated(transformed_rect);
394 } else {
395 result->NotImplemented();
396 return;
397 }
398 // All error conditions return early, so if nothing has gone wrong indicate
399 // success.
400 result->Success();
401}
402
403Rect TextInputPlugin::GetCursorRect() const {
404 Point transformed_point = {
405 composing_rect_.left() * editabletext_transform_[0][0] +
406 composing_rect_.top() * editabletext_transform_[1][0] +
407 editabletext_transform_[3][0],
408 composing_rect_.left() * editabletext_transform_[0][1] +
409 composing_rect_.top() * editabletext_transform_[1][1] +
410 editabletext_transform_[3][1]};
411 return {transformed_point, composing_rect_.size()};
412}
413
414void TextInputPlugin::SendStateUpdate(const TextInputModel& model) {
415 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
416 auto& allocator = args->GetAllocator();
417 args->PushBack(client_id_, allocator);
418
419 TextRange selection = model.selection();
420 rapidjson::Value editing_state(rapidjson::kObjectType);
421 editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
422 allocator);
423 editing_state.AddMember(kSelectionBaseKey, selection.base(), allocator);
424 editing_state.AddMember(kSelectionExtentKey, selection.extent(), allocator);
425 editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
426
427 int composing_base = model.composing() ? model.composing_range().base() : -1;
428 int composing_extent =
429 model.composing() ? model.composing_range().extent() : -1;
430 editing_state.AddMember(kComposingBaseKey, composing_base, allocator);
431 editing_state.AddMember(kComposingExtentKey, composing_extent, allocator);
432 editing_state.AddMember(
433 kTextKey, rapidjson::Value(model.GetText(), allocator).Move(), allocator);
434 args->PushBack(editing_state, allocator);
435
436 channel_->InvokeMethod(kUpdateEditingStateMethod, std::move(args));
437}
438
439void TextInputPlugin::SendStateUpdateWithDelta(const TextInputModel& model,
440 const TextEditingDelta* delta) {
441 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
442 auto& allocator = args->GetAllocator();
443 args->PushBack(client_id_, allocator);
444
445 rapidjson::Value object(rapidjson::kObjectType);
446 rapidjson::Value deltas(rapidjson::kArrayType);
447 rapidjson::Value deltaJson(rapidjson::kObjectType);
448
449 deltaJson.AddMember(kDeltaOldTextKey, delta->old_text(), allocator);
450 deltaJson.AddMember(kDeltaTextKey, delta->delta_text(), allocator);
451 deltaJson.AddMember(kDeltaStartKey, delta->delta_start(), allocator);
452 deltaJson.AddMember(kDeltaEndKey, delta->delta_end(), allocator);
453
454 TextRange selection = model.selection();
455 deltaJson.AddMember(kSelectionAffinityKey, kAffinityDownstream, allocator);
456 deltaJson.AddMember(kSelectionBaseKey, selection.base(), allocator);
457 deltaJson.AddMember(kSelectionExtentKey, selection.extent(), allocator);
458 deltaJson.AddMember(kSelectionIsDirectionalKey, false, allocator);
459
460 int composing_base = model.composing() ? model.composing_range().base() : -1;
461 int composing_extent =
462 model.composing() ? model.composing_range().extent() : -1;
463 deltaJson.AddMember(kComposingBaseKey, composing_base, allocator);
464 deltaJson.AddMember(kComposingExtentKey, composing_extent, allocator);
465
466 deltas.PushBack(deltaJson, allocator);
467 object.AddMember(kDeltasKey, deltas, allocator);
468 args->PushBack(object, allocator);
469
470 channel_->InvokeMethod(kUpdateEditingStateWithDeltasMethod, std::move(args));
471}
472
473void TextInputPlugin::EnterPressed(TextInputModel* model) {
474 if (input_type_ == kMultilineInputType &&
475 input_action_ == kInputActionNewline) {
476 std::u16string text_before_change = fml::Utf8ToUtf16(model->GetText());
477 TextRange selection_before_change = model->selection();
478 model->AddText(u"\n");
479 if (enable_delta_model) {
480 TextEditingDelta delta(text_before_change, selection_before_change,
481 u"\n");
482 SendStateUpdateWithDelta(*model, &delta);
483 } else {
484 SendStateUpdate(*model);
485 }
486 }
487 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
488 auto& allocator = args->GetAllocator();
489 args->PushBack(client_id_, allocator);
490 args->PushBack(rapidjson::Value(input_action_, allocator).Move(), allocator);
491
492 channel_->InvokeMethod(kPerformActionMethod, std::move(args));
493}
494
495} // namespace flutter
FlutterWindowsView * view(FlutterViewId view_id) const
double top() const
Definition: geometry.h:64
Size size() const
Definition: geometry.h:70
double left() const
Definition: geometry.h:63
void KeyboardHook(GLFWwindow *window, int key, int scancode, int action, int mods) override
TextInputPlugin(flutter::BinaryMessenger *messenger)
virtual void ComposeChangeHook(const std::u16string &text, int cursor_pos)
virtual void TextHook(const std::u16string &text)
size_t start() const
Definition: text_range.h:42
size_t length() const
Definition: text_range.h:74
FlutterEngine engine
Definition: main.cc:68
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS G_MODULE_EXPORT FlMethodCall * method_call
GAsyncResult * result
static float min(float r, float g, float b)
Definition: hsl.cpp:48
std::u16string text
double y
double x
def call(args)
Definition: dom.py:159
constexpr FlutterViewId kImplicitViewId
std::u16string Utf8ToUtf16(const std::string_view string)
TRect< Scalar > Rect
Definition: rect.h:769
TPoint< Scalar > Point
Definition: point.h:322
SkRange< size_t > TextRange
Definition: TextStyle.h:337
Definition: ref_ptr.h:256
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition: p3.cpp:47
int32_t height
int32_t width
A change in the state of an input field.
static constexpr char kClearClientMethod[]
static constexpr char kSetEditableSizeAndTransform[]
static constexpr char kSetMarkedTextRect[]
static constexpr char kEnableDeltaModel[]
static constexpr char kSelectionIsDirectionalKey[]
static constexpr char kDeltaEndKey[]
static constexpr char kHideMethod[]
static constexpr char kMultilineInputType[]
static constexpr char kDeltaOldTextKey[]
static constexpr char kWidthKey[]
static constexpr char kSetEditingStateMethod[]
static constexpr char kSelectionExtentKey[]
static constexpr char kHeightKey[]
static constexpr char kDeltaTextKey[]
static constexpr char kDeltasKey[]
static constexpr char kTextInputType[]
static constexpr char kChannelName[]
static constexpr char kXKey[]
static constexpr char kInputActionNewline[]
static constexpr char kInternalConsistencyError[]
static constexpr char kBadArgumentError[]
static constexpr char kSetClientMethod[]
static constexpr char kUpdateEditingStateMethod[]
static constexpr char kPerformActionMethod[]
static constexpr char kYKey[]
static constexpr char kShowMethod[]
static constexpr char kComposingExtentKey[]
static constexpr char kTextInputAction[]
static constexpr char kTransformKey[]
static constexpr char kComposingBaseKey[]
static constexpr char kTextInputTypeName[]
static constexpr char kUpdateEditingStateWithDeltasMethod[]
static constexpr char kAffinityDownstream[]
static constexpr char kSelectionAffinityKey[]
static constexpr char kTextKey[]
static constexpr char kSelectionBaseKey[]
static constexpr char kDeltaStartKey[]