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:
parent
061f4550f4
commit
dcd75b3b40
|
@ -23,7 +23,7 @@ class KeyboardState : public AModule {
|
||||||
auto update() -> void;
|
auto update() -> void;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto findKeyboards() -> void;
|
auto tryAddDevice(const std::string&) -> void;
|
||||||
|
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
Gtk::Label numlock_label_;
|
Gtk::Label numlock_label_;
|
||||||
|
@ -36,13 +36,12 @@ class KeyboardState : public AModule {
|
||||||
const std::chrono::seconds interval_;
|
const std::chrono::seconds interval_;
|
||||||
std::string icon_locked_;
|
std::string icon_locked_;
|
||||||
std::string icon_unlocked_;
|
std::string icon_unlocked_;
|
||||||
|
std::string devices_path_;
|
||||||
|
|
||||||
int fd_;
|
|
||||||
libevdev* dev_;
|
|
||||||
struct libinput* libinput_;
|
struct libinput* libinput_;
|
||||||
std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
|
std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
|
||||||
|
|
||||||
util::SleeperThread thread_;
|
util::SleeperThread libinput_thread_, hotplug_thread_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace waybar::modules
|
} // namespace waybar::modules
|
||||||
|
|
|
@ -11,6 +11,7 @@ extern "C" {
|
||||||
#include <libinput.h>
|
#include <libinput.h>
|
||||||
#include <linux/input-event-codes.h>
|
#include <linux/input-event-codes.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
#include <sys/inotify.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -77,51 +78,6 @@ auto supportsLockStates(const libevdev* dev) -> bool {
|
||||||
libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
|
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,
|
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar,
|
||||||
const Json::Value& config)
|
const Json::Value& config)
|
||||||
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
|
: 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()
|
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
|
||||||
? config_["format-icons"]["unlocked"].asString()
|
? config_["format-icons"]["unlocked"].asString()
|
||||||
: "unlocked"),
|
: "unlocked"),
|
||||||
fd_(0),
|
devices_path_("/dev/input/"),
|
||||||
dev_(nullptr),
|
|
||||||
libinput_(nullptr),
|
libinput_(nullptr),
|
||||||
libinput_devices_({}) {
|
libinput_devices_({}) {
|
||||||
struct libinput_interface interface = {
|
static struct libinput_interface interface = {
|
||||||
[](const char* path, int flags, void* user_data) { return open(path, flags); },
|
[](const char* path, int flags, void* user_data) { return open(path, flags); },
|
||||||
[](int fd, void* user_data) { close(fd); }};
|
[](int fd, void* user_data) { close(fd); }};
|
||||||
libinput_ = libinput_path_create_context(&interface, NULL);
|
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_);
|
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();
|
dp.emit();
|
||||||
while (1) {
|
while (1) {
|
||||||
struct pollfd fd = {libinput_get_fd(libinput_), POLLIN, 0};
|
struct pollfd fd = {libinput_get_fd(libinput_), POLLIN, 0};
|
||||||
|
@ -201,46 +173,84 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type == LIBINPUT_EVENT_DEVICE_REMOVED) {
|
|
||||||
// TODO: Handle device removal.
|
|
||||||
// Clear libinput_devices_ and re-find keyboards.
|
|
||||||
}
|
}
|
||||||
libinput_event_destroy(event);
|
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() {
|
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_) {
|
for (const auto& [_, dev_ptr] : libinput_devices_) {
|
||||||
libinput_path_remove_device(dev_ptr);
|
libinput_path_remove_device(dev_ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto waybar::modules::KeyboardState::update() -> void {
|
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;
|
try {
|
||||||
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
|
std::string dev_path = libinput_devices_.begin()->first;
|
||||||
input_event ev;
|
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
|
||||||
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
|
auto dev = openDevice(fd);
|
||||||
while (err == LIBEVDEV_READ_STATUS_SYNC) {
|
numl = libevdev_get_event_value(dev, EV_LED, LED_NUML);
|
||||||
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
|
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 {
|
struct {
|
||||||
bool state;
|
bool state;
|
||||||
|
@ -267,3 +277,25 @@ auto waybar::modules::KeyboardState::update() -> void {
|
||||||
|
|
||||||
AModule::update();
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue