Merge pull request #1661 from asas1asas200/zeng-feat-improve_keyboard
commit
e8e8ccb6cf
|
@ -21,7 +21,8 @@ jobs:
|
||||||
pkg install -y git # subprojects/date
|
pkg install -y git # subprojects/date
|
||||||
pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \
|
pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \
|
||||||
libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \
|
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: |
|
run: |
|
||||||
meson build -Dman-pages=enabled
|
meson build -Dman-pages=enabled
|
||||||
ninja -C build
|
ninja -C build
|
||||||
|
|
|
@ -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 tryAddDevice(const std::string&) -> void;
|
||||||
|
|
||||||
Gtk::Box box_;
|
Gtk::Box box_;
|
||||||
Gtk::Label numlock_label_;
|
Gtk::Label numlock_label_;
|
||||||
Gtk::Label capslock_label_;
|
Gtk::Label capslock_label_;
|
||||||
|
@ -31,11 +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_;
|
struct libinput* libinput_;
|
||||||
libevdev* dev_;
|
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
|
||||||
|
|
|
@ -13,6 +13,7 @@ You must be a member of the input group to use this module.
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
*interval*: ++
|
*interval*: ++
|
||||||
|
Deprecated, this module use event loop now, the interval has no effect.
|
||||||
typeof: integer ++
|
typeof: integer ++
|
||||||
default: 1 ++
|
default: 1 ++
|
||||||
The interval, in seconds, to poll the keyboard state.
|
The interval, in seconds, to poll the keyboard state.
|
||||||
|
|
|
@ -89,7 +89,9 @@ 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()))
|
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')
|
jsoncpp = dependency('jsoncpp')
|
||||||
sigcpp = dependency('sigc++-2.0')
|
sigcpp = dependency('sigc++-2.0')
|
||||||
|
libinotify = dependency('libinotify', required: false)
|
||||||
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'))
|
||||||
|
@ -260,8 +262,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() and (is_linux or libinotify.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
|
||||||
|
|
||||||
|
@ -321,12 +324,14 @@ executable(
|
||||||
gtkmm,
|
gtkmm,
|
||||||
dbusmenu_gtk,
|
dbusmenu_gtk,
|
||||||
giounix,
|
giounix,
|
||||||
|
libinput,
|
||||||
libnl,
|
libnl,
|
||||||
libnlgen,
|
libnlgen,
|
||||||
upower_glib,
|
upower_glib,
|
||||||
libpulse,
|
libpulse,
|
||||||
libjack,
|
libjack,
|
||||||
libudev,
|
libudev,
|
||||||
|
libinotify,
|
||||||
libepoll,
|
libepoll,
|
||||||
libmpdclient,
|
libmpdclient,
|
||||||
libevdev,
|
libevdev,
|
||||||
|
|
|
@ -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,13 @@
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <libinput.h>
|
||||||
|
#include <linux/input-event-codes.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>
|
||||||
}
|
}
|
||||||
|
|
||||||
class errno_error : public std::runtime_error {
|
class errno_error : public std::runtime_error {
|
||||||
|
@ -99,8 +104,18 @@ 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_devices_({}) {
|
||||||
|
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");
|
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");
|
||||||
|
@ -121,70 +136,135 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
|
||||||
|
|
||||||
if (config_["device-path"].isString()) {
|
if (config_["device-path"].isString()) {
|
||||||
std::string dev_path = config_["device-path"].asString();
|
std::string dev_path = config_["device-path"].asString();
|
||||||
fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
|
tryAddDevice(dev_path);
|
||||||
dev_ = openDevice(fd_);
|
if (libinput_devices_.empty()) {
|
||||||
} else {
|
spdlog::error("keyboard-state: Cannot find device {}", dev_path);
|
||||||
DIR* dev_dir = opendir("/dev/input");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR* dev_dir = opendir(devices_path_.c_str());
|
||||||
if (dev_dir == nullptr) {
|
if (dev_dir == nullptr) {
|
||||||
throw errno_error(errno, "Failed to open /dev/input");
|
throw errno_error(errno, "Failed to open " + devices_path_);
|
||||||
}
|
}
|
||||||
dirent* ep;
|
dirent* ep;
|
||||||
while ((ep = readdir(dev_dir))) {
|
while ((ep = readdir(dev_dir))) {
|
||||||
if (ep->d_type != DT_CHR) continue;
|
if (ep->d_type == DT_DIR) continue;
|
||||||
std::string dev_path = std::string("/dev/input/") + ep->d_name;
|
std::string dev_path = devices_path_ + ep->d_name;
|
||||||
int fd = openFile(dev_path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
|
tryAddDevice(dev_path);
|
||||||
try {
|
}
|
||||||
auto dev = openDevice(fd);
|
|
||||||
if (supportsLockStates(dev)) {
|
if (libinput_devices_.empty()) {
|
||||||
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
|
throw errno_error(errno, "Failed to find keyboard device");
|
||||||
fd_ = fd;
|
}
|
||||||
dev_ = dev;
|
|
||||||
|
libinput_thread_ = [this] {
|
||||||
|
dp.emit();
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
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 numl = 0, capsl = 0, scrolll = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
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) {
|
} catch (const errno_error& e) {
|
||||||
// ENOTTY just means the device isn't an evdev device, skip it
|
// ENOTTY just means the device isn't an evdev device, skip it
|
||||||
if (e.code != ENOTTY) {
|
if (e.code != ENOTTY) {
|
||||||
spdlog::warn(e.what());
|
spdlog::warn(e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
closeFile(fd);
|
|
||||||
}
|
|
||||||
if (dev_ == nullptr) {
|
|
||||||
throw errno_error(errno, "Failed to find keyboard device");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_ = [this] {
|
|
||||||
dp.emit();
|
|
||||||
thread_.sleep_for(interval_);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
waybar::modules::KeyboardState::~KeyboardState() {
|
|
||||||
libevdev_free(dev_);
|
|
||||||
try {
|
|
||||||
closeFile(fd_);
|
|
||||||
} catch (const std::runtime_error& e) {
|
|
||||||
spdlog::warn(e.what());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto waybar::modules::KeyboardState::update() -> void {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
||||||
|
@ -211,3 +291,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