mirror of
				https://github.com/rad4day/Waybar.git
				synced 2025-10-31 16:02:43 +01:00 
			
		
		
		
	Merge pull request #1661 from asas1asas200/zeng-feat-improve_keyboard
This commit is contained in:
		
							
								
								
									
										3
									
								
								.github/workflows/freebsd.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/freebsd.yml
									
									
									
									
										vendored
									
									
								
							| @@ -21,7 +21,8 @@ jobs: | ||||
|           pkg install -y git #  subprojects/date | ||||
|           pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \ | ||||
|             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: | | ||||
|           meson build -Dman-pages=enabled | ||||
|           ninja -C build | ||||
|   | ||||
| @@ -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 tryAddDevice(const std::string&) -> void; | ||||
|  | ||||
|   Gtk::Box box_; | ||||
|   Gtk::Label numlock_label_; | ||||
|   Gtk::Label capslock_label_; | ||||
| @@ -31,11 +36,12 @@ class KeyboardState : public AModule { | ||||
|   const std::chrono::seconds interval_; | ||||
|   std::string icon_locked_; | ||||
|   std::string icon_unlocked_; | ||||
|   std::string devices_path_; | ||||
|  | ||||
|   int fd_; | ||||
|   libevdev* dev_; | ||||
|   struct libinput* libinput_; | ||||
|   std::unordered_map<std::string, struct libinput_device*> libinput_devices_; | ||||
|  | ||||
|   util::SleeperThread thread_; | ||||
|   util::SleeperThread libinput_thread_, hotplug_thread_; | ||||
| }; | ||||
|  | ||||
| }  // namespace waybar::modules | ||||
|   | ||||
| @@ -13,6 +13,7 @@ You must be a member of the input group to use this module. | ||||
| # CONFIGURATION | ||||
|  | ||||
| *interval*: ++ | ||||
| 	Deprecated, this module use event loop now, the interval has no effect. | ||||
| 	typeof: integer ++ | ||||
| 	default: 1 ++ | ||||
| 	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())) | ||||
| jsoncpp = dependency('jsoncpp') | ||||
| sigcpp = dependency('sigc++-2.0') | ||||
| libinotify = dependency('libinotify', required: false) | ||||
| 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')) | ||||
| @@ -260,8 +262,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() and (is_linux or libinotify.found()) | ||||
|     add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp') | ||||
|     add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp') | ||||
|     src_files += 'src/modules/keyboard_state.cpp' | ||||
| endif | ||||
|  | ||||
| @@ -321,12 +324,14 @@ executable( | ||||
|         gtkmm, | ||||
|         dbusmenu_gtk, | ||||
|         giounix, | ||||
|         libinput, | ||||
|         libnl, | ||||
|         libnlgen, | ||||
|         upower_glib, | ||||
|         libpulse, | ||||
|         libjack, | ||||
|         libudev, | ||||
|         libinotify, | ||||
|         libepoll, | ||||
|         libmpdclient, | ||||
|         libevdev, | ||||
|   | ||||
| @@ -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,13 @@ | ||||
|  | ||||
| extern "C" { | ||||
| #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/types.h> | ||||
| #include <unistd.h> | ||||
| } | ||||
|  | ||||
| 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() | ||||
|                          ? config_["format-icons"]["unlocked"].asString() | ||||
|                          : "unlocked"), | ||||
|       fd_(0), | ||||
|       dev_(nullptr) { | ||||
|       devices_path_("/dev/input/"), | ||||
|       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"); | ||||
|   if (config_["numlock"].asBool()) { | ||||
|     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()) { | ||||
|     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"); | ||||
|     tryAddDevice(dev_path); | ||||
|     if (libinput_devices_.empty()) { | ||||
|       spdlog::error("keyboard-state: Cannot find device {}", dev_path); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   thread_ = [this] { | ||||
|   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"); | ||||
|   } | ||||
|  | ||||
|   libinput_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; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|         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() { | ||||
|   libevdev_free(dev_); | ||||
|   try { | ||||
|     closeFile(fd_); | ||||
|   } 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 { | ||||
|   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); | ||||
|   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) { | ||||
|     // 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 { | ||||
|     bool state; | ||||
| @@ -211,3 +291,25 @@ auto waybar::modules::KeyboardState::update() -> void { | ||||
|  | ||||
|   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()); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Alex
					Alex