| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/services/ime/input_engine.h" |
| |
| #include "base/json/json_reader.h" |
| #include "base/json/string_escape.h" |
| #include "base/logging.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "chromeos/services/ime/public/cpp/rulebased/engine.h" |
| |
| namespace chromeos { |
| namespace ime { |
| |
| namespace { |
| |
| std::string GetIdFromImeSpec(const std::string& ime_spec) { |
| static const std::string kPrefix("m17n:"); |
| return base::StartsWith(ime_spec, kPrefix, base::CompareCase::SENSITIVE) |
| ? ime_spec.substr(kPrefix.length()) |
| : std::string(); |
| } |
| |
| } // namespace |
| |
| InputEngineContext::InputEngineContext(const std::string& ime) : ime_spec(ime) { |
| // The |ime_spec|'s format for rule based imes is: "m17n:<id>". |
| std::string id = GetIdFromImeSpec(ime_spec); |
| if (rulebased::Engine::IsImeSupported(id)) { |
| engine = std::make_unique<rulebased::Engine>(); |
| engine->Activate(id); |
| } |
| } |
| |
| InputEngineContext::~InputEngineContext() {} |
| |
| InputEngine::InputEngine() {} |
| |
| InputEngine::~InputEngine() {} |
| |
| bool InputEngine::BindRequest(const std::string& ime_spec, |
| mojom::InputChannelRequest request, |
| mojom::InputChannelPtr client, |
| const std::vector<uint8_t>& extra) { |
| if (!IsImeSupported(ime_spec)) |
| return false; |
| |
| channel_bindings_.AddBinding(this, std::move(request), |
| std::make_unique<InputEngineContext>(ime_spec)); |
| |
| return true; |
| // TODO(https://crbug.com/837156): Registry connection error handler. |
| } |
| |
| bool InputEngine::IsImeSupported(const std::string& ime_spec) { |
| return rulebased::Engine::IsImeSupported(GetIdFromImeSpec(ime_spec)); |
| } |
| |
| void InputEngine::ProcessText(const std::string& message, |
| ProcessTextCallback callback) { |
| auto& context = channel_bindings_.dispatch_context(); |
| std::string result = Process(message, context.get()); |
| std::move(callback).Run(result); |
| } |
| |
| void InputEngine::ProcessMessage(const std::vector<uint8_t>& message, |
| ProcessMessageCallback callback) { |
| NOTIMPLEMENTED(); // Protobuf message is not used in the rulebased engine. |
| } |
| |
| std::string InputEngine::Process(const std::string& message, |
| const InputEngineContext* context) { |
| std::string ime_spec = context->ime_spec; |
| auto& engine = context->engine; |
| if (!engine) |
| return std::string(); |
| |
| const char kFalseResponse[] = "{\"result\":false}"; |
| |
| // The request message is in JSON format as: |
| // { |
| // 'method': <string>, // 'reset' or 'keyEvent' |
| // 'type': <string>, // 'keydown' or 'keyup' |
| // 'code': <string>, // e.g. 'KeyA', 'Backspace', etc. |
| // 'shift': <boolean>, |
| // 'altgr': <boolean>, |
| // 'caps': <boolean>, |
| // 'ctrl': <boolean>, |
| // 'alt': <boolean> |
| // } |
| // TODO(shuchen): make parser/writer util class for the JSON-based protocol. |
| int error_code; |
| std::string error_string; |
| std::unique_ptr<base::Value> message_value = |
| base::JSONReader::ReadAndReturnError(message, base::JSON_PARSE_RFC, |
| &error_code, &error_string); |
| if (!message_value) { |
| LOG(ERROR) << "Read message error: " << error_code << "; " << error_string; |
| return kFalseResponse; |
| } |
| base::Value* method = message_value->FindKey("method"); |
| if (!method) |
| return kFalseResponse; |
| |
| const std::string& method_str = method->GetString(); |
| if (method_str == "countKey") { |
| return std::to_string(engine->process_key_count()); |
| } |
| |
| if (method_str == "reset") { |
| engine->Reset(); |
| return "{\"result\":true}"; |
| } |
| |
| if (method_str == "keyEvent") { |
| base::Value* type = message_value->FindKey("type"); |
| if (!type || type->GetString() != "keydown") |
| return kFalseResponse; |
| } |
| |
| base::Value* code = message_value->FindKey("code"); |
| base::Value* shift = message_value->FindKey("shift"); |
| base::Value* altgr = message_value->FindKey("altgr"); |
| base::Value* caps = message_value->FindKey("caps"); |
| if (!code || !shift || !altgr || !caps) |
| return kFalseResponse; |
| |
| uint8_t modifiers = 0; |
| if (shift->GetBool()) |
| modifiers |= rulebased::MODIFIER_SHIFT; |
| if (altgr->GetBool()) |
| modifiers |= rulebased::MODIFIER_ALTGR; |
| if (caps->GetBool()) |
| modifiers |= rulebased::MODIFIER_CAPSLOCK; |
| |
| rulebased::ProcessKeyResult res = |
| engine->ProcessKey(code->GetString(), modifiers); |
| |
| // The response message is in JSON format as: |
| // { |
| // 'result': <boolean>, |
| // 'operations': [ |
| // { |
| // 'method': 'commitText|setComposition', |
| // 'arguments': <string> |
| // } |
| // ] |
| // } |
| std::string response_str = "{\"result\":"; |
| response_str += (res.key_handled ? "true" : "false"); |
| std::vector<std::string> ops; |
| if (!res.commit_text.empty()) { |
| std::string commit_text; |
| base::EscapeJSONString(res.commit_text, false, &commit_text); |
| ops.push_back("{\"method\":\"commitText\",\"arguments\":[\"" + commit_text + |
| "\"]}"); |
| } |
| // Need to add the setComposition operation to the result when the key is |
| // handled and commit_text and composition_text are both empty. |
| // That is the case of using Backspace to delete the last character in |
| // composition. |
| if (!res.composition_text.empty() || |
| (res.key_handled && res.commit_text.empty())) { |
| std::string composition_text; |
| base::EscapeJSONString(res.composition_text, false, &composition_text); |
| ops.push_back("{\"method\":\"setComposition\",\"arguments\":[\"" + |
| composition_text + "\"]}"); |
| } |
| if (ops.empty()) { |
| response_str += "}"; |
| } else { |
| response_str += ",\"operations\":["; |
| for (size_t i = 0; i < ops.size(); ++i) { |
| if (i > 0) |
| response_str += ","; |
| response_str += ops[i]; |
| } |
| response_str += "]}"; |
| } |
| |
| return response_str; |
| } |
| |
| } // namespace ime |
| } // namespace chromeos |