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
parent
fd24d7bcf6
commit
061f4550f4
|
@ -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_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue