110 lines
3.0 KiB
C++
110 lines
3.0 KiB
C++
|
|
// 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
|