Use in-house inotify watcher
Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
241
src/InotifyWatcher.cpp
Normal 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
57
src/InotifyWatcher.hpp
Normal 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
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user