diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 889ae181..325bd882 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -4,6 +4,7 @@ #include "debug/Log.hpp" #include "helpers/Splashes.hpp" #include "config/ConfigValue.hpp" +#include "config/ConfigWatcher.hpp" #include "managers/CursorManager.hpp" #include "managers/TokenManager.hpp" #include "managers/PointerManager.hpp" @@ -44,7 +45,6 @@ #include "managers/KeybindManager.hpp" #include "managers/SessionLockManager.hpp" -#include "managers/ThreadManager.hpp" #include "managers/XWaylandManager.hpp" #include "config/ConfigManager.hpp" @@ -551,7 +551,6 @@ void CCompositor::cleanup() { g_pProtocolManager.reset(); g_pHyprRenderer.reset(); g_pHyprOpenGL.reset(); - g_pThreadManager.reset(); g_pConfigManager.reset(); g_pLayoutManager.reset(); g_pHyprError.reset(); @@ -567,6 +566,7 @@ void CCompositor::cleanup() { g_pEventLoopManager.reset(); g_pVersionKeeperMgr.reset(); g_pDonationNagManager.reset(); + g_pConfigWatcher.reset(); if (m_pAqBackend) m_pAqBackend.reset(); @@ -631,9 +631,6 @@ void CCompositor::initManagers(eManagersInitStage stage) { g_pSeatManager = std::make_unique(); } break; case STAGE_LATE: { - Debug::log(LOG, "Creating the ThreadManager!"); - g_pThreadManager = std::make_unique(); - Debug::log(LOG, "Creating CHyprCtl"); g_pHyprCtl = std::make_unique(); diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 0a701a59..430e583c 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -5,7 +5,6 @@ #include #include "defines.hpp" -#include "managers/ThreadManager.hpp" #include "managers/XWaylandManager.hpp" #include "managers/KeybindManager.hpp" #include "managers/SessionLockManager.hpp" diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 52f2e316..ebeb2397 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -1,6 +1,7 @@ #include #include "ConfigManager.hpp" +#include "ConfigWatcher.hpp" #include "../managers/KeybindManager.hpp" #include "../Compositor.hpp" @@ -372,8 +373,8 @@ static Hyprlang::CParseResult handlePlugin(const char* c, const char* v) { CConfigManager::CConfigManager() { const auto ERR = verifyConfigExists(); - configPaths.emplace_back(getMainConfigPath()); - m_pConfig = std::make_unique(configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}); + m_configPaths.emplace_back(getMainConfigPath()); + m_pConfig = std::make_unique(m_configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}); m_pConfig->addConfigValue("general:border_size", Hyprlang::INT{1}); m_pConfig->addConfigValue("general:no_border_on_floating", Hyprlang::INT{0}); @@ -795,7 +796,7 @@ std::string CConfigManager::getConfigString() { std::string configString; std::string currFileContent; - for (const auto& path : configPaths) { + for (const auto& path : m_configPaths) { std::ifstream configFile(path); configString += ("\n\nConfig File: " + path + ": "); if (!configFile.is_open()) { @@ -883,10 +884,10 @@ std::optional CConfigManager::resetHLConfig() { finalExecRequests.clear(); // paths - configPaths.clear(); + m_configPaths.clear(); std::string mainConfigPath = getMainConfigPath(); Debug::log(LOG, "Using config: {}", mainConfigPath); - configPaths.push_back(mainConfigPath); + m_configPaths.emplace_back(mainConfigPath); const auto RET = verifyConfigExists(); @@ -897,6 +898,8 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { static const auto PENABLEEXPLICIT = CConfigValue("render:explicit_sync"); static int prevEnabledExplicit = *PENABLEEXPLICIT; + g_pConfigWatcher->setWatchList(m_configPaths); + for (auto const& w : g_pCompositor->m_vWindows) { w->uncacheWindowDecos(); } @@ -1029,17 +1032,14 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { void CConfigManager::init() { + g_pConfigWatcher->setOnChange([this](const CConfigWatcher::SConfigWatchEvent& e) { + Debug::log(LOG, "CConfigManager: file {} modified, reloading", e.file); + reload(); + }); + const std::string CONFIGPATH = getMainConfigPath(); reload(); - struct stat fileStat; - int err = stat(CONFIGPATH.c_str(), &fileStat); - if (err != 0) { - Debug::log(WARN, "Error at statting config, error {}", errno); - } - - configModifyTimes[CONFIGPATH] = fileStat.st_mtime; - isFirstLaunch = false; } @@ -1080,37 +1080,6 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std:: return RET.error ? RET.getError() : ""; } -void CConfigManager::tick() { - std::string CONFIGPATH = getMainConfigPath(); - if (!std::filesystem::exists(CONFIGPATH)) { - Debug::log(ERR, "Config doesn't exist??"); - return; - } - - bool parse = false; - - for (auto const& cf : configPaths) { - struct stat fileStat; - int err = stat(cf.c_str(), &fileStat); - if (err != 0) { - Debug::log(WARN, "Error at ticking config at {}, error {}: {}", cf, err, strerror(err)); - continue; - } - - // check if we need to reload cfg - if (fileStat.st_mtime != configModifyTimes[cf] || m_bForceReload) { - parse = true; - configModifyTimes[cf] = fileStat.st_mtime; - } - } - - if (parse) { - m_bForceReload = false; - - reload(); - } -} - Hyprlang::CConfigValue* CConfigManager::getConfigValueSafeDevice(const std::string& dev, const std::string& val, const std::string& fallback) { const auto VAL = m_pConfig->getSpecialConfigValuePtr("device", val.c_str(), dev.c_str()); @@ -1719,8 +1688,7 @@ void CConfigManager::handlePluginLoads() { if (pluginsChanged) { g_pHyprError->destroy(); - m_bForceReload = true; - tick(); + reload(); } } @@ -2732,16 +2700,8 @@ std::optional CConfigManager::handleSource(const std::string& comma Debug::log(ERR, "source= file doesn't exist: {}", value); return "source= file " + value + " doesn't exist!"; } - configPaths.push_back(value); + m_configPaths.emplace_back(value); - struct stat fileStat; - int err = stat(value.c_str(), &fileStat); - if (err != 0) { - Debug::log(WARN, "Error at ticking config at {}, error {}: {}", value, err, strerror(err)); - return {}; - } - - configModifyTimes[value] = fileStat.st_mtime; auto configCurrentPathBackup = configCurrentPath; configCurrentPath = value; diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index bef52584..1962e4d2 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -142,8 +142,8 @@ class CConfigManager { public: CConfigManager(); - void tick(); void init(); + void reload(); int getDeviceInt(const std::string&, const std::string&, const std::string& fallback = ""); float getDeviceFloat(const std::string&, const std::string&, const std::string& fallback = ""); @@ -258,15 +258,13 @@ class CConfigManager { {"scrolltouchpad", [](const PHLWINDOW& pWindow) { return &pWindow->m_sWindowData.scrollTouchpad; }}}; bool m_bWantsMonitorReload = false; - bool m_bForceReload = false; bool m_bNoMonitorReload = false; bool isLaunchingExecOnce = false; // For exec-once to skip initial ws tracking private: std::unique_ptr m_pConfig; - std::vector configPaths; // stores all the config paths - std::unordered_map configModifyTimes; // stores modify times + std::vector m_configPaths; Hyprutils::Animation::CAnimationConfigTree m_AnimationTree; @@ -302,7 +300,6 @@ class CConfigManager { std::optional generateConfig(std::string configPath); std::optional verifyConfigExists(); void postConfigReload(const Hyprlang::CParseResult& result); - void reload(); SWorkspaceRule mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&); }; diff --git a/src/config/ConfigWatcher.cpp b/src/config/ConfigWatcher.cpp new file mode 100644 index 00000000..38191e9e --- /dev/null +++ b/src/config/ConfigWatcher.cpp @@ -0,0 +1,72 @@ +#include "ConfigWatcher.hpp" +#include +#include "../debug/Log.hpp" +#include +#include + +CConfigWatcher::CConfigWatcher() : m_inotifyFd(inotify_init()) { + if (m_inotifyFd < 0) { + Debug::log(ERR, "CConfigWatcher couldn't open an inotify node. Config will not be automatically reloaded"); + return; + } + + const int FLAGS = fcntl(m_inotifyFd, F_GETFL, 0); + if (fcntl(m_inotifyFd, F_SETFL, FLAGS | O_NONBLOCK) < 0) { + Debug::log(ERR, "CConfigWatcher couldn't non-block inotify node. Config will not be automatically reloaded"); + close(m_inotifyFd); + m_inotifyFd = -1; + return; + } +} + +CConfigWatcher::~CConfigWatcher() { + if (m_inotifyFd >= 0) + close(m_inotifyFd); +} + +int CConfigWatcher::getInotifyFD() { + return m_inotifyFd; +} + +void CConfigWatcher::setWatchList(const std::vector& paths) { + + // we clear all watches first, because whichever fired is now invalid + // or that is at least what it seems to be. + // since we don't know which fired, + // plus it doesn't matter that much, these ops are done rarely and fast anyways. + + // cleanup old paths + for (auto& watch : m_watches) { + inotify_rm_watch(m_inotifyFd, watch.wd); + } + + m_watches.clear(); + + // add new paths + for (const auto& path : paths) { + m_watches.emplace_back(SInotifyWatch{ + .wd = inotify_add_watch(m_inotifyFd, path.c_str(), IN_MODIFY), + .file = path, + }); + } +} + +void CConfigWatcher::setOnChange(const std::function& fn) { + m_watchCallback = fn; +} + +void CConfigWatcher::onInotifyEvent() { + inotify_event ev; + while (read(m_inotifyFd, &ev, sizeof(ev)) > 0) { + const auto WD = std::ranges::find_if(m_watches.begin(), m_watches.end(), [wd = ev.wd](const auto& e) { return e.wd == wd; }); + + if (WD == m_watches.end()) { + Debug::log(ERR, "CConfigWatcher: got an event for wd {} which we don't have?!", ev.wd); + return; + } + + m_watchCallback(SConfigWatchEvent{ + .file = WD->file, + }); + } +} diff --git a/src/config/ConfigWatcher.hpp b/src/config/ConfigWatcher.hpp new file mode 100644 index 00000000..0a698fc8 --- /dev/null +++ b/src/config/ConfigWatcher.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include +#include + +class CConfigWatcher { + public: + CConfigWatcher(); + ~CConfigWatcher(); + + struct SConfigWatchEvent { + std::string file; + }; + + int getInotifyFD(); + void setWatchList(const std::vector& paths); + void setOnChange(const std::function& fn); + void onInotifyEvent(); + + private: + struct SInotifyWatch { + int wd = -1; + std::string file; + }; + + std::function m_watchCallback; + std::vector m_watches; + int m_inotifyFd = -1; +}; + +inline std::unique_ptr g_pConfigWatcher = std::make_unique(); \ No newline at end of file diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index 41b0abe1..cfdba100 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -1121,13 +1121,10 @@ static std::string reloadRequest(eHyprCtlOutputFormat format, std::string reques const auto REQMODE = request.substr(request.find_last_of(' ') + 1); - g_pConfigManager->m_bForceReload = true; - - if (REQMODE == "config-only") { + if (REQMODE == "config-only") g_pConfigManager->m_bNoMonitorReload = true; - } - g_pConfigManager->tick(); + g_pConfigManager->reload(); return "ok"; } diff --git a/src/managers/ThreadManager.cpp b/src/managers/ThreadManager.cpp deleted file mode 100644 index bd124c99..00000000 --- a/src/managers/ThreadManager.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "ThreadManager.hpp" -#include "../debug/HyprCtl.hpp" -#include "../Compositor.hpp" -#include "../config/ConfigValue.hpp" - -static int handleTimer(void* data) { - const auto PTM = (CThreadManager*)data; - - static auto PDISABLECFGRELOAD = CConfigValue("misc:disable_autoreload"); - - if (*PDISABLECFGRELOAD != 1) - g_pConfigManager->tick(); - - wl_event_source_timer_update(PTM->m_esConfigTimer, 1000); - - return 0; -} - -CThreadManager::CThreadManager() : m_esConfigTimer(wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, handleTimer, this)) { - wl_event_source_timer_update(m_esConfigTimer, 1000); -} - -CThreadManager::~CThreadManager() { - if (m_esConfigTimer) - wl_event_source_remove(m_esConfigTimer); -} diff --git a/src/managers/ThreadManager.hpp b/src/managers/ThreadManager.hpp deleted file mode 100644 index 13e2fcd8..00000000 --- a/src/managers/ThreadManager.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "../defines.hpp" -struct wl_event_source; - -class CThreadManager { - public: - CThreadManager(); - ~CThreadManager(); - - wl_event_source* m_esConfigTimer = nullptr; - - private: -}; - -inline std::unique_ptr g_pThreadManager; \ No newline at end of file diff --git a/src/managers/eventLoop/EventLoopManager.cpp b/src/managers/eventLoop/EventLoopManager.cpp index db8b49b6..f98efc92 100644 --- a/src/managers/eventLoop/EventLoopManager.cpp +++ b/src/managers/eventLoop/EventLoopManager.cpp @@ -1,6 +1,7 @@ #include "EventLoopManager.hpp" #include "../../debug/Log.hpp" #include "../../Compositor.hpp" +#include "../../config/ConfigWatcher.hpp" #include #include @@ -27,6 +28,8 @@ CEventLoopManager::~CEventLoopManager() { wl_event_source_remove(m_sWayland.eventSource); if (m_sIdle.eventSource) wl_event_source_remove(m_sIdle.eventSource); + if (m_configWatcherInotifySource) + wl_event_source_remove(m_configWatcherInotifySource); if (m_sTimers.timerfd >= 0) close(m_sTimers.timerfd); } @@ -42,9 +45,17 @@ static int aquamarineFDWrite(int fd, uint32_t mask, void* data) { return 1; } +static int configWatcherWrite(int fd, uint32_t mask, void* data) { + g_pConfigWatcher->onInotifyEvent(); + return 0; +} + void CEventLoopManager::enterLoop() { m_sWayland.eventSource = wl_event_loop_add_fd(m_sWayland.loop, m_sTimers.timerfd, WL_EVENT_READABLE, timerWrite, nullptr); + if (const auto FD = g_pConfigWatcher->getInotifyFD(); FD >= 0) + m_configWatcherInotifySource = wl_event_loop_add_fd(m_sWayland.loop, FD, WL_EVENT_READABLE, configWatcherWrite, nullptr); + aqPollFDs = g_pCompositor->m_pAqBackend->getPollFDs(); for (auto const& fd : aqPollFDs) { m_sWayland.aqEventSources.emplace_back(wl_event_loop_add_fd(m_sWayland.loop, fd->fd, WL_EVENT_READABLE, aquamarineFDWrite, fd.get())); diff --git a/src/managers/eventLoop/EventLoopManager.hpp b/src/managers/eventLoop/EventLoopManager.hpp index 39d8bbeb..90402de2 100644 --- a/src/managers/eventLoop/EventLoopManager.hpp +++ b/src/managers/eventLoop/EventLoopManager.hpp @@ -51,6 +51,8 @@ class CEventLoopManager { SIdleData m_sIdle; std::vector> aqPollFDs; + wl_event_source* m_configWatcherInotifySource = nullptr; + friend class CSyncTimeline; }; diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp index b6bbc460..fbb1ec1b 100644 --- a/src/plugins/PluginAPI.cpp +++ b/src/plugins/PluginAPI.cpp @@ -4,6 +4,7 @@ #include "../plugins/PluginSystem.hpp" #include "../managers/HookSystemManager.hpp" #include "../managers/LayoutManager.hpp" +#include "../managers/eventLoop/EventLoopManager.hpp" #include "../config/ConfigManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" #include @@ -72,7 +73,7 @@ APICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) { } APICALL bool HyprlandAPI::reloadConfig() { - g_pConfigManager->m_bForceReload = true; + g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); }); return true; } diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index ce7dd7c7..e849bf11 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -5,6 +5,7 @@ #include "../config/ConfigManager.hpp" #include "../managers/LayoutManager.hpp" #include "../managers/HookSystemManager.hpp" +#include "../managers/eventLoop/EventLoopManager.hpp" CPluginSystem::CPluginSystem() { g_pFunctionHookSystem = std::make_unique(); @@ -82,7 +83,7 @@ CPlugin* CPluginSystem::loadPlugin(const std::string& path) { PLUGIN->version = PLUGINDATA.version; PLUGIN->name = PLUGINDATA.name; - g_pConfigManager->m_bForceReload = true; + g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); }); Debug::log(LOG, R"( [PluginSystem] Plugin {} loaded. Handle: {:x}, path: "{}", author: "{}", description: "{}", version: "{}")", PLUGINDATA.name, (uintptr_t)MODULE, path, PLUGINDATA.author, PLUGINDATA.description, PLUGINDATA.version); @@ -137,7 +138,7 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { Debug::log(LOG, " [PluginSystem] Plugin {} unloaded.", PLNAME); // reload config to fix some stuf like e.g. unloadedPluginVars - g_pConfigManager->m_bForceReload = true; + g_pEventLoopManager->doLater([] { g_pConfigManager->reload(); }); } void CPluginSystem::unloadAllPlugins() {