From 061f4550f49a89330fab4157828d8638e4865097 Mon Sep 17 00:00:00 2001 From: asas1asas200 Date: Mon, 22 Aug 2022 20:36:21 +0800 Subject: [PATCH 1/5] feat(keyboard): improve keyboard response time Use libinput event for keyboard state updates. The state will update when CAPS_LOCK, NUM_LOCK or SCROLL_LOCK has been released, `interval` will have no effect after this change. --- include/modules/keyboard_state.hpp | 7 ++ meson.build | 5 +- meson_options.txt | 1 + src/modules/keyboard_state.cpp | 128 +++++++++++++++++++++-------- 4 files changed, 104 insertions(+), 37 deletions(-) diff --git a/include/modules/keyboard_state.hpp b/include/modules/keyboard_state.hpp index 05fbec1..07569e4 100644 --- a/include/modules/keyboard_state.hpp +++ b/include/modules/keyboard_state.hpp @@ -3,12 +3,15 @@ #include #include +#include + #include "AModule.hpp" #include "bar.hpp" #include "util/sleeper_thread.hpp" extern "C" { #include +#include } namespace waybar::modules { @@ -20,6 +23,8 @@ class KeyboardState : public AModule { auto update() -> void; private: + auto findKeyboards() -> void; + Gtk::Box box_; Gtk::Label numlock_label_; Gtk::Label capslock_label_; @@ -34,6 +39,8 @@ class KeyboardState : public AModule { int fd_; libevdev* dev_; + struct libinput* libinput_; + std::unordered_map libinput_devices_; util::SleeperThread thread_; }; diff --git a/meson.build b/meson.build index 3c32006..4035fda 100644 --- a/meson.build +++ b/meson.build @@ -90,6 +90,7 @@ giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabl jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') libepoll = dependency('epoll-shim', required: false) +libinput = dependency('libinput', required: get_option('libinput')) libnl = dependency('libnl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) upower_glib = dependency('upower-glib', required: get_option('upower_glib')) @@ -243,8 +244,9 @@ if libudev.found() and (is_linux or libepoll.found()) src_files += 'src/modules/backlight.cpp' endif -if libevdev.found() and (is_linux or libepoll.found()) +if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp') + add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp') src_files += 'src/modules/keyboard_state.cpp' endif @@ -304,6 +306,7 @@ executable( gtkmm, dbusmenu_gtk, giounix, + libinput, libnl, libnlgen, upower_glib, diff --git a/meson_options.txt b/meson_options.txt index d2e9847..82ae0d3 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,4 +1,5 @@ option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.') +option('libinput', type: 'feature', value: 'auto', description: 'Enable libinput support for libinput related features') option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features') option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features') option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features') diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index 38faacd..e936057 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -8,8 +8,12 @@ extern "C" { #include +#include +#include +#include #include #include +#include } class errno_error : public std::runtime_error { @@ -73,6 +77,51 @@ 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()), @@ -100,7 +149,14 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& ? config_["format-icons"]["unlocked"].asString() : "unlocked"), fd_(0), - dev_(nullptr) { + dev_(nullptr), + libinput_(nullptr), + libinput_devices_({}) { + 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); + box_.set_name("keyboard-state"); if (config_["numlock"].asBool()) { numlock_label_.get_style_context()->add_class("numlock"); @@ -119,44 +175,39 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& } event_box_.add(box_); - if (config_["device-path"].isString()) { - std::string dev_path = config_["device-path"].asString(); - fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); - dev_ = openDevice(fd_); - } else { - DIR* dev_dir = opendir("/dev/input"); - 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_CHR) continue; - std::string dev_path = std::string("/dev/input/") + 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); - fd_ = fd; - dev_ = dev; - break; - } - } 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()); - } - } - closeFile(fd); - } - if (dev_ == nullptr) { - throw errno_error(errno, "Failed to find keyboard device"); - } - } + findKeyboards(); thread_ = [this] { dp.emit(); - thread_.sleep_for(interval_); + while (1) { + struct pollfd fd = {libinput_get_fd(libinput_), POLLIN, 0}; + poll(&fd, 1, -1); + libinput_dispatch(libinput_); + struct libinput_event* event; + while ((event = libinput_get_event(libinput_))) { + auto type = libinput_event_get_type(event); + if (type == LIBINPUT_EVENT_KEYBOARD_KEY) { + auto keyboard_event = libinput_event_get_keyboard_event(event); + auto state = libinput_event_keyboard_get_key_state(keyboard_event); + if (state == LIBINPUT_KEY_STATE_RELEASED) { + uint32_t key = libinput_event_keyboard_get_key(keyboard_event); + switch (key) { + case KEY_CAPSLOCK: + case KEY_NUMLOCK: + case KEY_SCROLLLOCK: + dp.emit(); + break; + default: + break; + } + } + } else if (type == LIBINPUT_EVENT_DEVICE_REMOVED) { + // TODO: Handle device removal. + // Clear libinput_devices_ and re-find keyboards. + } + libinput_event_destroy(event); + } + } }; } @@ -167,9 +218,14 @@ waybar::modules::KeyboardState::~KeyboardState() { } 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 + int err = LIBEVDEV_READ_STATUS_SUCCESS; while (err == LIBEVDEV_READ_STATUS_SUCCESS) { input_event ev; From dcd75b3b4018a7dbf3484addbf99e0fe26615da4 Mon Sep 17 00:00:00 2001 From: asas1asas200 Date: Tue, 23 Aug 2022 23:18:40 +0800 Subject: [PATCH 2/5] 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()); + } + } +} From 58a399b9afc961053b6c93501e6c29aa63ca45de Mon Sep 17 00:00:00 2001 From: asas1asas200 Date: Wed, 24 Aug 2022 02:22:40 +0800 Subject: [PATCH 3/5] chore(ci, meson): add inotify dependency for BSD --- .github/workflows/freebsd.yml | 3 ++- meson.build | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index cc1c4b4..a6da7ef 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -21,7 +21,8 @@ jobs: pkg install -y git # subprojects/date pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \ libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \ - pkgconf pulseaudio scdoc sndio spdlog wayland-protocols upower + pkgconf pulseaudio scdoc sndio spdlog wayland-protocols upower \ + libinotify run: | meson build -Dman-pages=enabled ninja -C build diff --git a/meson.build b/meson.build index 4035fda..0a9ef28 100644 --- a/meson.build +++ b/meson.build @@ -89,6 +89,7 @@ dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gt giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled())) jsoncpp = dependency('jsoncpp') sigcpp = dependency('sigc++-2.0') +libinotify = dependency('libinotify', required: false) libepoll = dependency('epoll-shim', required: false) libinput = dependency('libinput', required: get_option('libinput')) libnl = dependency('libnl-3.0', required: get_option('libnl')) @@ -244,7 +245,7 @@ if libudev.found() and (is_linux or libepoll.found()) src_files += 'src/modules/backlight.cpp' endif -if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() +if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found()) add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp') add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp') src_files += 'src/modules/keyboard_state.cpp' @@ -312,6 +313,7 @@ executable( upower_glib, libpulse, libudev, + libinotify, libepoll, libmpdclient, libevdev, From 5944989a8aab120ea695193b63c5affb8691c868 Mon Sep 17 00:00:00 2001 From: asas1asas200 Date: Wed, 24 Aug 2022 02:41:12 +0800 Subject: [PATCH 4/5] doc(keyboard): add deprecated warning --- man/waybar-keyboard-state.5.scd | 1 + src/modules/keyboard_state.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/man/waybar-keyboard-state.5.scd b/man/waybar-keyboard-state.5.scd index 905a321..fef4670 100644 --- a/man/waybar-keyboard-state.5.scd +++ b/man/waybar-keyboard-state.5.scd @@ -13,6 +13,7 @@ You must be a member of the input group to use this module. # CONFIGURATION *interval*: ++ + Deprecated, this module use event loop now, the interval has no effect. typeof: integer ++ default: 1 ++ The interval, in seconds, to poll the keyboard state. diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index 40e8d93..f3e846d 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -110,6 +110,10 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& static struct libinput_interface interface = { [](const char* path, int flags, void* user_data) { return open(path, flags); }, [](int fd, void* user_data) { close(fd); }}; + if (config_["interval"].isUInt()) { + spdlog::warn("keyboard-state: interval is deprecated"); + } + libinput_ = libinput_path_create_context(&interface, NULL); box_.set_name("keyboard-state"); From 8b03e38594261bf623f306606b278ca3c1c5a804 Mon Sep 17 00:00:00 2001 From: asas1asas200 Date: Wed, 24 Aug 2022 14:08:34 +0800 Subject: [PATCH 5/5] fix(keyboard): correct device-path config behavior --- src/modules/keyboard_state.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index f3e846d..b2750b6 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -137,18 +137,22 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& 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()) { + spdlog::error("keyboard-state: Cannot find device {}", dev_path); } } + + 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"); } @@ -241,7 +245,13 @@ auto waybar::modules::KeyboardState::update() -> void { int numl = 0, capsl = 0, scrolll = 0; try { - std::string dev_path = libinput_devices_.begin()->first; + std::string dev_path; + if (config_["device-path"].isString() && + libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { + dev_path = config_["device-path"].asString(); + } else { + 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);