// gKeyMapper daemon - watches the mappings file for changes (inotify). #include "config_watch.h" #include #include #include #include #include #include #include namespace gkeymapper::daemon { namespace { // IN_CLOSE_WRITE is the useful one (the writer closed the file); IN_MODIFY // covers writers that hold the file open, IN_MOVED_TO/CREATE the atomic save. constexpr uint32_t kMask = IN_CLOSE_WRITE | IN_MODIFY | IN_MOVED_TO | IN_CREATE; std::string directory_of(const std::string& path) { size_t slash = path.find_last_of('/'); if (slash == std::string::npos) { return "."; } return slash == 0 ? std::string("/") : path.substr(0, slash); } std::string basename_of(const std::string& path) { size_t slash = path.find_last_of('/'); return slash == std::string::npos ? path : path.substr(slash + 1); } } // namespace ConfigWatcher::~ConfigWatcher() { if (fd_ >= 0) { ::close(fd_); } } bool ConfigWatcher::init(const std::string& path) { name_ = basename_of(path); directory_ = directory_of(path); fd_ = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); if (fd_ < 0) { std::fprintf(stderr, "gkeymapper-daemon: inotify unavailable (%s); " "use SIGHUP to reload\n", std::strerror(errno)); return false; } int watch = inotify_add_watch(fd_, directory_.c_str(), kMask); if (watch < 0 && errno == ENOENT) { // First start before anything was ever saved: create the directory the // GUI would create and try once more. ::mkdir(directory_.c_str(), 0755); watch = inotify_add_watch(fd_, directory_.c_str(), kMask); } if (watch < 0) { std::fprintf(stderr, "gkeymapper-daemon: cannot watch %s (%s); " "use SIGHUP to reload\n", directory_.c_str(), std::strerror(errno)); ::close(fd_); fd_ = -1; return false; } std::fprintf(stderr, "gkeymapper-daemon: watching %s for changes\n", directory_.c_str()); return true; } void ConfigWatcher::poll_events() { if (fd_ < 0) { return; } alignas(inotify_event) char buffer[4096]; for (;;) { ssize_t length = ::read(fd_, buffer, sizeof(buffer)); if (length <= 0) { return; // nothing queued (EAGAIN) or a read error } for (ssize_t offset = 0; offset < length;) { const auto* event = reinterpret_cast(buffer + offset); offset += static_cast(sizeof(inotify_event)) + event->len; // Only the configured file matters; the directory may hold others. if (event->len == 0 || name_ == event->name) { pending_ = true; } } } } bool ConfigWatcher::take_change() { if (!pending_) { return false; } pending_ = false; return true; } } // namespace gkeymapper::daemon