diff --git a/src/ConfigManager.cpp b/src/ConfigManager.cpp index 70ada2e..5cff2ea 100644 --- a/src/ConfigManager.cpp +++ b/src/ConfigManager.cpp @@ -5,6 +5,10 @@ #include #include #include "helpers/Log.hpp" +#include "helpers/MiscFunctions.hpp" +#include +#include +#include static std::string getMainConfigPath() { static const auto paths = Hyprutils::Path::findConfig("hyprsunset"); @@ -17,6 +21,18 @@ CConfigManager::CConfigManager(std::string configPath) : currentConfigPath = 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() { m_config.addConfigValue("max-gamma", Hyprlang::INT{100}); @@ -26,6 +42,12 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("profile", "gamma", Hyprlang::FLOAT{1.0f}); m_config.addSpecialConfigValue("profile", "identity", Hyprlang::INT{0}); + // 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(); @@ -94,3 +116,56 @@ float CConfigManager::getMaxGamma() { RASSERT(false, "Failed to construct max-gamma: {}", e.what()); // } } + +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(currentConfigPath).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 == currentConfigPath) { + 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 = currentConfigPath; + currentConfigPath = PATH; + + m_config.parseFile(PATH.c_str()); + + currentConfigPath = backupConfigPath; + } + + return {}; +} diff --git a/src/ConfigManager.hpp b/src/ConfigManager.hpp index 3768d0a..15fe054 100644 --- a/src/ConfigManager.hpp +++ b/src/ConfigManager.hpp @@ -3,6 +3,7 @@ #include "Hyprsunset.hpp" #include #include +#include class CConfigManager { public: @@ -13,10 +14,13 @@ class CConfigManager { void init(); + std::optional handleSource(const std::string&, const std::string&); + private: - Hyprlang::CConfig m_config; + Hyprlang::CConfig m_config; - std::string currentConfigPath; + std::string currentConfigPath; + std::set alreadyIncludedSourceFiles; }; inline UP g_pConfigManager; diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp new file mode 100644 index 0000000..4afc47e --- /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); +} 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&);