blob: c08b5e24bc76a95fdbbb7cfc53b72a9cf0eab1d2 [file] [log] [blame]
// 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