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.
This commit is contained in:
		
							parent
							
								
									fd24d7bcf6
								
							
						
					
					
						commit
						061f4550f4
					
				|  | @ -3,12 +3,15 @@ | |||
| #include <fmt/chrono.h> | ||||
| #include <gtkmm/label.h> | ||||
| 
 | ||||
| #include <unordered_map> | ||||
| 
 | ||||
| #include "AModule.hpp" | ||||
| #include "bar.hpp" | ||||
| #include "util/sleeper_thread.hpp" | ||||
| 
 | ||||
| extern "C" { | ||||
| #include <libevdev/libevdev.h> | ||||
| #include <libinput.h> | ||||
| } | ||||
| 
 | ||||
| namespace waybar::modules { | ||||
|  | @ -20,6 +23,8 @@ class KeyboardState : public AModule { | |||
|   auto update() -> void; | ||||
| 
 | ||||
|  private: | ||||
|   auto findKeyboards() -> void; | ||||
| 
 | ||||
|   Gtk::Box box_; | ||||
|   Gtk::Label numlock_label_; | ||||
|   Gtk::Label capslock_label_; | ||||
|  | @ -34,6 +39,8 @@ class KeyboardState : public AModule { | |||
| 
 | ||||
|   int fd_; | ||||
|   libevdev* dev_; | ||||
|   struct libinput* libinput_; | ||||
|   std::unordered_map<std::string, struct libinput_device*> libinput_devices_; | ||||
| 
 | ||||
|   util::SleeperThread thread_; | ||||
| }; | ||||
|  |  | |||
|  | @ -90,6 +90,7 @@ giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabl | |||
| jsoncpp = dependency('jsoncpp') | ||||
| sigcpp = dependency('sigc++-2.0') | ||||
| libepoll = dependency('epoll-shim', required: false) | ||||
| libinput = dependency('libinput', required: get_option('libinput')) | ||||
| libnl = dependency('libnl-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')) | ||||
|  | @ -243,8 +244,9 @@ if libudev.found() and (is_linux or libepoll.found()) | |||
|     src_files += 'src/modules/backlight.cpp' | ||||
| 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_LIBINPUT', language: 'cpp') | ||||
|     src_files += 'src/modules/keyboard_state.cpp' | ||||
| endif | ||||
| 
 | ||||
|  | @ -304,6 +306,7 @@ executable( | |||
|         gtkmm, | ||||
|         dbusmenu_gtk, | ||||
|         giounix, | ||||
|         libinput, | ||||
|         libnl, | ||||
|         libnlgen, | ||||
|         upower_glib, | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| 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('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') | ||||
|  |  | |||
|  | @ -8,8 +8,12 @@ | |||
| 
 | ||||
| extern "C" { | ||||
| #include <fcntl.h> | ||||
| #include <libinput.h> | ||||
| #include <linux/input-event-codes.h> | ||||
| #include <poll.h> | ||||
| #include <sys/stat.h> | ||||
| #include <sys/types.h> | ||||
| #include <unistd.h> | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| 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()), | ||||
|  | @ -100,7 +149,14 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& | |||
|                          ? config_["format-icons"]["unlocked"].asString() | ||||
|                          : "unlocked"), | ||||
|       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"); | ||||
|   if (config_["numlock"].asBool()) { | ||||
|     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_); | ||||
| 
 | ||||
|   if (config_["device-path"].isString()) { | ||||
|     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"); | ||||
|     } | ||||
|   } | ||||
|   findKeyboards(); | ||||
| 
 | ||||
|   thread_ = [this] { | ||||
|     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) { | ||||
|     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
 | ||||
| 
 | ||||
|   int err = LIBEVDEV_READ_STATUS_SUCCESS; | ||||
|   while (err == LIBEVDEV_READ_STATUS_SUCCESS) { | ||||
|     input_event ev; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue