feat(keybaord): enable hotplug support

Use inotify listening devices path changes to implement hotplug support.
The new hotplug thread is also an event loop, so the interval value has
no effect.
The evdev is now open on demand.

Fix libinput_interface object life-time.
This commit is contained in:
asas1asas200 2022-08-23 23:18:40 +08:00
parent 061f4550f4
commit dcd75b3b40
2 changed files with 108 additions and 77 deletions

View File

@ -23,7 +23,7 @@ class KeyboardState : public AModule {
auto update() -> void;
private:
auto findKeyboards() -> void;
auto tryAddDevice(const std::string&) -> void;
Gtk::Box box_;
Gtk::Label numlock_label_;
@ -36,13 +36,12 @@ class KeyboardState : public AModule {
const std::chrono::seconds interval_;
std::string icon_locked_;
std::string icon_unlocked_;
std::string devices_path_;
int fd_;
libevdev* dev_;
struct libinput* libinput_;
std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
util::SleeperThread thread_;
util::SleeperThread libinput_thread_, hotplug_thread_;
};
} // namespace waybar::modules

View File

@ -11,6 +11,7 @@ extern "C" {
#include <libinput.h>
#include <linux/input-event-codes.h>
#include <poll.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@ -77,51 +78,6 @@ auto supportsLockStates(const libevdev* dev) -> bool {
libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
}
auto waybar::modules::KeyboardState::findKeyboards() -> void {
if (config_["device-path"].isString()) {
std::string dev_path = config_["device-path"].asString();
libinput_devices_[dev_path] = nullptr;
fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
dev_ = openDevice(fd_);
} else {
DIR* dev_dir = opendir("/dev/input/by-path");
if (dev_dir == nullptr) {
throw errno_error(errno, "Failed to open /dev/input");
}
dirent* ep;
while ((ep = readdir(dev_dir))) {
if (ep->d_type == DT_DIR) continue;
std::string dev_path = std::string("/dev/input/by-path/") + ep->d_name;
int fd = openFile(dev_path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
try {
auto dev = openDevice(fd);
if (supportsLockStates(dev)) {
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
if (libinput_devices_.empty()) {
fd_ = fd;
dev_ = dev;
} else {
libevdev_free(dev);
closeFile(fd);
}
libinput_devices_[dev_path] = libinput_path_add_device(libinput_, dev_path.c_str());
} else {
libevdev_free(dev);
closeFile(fd);
}
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
}
}
}
if (dev_ == nullptr) {
throw errno_error(errno, "Failed to find keyboard device");
}
}
}
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar,
const Json::Value& config)
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
@ -148,11 +104,10 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
? config_["format-icons"]["unlocked"].asString()
: "unlocked"),
fd_(0),
dev_(nullptr),
devices_path_("/dev/input/"),
libinput_(nullptr),
libinput_devices_({}) {
struct libinput_interface interface = {
static struct libinput_interface interface = {
[](const char* path, int flags, void* user_data) { return open(path, flags); },
[](int fd, void* user_data) { close(fd); }};
libinput_ = libinput_path_create_context(&interface, NULL);
@ -175,9 +130,26 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
}
event_box_.add(box_);
findKeyboards();
if (config_["device-path"].isString()) {
std::string dev_path = config_["device-path"].asString();
tryAddDevice(dev_path);
} else {
DIR* dev_dir = opendir(devices_path_.c_str());
if (dev_dir == nullptr) {
throw errno_error(errno, "Failed to open " + devices_path_);
}
dirent* ep;
while ((ep = readdir(dev_dir))) {
if (ep->d_type == DT_DIR) continue;
std::string dev_path = devices_path_ + ep->d_name;
tryAddDevice(dev_path);
}
}
if (libinput_devices_.empty()) {
throw errno_error(errno, "Failed to find keyboard device");
}
thread_ = [this] {
libinput_thread_ = [this] {
dp.emit();
while (1) {
struct pollfd fd = {libinput_get_fd(libinput_), POLLIN, 0};
@ -201,46 +173,84 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
break;
}
}
} else if (type == LIBINPUT_EVENT_DEVICE_REMOVED) {
// TODO: Handle device removal.
// Clear libinput_devices_ and re-find keyboards.
}
libinput_event_destroy(event);
}
}
};
hotplug_thread_ = [this] {
int fd;
fd = inotify_init();
if (fd < 0) {
spdlog::error("Failed to initialize inotify: {}", strerror(errno));
return;
}
inotify_add_watch(fd, devices_path_.c_str(), IN_CREATE | IN_DELETE);
while (1) {
int BUF_LEN = 1024 * (sizeof(struct inotify_event) + 16);
char buf[BUF_LEN];
int length = read(fd, buf, 1024);
if (length < 0) {
spdlog::error("Failed to read inotify: {}", strerror(errno));
return;
}
for (int i = 0; i < length;) {
struct inotify_event* event = (struct inotify_event*)&buf[i];
std::string dev_path = devices_path_ + event->name;
if (event->mask & IN_CREATE) {
// Wait for device setup
int timeout = 10;
while (timeout--) {
try {
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
closeFile(fd);
break;
} catch (const errno_error& e) {
if (e.code == EACCES) {
sleep(1);
}
}
}
tryAddDevice(dev_path);
} else if (event->mask & IN_DELETE) {
auto it = libinput_devices_.find(dev_path);
if (it != libinput_devices_.end()) {
spdlog::info("Keyboard {} has been removed.", dev_path);
libinput_devices_.erase(it);
}
}
i += sizeof(struct inotify_event) + event->len;
}
}
};
}
waybar::modules::KeyboardState::~KeyboardState() {
libevdev_free(dev_);
try {
closeFile(fd_);
} catch (const std::runtime_error& e) {
spdlog::warn(e.what());
}
for (const auto& [_, dev_ptr] : libinput_devices_) {
libinput_path_remove_device(dev_ptr);
}
}
auto waybar::modules::KeyboardState::update() -> void {
sleep(0); // wait for keyboard status change
sleep(0); // Wait for keyboard status change
int numl = 0, capsl = 0, scrolll = 0;
int err = LIBEVDEV_READ_STATUS_SUCCESS;
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
input_event ev;
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
while (err == LIBEVDEV_READ_STATUS_SYNC) {
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
try {
std::string dev_path = libinput_devices_.begin()->first;
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd);
numl = libevdev_get_event_value(dev, EV_LED, LED_NUML);
capsl = libevdev_get_event_value(dev, EV_LED, LED_CAPSL);
scrolll = libevdev_get_event_value(dev, EV_LED, LED_SCROLLL);
libevdev_free(dev);
closeFile(fd);
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
}
}
if (-err != EAGAIN) {
throw errno_error(-err, "Failed to sync evdev device");
}
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);
int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL);
int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL);
struct {
bool state;
@ -267,3 +277,25 @@ auto waybar::modules::KeyboardState::update() -> void {
AModule::update();
}
auto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path) -> void {
try {
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd);
if (supportsLockStates(dev)) {
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
if (libinput_devices_.find(dev_path) == libinput_devices_.end()) {
auto device = libinput_path_add_device(libinput_, dev_path.c_str());
libinput_device_ref(device);
libinput_devices_[dev_path] = device;
}
}
libevdev_free(dev);
closeFile(fd);
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
}
}
}