Use in-house inotify watcher

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-10-17 00:03:29 +03:00
parent 6f45a3bc70
commit 42a9de3ba3
6 changed files with 329 additions and 33 deletions

View File

@@ -91,19 +91,6 @@ FetchContent_Declare(
)
FetchContent_MakeAvailable(cpptrace)
FetchContent_Declare(
inotify-cpp
GIT_REPOSITORY https://github.com/erikzenker/inotify-cpp.git
GIT_TAG "v1.0.0"
GIT_SHALLOW 1
)
set(BUILD_EXAMPLE OFF)
set(BUILD_SHARED_LIBS OFF)
set(BUILD_STATIC_LIBS ON)
set(BUILD_TEST OFF)
set(USE_BOOST_FILESYSTEM OFF)
FetchContent_MakeAvailable(inotify-cpp)
add_subdirectory(vendor)
find_program(WAYLAND_SCANNER wayland-scanner REQUIRED)
@@ -204,6 +191,7 @@ add_executable(waylight
${CMAKE_CURRENT_SOURCE_DIR}/src/IconRegistry.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Cache.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/InotifyWatcher.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/TextRenderer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/ImGui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/App.cpp
@@ -235,7 +223,6 @@ target_link_libraries(waylight PRIVATE
msdfgen::msdfgen-ext
lunasvg::lunasvg
SQLiteCpp
inotify-cpp-static
m
dl
@@ -247,4 +234,3 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
endif()
install(TARGETS waylight RUNTIME DESTINATION bin)

View File

@@ -11,6 +11,7 @@
#include <raylib.h>
#include "common.hpp"
#include <sys/inotify.h>
void replace_all(
std::string &str, std::string const &from, std::string const &to)
@@ -88,25 +89,37 @@ Cache::Cache(std::shared_ptr<SQLite::Database> db)
}
for (auto const &dir : m_app_dirs) {
m_inot.watchPathRecursively(dir);
m_inotify.watch_path_recursively(dir);
}
auto events = {
inotify::Event::open | inotify::Event::is_dir,
inotify::Event::create,
inotify::Event::modify,
inotify::Event::remove,
inotify::Event::move,
};
m_inot.onEvents(events, [this](auto notification) { rescan(); });
m_inotify.set_callback([this](FileWatchEvent const &event) {
auto const mask = event.mask;
if (mask & IN_Q_OVERFLOW) {
rescan();
return;
}
m_inot_th = std::thread([this]() { m_inot.run(); });
if (mask & IN_ISDIR) {
rescan();
return;
}
if (mask & (IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM
| IN_MOVED_TO | IN_CLOSE_WRITE)) {
if (event.path.extension() == ".desktop") {
rescan();
}
}
});
m_inotify_thread = std::thread([this]() { m_inotify.run(); });
}
Cache::~Cache()
{
m_inot.stop();
m_inot_th.join();
m_inotify.stop();
if (m_inotify_thread.joinable())
m_inotify_thread.join();
}
void Cache::rescan()

View File

@@ -7,8 +7,8 @@
#include <variant>
#include <vector>
#include "InotifyWatcher.hpp"
#include <SQLiteCpp/Database.h>
#include <inotify-cpp/NotifierBuilder.h>
namespace Waylight {
@@ -59,8 +59,8 @@ private:
std::vector<std::filesystem::path> m_app_dirs;
std::shared_ptr<SQLite::Database> m_db;
inotify::NotifierBuilder m_inot;
std::thread m_inot_th;
InotifyWatcher m_inotify;
std::thread m_inotify_thread;
};
} // namespace Cache
} // namespace Waylight

241
src/InotifyWatcher.cpp Normal file
View File

@@ -0,0 +1,241 @@
#include "InotifyWatcher.hpp"
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstring>
#include <poll.h>
#include <stdexcept>
#include <system_error>
#include <unistd.h>
#include <utility>
#include <sys/inotify.h>
namespace Waylight {
namespace {
constexpr std::uint32_t watch_mask { IN_ATTRIB | IN_CREATE | IN_DELETE
| IN_DELETE_SELF | IN_MODIFY | IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO
| IN_CLOSE_WRITE };
constexpr int poll_timeout_ms { 250 };
} // namespace
InotifyWatcher::InotifyWatcher()
: m_fd(inotify_init1(IN_CLOEXEC))
{
if (m_fd < 0) {
throw std::system_error(
errno, std::generic_category(), "inotify_init1 failed");
}
}
InotifyWatcher::~InotifyWatcher()
{
stop();
if (m_fd >= 0) {
::close(m_fd);
m_fd = -1;
}
}
void InotifyWatcher::watch_path_recursively(std::filesystem::path const &path)
{
add_watch_recursively(path);
}
void InotifyWatcher::set_callback(callback_t cb)
{
std::scoped_lock lock(m_watch_mutex);
m_callback = std::move(cb);
}
void InotifyWatcher::run()
{
if (m_running.exchange(true)) {
throw std::runtime_error("InotifyWatcher::run called while running");
}
std::array<char, 4096> buffer {};
while (m_running.load()) {
pollfd fd_set {
.fd = m_fd,
.events = POLLIN,
.revents = 0,
};
int const poll_res { ::poll(&fd_set, 1, poll_timeout_ms) };
if (!m_running.load())
break;
if (poll_res < 0) {
if (errno == EINTR)
continue;
if (errno == EBADF || errno == EINVAL)
break;
throw std::system_error(errno, std::generic_category(), "poll");
}
if (poll_res == 0)
continue;
if (fd_set.revents & (POLLERR | POLLHUP | POLLNVAL))
break;
if (!(fd_set.revents & POLLIN))
continue;
ssize_t const bytes_read { ::read(m_fd, buffer.data(), buffer.size()) };
if (bytes_read < 0) {
if (errno == EINTR || errno == EAGAIN)
continue;
if (errno == EBADF || errno == EINVAL)
break;
throw std::system_error(errno, std::generic_category(), "read");
}
ssize_t offset { 0 };
while (offset + static_cast<ssize_t>(sizeof(inotify_event))
<= bytes_read) {
auto const *event = reinterpret_cast<inotify_event const *>(
buffer.data() + offset);
std::string_view name;
if (event->len > 0) {
auto const *raw_name
= buffer.data() + offset + sizeof(inotify_event);
auto const *end
= std::find(raw_name, raw_name + event->len, '\0');
name = std::string_view(raw_name, end - raw_name);
}
dispatch_event(event->wd, event->mask, name);
offset += sizeof(inotify_event) + event->len;
}
}
m_running.store(false);
}
void InotifyWatcher::stop() { m_running.store(false); }
void InotifyWatcher::add_watch_for_path(std::filesystem::path const &path)
{
auto normalized { normalize_path(path) };
if (normalized.empty())
return;
auto const key = normalized.string();
{
std::scoped_lock lock(m_watch_mutex);
auto it = m_path_to_watch.find(key);
if (it != m_path_to_watch.end())
return;
}
int const wd { ::inotify_add_watch(m_fd, normalized.c_str(), watch_mask) };
if (wd < 0) {
if (errno == ENOENT || errno == ENOTDIR)
return;
throw std::system_error(errno, std::generic_category(),
"inotify_add_watch failed for " + normalized.string());
}
std::scoped_lock lock(m_watch_mutex);
m_watch_to_path.emplace(wd, normalized);
m_path_to_watch.emplace(key, wd);
}
void InotifyWatcher::add_watch_recursively(std::filesystem::path const &path)
{
auto normalized = normalize_path(path);
if (normalized.empty())
return;
add_watch_for_path(normalized);
std::error_code ec;
if (!std::filesystem::is_directory(normalized, ec))
return;
try {
auto const options {
std::filesystem::directory_options::follow_directory_symlink
| std::filesystem::directory_options::skip_permission_denied
};
for (auto const &entry : std::filesystem::recursive_directory_iterator(
normalized, options)) {
std::error_code inner_ec;
if (!entry.is_directory(inner_ec) || inner_ec)
continue;
add_watch_for_path(entry.path());
}
} catch (std::filesystem::filesystem_error const &) {
}
}
void InotifyWatcher::remove_watch(int wd)
{
std::filesystem::path removed_path;
{
std::scoped_lock lock(m_watch_mutex);
auto it { m_watch_to_path.find(wd) };
if (it == m_watch_to_path.end())
return;
removed_path = it->second;
m_watch_to_path.erase(it);
m_path_to_watch.erase(removed_path.string());
}
::inotify_rm_watch(m_fd, wd);
}
void InotifyWatcher::dispatch_event(
int wd, std::uint32_t mask, std::string_view name)
{
std::filesystem::path base_path;
callback_t callback_copy;
{
std::scoped_lock lock(m_watch_mutex);
auto it { m_watch_to_path.find(wd) };
if (it == m_watch_to_path.end())
return;
base_path = it->second;
callback_copy = m_callback;
}
auto event_path { base_path };
if (!name.empty())
event_path /= name;
if ((mask & IN_ISDIR)
&& (mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB | IN_CLOSE_WRITE))) {
add_watch_recursively(event_path);
}
if (mask & (IN_DELETE_SELF | IN_MOVE_SELF | IN_IGNORED)) {
remove_watch(wd);
}
if (callback_copy) {
callback_copy(FileWatchEvent {
.path = std::move(event_path),
.mask = mask,
});
}
}
std::filesystem::path InotifyWatcher::normalize_path(
std::filesystem::path const &path)
{
std::error_code ec;
auto normalized { std::filesystem::weakly_canonical(path, ec) };
if (ec)
normalized = std::filesystem::absolute(path, ec);
if (ec)
return {};
return normalized;
}
} // namespace Waylight

57
src/InotifyWatcher.hpp Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <mutex>
#include <string>
#include <string_view>
#include <unordered_map>
namespace Waylight {
struct FileWatchEvent {
std::filesystem::path path;
std::uint32_t mask;
};
class InotifyWatcher {
public:
using callback_t = std::function<void(FileWatchEvent const &)>;
InotifyWatcher();
~InotifyWatcher();
InotifyWatcher(InotifyWatcher const &) = delete;
InotifyWatcher &operator=(InotifyWatcher const &) = delete;
InotifyWatcher(InotifyWatcher &&) = delete;
InotifyWatcher &operator=(InotifyWatcher &&) = delete;
void watch_path_recursively(std::filesystem::path const &path);
void set_callback(callback_t cb);
void run();
void stop();
private:
void add_watch_for_path(std::filesystem::path const &path);
void add_watch_recursively(std::filesystem::path const &path);
void remove_watch(int wd);
void dispatch_event(int wd, std::uint32_t mask, std::string_view name);
static std::filesystem::path normalize_path(
std::filesystem::path const &path);
int m_fd;
std::atomic<bool> m_running { false };
callback_t m_callback;
std::mutex m_watch_mutex;
std::unordered_map<int, std::filesystem::path> m_watch_to_path;
std::unordered_map<std::string, int> m_path_to_watch;
};
} // namespace Waylight

View File

@@ -11,8 +11,7 @@ template<class E>
concept EnumLike = std::is_enum_v<E>;
template<EnumLike E>
constexpr usize enum_count_v
= static_cast<usize>(enum_traits<E>::last)
constexpr usize enum_count_v = static_cast<usize>(enum_traits<E>::last)
- static_cast<usize>(enum_traits<E>::first) + 1;
template<EnumLike E, class T> struct enum_array {