Files
gKeyMapper/daemon/config_watch.cpp

110 lines
3.0 KiB
C++
Raw Permalink Normal View History

2026-10-17 03:53:51 +02:00
// gKeyMapper daemon - watches the mappings file for changes (inotify).
#include "config_watch.h"
#include <cerrno>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <unistd.h>
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<const inotify_event*>(buffer + offset);
offset += static_cast<ssize_t>(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