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.
pull/1661/head
asas1asas200 2022-08-22 20:36:21 +08:00
parent fd24d7bcf6
commit 061f4550f4
4 changed files with 104 additions and 37 deletions

View File

@ -3,12 +3,15 @@
#include <fmt/chrono.h> #include <fmt/chrono.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <unordered_map>
#include "AModule.hpp" #include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
extern "C" { extern "C" {
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
#include <libinput.h>
} }
namespace waybar::modules { namespace waybar::modules {
@ -20,6 +23,8 @@ class KeyboardState : public AModule {
auto update() -> void; auto update() -> void;
private: private:
auto findKeyboards() -> void;
Gtk::Box box_; Gtk::Box box_;
Gtk::Label numlock_label_; Gtk::Label numlock_label_;
Gtk::Label capslock_label_; Gtk::Label capslock_label_;
@ -34,6 +39,8 @@ class KeyboardState : public AModule {
int fd_; int fd_;
libevdev* dev_; libevdev* dev_;
struct libinput* libinput_;
std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
util::SleeperThread thread_; util::SleeperThread thread_;
}; };

View File

@ -90,6 +90,7 @@ giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabl
jsoncpp = dependency('jsoncpp') jsoncpp = dependency('jsoncpp')
sigcpp = dependency('sigc++-2.0') sigcpp = dependency('sigc++-2.0')
libepoll = dependency('epoll-shim', required: false) libepoll = dependency('epoll-shim', required: false)
libinput = dependency('libinput', required: get_option('libinput'))
libnl = dependency('libnl-3.0', required: get_option('libnl')) libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-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')) 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' src_files += 'src/modules/backlight.cpp'
endif 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_LIBEVDEV', language: 'cpp')
add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp')
src_files += 'src/modules/keyboard_state.cpp' src_files += 'src/modules/keyboard_state.cpp'
endif endif
@ -304,6 +306,7 @@ executable(
gtkmm, gtkmm,
dbusmenu_gtk, dbusmenu_gtk,
giounix, giounix,
libinput,
libnl, libnl,
libnlgen, libnlgen,
upower_glib, upower_glib,

View File

@ -1,4 +1,5 @@
option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.') 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('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('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') option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')

View File

@ -8,8 +8,12 @@
extern "C" { extern "C" {
#include <fcntl.h> #include <fcntl.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
#include <poll.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
} }
class errno_error : public std::runtime_error { 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); 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()),
@ -100,7 +149,14 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
? config_["format-icons"]["unlocked"].asString() ? config_["format-icons"]["unlocked"].asString()
: "unlocked"), : "unlocked"),
fd_(0), 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"); box_.set_name("keyboard-state");
if (config_["numlock"].asBool()) { if (config_["numlock"].asBool()) {
numlock_label_.get_style_context()->add_class("numlock"); 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_); event_box_.add(box_);
if (config_["device-path"].isString()) { findKeyboards();
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");
}
}
thread_ = [this] { thread_ = [this] {
dp.emit(); 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) { } catch (const std::runtime_error& e) {
spdlog::warn(e.what()); spdlog::warn(e.what());
} }
for (const auto& [_, dev_ptr] : libinput_devices_) {
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
int err = LIBEVDEV_READ_STATUS_SUCCESS; int err = LIBEVDEV_READ_STATUS_SUCCESS;
while (err == LIBEVDEV_READ_STATUS_SUCCESS) { while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
input_event ev; input_event ev;