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