hyprpm: use glaze to parse hyprctl plugin list (#8812)

* Use std::filesystem::path in hyprpm DataState to avoid concatenating strings with (folder + "/" + file)
* Added getPluginStates helper method in DataState
* Small clang-tidy improvements
This commit is contained in:
Tuur Vanhoutte 2024-12-27 15:40:46 +01:00 committed by GitHub
parent e75e2cdac7
commit 43ca66779b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 97 additions and 97 deletions

View file

@ -63,6 +63,15 @@ runs:
librsvg \ librsvg \
re2 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 - name: Get hyprwayland-scanner-git
shell: bash shell: bash
run: | run: |

View file

@ -10,10 +10,11 @@ file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)
pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.2.4) pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.2.4)
find_package(glaze REQUIRED)
add_executable(hyprpm ${SRCFILES}) add_executable(hyprpm ${SRCFILES})
target_link_libraries(hyprpm PUBLIC PkgConfig::hyprpm_deps) target_link_libraries(hyprpm PUBLIC PkgConfig::hyprpm_deps glaze::glaze)
# binary # binary
install(TARGETS hyprpm) install(TARGETS hyprpm)

View file

@ -1,11 +1,10 @@
#include "DataState.hpp" #include "DataState.hpp"
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
#include <print> #include <print>
#include <filesystem>
#include <fstream> #include <fstream>
#include "PluginManager.hpp" #include "PluginManager.hpp"
std::string DataState::getDataStatePath() { std::filesystem::path DataState::getDataStatePath() {
const auto HOME = getenv("HOME"); const auto HOME = getenv("HOME");
if (!HOME) { if (!HOME) {
std::println(stderr, "DataState: no $HOME"); std::println(stderr, "DataState: no $HOME");
@ -16,12 +15,29 @@ std::string DataState::getDataStatePath() {
const auto XDG_DATA_HOME = getenv("XDG_DATA_HOME"); const auto XDG_DATA_HOME = getenv("XDG_DATA_HOME");
if (XDG_DATA_HOME) if (XDG_DATA_HOME)
return std::string{XDG_DATA_HOME} + "/hyprpm"; return std::filesystem::path{XDG_DATA_HOME} / "hyprpm";
return std::string{HOME} + "/.local/share/hyprpm"; return std::filesystem::path{HOME} / ".local/share/hyprpm";
} }
std::string DataState::getHeadersPath() { std::string DataState::getHeadersPath() {
return getDataStatePath() + "/headersRoot"; return getDataStatePath() / "headersRoot";
}
std::vector<std::filesystem::path> DataState::getPluginStates() {
ensureStateStoreExists();
std::vector<std::filesystem::path> 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() { void DataState::ensureStateStoreExists() {
@ -37,7 +53,7 @@ void DataState::ensureStateStoreExists() {
void DataState::addNewPluginRepo(const SPluginRepository& repo) { void DataState::addNewPluginRepo(const SPluginRepository& repo) {
ensureStateStoreExists(); ensureStateStoreExists();
const auto PATH = getDataStatePath() + "/" + repo.name; const auto PATH = getDataStatePath() / repo.name;
std::filesystem::create_directories(PATH); std::filesystem::create_directories(PATH);
// clang-format off // clang-format off
@ -50,19 +66,21 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) {
}} }}
}; };
for (auto const& p : repo.plugins) { for (auto const& p : repo.plugins) {
const auto filename = p.name + ".so";
// copy .so to the good place // copy .so to the good place
if (std::filesystem::exists(p.filename)) 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{ DATA.emplace(p.name, toml::table{
{"filename", p.name + ".so"}, {"filename", filename},
{"enabled", p.enabled}, {"enabled", p.enabled},
{"failed", p.failed} {"failed", p.failed}
}); });
} }
// clang-format on // clang-format on
std::ofstream ofs(PATH + "/state.toml", std::ios::trunc); std::ofstream ofs(PATH / "state.toml", std::ios::trunc);
ofs << DATA; ofs << DATA;
ofs.close(); ofs.close();
} }
@ -72,17 +90,10 @@ bool DataState::pluginRepoExists(const std::string& urlOrName) {
const auto PATH = getDataStatePath(); const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) { for (const auto& stateFile : getPluginStates()) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot") const auto STATE = toml::parse_file(stateFile.c_str());
continue; const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
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("");
if (URL == urlOrName || NAME == urlOrName) if (URL == urlOrName || NAME == urlOrName)
return true; return true;
@ -96,29 +107,22 @@ void DataState::removePluginRepo(const std::string& urlOrName) {
const auto PATH = getDataStatePath(); const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) { for (const auto& stateFile : getPluginStates()) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot") const auto STATE = toml::parse_file(stateFile.c_str());
continue; const auto NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].value_or("");
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("");
if (URL == urlOrName || NAME == urlOrName) { if (URL == urlOrName || NAME == urlOrName) {
// unload the plugins!! // 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")) if (!file.path().string().ends_with(".so"))
continue; continue;
g_pPluginManager->loadUnloadPlugin(std::filesystem::absolute(file.path()), false); g_pPluginManager->loadUnloadPlugin(std::filesystem::absolute(file.path()), false);
} }
std::filesystem::remove_all(entry.path()); std::filesystem::remove_all(stateFile.parent_path());
return; return;
} }
} }
@ -139,7 +143,7 @@ void DataState::updateGlobalState(const SGlobalState& state) {
}; };
// clang-format on // clang-format on
std::ofstream ofs(PATH + "/state.toml", std::ios::trunc); std::ofstream ofs(PATH / "state.toml", std::ios::trunc);
ofs << DATA; ofs << DATA;
ofs.close(); ofs.close();
} }
@ -147,12 +151,12 @@ void DataState::updateGlobalState(const SGlobalState& state) {
SGlobalState DataState::getGlobalState() { SGlobalState DataState::getGlobalState() {
ensureStateStoreExists(); 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{}; return SGlobalState{};
auto DATA = toml::parse_file(PATH + "/state.toml"); auto DATA = toml::parse_file(stateFile.c_str());
SGlobalState state; SGlobalState state;
state.headersHashCompiled = DATA["state"]["hash"].value_or(""); state.headersHashCompiled = DATA["state"]["hash"].value_or("");
@ -167,15 +171,8 @@ std::vector<SPluginRepository> DataState::getAllRepositories() {
const auto PATH = getDataStatePath(); const auto PATH = getDataStatePath();
std::vector<SPluginRepository> repos; std::vector<SPluginRepository> repos;
for (const auto& stateFile : getPluginStates()) {
for (const auto& entry : std::filesystem::directory_iterator(PATH)) { const auto STATE = toml::parse_file(stateFile.c_str());
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 NAME = STATE["repository"]["name"].value_or("");
const auto URL = STATE["repository"]["url"].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(); const auto PATH = getDataStatePath();
for (const auto& entry : std::filesystem::directory_iterator(PATH)) { for (const auto& stateFile : getPluginStates()) {
if (!entry.is_directory() || entry.path().stem() == "headersRoot") const auto STATE = toml::parse_file(stateFile.c_str());
continue;
if (!std::filesystem::exists(entry.path().string() + "/state.toml"))
continue;
auto STATE = toml::parse_file(entry.path().string() + "/state.toml");
for (const auto& [key, val] : STATE) { for (const auto& [key, val] : STATE) {
if (key == "repository") if (key == "repository")
continue; continue;
@ -231,10 +221,11 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) {
if (FAILED) if (FAILED)
return false; 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); std::ofstream state(stateFile, std::ios::trunc);
state << STATE; state << modifiedState;
state.close(); state.close();
return true; return true;

View file

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <filesystem>
#include <string> #include <string>
#include <vector> #include <vector>
#include "Plugin.hpp" #include "Plugin.hpp"
@ -9,14 +10,15 @@ struct SGlobalState {
}; };
namespace DataState { namespace DataState {
std::string getDataStatePath(); std::filesystem::path getDataStatePath();
std::string getHeadersPath(); std::string getHeadersPath();
void ensureStateStoreExists(); std::vector<std::filesystem::path> getPluginStates();
void addNewPluginRepo(const SPluginRepository& repo); void ensureStateStoreExists();
void removePluginRepo(const std::string& urlOrName); void addNewPluginRepo(const SPluginRepository& repo);
bool pluginRepoExists(const std::string& urlOrName); void removePluginRepo(const std::string& urlOrName);
void updateGlobalState(const SGlobalState& state); bool pluginRepoExists(const std::string& urlOrName);
SGlobalState getGlobalState(); void updateGlobalState(const SGlobalState& state);
bool setPluginEnabled(const std::string& name, bool enabled); SGlobalState getGlobalState();
std::vector<SPluginRepository> getAllRepositories(); bool setPluginEnabled(const std::string& name, bool enabled);
std::vector<SPluginRepository> getAllRepositories();
}; };

View file

@ -7,10 +7,8 @@
#include <cstdio> #include <cstdio>
#include <iostream> #include <iostream>
#include <array>
#include <filesystem> #include <filesystem>
#include <print> #include <print>
#include <thread>
#include <fstream> #include <fstream>
#include <algorithm> #include <algorithm>
#include <format> #include <format>
@ -21,6 +19,7 @@
#include <unistd.h> #include <unistd.h>
#include <toml++/toml.hpp> #include <toml++/toml.hpp>
#include <glaze/glaze.hpp>
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
#include <hyprutils/os/Process.hpp> #include <hyprutils/os/Process.hpp>
@ -83,13 +82,13 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) {
hlbranch = hlbranch.substr(0, hlbranch.find(" at commit ")); hlbranch = hlbranch.substr(0, hlbranch.find(" at commit "));
std::string hldate = HLVERCALL.substr(HLVERCALL.find("Date: ") + 6); 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; std::string hlcommits;
if (HLVERCALL.contains("commits:")) { if (HLVERCALL.contains("commits:")) {
hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9); hlcommits = HLVERCALL.substr(HLVERCALL.find("commits:") + 9);
hlcommits = hlcommits.substr(0, hlcommits.find(" ")); hlcommits = hlcommits.substr(0, hlcommits.find(' '));
} }
int commits = 0; int commits = 0;
@ -378,7 +377,7 @@ eHeadersErrors CPluginManager::headersValid() {
// find headers commit // find headers commit
const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath()); 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/")) if (!headers.contains("-I/"))
return HEADERS_MISSING; return HEADERS_MISSING;
@ -790,35 +789,28 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() {
const auto HOME = getenv("HOME"); const auto HOME = getenv("HOME");
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HOME || !HIS) { if (!HOME || !HIS) {
std::println(stderr, "PluginManager: no $HOME or HIS"); std::println(stderr, "PluginManager: no $HOME or $HYPRLAND_INSTANCE_SIGNATURE");
return LOADSTATE_FAIL; 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<glz::json_t::array_t>(execAndGet("hyprctl plugins list -j"));
if (!json) {
std::println(stderr, "PluginManager: couldn't parse hyprctl output");
return LOADSTATE_FAIL;
}
std::vector<std::string> loadedPlugins; std::vector<std::string> 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::string>());
}
std::println("{}", successString("Ensuring plugin load state")); 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 // get state
const auto REPOS = DataState::getAllRepositories(); const auto REPOS = DataState::getAllRepositories();
@ -853,7 +845,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() {
for (auto const& p : loadedPlugins) { for (auto const& p : loadedPlugins) {
if (!enabled(p)) { if (!enabled(p)) {
// unload // 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)); std::println("{}", infoString("{} will be unloaded after restarting Hyprland", p));
hyprlandVersionMismatch = true; hyprlandVersionMismatch = true;
} else } 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()) if (std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end())
continue; 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)); std::println("{}", infoString("{} will be loaded after restarting Hyprland", p.name));
hyprlandVersionMismatch = true; hyprlandVersionMismatch = true;
} else } else

View file

@ -8,6 +8,7 @@ executable(
dependency('hyprutils', version: '>= 0.1.1'), dependency('hyprutils', version: '>= 0.1.1'),
dependency('threads'), dependency('threads'),
dependency('tomlplusplus'), dependency('tomlplusplus'),
dependency('glaze', method: 'cmake'),
], ],
install: true, install: true,
) )

View file

@ -5,12 +5,14 @@
pkg-config, pkg-config,
pkgconf, pkgconf,
makeWrapper, makeWrapper,
cmake,
meson, meson,
ninja, ninja,
aquamarine, aquamarine,
binutils, binutils,
cairo, cairo,
git, git,
glaze,
hyprcursor, hyprcursor,
hyprgraphics, hyprgraphics,
hyprland-protocols, hyprland-protocols,
@ -102,6 +104,7 @@ in
makeWrapper makeWrapper
meson meson
ninja ninja
cmake # needed for glaze
pkg-config pkg-config
]; ];
@ -116,6 +119,7 @@ in
aquamarine aquamarine
cairo cairo
git git
glaze
hyprcursor hyprcursor
hyprgraphics hyprgraphics
hyprland-protocols hyprland-protocols