From dcd75b3b4018a7dbf3484addbf99e0fe26615da4 Mon Sep 17 00:00:00 2001 From: asas1asas200 Date: Tue, 23 Aug 2022 23:18:40 +0800 Subject: [PATCH] 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. --- include/modules/keyboard_state.hpp | 7 +- src/modules/keyboard_state.cpp | 178 +++++++++++++++++------------ 2 files changed, 108 insertions(+), 77 deletions(-) diff --git a/include/modules/keyboard_state.hpp b/include/modules/keyboard_state.hpp index 07569e4..ce9faba 100644 --- a/include/modules/keyboard_state.hpp +++ b/include/modules/keyboard_state.hpp @@ -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 libinput_devices_; - util::SleeperThread thread_; + util::SleeperThread libinput_thread_, hotplug_thread_; }; } // namespace waybar::modules diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index e936057..40e8d93 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -11,6 +11,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -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()); + } + } +}