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/cpp/json_method_codec.h"
11 
12 static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState";
13 static constexpr char kClearClientMethod[] = "TextInput.clearClient";
14 static constexpr char kSetClientMethod[] = "TextInput.setClient";
15 static constexpr char kShowMethod[] = "TextInput.show";
16 static constexpr char kHideMethod[] = "TextInput.hide";
17 
18 static constexpr char kMultilineInputType[] = "TextInputType.multiline";
19 
20 static constexpr char kUpdateEditingStateMethod[] =
21  "TextInputClient.updateEditingState";
22 static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
23 
24 static constexpr char kTextInputAction[] = "inputAction";
25 static constexpr char kTextInputType[] = "inputType";
26 static constexpr char kTextInputTypeName[] = "name";
27 static constexpr char kComposingBaseKey[] = "composingBase";
28 static constexpr char kComposingExtentKey[] = "composingExtent";
29 static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
30 static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
31 static constexpr char kSelectionBaseKey[] = "selectionBase";
32 static constexpr char kSelectionExtentKey[] = "selectionExtent";
33 static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
34 static constexpr char kTextKey[] = "text";
35 
36 static constexpr char kChannelName[] = "flutter/textinput";
37 
38 static constexpr char kBadArgumentError[] = "Bad Arguments";
39 static constexpr char kInternalConsistencyError[] =
40  "Internal Consistency Error";
41 
42 namespace flutter {
43 
44 void 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 
52 void TextInputPlugin::KeyboardHook(GLFWwindow* window,
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,
102  kChannelName,
103  &flutter::JsonMethodCodec::GetInstance())),
104  active_model_(nullptr) {
105  channel_->SetMethodCallHandler(
106  [this](
108  std::unique_ptr<flutter::MethodResult<rapidjson::Document>> result) {
109  HandleMethodCall(call, std::move(result));
110  });
111 }
112 
114 
115 void TextInputPlugin::HandleMethodCall(
117  std::unique_ptr<flutter::MethodResult<rapidjson::Document>> result) {
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()) {
140  result->Error(kBadArgumentError,
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()) {
178  result->Error(kBadArgumentError,
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()) {
187  result->Error(kInternalConsistencyError,
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 
208 void 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 
229 void 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
static constexpr char kComposingBaseKey[]
G_BEGIN_DECLS FlValue * args
static constexpr char kTextInputAction[]
void CharHook(GLFWwindow *window, unsigned int code_point) override
static constexpr char kTextInputTypeName[]
G_BEGIN_DECLS FlMethodCall * method_call
static constexpr char kTextKey[]
size_t base() const
Definition: text_range.h:25
const T * arguments() const
Definition: method_call.h:34
static constexpr char kAffinityDownstream[]
static constexpr char kChannelName[]
static constexpr char kSelectionBaseKey[]
static constexpr char kSelectionExtentKey[]
Definition: ref_ptr.h:252
std::string GetText() const
void AddCodePoint(char32_t c)
static constexpr char kSelectionIsDirectionalKey[]
static constexpr char kMultilineInputType[]
static constexpr char kSelectionAffinityKey[]
static constexpr char kUpdateEditingStateMethod[]
static constexpr char kSetEditingStateMethod[]
static constexpr char kPerformActionMethod[]
static constexpr char kBadArgumentError[]
static constexpr char kTextInputType[]
static constexpr char kComposingExtentKey[]
static constexpr char kShowMethod[]
SemanticsAction action
static constexpr char kInternalConsistencyError[]
TextRange selection() const
void KeyboardHook(GLFWwindow *window, int key, int scancode, int action, int mods) override
size_t extent() const
Definition: text_range.h:31
static constexpr char kClearClientMethod[]
static constexpr char kSetClientMethod[]
const std::string & method_name() const
Definition: method_call.h:31
static constexpr char kHideMethod[]
TextInputPlugin(flutter::BinaryMessenger *messenger)