diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 777c60d..8107978 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -1,6 +1,10 @@ #include "ConfigManager.hpp" +#include "../helpers/Log.hpp" +#include "../helpers/MiscFunctions.hpp" #include #include +#include +#include static std::string getMainConfigPath() { static const auto paths = Hyprutils::Path::findConfig("hypridle"); @@ -13,6 +17,19 @@ static std::string getMainConfigPath() { CConfigManager::CConfigManager(std::string configPath) : m_config(configPath.empty() ? getMainConfigPath().c_str() : configPath.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = false}) { ; + configCurrentPath = configPath.empty() ? getMainConfigPath() : configPath; +} + +static Hyprlang::CParseResult handleSource(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = g_pConfigManager->handleSource(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; } void CConfigManager::init() { @@ -31,6 +48,12 @@ void CConfigManager::init() { m_config.addConfigValue("general:ignore_systemd_inhibit", Hyprlang::INT{0}); m_config.addConfigValue("general:inhibit_sleep", Hyprlang::INT{2}); + // track the file in the circular dependency chain + const auto mainConfigPath = getMainConfigPath(); + alreadyIncludedSourceFiles.insert(std::filesystem::canonical(mainConfigPath)); + + m_config.registerHandler(&::handleSource, "source", {.allowFlags = false}); + m_config.commence(); auto result = m_config.parse(); @@ -80,3 +103,56 @@ Hyprlang::CParseResult CConfigManager::postParse() { std::vector CConfigManager::getRules() { return m_vRules; } + +std::optional CConfigManager::handleSource(const std::string& command, const std::string& rawpath) { + if (rawpath.length() < 2) { + return "source path " + rawpath + " bogus!"; + } + std::unique_ptr glob_buf{new glob_t, [](glob_t* g) { globfree(g); }}; + memset(glob_buf.get(), 0, sizeof(glob_t)); + + const auto CURRENTDIR = std::filesystem::path(configCurrentPath).parent_path().string(); + + if (auto r = glob(absolutePath(rawpath, CURRENTDIR).c_str(), GLOB_TILDE, nullptr, glob_buf.get()); r != 0) { + std::string err = std::format("source= globbing error: {}", r == GLOB_NOMATCH ? "found no match" : GLOB_ABORTED ? "read error" : "out of memory"); + Debug::log(ERR, "{}", err); + return err; + } + + for (size_t i = 0; i < glob_buf->gl_pathc; i++) { + const auto PATH = absolutePath(glob_buf->gl_pathv[i], CURRENTDIR); + + if (PATH.empty() || PATH == configCurrentPath) { + Debug::log(WARN, "source= skipping invalid path"); + continue; + } + + if (std::find(alreadyIncludedSourceFiles.begin(), alreadyIncludedSourceFiles.end(), PATH) != alreadyIncludedSourceFiles.end()) { + Debug::log(WARN, "source= skipping already included source file {} to prevent circular dependency", PATH); + continue; + } + + if (!std::filesystem::is_regular_file(PATH)) { + if (std::filesystem::exists(PATH)) { + Debug::log(WARN, "source= skipping non-file {}", PATH); + continue; + } + + Debug::log(ERR, "source= file doesnt exist"); + return "source file " + PATH + " doesn't exist!"; + } + + // track the file in the circular dependency chain + alreadyIncludedSourceFiles.insert(PATH); + + // allow for nested config parsing + auto backupConfigPath = configCurrentPath; + configCurrentPath = PATH; + + m_config.parseFile(PATH.c_str()); + + configCurrentPath = backupConfigPath; + } + + return {}; +} diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 6160914..c5f7dfb 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -4,6 +4,7 @@ #include +#include #include #include @@ -18,7 +19,10 @@ class CConfigManager { std::string onResume = ""; }; - std::vector getRules(); + std::vector getRules(); + std::optional handleSource(const std::string&, const std::string&); + std::string configCurrentPath; + std::set alreadyIncludedSourceFiles; template Hyprlang::CSimpleConfigValue getValue(const std::string& name) { diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp new file mode 100644 index 0000000..15fde27 --- /dev/null +++ b/src/helpers/MiscFunctions.cpp @@ -0,0 +1,19 @@ +#include + +#include "MiscFunctions.hpp" + +std::string absolutePath(const std::string& rawpath, const std::string& currentDir) { + std::filesystem::path path(rawpath); + + // Handling where rawpath starts with '~' + if (!rawpath.empty() && rawpath[0] == '~') { + static const char* const ENVHOME = getenv("HOME"); + path = std::filesystem::path(ENVHOME) / path.relative_path().string().substr(2); + } + + // Handling e.g. ./, ../ + if (path.is_relative()) + return std::filesystem::weakly_canonical(std::filesystem::path(currentDir) / path); + else + return std::filesystem::weakly_canonical(path); +} \ No newline at end of file diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp new file mode 100644 index 0000000..afab9be --- /dev/null +++ b/src/helpers/MiscFunctions.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::string absolutePath(const std::string&, const std::string&);