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/glfw/text_input_plugin.h"
6
7#include <cstdint>
8#include <iostream>
9
10#include "flutter/shell/platform/common/json_method_codec.h"
11
12static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState";
13static constexpr char kClearClientMethod[] = "TextInput.clearClient";
14static constexpr char kSetClientMethod[] = "TextInput.setClient";
15static constexpr char kShowMethod[] = "TextInput.show";
16static constexpr char kHideMethod[] = "TextInput.hide";
17
18static constexpr char kMultilineInputType[] = "TextInputType.multiline";
19
20static constexpr char kUpdateEditingStateMethod[] =
21 "TextInputClient.updateEditingState";
22static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
23
24static constexpr char kTextInputAction[] = "inputAction";
25static constexpr char kTextInputType[] = "inputType";
26static constexpr char kTextInputTypeName[] = "name";
27static constexpr char kComposingBaseKey[] = "composingBase";
28static constexpr char kComposingExtentKey[] = "composingExtent";
29static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
30static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
31static constexpr char kSelectionBaseKey[] = "selectionBase";
32static constexpr char kSelectionExtentKey[] = "selectionExtent";
33static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
34static constexpr char kTextKey[] = "text";
35
36static constexpr char kChannelName[] = "flutter/textinput";
37
38static constexpr char kBadArgumentError[] = "Bad Arguments";
39static constexpr char kInternalConsistencyError[] =
40 "Internal Consistency Error";
41
42namespace flutter {
43
44void TextInputPlugin::CharHook(GLFWwindow* window, unsigned int code_point) {
45 if (active_model_ == nullptr) {
46 return;
47 }
48 active_model_->AddCodePoint(code_point);
49 SendStateUpdate(*active_model_);
50}
51
53 int key,
54 int scancode,
55 int action,
56 int mods) {
57 if (active_model_ == nullptr) {
58 return;
59 }
60 if (action == GLFW_PRESS || action == GLFW_REPEAT) {
61 switch (key) {
62 case GLFW_KEY_LEFT:
63 if (active_model_->MoveCursorBack()) {
64 SendStateUpdate(*active_model_);
65 }
66 break;
67 case GLFW_KEY_RIGHT:
68 if (active_model_->MoveCursorForward()) {
69 SendStateUpdate(*active_model_);
70 }
71 break;
72 case GLFW_KEY_END:
73 active_model_->MoveCursorToEnd();
74 SendStateUpdate(*active_model_);
75 break;
76 case GLFW_KEY_HOME:
77 active_model_->MoveCursorToBeginning();
78 SendStateUpdate(*active_model_);
79 break;
80 case GLFW_KEY_BACKSPACE:
81 if (active_model_->Backspace()) {
82 SendStateUpdate(*active_model_);
83 }
84 break;
85 case GLFW_KEY_DELETE:
86 if (active_model_->Delete()) {
87 SendStateUpdate(*active_model_);
88 }
89 break;
90 case GLFW_KEY_ENTER:
91 EnterPressed(active_model_.get());
92 break;
93 default:
94 break;
95 }
96 }
97}
98
100 : channel_(std::make_unique<flutter::MethodChannel<rapidjson::Document>>(
101 messenger,
103 &flutter::JsonMethodCodec::GetInstance())),
104 active_model_(nullptr) {
105 channel_->SetMethodCallHandler(
106 [this](
109 HandleMethodCall(call, std::move(result));
110 });
111}
112
114
115void TextInputPlugin::HandleMethodCall(
118 const std::string& method = method_call.method_name();
119
120 if (method.compare(kShowMethod) == 0 || method.compare(kHideMethod) == 0) {
121 // These methods are no-ops.
122 } else if (method.compare(kClearClientMethod) == 0) {
123 active_model_ = nullptr;
124 } else if (method.compare(kSetClientMethod) == 0) {
125 if (!method_call.arguments() || method_call.arguments()->IsNull()) {
126 result->Error(kBadArgumentError, "Method invoked without args");
127 return;
128 }
129 const rapidjson::Document& args = *method_call.arguments();
130
131 // TODO(awdavies): There's quite a wealth of arguments supplied with this
132 // method, and they should be inspected/used.
133 const rapidjson::Value& client_id_json = args[0];
134 const rapidjson::Value& client_config = args[1];
135 if (client_id_json.IsNull()) {
136 result->Error(kBadArgumentError, "Could not set client, ID is null.");
137 return;
138 }
139 if (client_config.IsNull()) {
141 "Could not set client, missing arguments.");
142 return;
143 }
144 client_id_ = client_id_json.GetInt();
145 input_action_ = "";
146 auto input_action_json = client_config.FindMember(kTextInputAction);
147 if (input_action_json != client_config.MemberEnd() &&
148 input_action_json->value.IsString()) {
149 input_action_ = input_action_json->value.GetString();
150 }
151 input_type_ = "";
152 auto input_type_info_json = client_config.FindMember(kTextInputType);
153 if (input_type_info_json != client_config.MemberEnd() &&
154 input_type_info_json->value.IsObject()) {
155 auto input_type_json =
156 input_type_info_json->value.FindMember(kTextInputTypeName);
157 if (input_type_json != input_type_info_json->value.MemberEnd() &&
158 input_type_json->value.IsString()) {
159 input_type_ = input_type_json->value.GetString();
160 }
161 }
162 active_model_ = std::make_unique<TextInputModel>();
163 } else if (method.compare(kSetEditingStateMethod) == 0) {
164 if (!method_call.arguments() || method_call.arguments()->IsNull()) {
165 result->Error(kBadArgumentError, "Method invoked without args");
166 return;
167 }
168 const rapidjson::Document& args = *method_call.arguments();
169
170 if (active_model_ == nullptr) {
171 result->Error(
173 "Set editing state has been invoked, but no client is set.");
174 return;
175 }
176 auto text = args.FindMember(kTextKey);
177 if (text == args.MemberEnd() || text->value.IsNull()) {
179 "Set editing state has been invoked, but without text.");
180 return;
181 }
182 auto selection_base = args.FindMember(kSelectionBaseKey);
183 auto selection_extent = args.FindMember(kSelectionExtentKey);
184 if (selection_base == args.MemberEnd() || selection_base->value.IsNull() ||
185 selection_extent == args.MemberEnd() ||
186 selection_extent->value.IsNull()) {
188 "Selection base/extent values invalid.");
189 return;
190 }
191 // Flutter uses -1/-1 for invalid; translate that to 0/0 for the model.
192 int base = selection_base->value.GetInt();
193 int extent = selection_extent->value.GetInt();
194 if (base == -1 && extent == -1) {
195 base = extent = 0;
196 }
197 active_model_->SetText(text->value.GetString());
198 active_model_->SetSelection(TextRange(base, extent));
199 } else {
200 result->NotImplemented();
201 return;
202 }
203 // All error conditions return early, so if nothing has gone wrong indicate
204 // success.
205 result->Success();
206}
207
208void TextInputPlugin::SendStateUpdate(const TextInputModel& model) {
209 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
210 auto& allocator = args->GetAllocator();
211 args->PushBack(client_id_, allocator);
212
213 TextRange selection = model.selection();
214 rapidjson::Value editing_state(rapidjson::kObjectType);
215 editing_state.AddMember(kComposingBaseKey, -1, allocator);
216 editing_state.AddMember(kComposingExtentKey, -1, allocator);
217 editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
218 allocator);
219 editing_state.AddMember(kSelectionBaseKey, selection.base(), allocator);
220 editing_state.AddMember(kSelectionExtentKey, selection.extent(), allocator);
221 editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
222 editing_state.AddMember(
223 kTextKey, rapidjson::Value(model.GetText(), allocator).Move(), allocator);
224 args->PushBack(editing_state, allocator);
225
226 channel_->InvokeMethod(kUpdateEditingStateMethod, std::move(args));
227}
228
229void TextInputPlugin::EnterPressed(TextInputModel* model) {
230 if (input_type_ == kMultilineInputType) {
231 model->AddCodePoint('\n');
232 SendStateUpdate(*model);
233 }
234 auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
235 auto& allocator = args->GetAllocator();
236 args->PushBack(client_id_, allocator);
237 args->PushBack(rapidjson::Value(input_action_, allocator).Move(), allocator);
238
239 channel_->InvokeMethod(kPerformActionMethod, std::move(args));
240}
241
242} // namespace flutter
void KeyboardHook(GLFWwindow *window, int key, int scancode, int action, int mods) override
TextInputPlugin(flutter::BinaryMessenger *messenger)
void CharHook(GLFWwindow *window, unsigned int code_point) override
GLFWwindow * window
Definition: main.cc:45
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS G_MODULE_EXPORT FlMethodCall * method_call
GAsyncResult * result
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[]
std::u16string text
def call(args)
Definition: dom.py:159
SkRange< size_t > TextRange
Definition: TextStyle.h:337
Definition: ref_ptr.h:256