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()); + } + } +}