diff --git a/.github/actions/setup_base/action.yml b/.github/actions/setup_base/action.yml index c510bb74f..6df442b32 100644 --- a/.github/actions/setup_base/action.yml +++ b/.github/actions/setup_base/action.yml @@ -63,6 +63,15 @@ runs: librsvg \ re2 + - name: Get glaze + shell: bash + run: | + git clone https://github.com/stephenberry/glaze.git + cd glaze + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -S . -B ./build + cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` + cmake --install build + - name: Get hyprwayland-scanner-git shell: bash run: | diff --git a/hyprpm/CMakeLists.txt b/hyprpm/CMakeLists.txt index 22cb8caaa..8eb325fde 100644 --- a/hyprpm/CMakeLists.txt +++ b/hyprpm/CMakeLists.txt @@ -10,10 +10,11 @@ file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") set(CMAKE_CXX_STANDARD 23) pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.2.4) +find_package(glaze REQUIRED) add_executable(hyprpm ${SRCFILES}) -target_link_libraries(hyprpm PUBLIC PkgConfig::hyprpm_deps) +target_link_libraries(hyprpm PUBLIC PkgConfig::hyprpm_deps glaze::glaze) # binary install(TARGETS hyprpm) diff --git a/hyprpm/src/core/DataState.cpp b/hyprpm/src/core/DataState.cpp index fb8679d62..55f148a4d 100644 --- a/hyprpm/src/core/DataState.cpp +++ b/hyprpm/src/core/DataState.cpp @@ -1,11 +1,10 @@ #include "DataState.hpp" #include #include -#include #include #include "PluginManager.hpp" -std::string DataState::getDataStatePath() { +std::filesystem::path DataState::getDataStatePath() { const auto HOME = getenv("HOME"); if (!HOME) { std::println(stderr, "DataState: no $HOME"); @@ -16,12 +15,29 @@ std::string DataState::getDataStatePath() { const auto XDG_DATA_HOME = getenv("XDG_DATA_HOME"); if (XDG_DATA_HOME) - return std::string{XDG_DATA_HOME} + "/hyprpm"; - return std::string{HOME} + "/.local/share/hyprpm"; + return std::filesystem::path{XDG_DATA_HOME} / "hyprpm"; + return std::filesystem::path{HOME} / ".local/share/hyprpm"; } std::string DataState::getHeadersPath() { - return getDataStatePath() + "/headersRoot"; + return getDataStatePath() / "headersRoot"; +} + +std::vector DataState::getPluginStates() { + ensureStateStoreExists(); + + std::vector states; + for (const auto& entry : std::filesystem::directory_iterator(getDataStatePath())) { + if (!entry.is_directory() || entry.path().stem() == "headersRoot") + continue; + + const auto stateFile = entry.path() / "state.toml"; + if (!std::filesystem::exists(stateFile)) + continue; + + states.emplace_back(stateFile); + } + return states; } void DataState::ensureStateStoreExists() { @@ -37,7 +53,7 @@ void DataState::ensureStateStoreExists() { void DataState::addNewPluginRepo(const SPluginRepository& repo) { ensureStateStoreExists(); - const auto PATH = getDataStatePath() + "/" + repo.name; + const auto PATH = getDataStatePath() / repo.name; std::filesystem::create_directories(PATH); // clang-format off @@ -50,19 +66,21 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) { }} }; for (auto const& p : repo.plugins) { + const auto filename = p.name + ".so"; + // copy .so to the good place if (std::filesystem::exists(p.filename)) - std::filesystem::copy_file(p.filename, PATH + "/" + p.name + ".so"); + std::filesystem::copy_file(p.filename, PATH / filename); DATA.emplace(p.name, toml::table{ - {"filename", p.name + ".so"}, + {"filename", filename}, {"enabled", p.enabled}, {"failed", p.failed} }); } // clang-format on - std::ofstream ofs(PATH + "/state.toml", std::ios::trunc); + std::ofstream ofs(PATH / "state.toml", std::ios::trunc); ofs << DATA; ofs.close(); } @@ -72,17 +90,10 @@ bool DataState::pluginRepoExists(const std::string& urlOrName) { const auto PATH = getDataStatePath(); - for (const auto& entry : std::filesystem::directory_iterator(PATH)) { - if (!entry.is_directory() || entry.path().stem() == "headersRoot") - continue; - - if (!std::filesystem::exists(entry.path().string() + "/state.toml")) - continue; - - auto STATE = toml::parse_file(entry.path().string() + "/state.toml"); - - const auto NAME = STATE["repository"]["name"].value_or(""); - const auto URL = STATE["repository"]["url"].value_or(""); + for (const auto& stateFile : getPluginStates()) { + const auto STATE = toml::parse_file(stateFile.c_str()); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); if (URL == urlOrName || NAME == urlOrName) return true; @@ -96,29 +107,22 @@ void DataState::removePluginRepo(const std::string& urlOrName) { const auto PATH = getDataStatePath(); - for (const auto& entry : std::filesystem::directory_iterator(PATH)) { - if (!entry.is_directory() || entry.path().stem() == "headersRoot") - continue; - - if (!std::filesystem::exists(entry.path().string() + "/state.toml")) - continue; - - auto STATE = toml::parse_file(entry.path().string() + "/state.toml"); - - const auto NAME = STATE["repository"]["name"].value_or(""); - const auto URL = STATE["repository"]["url"].value_or(""); + for (const auto& stateFile : getPluginStates()) { + const auto STATE = toml::parse_file(stateFile.c_str()); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); if (URL == urlOrName || NAME == urlOrName) { // unload the plugins!! - for (const auto& file : std::filesystem::directory_iterator(entry.path())) { + for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) { if (!file.path().string().ends_with(".so")) continue; g_pPluginManager->loadUnloadPlugin(std::filesystem::absolute(file.path()), false); } - std::filesystem::remove_all(entry.path()); + std::filesystem::remove_all(stateFile.parent_path()); return; } } @@ -139,7 +143,7 @@ void DataState::updateGlobalState(const SGlobalState& state) { }; // clang-format on - std::ofstream ofs(PATH + "/state.toml", std::ios::trunc); + std::ofstream ofs(PATH / "state.toml", std::ios::trunc); ofs << DATA; ofs.close(); } @@ -147,12 +151,12 @@ void DataState::updateGlobalState(const SGlobalState& state) { SGlobalState DataState::getGlobalState() { ensureStateStoreExists(); - const auto PATH = getDataStatePath(); + const auto stateFile = getDataStatePath() / "state.toml"; - if (!std::filesystem::exists(PATH + "/state.toml")) + if (!std::filesystem::exists(stateFile)) return SGlobalState{}; - auto DATA = toml::parse_file(PATH + "/state.toml"); + auto DATA = toml::parse_file(stateFile.c_str()); SGlobalState state; state.headersHashCompiled = DATA["state"]["hash"].value_or(""); @@ -167,15 +171,8 @@ std::vector DataState::getAllRepositories() { const auto PATH = getDataStatePath(); std::vector repos; - - for (const auto& entry : std::filesystem::directory_iterator(PATH)) { - if (!entry.is_directory() || entry.path().stem() == "headersRoot") - continue; - - if (!std::filesystem::exists(entry.path().string() + "/state.toml")) - continue; - - auto STATE = toml::parse_file(entry.path().string() + "/state.toml"); + for (const auto& stateFile : getPluginStates()) { + const auto STATE = toml::parse_file(stateFile.c_str()); const auto NAME = STATE["repository"]["name"].value_or(""); const auto URL = STATE["repository"]["url"].value_or(""); @@ -210,15 +207,8 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) { const auto PATH = getDataStatePath(); - for (const auto& entry : std::filesystem::directory_iterator(PATH)) { - if (!entry.is_directory() || entry.path().stem() == "headersRoot") - continue; - - if (!std::filesystem::exists(entry.path().string() + "/state.toml")) - continue; - - auto STATE = toml::parse_file(entry.path().string() + "/state.toml"); - + for (const auto& stateFile : getPluginStates()) { + const auto STATE = toml::parse_file(stateFile.c_str()); for (const auto& [key, val] : STATE) { if (key == "repository") continue; @@ -231,10 +221,11 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) { if (FAILED) return false; - (*STATE[key].as_table()).insert_or_assign("enabled", enabled); + auto modifiedState = STATE; + (*modifiedState[key].as_table()).insert_or_assign("enabled", enabled); - std::ofstream state(entry.path().string() + "/state.toml", std::ios::trunc); - state << STATE; + std::ofstream state(stateFile, std::ios::trunc); + state << modifiedState; state.close(); return true; diff --git a/hyprpm/src/core/DataState.hpp b/hyprpm/src/core/DataState.hpp index 70788f72b..ebb550ba9 100644 --- a/hyprpm/src/core/DataState.hpp +++ b/hyprpm/src/core/DataState.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include "Plugin.hpp" @@ -9,14 +10,15 @@ struct SGlobalState { }; namespace DataState { - std::string getDataStatePath(); - std::string getHeadersPath(); - void ensureStateStoreExists(); - void addNewPluginRepo(const SPluginRepository& repo); - void removePluginRepo(const std::string& urlOrName); - bool pluginRepoExists(const std::string& urlOrName); - void updateGlobalState(const SGlobalState& state); - SGlobalState getGlobalState(); - bool setPluginEnabled(const std::string& name, bool enabled); - std::vector getAllRepositories(); + std::filesystem::path getDataStatePath(); + std::string getHeadersPath(); + std::vector getPluginStates(); + void ensureStateStoreExists(); + void addNewPluginRepo(const SPluginRepository& repo); + void removePluginRepo(const std::string& urlOrName); + bool pluginRepoExists(const std::string& urlOrName); + void updateGlobalState(const SGlobalState& state); + SGlobalState getGlobalState(); + bool setPluginEnabled(const std::string& name, bool enabled); + std::vector getAllRepositories(); }; \ No newline at end of file diff --git a/hyprpm/src/core/PluginManager.cpp b/hyprpm/src/core/PluginManager.cpp index 48b108c84..a3bd5a098 100644 --- a/hyprpm/src/core/PluginManager.cpp +++ b/hyprpm/src/core/PluginManager.cpp @@ -7,10 +7,8 @@ #include #include -#include #include #include -#include #include #include #include @@ -21,6 +19,7 @@ #include #include +#include #include #include @@ -83,13 +82,13 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) { hlbranch = hlbranch.substr(0, hlbranch.find(" at commit ")); std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6); - hldate = hldate.substr(0, hldate.find("\n")); + hldate = hldate.substr(0, hldate.find('\n')); std::string hlcommits; if (HLVERCALL.contains("commits:")) { hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9); - hlcommits = hlcommits.substr(0, hlcommits.find(" ")); + hlcommits = hlcommits.substr(0, hlcommits.find(' ')); } int commits = 0; @@ -378,7 +377,7 @@ eHeadersErrors CPluginManager::headersValid() { // find headers commit const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath()); - auto headers = execAndGet(cmd.c_str()); + auto headers = execAndGet(cmd); if (!headers.contains("-I/")) return HEADERS_MISSING; @@ -790,35 +789,28 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { const auto HOME = getenv("HOME"); const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); if (!HOME || !HIS) { - std::println(stderr, "PluginManager: no $HOME or HIS"); + std::println(stderr, "PluginManager: no $HOME or $HYPRLAND_INSTANCE_SIGNATURE"); return LOADSTATE_FAIL; } - const auto HYPRPMPATH = DataState::getDataStatePath() + "/"; + const auto HYPRPMPATH = DataState::getDataStatePath(); - auto pluginLines = execAndGet("hyprctl plugins list | grep Plugin"); + const auto json = glz::read_json(execAndGet("hyprctl plugins list -j")); + if (!json) { + std::println(stderr, "PluginManager: couldn't parse hyprctl output"); + return LOADSTATE_FAIL; + } std::vector loadedPlugins; + for (const auto& plugin : json.value()) { + if (!plugin.is_object() || !plugin.contains("name")) { + std::println(stderr, "PluginManager: couldn't parse plugin object"); + return LOADSTATE_FAIL; + } + loadedPlugins.emplace_back(plugin["name"].get()); + } std::println("{}", successString("Ensuring plugin load state")); - // iterate line by line - while (!pluginLines.empty()) { - auto plLine = pluginLines.substr(0, pluginLines.find('\n')); - - if (pluginLines.find('\n') != std::string::npos) - pluginLines = pluginLines.substr(pluginLines.find('\n') + 1); - else - pluginLines = ""; - - if (plLine.back() != ':') - continue; - - plLine = plLine.substr(7); - plLine = plLine.substr(0, plLine.find(" by ")); - - loadedPlugins.push_back(plLine); - } - // get state const auto REPOS = DataState::getAllRepositories(); @@ -853,7 +845,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { for (auto const& p : loadedPlugins) { if (!enabled(p)) { // unload - if (!loadUnloadPlugin(HYPRPMPATH + repoForName(p) + "/" + p + ".so", false)) { + if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p) / (p + ".so"), false)) { std::println("{}", infoString("{} will be unloaded after restarting Hyprland", p)); hyprlandVersionMismatch = true; } else @@ -870,7 +862,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { if (std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) continue; - if (!loadUnloadPlugin(HYPRPMPATH + repoForName(p.name) + "/" + p.filename, true)) { + if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) { std::println("{}", infoString("{} will be loaded after restarting Hyprland", p.name)); hyprlandVersionMismatch = true; } else diff --git a/hyprpm/src/meson.build b/hyprpm/src/meson.build index 2ef6c3238..fd914f9d2 100644 --- a/hyprpm/src/meson.build +++ b/hyprpm/src/meson.build @@ -8,6 +8,7 @@ executable( dependency('hyprutils', version: '>= 0.1.1'), dependency('threads'), dependency('tomlplusplus'), + dependency('glaze', method: 'cmake'), ], install: true, ) diff --git a/nix/default.nix b/nix/default.nix index 9293a35c7..8e3af31d1 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,12 +5,14 @@ pkg-config, pkgconf, makeWrapper, + cmake, meson, ninja, aquamarine, binutils, cairo, git, + glaze, hyprcursor, hyprgraphics, hyprland-protocols, @@ -102,6 +104,7 @@ in makeWrapper meson ninja + cmake # needed for glaze pkg-config ]; @@ -116,6 +119,7 @@ in aquamarine cairo git + glaze hyprcursor hyprgraphics hyprland-protocols