From 05ccbb2f2dc3121a48e9c1925357039b27d22a92 Mon Sep 17 00:00:00 2001 From: Luke Barkess <57995669+Brumus14@users.noreply.github.com> Date: Sun, 14 Dec 2025 17:16:58 +0000 Subject: [PATCH] hyprpm: added plugin author (#12594) --- hyprpm/src/core/DataState.cpp | 57 +++++++++++++++++++------------ hyprpm/src/core/DataState.hpp | 8 ++--- hyprpm/src/core/Plugin.cpp | 48 ++++++++++++++++++++++++++ hyprpm/src/core/Plugin.hpp | 23 ++++++++++++- hyprpm/src/core/PluginManager.cpp | 45 ++++++++++++++---------- hyprpm/src/core/PluginManager.hpp | 8 +++-- hyprpm/src/main.cpp | 38 ++++++++++----------- 7 files changed, 160 insertions(+), 67 deletions(-) create mode 100644 hyprpm/src/core/Plugin.cpp diff --git a/hyprpm/src/core/DataState.cpp b/hyprpm/src/core/DataState.cpp index 42f1d4289..64f3cfa02 100644 --- a/hyprpm/src/core/DataState.cpp +++ b/hyprpm/src/core/DataState.cpp @@ -93,6 +93,7 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) { auto DATA = toml::table{ {"repository", toml::table{ {"name", repo.name}, + {"author", repo.author}, {"hash", repo.hash}, {"url", repo.url}, {"rev", repo.rev} @@ -122,31 +123,32 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) { Debug::die("{}", failureString("Failed to write plugin state")); } -bool DataState::pluginRepoExists(const std::string& urlOrName) { +bool DataState::pluginRepoExists(const SPluginRepoIdentifier identifier) { ensureStateStoreExists(); 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(""); + const auto STATE = toml::parse_file(stateFile.c_str()); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); - if (URL == urlOrName || NAME == urlOrName) + if (identifier.matches(URL, NAME, AUTHOR)) return true; } return false; } -void DataState::removePluginRepo(const std::string& urlOrName) { +void DataState::removePluginRepo(const SPluginRepoIdentifier identifier) { ensureStateStoreExists(); 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) { + const auto STATE = toml::parse_file(stateFile.c_str()); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); + if (identifier.matches(URL, NAME, AUTHOR)) { // unload the plugins!! for (const auto& file : std::filesystem::directory_iterator(stateFile.parent_path())) { if (!file.path().string().ends_with(".so")) @@ -219,16 +221,18 @@ std::vector DataState::getAllRepositories() { 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(""); - const auto REV = STATE["repository"]["rev"].value_or(""); - const auto HASH = STATE["repository"]["hash"].value_or(""); + const auto NAME = STATE["repository"]["name"].value_or(""); + const auto AUTHOR = STATE["repository"]["author"].value_or(""); + const auto URL = STATE["repository"]["url"].value_or(""); + const auto REV = STATE["repository"]["rev"].value_or(""); + const auto HASH = STATE["repository"]["hash"].value_or(""); SPluginRepository repo; - repo.hash = HASH; - repo.name = NAME; - repo.url = URL; - repo.rev = REV; + repo.hash = HASH; + repo.name = NAME; + repo.author = AUTHOR; + repo.url = URL; + repo.rev = REV; for (const auto& [key, val] : STATE) { if (key == "repository") @@ -247,7 +251,7 @@ std::vector DataState::getAllRepositories() { return repos; } -bool DataState::setPluginEnabled(const std::string& name, bool enabled) { +bool DataState::setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled) { ensureStateStoreExists(); for (const auto& stateFile : getPluginStates()) { @@ -256,8 +260,17 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) { if (key == "repository") continue; - if (key.str() != name) - continue; + switch (identifier.type) { + case IDENTIFIER_NAME: + if (key.str() != identifier.name) + continue; + break; + case IDENTIFIER_AUTHOR_NAME: + if (STATE["repository"]["author"] != identifier.author || key.str() != identifier.name) + continue; + break; + default: return false; + } const auto FAILED = STATE[key]["failed"].value_or(false); diff --git a/hyprpm/src/core/DataState.hpp b/hyprpm/src/core/DataState.hpp index dfab535a7..d9872b907 100644 --- a/hyprpm/src/core/DataState.hpp +++ b/hyprpm/src/core/DataState.hpp @@ -15,11 +15,11 @@ namespace DataState { std::vector getPluginStates(); void ensureStateStoreExists(); void addNewPluginRepo(const SPluginRepository& repo); - void removePluginRepo(const std::string& urlOrName); - bool pluginRepoExists(const std::string& urlOrName); + void removePluginRepo(const SPluginRepoIdentifier identifier); + bool pluginRepoExists(const SPluginRepoIdentifier identifier); void updateGlobalState(const SGlobalState& state); void purgeAllCache(); SGlobalState getGlobalState(); - bool setPluginEnabled(const std::string& name, bool enabled); + bool setPluginEnabled(const SPluginRepoIdentifier identifier, bool enabled); std::vector getAllRepositories(); -}; \ No newline at end of file +}; diff --git a/hyprpm/src/core/Plugin.cpp b/hyprpm/src/core/Plugin.cpp new file mode 100644 index 000000000..3aaa85925 --- /dev/null +++ b/hyprpm/src/core/Plugin.cpp @@ -0,0 +1,48 @@ +#include "Plugin.hpp" + +SPluginRepoIdentifier SPluginRepoIdentifier::fromUrl(const std::string& url) { + return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = url}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromName(const std::string& name) { + return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = name}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromAuthorName(const std::string& author, const std::string& name) { + return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author}; +} + +SPluginRepoIdentifier SPluginRepoIdentifier::fromString(const std::string& string) { + if (string.find(':') != std::string::npos) { + return SPluginRepoIdentifier{.type = IDENTIFIER_URL, .url = string}; + } else { + auto slashPos = string.find('/'); + if (slashPos != std::string::npos) { + std::string author = string.substr(0, slashPos); + std::string name = string.substr(slashPos + 1, string.size() - slashPos - 1); + return SPluginRepoIdentifier{.type = IDENTIFIER_AUTHOR_NAME, .name = name, .author = author}; + } else { + return SPluginRepoIdentifier{.type = IDENTIFIER_NAME, .name = string}; + } + } +} + +std::string SPluginRepoIdentifier::toString() const { + switch (type) { + case IDENTIFIER_NAME: return name; + case IDENTIFIER_AUTHOR_NAME: return author + '/' + name; + case IDENTIFIER_URL: return url; + } + + return ""; +} + +bool SPluginRepoIdentifier::matches(const std::string& url, const std::string& name, const std::string& author) const { + switch (type) { + case IDENTIFIER_URL: return this->url == url; + case IDENTIFIER_NAME: return this->name == name; + case IDENTIFIER_AUTHOR_NAME: return this->author == author && this->name == name; + } + + return false; +} diff --git a/hyprpm/src/core/Plugin.hpp b/hyprpm/src/core/Plugin.hpp index e66031c98..a8c740848 100644 --- a/hyprpm/src/core/Plugin.hpp +++ b/hyprpm/src/core/Plugin.hpp @@ -14,6 +14,27 @@ struct SPluginRepository { std::string url; std::string rev; std::string name; + std::string author; std::vector plugins; std::string hash; -}; \ No newline at end of file +}; + +enum ePluginRepoIdentifierType { + IDENTIFIER_URL, + IDENTIFIER_NAME, + IDENTIFIER_AUTHOR_NAME +}; + +struct SPluginRepoIdentifier { + ePluginRepoIdentifierType type; + std::string url = ""; + std::string name = ""; + std::string author = ""; + + static SPluginRepoIdentifier fromString(const std::string& string); + static SPluginRepoIdentifier fromUrl(const std::string& Url); + static SPluginRepoIdentifier fromName(const std::string& name); + static SPluginRepoIdentifier fromAuthorName(const std::string& author, const std::string& name); + std::string toString() const; + bool matches(const std::string& url, const std::string& name, const std::string& author) const; +}; diff --git a/hyprpm/src/core/PluginManager.cpp b/hyprpm/src/core/PluginManager.cpp index ed952eecf..0d35b4ae0 100644 --- a/hyprpm/src/core/PluginManager.cpp +++ b/hyprpm/src/core/PluginManager.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -136,7 +137,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& return false; } - if (DataState::pluginRepoExists(url)) { + if (DataState::pluginRepoExists(SPluginRepoIdentifier::fromUrl(url))) { std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Repository already installed.")); return false; } @@ -333,10 +334,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& std::string repohash = execAndGet("cd " + m_szWorkingPluginDirectory + " && git rev-parse HEAD"); if (repohash.length() > 0) repohash.pop_back(); - repo.name = pManifest->m_repository.name.empty() ? url.substr(url.find_last_of('/') + 1) : pManifest->m_repository.name; - repo.url = url; - repo.rev = rev; - repo.hash = repohash; + auto lastSlash = url.find_last_of('/'); + auto secondLastSlash = url.find_last_of('/', lastSlash - 1); + repo.name = pManifest->m_repository.name.empty() ? url.substr(lastSlash + 1) : pManifest->m_repository.name; + repo.author = url.substr(secondLastSlash + 1, lastSlash - secondLastSlash - 1); + repo.url = url; + repo.rev = rev; + repo.hash = repohash; for (auto const& p : pManifest->m_plugins) { repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed}); } @@ -356,13 +360,13 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& return true; } -bool CPluginManager::removePluginRepo(const std::string& urlOrName) { - if (!DataState::pluginRepoExists(urlOrName)) { +bool CPluginManager::removePluginRepo(const SPluginRepoIdentifier identifier) { + if (!DataState::pluginRepoExists(identifier)) { std::println(stderr, "\n{}", failureString("Could not remove the repository. Repository is not installed.")); return false; } - std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << urlOrName << "\n " + std::cout << Colors::YELLOW << "!" << Colors::RESET << Colors::RED << " removing a plugin repository: " << Colors::RESET << identifier.toString() << "\n " << "Are you sure? [Y/n] "; std::fflush(stdout); std::string input; @@ -373,7 +377,7 @@ bool CPluginManager::removePluginRepo(const std::string& urlOrName) { return false; } - DataState::removePluginRepo(urlOrName); + DataState::removePluginRepo(identifier); return true; } @@ -444,7 +448,6 @@ eHeadersErrors CPluginManager::headersValid() { } bool CPluginManager::updateHeaders(bool force) { - DataState::ensureStateStoreExists(); const auto HLVER = getHyprlandVersion(false); @@ -772,7 +775,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; }); newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false}); } - DataState::removePluginRepo(newrepo.name); + DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name)); DataState::addNewPluginRepo(newrepo); std::filesystem::remove_all(m_szWorkingPluginDirectory); @@ -797,17 +800,23 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { return true; } -bool CPluginManager::enablePlugin(const std::string& name) { - bool ret = DataState::setPluginEnabled(name, true); +bool CPluginManager::enablePlugin(const SPluginRepoIdentifier identifier) { + bool ret = false; + + switch (identifier.type) { + case IDENTIFIER_NAME: + case IDENTIFIER_AUTHOR_NAME: ret = DataState::setPluginEnabled(identifier, true); break; + default: return false; + } if (ret) - std::println("{}", successString("Enabled {}", name)); + std::println("{}", successString("Enabled {}", identifier.name)); return ret; } -bool CPluginManager::disablePlugin(const std::string& name) { - bool ret = DataState::setPluginEnabled(name, false); +bool CPluginManager::disablePlugin(const SPluginRepoIdentifier identifier) { + bool ret = DataState::setPluginEnabled(identifier, false); if (ret) - std::println("{}", successString("Disabled {}", name)); + std::println("{}", successString("Disabled {}", identifier.name)); return ret; } @@ -928,7 +937,7 @@ void CPluginManager::listAllPlugins() { const auto REPOS = DataState::getAllRepositories(); for (auto const& r : REPOS) { - std::println("{}", infoString("Repository {}:", r.name)); + std::println("{}", infoString("Repository {} (by {}):", r.name, r.author)); for (auto const& p : r.plugins) { std::println(" │ Plugin {}", p.name); diff --git a/hyprpm/src/core/PluginManager.hpp b/hyprpm/src/core/PluginManager.hpp index 2425f5ec9..10a71469d 100644 --- a/hyprpm/src/core/PluginManager.hpp +++ b/hyprpm/src/core/PluginManager.hpp @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include "Plugin.hpp" enum eHeadersErrors { HEADERS_OK = 0, @@ -46,7 +48,7 @@ class CPluginManager { CPluginManager(); bool addNewPluginRepo(const std::string& url, const std::string& rev); - bool removePluginRepo(const std::string& urlOrName); + bool removePluginRepo(const SPluginRepoIdentifier identifier); eHeadersErrors headersValid(); bool updateHeaders(bool force = false); @@ -54,8 +56,8 @@ class CPluginManager { void listAllPlugins(); - bool enablePlugin(const std::string& name); - bool disablePlugin(const std::string& name); + bool enablePlugin(const SPluginRepoIdentifier identifier); + bool disablePlugin(const SPluginRepoIdentifier identifier); ePluginLoadStateReturn ensurePluginsLoadState(bool forceReload = false); bool loadUnloadPlugin(const std::string& path, bool load); diff --git a/hyprpm/src/main.cpp b/hyprpm/src/main.cpp index 817049ff5..dced58e75 100644 --- a/hyprpm/src/main.cpp +++ b/hyprpm/src/main.cpp @@ -13,25 +13,25 @@ using namespace Hyprutils::Utils; constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager ┃ -┣ add [url] [git rev] → Install a new plugin repository from git. Git revision -┃ is optional, when set, commit locks are ignored. -┣ remove [url/name] → Remove an installed plugin repository. -┣ enable [name] → Enable a plugin. -┣ disable [name] → Disable a plugin. -┣ update → Check and update all plugins if needed. -┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded. -┣ list → List all installed plugins. -┣ purge-cache → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers. +┣ add [git rev] → Install a new plugin repository from git. Git revision +┃ is optional, when set, commit locks are ignored. +┣ remove → Remove an installed plugin repository. +┣ enable → Enable a plugin. +┣ disable → Disable a plugin. +┣ update → Check and update all plugins if needed. +┣ reload → Reload hyprpm state. Ensure all enabled plugins are loaded. +┣ list → List all installed plugins. +┣ purge-cache → Remove the entire hyprpm cache, built plugins, hyprpm settings and headers. ┃ ┣ Flags: ┃ -┣ --notify | -n → Send a hyprland notification confirming successful plugin load. -┃ Warnings/Errors trigger notifications regardless of this flag. -┣ --help | -h → Show this menu. -┣ --verbose | -v → Enable too much logging. -┣ --force | -f → Force an operation ignoring checks (e.g. update -f). -┣ --no-shallow | -s → Disable shallow cloning of Hyprland sources. -┣ --hl-url | → Pass a custom hyprland source url. +┣ --notify | -n → Send a hyprland notification confirming successful plugin load. +┃ Warnings/Errors trigger notifications regardless of this flag. +┣ --help | -h → Show this menu. +┣ --verbose | -v → Enable too much logging. +┣ --force | -f → Force an operation ignoring checks (e.g. update -f). +┣ --no-shallow | -s → Disable shallow cloning of Hyprland sources. +┣ --hl-url | → Pass a custom hyprland source url. ┗ )#"; @@ -126,7 +126,7 @@ int main(int argc, char** argv, char** envp) { NSys::root::cacheSudo(); CScopeGuard x([] { NSys::root::dropSudo(); }); - return g_pPluginManager->removePluginRepo(command[1]) ? 0 : 1; + return g_pPluginManager->removePluginRepo(SPluginRepoIdentifier::fromString(command[1])) ? 0 : 1; } else if (command[0] == "update") { NSys::root::cacheSudo(); CScopeGuard x([] { NSys::root::dropSudo(); }); @@ -160,7 +160,7 @@ int main(int argc, char** argv, char** envp) { return 1; } - if (!g_pPluginManager->enablePlugin(command[1])) { + if (!g_pPluginManager->enablePlugin(SPluginRepoIdentifier::fromString(command[1]))) { std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)")); return 1; } @@ -181,7 +181,7 @@ int main(int argc, char** argv, char** envp) { return 1; } - if (!g_pPluginManager->disablePlugin(command[1])) { + if (!g_pPluginManager->disablePlugin(SPluginRepoIdentifier::fromString(command[1]))) { std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)")); return 1; }