| // Copyright 2020 The Chromium OS 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 "runtime_probe/functions/input_device.h" |
| |
| #include <map> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/check_op.h> |
| #include <base/files/file_util.h> |
| #include <base/json/json_writer.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <pcrecpp.h> |
| |
| #include "runtime_probe/utils/file_utils.h" |
| |
| namespace runtime_probe { |
| |
| namespace { |
| constexpr auto kInputDevicesPath = "/proc/bus/input/devices"; |
| const pcrecpp::RE kEventPatternRe(R"(event[\d]+)"); |
| |
| using FieldType = std::pair<std::string, std::string>; |
| |
| const std::vector<FieldType> kTouchscreenI2cFields = { |
| {"name", "name"}, {"product", "hw_version"}, {"fw_version", "fw_version"}}; |
| const std::map<std::string, std::string> kTouchscreenI2cDriverToVid = { |
| {"elants_i2c", "04f3"}, {"raydium_ts", "27a3"}, {"atmel_ext_ts", "03eb"}}; |
| |
| void AppendInputDevice(base::Value* list_value, base::Value&& value) { |
| const auto* sysfs = value.FindStringKey("sysfs"); |
| if (sysfs) { |
| const auto path = base::StringPrintf("/sys%s", sysfs->c_str()); |
| value.RemoveKey("sysfs"); |
| value.SetStringKey("path", path); |
| } |
| list_value->Append(std::move(value)); |
| } |
| |
| base::Value LoadInputDevices() { |
| base::Value results(base::Value::Type::LIST); |
| std::string input_devices_str; |
| if (!base::ReadFileToString(base::FilePath(kInputDevicesPath), |
| &input_devices_str)) { |
| LOG(ERROR) << "Failed to read " << kInputDevicesPath << "."; |
| return results; |
| } |
| |
| base::Value data; |
| auto input_devices_lines = base::SplitStringPiece( |
| input_devices_str, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (const auto& line : input_devices_lines) { |
| if (line.length() < 3) { |
| DCHECK_EQ(line.length(), 0); |
| continue; |
| } |
| const auto& content = line.substr(3); |
| base::StringPairs keyVals; |
| switch (const auto prefix = line[0]; prefix) { |
| case 'I': { |
| if (data.is_dict() && !data.DictEmpty()) { |
| AppendInputDevice(&results, std::move(data)); |
| } |
| if (!base::SplitStringIntoKeyValuePairs(content, '=', ' ', &keyVals)) { |
| LOG(ERROR) << "Failed to parse input devices (I)."; |
| return base::Value(base::Value::Type::LIST); |
| } |
| data = base::Value(base::Value::Type::DICTIONARY); |
| for (const auto& [key, value] : keyVals) { |
| data.SetStringKey(base::ToLowerASCII(key), value); |
| } |
| break; |
| } |
| case 'N': |
| case 'S': { |
| if (!base::SplitStringIntoKeyValuePairs(content, '=', '\n', &keyVals)) { |
| LOG(ERROR) << "Failed to parse input devices (N/S)."; |
| return base::Value(base::Value::Type::LIST); |
| } |
| const auto& [key, value] = keyVals[0]; |
| data.SetStringKey(base::ToLowerASCII(key), |
| base::TrimString(value, "\"", base::TRIM_ALL)); |
| break; |
| } |
| case 'H': { |
| if (!base::SplitStringIntoKeyValuePairs(content, '=', '\n', &keyVals)) { |
| LOG(ERROR) << "Failed to parse input devices (H)."; |
| return base::Value(base::Value::Type::LIST); |
| } |
| const auto& value = keyVals[0].second; |
| const auto& handlers = base::SplitStringPiece( |
| value, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| for (const auto& handler : handlers) { |
| if (kEventPatternRe.FullMatch(handler.as_string())) { |
| data.SetStringKey("event", handler); |
| break; |
| } |
| } |
| break; |
| } |
| default: { |
| break; |
| } |
| } |
| } |
| if (!data.DictEmpty()) { |
| AppendInputDevice(&results, std::move(data)); |
| } |
| return results; |
| } |
| |
| std::string GetDriverName(const base::FilePath& node_path) { |
| const auto driver_path = node_path.Append("driver"); |
| const auto real_driver_path = base::MakeAbsoluteFilePath(driver_path); |
| if (real_driver_path.value().length() == 0) |
| return ""; |
| const auto driver_name = real_driver_path.BaseName().value(); |
| return driver_name; |
| } |
| |
| void FixTouchscreenI2cDevices(base::Value* devices) { |
| for (auto& device : devices->GetList()) { |
| const auto* path = device.FindStringKey("path"); |
| if (!path) |
| continue; |
| |
| if (const auto* vid_old = device.FindStringKey("vendor"); |
| vid_old && *vid_old != "0000") |
| continue; |
| |
| const auto node_path = base::FilePath{*path}.Append("device"); |
| const auto driver_name = GetDriverName(node_path); |
| const auto entry = kTouchscreenI2cDriverToVid.find(driver_name); |
| if (entry == kTouchscreenI2cDriverToVid.end()) |
| continue; |
| |
| auto dict_value = MapFilesToDict(node_path, kTouchscreenI2cFields, {}); |
| if (!dict_value) { |
| DVLOG(1) << "touchscreen_i2c-specific fields do not exist on node \"" |
| << node_path << "\""; |
| continue; |
| } |
| |
| device.SetStringKey("vendor", entry->second); |
| device.MergeDictionary(&*dict_value); |
| } |
| } |
| |
| } // namespace |
| |
| InputDeviceFunction::DataType InputDeviceFunction::Eval() const { |
| auto json_output = InvokeHelperToJSON(); |
| if (!json_output) { |
| LOG(ERROR) << "Failed to invoke helper to retrieve sysfs results."; |
| return {}; |
| } |
| if (!json_output->is_list()) { |
| LOG(ERROR) << "Failed to parse json output as list."; |
| return {}; |
| } |
| |
| return DataType(json_output->TakeList()); |
| } |
| |
| int InputDeviceFunction::EvalInHelper(std::string* output) const { |
| auto results = LoadInputDevices(); |
| FixTouchscreenI2cDevices(&results); |
| |
| if (!base::JSONWriter::Write(results, output)) { |
| LOG(ERROR) << "Failed to serialize usb probed result to json string"; |
| return -1; |
| } |
| return 0; |
| } |
| |
| } // namespace runtime_probe |