diff --git a/CMakeLists.txt b/CMakeLists.txt index e38718f..75abd4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) - diff --git a/src/Cache.cpp b/src/Cache.cpp index 68d1378..2eb267d 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -11,6 +11,7 @@ #include #include "common.hpp" +#include void replace_all( std::string &str, std::string const &from, std::string const &to) @@ -88,25 +89,37 @@ Cache::Cache(std::shared_ptr 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() diff --git a/src/Cache.hpp b/src/Cache.hpp index ecd03b8..2fb42a8 100644 --- a/src/Cache.hpp +++ b/src/Cache.hpp @@ -7,8 +7,8 @@ #include #include +#include "InotifyWatcher.hpp" #include -#include namespace Waylight { @@ -59,8 +59,8 @@ private: std::vector m_app_dirs; std::shared_ptr m_db; - inotify::NotifierBuilder m_inot; - std::thread m_inot_th; + InotifyWatcher m_inotify; + std::thread m_inotify_thread; }; -} // namespace Cache +} // namespace Waylight diff --git a/src/InotifyWatcher.cpp b/src/InotifyWatcher.cpp new file mode 100644 index 0000000..207be15 --- /dev/null +++ b/src/InotifyWatcher.cpp @@ -0,0 +1,241 @@ +#include "InotifyWatcher.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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 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(sizeof(inotify_event)) + <= bytes_read) { + auto const *event = reinterpret_cast( + 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 diff --git a/src/InotifyWatcher.hpp b/src/InotifyWatcher.hpp new file mode 100644 index 0000000..2e0047d --- /dev/null +++ b/src/InotifyWatcher.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Waylight { + +struct FileWatchEvent { + std::filesystem::path path; + std::uint32_t mask; +}; + +class InotifyWatcher { +public: + using callback_t = std::function; + + 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 m_running { false }; + callback_t m_callback; + + std::mutex m_watch_mutex; + std::unordered_map m_watch_to_path; + std::unordered_map m_path_to_watch; +}; + +} // namespace Waylight diff --git a/src/enum_array.hpp b/src/enum_array.hpp index bef30a1..1fd53de 100644 --- a/src/enum_array.hpp +++ b/src/enum_array.hpp @@ -11,8 +11,7 @@ template concept EnumLike = std::is_enum_v; template -constexpr usize enum_count_v - = static_cast(enum_traits::last) +constexpr usize enum_count_v = static_cast(enum_traits::last) - static_cast(enum_traits::first) + 1; template struct enum_array {