core: move to inotify for monitoring the config files

instead of manually polling every second which is not efficient, use inotify.

an added bonus is that inotify is much much faster
This commit is contained in:
Vaxry 2025-01-19 15:39:19 +01:00
parent 0a0e56d99c
commit 8dd2cd41fb
13 changed files with 143 additions and 116 deletions

View file

@ -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<CSeatManager>();
} break;
case STAGE_LATE: {
Debug::log(LOG, "Creating the ThreadManager!");
g_pThreadManager = std::make_unique<CThreadManager>();
Debug::log(LOG, "Creating CHyprCtl");
g_pHyprCtl = std::make_unique<CHyprCtl>();

View file

@ -5,7 +5,6 @@
#include <sys/resource.h>
#include "defines.hpp"
#include "managers/ThreadManager.hpp"
#include "managers/XWaylandManager.hpp"
#include "managers/KeybindManager.hpp"
#include "managers/SessionLockManager.hpp"

View file

@ -1,6 +1,7 @@
#include <re2/re2.h>
#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<Hyprlang::CConfig>(configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true});
m_configPaths.emplace_back(getMainConfigPath());
m_pConfig = std::make_unique<Hyprlang::CConfig>(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<std::string> 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<Hyprlang::INT>("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<std::string> 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;

View file

@ -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<Hyprlang::CConfig> m_pConfig;
std::vector<std::string> configPaths; // stores all the config paths
std::unordered_map<std::string, time_t> configModifyTimes; // stores modify times
std::vector<std::string> m_configPaths;
Hyprutils::Animation::CAnimationConfigTree m_AnimationTree;
@ -302,7 +300,6 @@ class CConfigManager {
std::optional<std::string> generateConfig(std::string configPath);
std::optional<std::string> verifyConfigExists();
void postConfigReload(const Hyprlang::CParseResult& result);
void reload();
SWorkspaceRule mergeWorkspaceRules(const SWorkspaceRule&, const SWorkspaceRule&);
};

View file

@ -0,0 +1,72 @@
#include "ConfigWatcher.hpp"
#include <sys/inotify.h>
#include "../debug/Log.hpp"
#include <ranges>
#include <fcntl.h>
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<std::string>& 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<void(const SConfigWatchEvent&)>& 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,
});
}
}

View file

@ -0,0 +1,32 @@
#pragma once
#include <memory>
#include <vector>
#include <string>
#include <functional>
class CConfigWatcher {
public:
CConfigWatcher();
~CConfigWatcher();
struct SConfigWatchEvent {
std::string file;
};
int getInotifyFD();
void setWatchList(const std::vector<std::string>& paths);
void setOnChange(const std::function<void(const SConfigWatchEvent&)>& fn);
void onInotifyEvent();
private:
struct SInotifyWatch {
int wd = -1;
std::string file;
};
std::function<void(const SConfigWatchEvent&)> m_watchCallback;
std::vector<SInotifyWatch> m_watches;
int m_inotifyFd = -1;
};
inline std::unique_ptr<CConfigWatcher> g_pConfigWatcher = std::make_unique<CConfigWatcher>();

View file

@ -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";
}

View file

@ -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<Hyprlang::INT>("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);
}

View file

@ -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<CThreadManager> g_pThreadManager;

View file

@ -1,6 +1,7 @@
#include "EventLoopManager.hpp"
#include "../../debug/Log.hpp"
#include "../../Compositor.hpp"
#include "../../config/ConfigWatcher.hpp"
#include <algorithm>
#include <limits>
@ -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()));

View file

@ -51,6 +51,8 @@ class CEventLoopManager {
SIdleData m_sIdle;
std::vector<SP<Aquamarine::SPollFD>> aqPollFDs;
wl_event_source* m_configWatcherInotifySource = nullptr;
friend class CSyncTimeline;
};

View file

@ -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 <dlfcn.h>
@ -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;
}

View file

@ -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<CHookSystem>();
@ -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() {