1 // Copyright 2018 The Chromium 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 "chromeos/services/ime/input_engine.h"
6 
7 #include "base/notreached.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "base/values.h"
11 #include "chromeos/services/ime/public/cpp/rulebased/engine.h"
12 
13 namespace chromeos {
14 namespace ime {
15 
16 namespace {
17 
GetIdFromImeSpec(const std::string & ime_spec)18 std::string GetIdFromImeSpec(const std::string& ime_spec) {
19   static const std::string kPrefix("m17n:");
20   return base::StartsWith(ime_spec, kPrefix, base::CompareCase::SENSITIVE)
21              ? ime_spec.substr(kPrefix.length())
22              : std::string();
23 }
24 
GenerateModifierValueForRulebased(const mojom::ModifierStatePtr & modifier_state,bool isAltRightDown)25 uint8_t GenerateModifierValueForRulebased(
26     const mojom::ModifierStatePtr& modifier_state,
27     bool isAltRightDown) {
28   uint8_t modifiers = 0;
29   if (modifier_state->shift)
30     modifiers |= rulebased::MODIFIER_SHIFT;
31   if (modifier_state->alt_graph || isAltRightDown)
32     modifiers |= rulebased::MODIFIER_ALTGR;
33   if (modifier_state->caps_lock)
34     modifiers |= rulebased::MODIFIER_CAPSLOCK;
35   return modifiers;
36 }
37 
GenerateKeypressResponseForRulebased(rulebased::ProcessKeyResult & process_key_result)38 mojom::KeypressResponseForRulebasedPtr GenerateKeypressResponseForRulebased(
39     rulebased::ProcessKeyResult& process_key_result) {
40   mojom::KeypressResponseForRulebasedPtr keypress_response =
41       mojom::KeypressResponseForRulebased::New();
42   keypress_response->result = process_key_result.key_handled;
43   if (!process_key_result.commit_text.empty()) {
44     keypress_response->operations.push_back(mojom::OperationForRulebased::New(
45         mojom::OperationMethodForRulebased::COMMIT_TEXT,
46         process_key_result.commit_text));
47   }
48   // Need to add the setComposition operation to the result when the key is
49   // handled and commit_text and composition_text are both empty.
50   // That is the case of using Backspace to delete the last character in
51   // composition.
52   if (!process_key_result.composition_text.empty() ||
53       (process_key_result.key_handled &&
54        process_key_result.commit_text.empty())) {
55     keypress_response->operations.push_back(mojom::OperationForRulebased::New(
56         mojom::OperationMethodForRulebased::SET_COMPOSITION,
57         process_key_result.composition_text));
58   }
59   return keypress_response;
60 }
61 
IsModifierKey(const std::string & key_code)62 bool IsModifierKey(const std::string& key_code) {
63   return key_code == "AltLeft" || key_code == "AltRight" ||
64          key_code == "ShiftLeft" || key_code == "ShiftRight" ||
65          key_code == "ControlLeft" || key_code == "ControlRight" ||
66          key_code == "CapsLock";
67 }
68 
69 }  // namespace
70 
InputEngineContext(const std::string & ime)71 InputEngineContext::InputEngineContext(const std::string& ime) : ime_spec(ime) {
72   // The |ime_spec|'s format for rule based imes is: "m17n:<id>".
73   std::string id = GetIdFromImeSpec(ime_spec);
74   if (rulebased::Engine::IsImeSupported(id)) {
75     engine = std::make_unique<rulebased::Engine>();
76     engine->Activate(id);
77   }
78 }
79 
~InputEngineContext()80 InputEngineContext::~InputEngineContext() {}
81 
InputEngine()82 InputEngine::InputEngine() {}
83 
~InputEngine()84 InputEngine::~InputEngine() {}
85 
BindRequest(const std::string & ime_spec,mojo::PendingReceiver<mojom::InputChannel> receiver,mojo::PendingRemote<mojom::InputChannel> remote,const std::vector<uint8_t> & extra)86 bool InputEngine::BindRequest(
87     const std::string& ime_spec,
88     mojo::PendingReceiver<mojom::InputChannel> receiver,
89     mojo::PendingRemote<mojom::InputChannel> remote,
90     const std::vector<uint8_t>& extra) {
91   if (!IsImeSupportedByRulebased(ime_spec))
92     return false;
93 
94   channel_receivers_.Add(this, std::move(receiver),
95                          std::make_unique<InputEngineContext>(ime_spec));
96 
97   return true;
98   // TODO(https://crbug.com/837156): Registry connection error handler.
99 }
100 
IsImeSupportedByRulebased(const std::string & ime_spec)101 bool InputEngine::IsImeSupportedByRulebased(const std::string& ime_spec) {
102   return rulebased::Engine::IsImeSupported(GetIdFromImeSpec(ime_spec));
103 }
104 
ProcessMessage(const std::vector<uint8_t> & message,ProcessMessageCallback callback)105 void InputEngine::ProcessMessage(const std::vector<uint8_t>& message,
106                                  ProcessMessageCallback callback) {
107   NOTIMPLEMENTED();  // Protobuf message is not used in the rulebased engine.
108 }
109 
OnInputMethodChanged(const std::string & engine_id)110 void InputEngine::OnInputMethodChanged(const std::string& engine_id) {
111   NOTIMPLEMENTED();  // Not used in the rulebased engine.
112 }
113 
OnFocus(mojom::InputFieldInfoPtr input_field_info)114 void InputEngine::OnFocus(mojom::InputFieldInfoPtr input_field_info) {
115   NOTIMPLEMENTED();  // Not used in the rulebased engine.
116 }
117 
OnBlur()118 void InputEngine::OnBlur() {
119   NOTIMPLEMENTED();  // Not used in the rulebased engine.
120 }
121 
OnSurroundingTextChanged(const std::string & text,uint32_t offset,mojom::SelectionRangePtr selection_range)122 void InputEngine::OnSurroundingTextChanged(
123     const std::string& text,
124     uint32_t offset,
125     mojom::SelectionRangePtr selection_range) {
126   NOTIMPLEMENTED();  // Not used in the rulebased engine.
127 }
128 
OnCompositionCanceled()129 void InputEngine::OnCompositionCanceled() {
130   NOTIMPLEMENTED();  // Not used in the rulebased engine.
131 }
132 
ProcessKeypressForRulebased(mojom::PhysicalKeyEventPtr event,ProcessKeypressForRulebasedCallback callback)133 void InputEngine::ProcessKeypressForRulebased(
134     mojom::PhysicalKeyEventPtr event,
135     ProcessKeypressForRulebasedCallback callback) {
136   auto& context = channel_receivers_.current_context();
137   auto& engine = context.get()->engine;
138 
139   // According to the W3C spec, |altKey| is false if the AltGr key
140   // is pressed [1]. However, all rule-based input methods on Chrome OS use
141   // the US QWERTY layout as a base layout, with AltGr implemented at this
142   // layer. This means the right Alt key reports as being a normal Alt key, so
143   // |altKey| is true. Thus, we need to take |altKey| and exclude the
144   // right Alt key to determine the status of the "true" Alt key.
145   // [1] https://www.w3.org/TR/uievents-key/#keys-modifier
146   // TODO(https://crbug.com/1014778): Change the base layouts for the
147   // rule-based input methods so that |altKey| is false when AltGr is pressed.
148   if (event->code == "AltRight") {
149     isAltRightDown_ = event->type == mojom::KeyEventType::kKeyDown;
150   }
151 
152   const bool isAltDown = event->modifier_state->alt && !isAltRightDown_;
153 
154   // - Shift/AltRight/Caps/Ctrl are modifier keys for the characters which the
155   // Mojo service may accept, but don't send the keys themselves to Mojo.
156   // - Ctrl+? and Alt+? are shortcut keys, so don't send them to the rule based
157   // engine.
158   if (!engine || event->type != mojom::KeyEventType::kKeyDown ||
159       (IsModifierKey(event->code) || event->modifier_state->control ||
160        isAltDown)) {
161     std::move(callback).Run(mojom::KeypressResponseForRulebased::New(
162         false, std::vector<mojom::OperationForRulebasedPtr>(0)));
163     return;
164   }
165 
166   rulebased::ProcessKeyResult process_key_result = engine->ProcessKey(
167       event->code, GenerateModifierValueForRulebased(event->modifier_state,
168                                                      isAltRightDown_));
169   mojom::KeypressResponseForRulebasedPtr keypress_response =
170       GenerateKeypressResponseForRulebased(process_key_result);
171 
172   std::move(callback).Run(std::move(keypress_response));
173 }
174 
OnKeyEvent(mojom::PhysicalKeyEventPtr event,OnKeyEventCallback callback)175 void InputEngine::OnKeyEvent(mojom::PhysicalKeyEventPtr event,
176                              OnKeyEventCallback callback) {
177   NOTIMPLEMENTED();  // Not used in the rulebased engine.
178 }
179 
ResetForRulebased()180 void InputEngine::ResetForRulebased() {
181   auto& context = channel_receivers_.current_context();
182   auto& engine = context.get()->engine;
183   // TODO(https://crbug.com/1633694) Handle the case when the engine is not
184   // defined
185   if (engine) {
186     engine->Reset();
187   }
188   isAltRightDown_ = false;
189 }
190 
GetRulebasedKeypressCountForTesting(GetRulebasedKeypressCountForTestingCallback callback)191 void InputEngine::GetRulebasedKeypressCountForTesting(
192     GetRulebasedKeypressCountForTestingCallback callback) {
193   auto& context = channel_receivers_.current_context();
194   auto& engine = context.get()->engine;
195   std::move(callback).Run(engine ? engine->process_key_count() : -1);
196 }
197 
CommitText(const std::string & text)198 void InputEngine::CommitText(const std::string& text) {
199   NOTIMPLEMENTED();  // Not used in the rulebased engine.
200 }
201 
SetComposition(const std::string & text)202 void InputEngine::SetComposition(const std::string& text) {
203   NOTIMPLEMENTED();  // Not used in the rulebased engine.
204 }
205 
SetCompositionRange(uint32_t start,uint32_t end)206 void InputEngine::SetCompositionRange(uint32_t start, uint32_t end) {
207   NOTIMPLEMENTED();  // Not used in the rulebased engine.
208 }
209 
FinishComposition()210 void InputEngine::FinishComposition() {
211   NOTIMPLEMENTED();  // Not used in the rulebased engine.
212 }
213 
DeleteSurroundingText(uint32_t num_bytes_before_cursor,uint32_t num_bytes_after_cursor)214 void InputEngine::DeleteSurroundingText(uint32_t num_bytes_before_cursor,
215                                         uint32_t num_bytes_after_cursor) {
216   NOTIMPLEMENTED();  // Not used in the rulebased engine.
217 }
218 
219 }  // namespace ime
220 }  // namespace chromeos
221