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