* Copyright (c) 2023-2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fstream>
#include <unistd.h>
#include <libudev.h>
#include <linux/input.h>
#include "mmi_log.h"
#undef MMI_LOG_DOMAIN
#define MMI_LOG_DOMAIN MMI_LOG_SERVER
#undef MMI_LOG_TAG
#define MMI_LOG_TAG "MmiLibudev"
using namespace std::literals;
namespace {
constexpr int UTIL_PATH_SIZE { 1024 };
constexpr int UTIL_LINE_SIZE { 16384 };
bool StartsWith(std::string_view str, std::string_view prefix)
{
return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix;
}
bool ChopTail(std::string_view &str, char sep)
{
auto pos = str.rfind(sep);
if (pos == std::string_view::npos) {
return false;
}
str.remove_suffix(str.size() - pos);
return true;
}
std::string ResolveSymLink(const std::string &syspath)
{
constexpr auto backStr = "../"sv;
char linkTarget[UTIL_PATH_SIZE];
ssize_t len = readlink(syspath.c_str(), linkTarget, sizeof(linkTarget));
if (len <= 0 || len == static_cast<ssize_t>(sizeof(linkTarget))) {
return syspath;
}
std::string_view tail{ linkTarget, len };
int32_t back = 0;
for (; StartsWith(tail, backStr); back++) {
tail.remove_prefix(backStr.size());
}
std::string_view base = syspath;
for (int32_t i = 0; i <= back; i++) {
if (!ChopTail(base, '/')) {
return syspath;
}
}
return std::string{ base }.append("/").append(tail);
}
std::optional<std::string> GetLinkValue(const std::string &slink, const std::string &syspath)
{
auto path = syspath + "/" + slink;
char target[UTIL_PATH_SIZE];
ssize_t len = readlink(path.c_str(), target, sizeof(target));
if (len <= 0 || len == static_cast<ssize_t>(sizeof(target))) {
MMI_HILOGE("Failed to read link");
return std::nullopt;
}
std::string_view result{ target, len };
auto pos = result.rfind('/');
if (pos == std::string_view::npos) {
MMI_HILOGE("Failed to get link value");
return std::nullopt;
}
return std::string{ result.substr(pos + 1) };
}
class BitVector {
public:
using val_t = unsigned long;
explicit BitVector(const std::string &str)
{
std::istringstream ss{ str };
ss >> std::hex;
std::copy(std::istream_iterator<val_t>(ss), std::istream_iterator<val_t>(), std::back_inserter(bits_));
std::reverse(bits_.begin(), bits_.end());
}
[[nodiscard]] bool CheckBit(size_t idx) const
{
auto vidx = idx / (sizeof(val_t) * CHAR_BIT);
auto bidx = idx % (sizeof(val_t) * CHAR_BIT);
if (vidx >= bits_.size()) {
return false;
}
return (bits_[vidx] & (1ULL << bidx)) != 0;
}
private:
std::vector<val_t> bits_;
};
}
struct udev {};
typedef std::map<std::string, std::vector<std::string>> Propertys;
typedef std::map<std::string, Propertys> AllPropertys;
struct udev_device {
public:
udev_device(udev_device &) = delete;
udev_device(udev_device &&) = delete;
udev_device &operator = (udev_device &) = delete;
udev_device &operator = (udev_device &&) = delete;
static udev_device *NewFromSyspath(const std::string &syspathParam)
{
if (!StartsWith(syspathParam, "/sys/") || syspathParam.back() == '/') {
errno = EINVAL;
return nullptr;
}
std::string path = ResolveSymLink(syspathParam);
if (StartsWith(path, "/sys/devices/")) {
struct stat statbuf;
std::string filename = path + "/uevent";
if (stat(filename.c_str(), &statbuf) != 0) {
return nullptr;
}
} else {
return nullptr;
}
auto *inst = new udev_device;
inst->SetSyspath(std::move(path));
return inst;
}
static udev_device *NewFromDevnum(char type, dev_t devnum)
{
const char *typeStr = nullptr;
if (type == 'b') {
typeStr = "block";
} else if (type == 'c') {
typeStr = "char";
} else {
MMI_HILOGE("Param invalid");
errno = EINVAL;
return nullptr;
}
auto majStr = std::to_string(major(devnum));
auto minStr = std::to_string(minor(devnum));
return NewFromSyspath("/sys/dev/"s + typeStr + "/" + majStr + ":" + minStr);
}
static void AddPropertys(char type, dev_t devnum, const char *devnode)
{
const char *typeStr = nullptr;
if (type == 'b') {
typeStr = "block";
} else if (type == 'c') {
typeStr = "char";
} else {
MMI_HILOGE("Param invalid");
errno = EINVAL;
return;
}
auto majStr = std::to_string(major(devnum));
auto minStr = std::to_string(minor(devnum));
std::string syspathParam = "/sys/dev/"s + typeStr + "/" + majStr + ":" + minStr;
Propertys propertys;
if (IsFromSyspath(syspathParam)) {
AddProperty(syspathParam, propertys);
}
while (true) {
std::string_view path = syspathParam;
if (!ChopTail(path, '/')) {
break;
}
syspathParam = std::string {path};
if (IsFromSyspath(syspathParam)) {
AddProperty(syspathParam, propertys);
}
}
std::lock_guard<std::mutex> lock(mutex_);
allPropertys_[devnode] = propertys;
}
static bool IsFromSyspath(std::string& syspathParam)
{
if (!StartsWith(syspathParam, "/sys/") || syspathParam.back() == '/') {
errno = EINVAL;
return false;
}
std::string path = ResolveSymLink(syspathParam);
if (StartsWith(path, "/sys/devices/")) {
struct stat statbuf;
std::string filename = path + "/uevent";
if (stat(filename.c_str(), &statbuf) != 0) {
return false;
}
} else {
return false;
}
syspathParam = path;
return true;
}
static void AddProperty(const std::string& path, Propertys& propertys)
{
auto filename = path + "/uevent";
char realPath[PATH_MAX] = {};
CHKPV(realpath(filename.c_str(), realPath));
std::ifstream f(realPath, std::ios_base::in);
if (!f.is_open()) {
MMI_HILOGE("ReadUeventFile(): path:%{private}s, error:%{public}s", realPath, std::strerror(errno));
return;
}
char line[UTIL_LINE_SIZE];
while (f.getline(line, sizeof(line))) {
propertys[filename].push_back(line);
}
}
static void RemoveProperty(const char *devnode)
{
std::lock_guard<std::mutex> lock(mutex_);
allPropertys_.erase(devnode);
}
static void RecordDevNode(const char *devnode)
{
devnode_ = devnode;
}
void Ref()
{
refcount++;
}
void Unref()
{
if (--refcount <= 0) {
delete this;
}
}
udev_device *GetParent()
{
if (!parentDevice_.has_value()) {
parentDevice_ = NewFromChild(this);
}
if (parentDevice_.value() != nullptr) {
return *parentDevice_;
}
return nullptr;
}
const std::string &GetSyspath() const
{
return syspath;
}
const std::string &GetSysname() const
{
return sysname;
}
const std::string &GetDevnode()
{
return GetProperty("DEVNAME");
}
bool IsInitialized()
{
if (!ueventLoaded) {
ReadUeventFile();
}
return ueventLoaded;
}
udev_device *GetParentWithSubsystem(const std::string &subsystem)
{
udev_device *parent = GetParent();
while (parent != nullptr) {
auto parentSubsystem = parent->GetSubsystem();
if (parentSubsystem.has_value() && parentSubsystem.value() == subsystem) {
break;
}
parent = parent->GetParent();
}
if (parent == nullptr) {
errno = ENOENT;
}
return parent;
}
bool HasProperty(const std::string &key)
{
if (!ueventLoaded) {
ReadUeventFile();
}
return property_.find(key) != property_.end();
}
const std::string &GetProperty(const std::string &key)
{
if (!ueventLoaded) {
ReadUeventFile();
}
return property_[key];
}
private:
udev_device() = default;
~udev_device()
{
if (parentDevice_.has_value() && parentDevice_.value() != nullptr) {
parentDevice_.value()->Unref();
}
}
static udev_device *NewFromChild(udev_device *child)
{
std::string_view path = child->GetSyspath();
while (true) {
if (!ChopTail(path, '/')) {
break;
}
udev_device *parent = NewFromSyspath(std::string{ path });
if (parent != nullptr) {
return parent;
}
}
return nullptr;
}
void SetSyspath(std::string newSyspath)
{
syspath = std::move(newSyspath);
AddProperty("DEVPATH", syspath.substr(0, "/sys"sv.size()));
auto pos = syspath.rfind('/');
if (pos == std::string::npos) {
return;
}
sysname = syspath.substr(pos + 1);
for (char &c : sysname) {
if (c == '!') {
c = '/';
}
}
}
void AddPropertyFromString(const std::string &line)
{
auto pos = line.find('=');
if (pos == std::string::npos) {
return;
}
std::string key = line.substr(0, pos);
if (key == "DEVNAME") {
SetDevnode(line.substr(pos + 1));
return;
}
AddProperty(std::move(key), line.substr(pos + 1));
}
void ReadUeventFile()
{
if (ueventLoaded) {
return;
}
auto filename = syspath + "/uevent";
ueventLoaded = true;
std::lock_guard<std::mutex> lock(mutex_);
auto propertys = allPropertys_[devnode_];
auto lines = propertys[filename];
for (auto it = lines.begin(); it != lines.end(); it++) {
AddPropertyFromString(*it);
}
DispProperty();
MMI_HILOGI("devnode:%{public}s", devnode_.c_str());
AddProperty("DEVNAME", devnode_);
CheckInputProperties();
}
bool CheckAccel(const BitVector &ev, const BitVector &abs, const BitVector &prop)
{
bool hasKeys = ev.CheckBit(EV_KEY);
bool has3dCoordinates = abs.CheckBit(ABS_X) && abs.CheckBit(ABS_Y) && abs.CheckBit(ABS_Z);
bool isAccelerometer = prop.CheckBit(INPUT_PROP_ACCELEROMETER);
if (!hasKeys && has3dCoordinates) {
isAccelerometer = true;
}
if (isAccelerometer) {
SetInputProperty("ID_INPUT_ACCELEROMETER");
}
return isAccelerometer;
}
bool HasJoystickAxesOrButtons(const BitVector &abs, const BitVector &key)
{
bool hasJoystickAxesOrButtons = false;
if (!key.CheckBit(BTN_JOYSTICK - 1)) {
for (int32_t button = BTN_JOYSTICK; button < BTN_DIGI && !hasJoystickAxesOrButtons; button++) {
hasJoystickAxesOrButtons = key.CheckBit(button);
}
for (int32_t button = BTN_TRIGGER_HAPPY1; button <= BTN_TRIGGER_HAPPY40 && !hasJoystickAxesOrButtons;
button++) {
hasJoystickAxesOrButtons = key.CheckBit(button);
}
for (int32_t button = BTN_DPAD_UP; button <= BTN_DPAD_RIGHT && !hasJoystickAxesOrButtons; button++) {
hasJoystickAxesOrButtons = key.CheckBit(button);
}
}
for (int32_t axis = ABS_RX; axis < ABS_PRESSURE && !hasJoystickAxesOrButtons; axis++) {
hasJoystickAxesOrButtons = abs.CheckBit(axis);
}
return hasJoystickAxesOrButtons;
}
bool CheckPointingStick(const BitVector &prop)
{
if (prop.CheckBit(INPUT_PROP_POINTING_STICK)) {
SetInputProperty("ID_INPUT_POINTINGSTICK");
return true;
}
return false;
}
void CheckAndSetProp(std::string prop, const bool &flag)
{
if (flag) {
SetInputProperty(prop);
MMI_HILOGD("The device has prop with %{public}s", prop.c_str());
}
}
void CheckMouseButton(const BitVector &key, bool &flag)
{
for (int32_t button = BTN_MOUSE; button < BTN_JOYSTICK && !flag; button++) {
flag = key.CheckBit(button);
}
}
void UpdateProByKey(const BitVector &key, const bool &isDirect, bool &probablyTablet, bool &probablyTouchpad,
bool &probablyTouchscreen)
{
probablyTablet = key.CheckBit(BTN_STYLUS) || key.CheckBit(BTN_TOOL_PEN);
probablyTouchpad = key.CheckBit(BTN_TOOL_FINGER) && !key.CheckBit(BTN_TOOL_PEN) && !isDirect;
probablyTouchscreen = key.CheckBit(BTN_TOUCH) && isDirect;
}
bool CheckMtCoordinates(const BitVector &abs)
{
bool hasMtCoordinates = abs.CheckBit(ABS_MT_POSITION_X) && abs.CheckBit(ABS_MT_POSITION_Y);
if (hasMtCoordinates && abs.CheckBit(ABS_MT_SLOT) && abs.CheckBit(ABS_MT_SLOT - 1)) {
hasMtCoordinates = false;
}
return hasMtCoordinates;
}
void UpdateProByStatus(const bool &isMouse, const bool &isTouchpad, const bool &isTouchscreen,
const bool &isJoystick, const bool &isTablet)
{
CheckAndSetProp("ID_INPUT_MOUSE", isMouse);
CheckAndSetProp("ID_INPUT_TOUCHPAD", isTouchpad);
CheckAndSetProp("ID_INPUT_TOUCHSCREEN", isTouchscreen);
CheckAndSetProp("ID_INPUT_JOYSTICK", isJoystick);
CheckAndSetProp("ID_INPUT_TABLET", isTablet);
}
bool CheckPointers(const BitVector &ev, const BitVector &abs, const BitVector &key, const BitVector &rel,
const BitVector &prop)
{
bool isDirect = prop.CheckBit(INPUT_PROP_DIRECT);
bool hasAbsCoordinates = abs.CheckBit(ABS_X) && abs.CheckBit(ABS_Y);
bool hasRelCoordinates = ev.CheckBit(EV_REL) && rel.CheckBit(REL_X) && rel.CheckBit(REL_Y);
bool hasMtCoordinates = CheckMtCoordinates(abs);
bool hasMouseButton = false;
CheckMouseButton(key, hasMouseButton);
bool probablyTablet;
bool probablyTouchpad;
bool probablyTouchscreen;
UpdateProByKey(key, isDirect, probablyTablet, probablyTouchpad, probablyTouchscreen);
bool probablyJoystick = HasJoystickAxesOrButtons(abs, key);
bool isTablet = false;
bool isMouse = false;
bool isTouchpad = false;
bool isTouchscreen = false;
bool isJoystick = false;
if (hasAbsCoordinates) {
if (probablyTablet) {
isTablet = true;
} else if (probablyTouchpad) {
isTouchpad = true;
} else if (hasMouseButton) {
* absolute axes, but no touch/pressure button. */
isMouse = true;
} else if (probablyTouchscreen) {
isTouchscreen = true;
} else {
isJoystick = probablyJoystick;
}
} else {
isJoystick = probablyJoystick;
}
if (hasMtCoordinates) {
if (probablyTablet) {
isTablet = true;
} else if (probablyTouchpad) {
isTouchpad = true;
} else if (probablyTouchscreen) {
isTouchscreen = true;
}
}
if (!isTablet && !isTouchpad && !isJoystick && hasMouseButton && (hasRelCoordinates || !hasAbsCoordinates)) {
isMouse = true;
}
UpdateProByStatus(isMouse, isTouchpad, isTouchscreen, isJoystick, isTablet);
return isTablet || isMouse || isTouchpad || isTouchscreen || isJoystick || CheckPointingStick(prop);
}
bool CheckKeys(const BitVector &ev, const BitVector &key)
{
if (!ev.CheckBit(EV_KEY)) {
return false;
}
bool found = false;
for (int32_t i = 0; i < BTN_MISC && !found; ++i) {
found = key.CheckBit(i);
}
for (int32_t i = KEY_OK; i < BTN_DPAD_UP && !found; ++i) {
found = key.CheckBit(i);
}
for (int32_t i = KEY_ALS_TOGGLE; i < BTN_TRIGGER_HAPPY && !found; ++i) {
found = key.CheckBit(i);
}
if (found) {
SetInputProperty("ID_INPUT_KEY");
}
* those, consider it a full keyboard; do not test KEY_RESERVED, though */
bool isKeyboard = true;
for (int32_t i = KEY_ESC; i < KEY_D && isKeyboard; i++) {
isKeyboard = key.CheckBit(i);
}
if (isKeyboard) {
SetInputProperty("ID_INPUT_KEYBOARD");
}
return found || isKeyboard;
}
void SetInputProperty(std::string prop)
{
AddProperty("ID_INPUT", "1");
AddProperty(std::move(prop), "1");
}
void CheckInputProperties()
{
BitVector ev{ GetProperty("EV") };
BitVector abs{ GetProperty("ABS") };
BitVector key{ GetProperty("KEY") };
BitVector rel{ GetProperty("REL") };
BitVector prop{ GetProperty("PROP") };
bool isPointer = CheckAccel(ev, abs, prop) || CheckPointers(ev, abs, key, rel, prop);
bool isKey = CheckKeys(ev, key);
if (!isPointer && !isKey && ev.CheckBit(EV_REL) && (rel.CheckBit(REL_WHEEL) || rel.CheckBit(REL_HWHEEL))) {
SetInputProperty("ID_INPUT_KEY");
}
if (ev.CheckBit(EV_SW)) {
SetInputProperty("ID_INPUT_SWITCH");
}
}
void SetDevnode(std::string newDevnode)
{
if (newDevnode[0] != '/') {
newDevnode = "/dev/" + newDevnode;
}
AddProperty("DEVNAME", std::move(newDevnode));
}
void AddProperty(std::string key, std::string value)
{
property_[std::move(key)] = std::move(value);
}
void DispProperty()
{
for (const auto &elem: property_) {
MMI_HILOGI("%{public}s -> %{public}s", elem.first.c_str(), elem.second.c_str());
}
}
std::optional<std::string> GetSubsystem()
{
if (!subsystem_.has_value()) {
auto res = GetLinkValue("subsystem", syspath);
if (res.has_value()) {
SetSubsystem(std::move(*res));
return subsystem_;
}
subsystem_ = "";
}
return subsystem_;
}
void SetSubsystem(std::string newSubsystem)
{
subsystem_ = newSubsystem;
AddProperty("SUBSYSTEM", std::move(newSubsystem));
}
private:
int refcount = 1;
std::string syspath;
std::string sysname;
std::optional<udev_device *> parentDevice_;
std::optional<std::string> subsystem_;
bool ueventLoaded = false;
std::unordered_map<std::string, std::string> property_;
static AllPropertys allPropertys_;
static std::mutex mutex_;
static std::string devnode_;
};
AllPropertys udev_device::allPropertys_;
std::mutex udev_device::mutex_;
std::string udev_device::devnode_;
udev *udev_new(void)
{
static udev instance{};
return &instance;
}
udev *udev_unref([[maybe_unused]] udev *udev)
{
return nullptr;
}
udev_device *udev_device_ref(udev_device *device)
{
CHKPP(device);
device->Ref();
return device;
}
udev_device *udev_device_unref(udev_device *device)
{
CHKPP(device);
device->Unref();
return nullptr;
}
udev *udev_device_get_udev(udev_device *device)
{
CHKPP(device);
return udev_new();
}
udev_device *udev_device_new_from_syspath(udev *udev, const char *syspath)
{
if (udev == nullptr || syspath == nullptr) {
errno = EINVAL;
return nullptr;
}
return udev_device::NewFromSyspath(syspath);
}
udev_device *udev_device_new_from_devnum(udev *udev, char type, dev_t devnum)
{
if (udev == nullptr) {
errno = EINVAL;
return nullptr;
}
return udev_device::NewFromDevnum(type, devnum);
}
udev_device *udev_device_get_parent(udev_device *device)
{
if (device == nullptr) {
errno = EINVAL;
return nullptr;
}
return device->GetParent();
}
udev_device *udev_device_get_parent_with_subsystem_devtype(udev_device *device, const char *subsystem,
const char *devtype)
{
CHKPP(device);
if (subsystem == nullptr) {
errno = EINVAL;
return nullptr;
}
CHKPP(devtype);
return device->GetParentWithSubsystem(subsystem);
}
const char *udev_device_get_syspath(udev_device *device)
{
CHKPP(device);
return device->GetSyspath().c_str();
}
const char *udev_device_get_sysname(udev_device *device)
{
CHKPP(device);
return device->GetSysname().c_str();
}
const char *udev_device_get_devnode(udev_device *device)
{
CHKPP(device);
return device->GetDevnode().c_str();
}
int udev_device_get_is_initialized(udev_device *device)
{
return (device != nullptr) ? static_cast<int>(device->IsInitialized()) : -1;
}
const char *udev_device_get_property_value(udev_device *device, const char *key)
{
CHKPP(device);
CHKPP(key);
std::string skey{ key };
if (!device->HasProperty(key)) {
return nullptr;
}
return device->GetProperty(key).c_str();
}
bool udev_device_property_add(char type, const char *devnode)
{
struct stat st;
if (stat(devnode, &st) < 0) {
MMI_HILOGW("udev_device_property_add stat failed, devnode:%{public}s", devnode);
return false;
}
udev_device::AddPropertys(type, st.st_rdev, devnode);
return true;
}
void udev_device_property_remove(const char *devnode)
{
udev_device::RemoveProperty(devnode);
}
void udev_device_record_devnode(const char *devnode)
{
udev_device::RecordDevNode(devnode);
}