diff --git a/example/hyprland.conf b/example/hyprland.conf index e21b7844..b1240c71 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -64,6 +64,7 @@ env = HYPRCURSOR_SIZE,24 # permission = /usr/(bin|local/bin)/grim, screencopy, allow # permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow +# permission = /usr/(bin|local/bin)/hyprpm, plugin, allow ##################### diff --git a/hyprpm/src/core/HyprlandSocket.cpp b/hyprpm/src/core/HyprlandSocket.cpp new file mode 100644 index 00000000..4d86192c --- /dev/null +++ b/hyprpm/src/core/HyprlandSocket.cpp @@ -0,0 +1,85 @@ +#include "HyprlandSocket.hpp" +#include +#include +#include "../helpers/StringUtils.hpp" +#include +#include +#include + +static int getUID() { + const auto UID = getuid(); + const auto PWUID = getpwuid(UID); + return PWUID ? PWUID->pw_uid : UID; +} + +static std::string getRuntimeDir() { + const auto XDG = getenv("XDG_RUNTIME_DIR"); + + if (!XDG) { + const std::string USERID = std::to_string(getUID()); + return "/run/user/" + USERID + "/hypr"; + } + + return std::string{XDG} + "/hypr"; +} + +std::string NHyprlandSocket::send(const std::string& cmd) { + const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0); + + if (SERVERSOCKET < 0) { + std::println("{}", failureString("Couldn't open a socket (1)")); + return ""; + } + + const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); + + if (!HIS) { + std::println("{}", failureString("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?) (3)")); + return ""; + } + + sockaddr_un serverAddress = {0}; + serverAddress.sun_family = AF_UNIX; + + std::string socketPath = getRuntimeDir() + "/" + HIS + "/.socket.sock"; + + strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1); + + if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) { + std::println("{}", failureString("Couldn't connect to " + socketPath + ". (4)")); + return ""; + } + + auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length()); + + if (sizeWritten < 0) { + std::println("{}", failureString("Couldn't write (5)")); + return ""; + } + + std::string reply = ""; + constexpr size_t BUFFER_SIZE = 8192; + char buffer[BUFFER_SIZE] = {0}; + + sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); + + if (sizeWritten < 0) { + std::println("{}", failureString("Couldn't read (6)")); + return ""; + } + + reply += std::string(buffer, sizeWritten); + + while (sizeWritten == BUFFER_SIZE) { + sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); + if (sizeWritten < 0) { + std::println("{}", failureString("Couldn't read (7)")); + return ""; + } + reply += std::string(buffer, sizeWritten); + } + + close(SERVERSOCKET); + + return reply; +} diff --git a/hyprpm/src/core/HyprlandSocket.hpp b/hyprpm/src/core/HyprlandSocket.hpp new file mode 100644 index 00000000..e33be5ca --- /dev/null +++ b/hyprpm/src/core/HyprlandSocket.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace NHyprlandSocket { + std::string send(const std::string& cmd); +}; \ No newline at end of file diff --git a/hyprpm/src/core/PluginManager.cpp b/hyprpm/src/core/PluginManager.cpp index 22954996..ee7d8e8f 100644 --- a/hyprpm/src/core/PluginManager.cpp +++ b/hyprpm/src/core/PluginManager.cpp @@ -4,6 +4,7 @@ #include "../progress/CProgressBar.hpp" #include "Manifest.hpp" #include "DataState.hpp" +#include "HyprlandSocket.hpp" #include #include @@ -66,7 +67,7 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) { else onceInstalled = true; - const auto HLVERCALL = running ? execAndGet("hyprctl version") : execAndGet("Hyprland --version"); + const auto HLVERCALL = running ? NHyprlandSocket::send("/version") : execAndGet("Hyprland --version"); if (m_bVerbose) std::println("{}", verboseString("{} version returned: {}", running ? "running" : "installed", HLVERCALL)); @@ -797,9 +798,9 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload) } const auto HYPRPMPATH = DataState::getDataStatePath(); - const auto json = glz::read_json(execAndGet("hyprctl plugins list -j")); + const auto json = glz::read_json(NHyprlandSocket::send("j/plugins list")); if (!json) { - std::println(stderr, "PluginManager: couldn't parse hyprctl output"); + std::println(stderr, "PluginManager: couldn't parse plugin list output"); return LOADSTATE_FAIL; } @@ -888,9 +889,9 @@ bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) { } if (load) - execAndGet("hyprctl plugin load " + path); + NHyprlandSocket::send("/plugin load " + path); else - execAndGet("hyprctl plugin unload " + path); + NHyprlandSocket::send("/plugin unload " + path); return true; } @@ -915,7 +916,7 @@ void CPluginManager::listAllPlugins() { } void CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message) { - execAndGet("hyprctl notify " + std::to_string((int)icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message); + NHyprlandSocket::send("/notify " + std::to_string((int)icon) + " " + std::to_string(durationMs) + " " + std::to_string(color) + " " + message); } std::string CPluginManager::headerError(const eHeadersErrors err) { diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 26b14dda..790e8484 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -1789,18 +1789,7 @@ void CConfigManager::handlePluginLoads() { return; bool pluginsChanged = false; - auto failedPlugins = g_pPluginSystem->updateConfigPlugins(m_declaredPlugins, pluginsChanged); - - if (!failedPlugins.empty()) { - std::stringstream error; - error << "Failed to load the following plugins:"; - - for (const auto& path : failedPlugins) { - error << "\n" << path; - } - - g_pHyprError->queueCreate(error.str(), CHyprColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0)); - } + g_pPluginSystem->updateConfigPlugins(m_declaredPlugins, pluginsChanged); if (pluginsChanged) { g_pHyprError->destroy(); diff --git a/src/config/defaultConfig.hpp b/src/config/defaultConfig.hpp index 01ad1e72..22fc9f00 100644 --- a/src/config/defaultConfig.hpp +++ b/src/config/defaultConfig.hpp @@ -77,6 +77,7 @@ env = HYPRCURSOR_SIZE,24 # permission = /usr/(bin|local/bin)/grim, screencopy, allow # permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow +# permission = /usr/(bin|local/bin)/hyprpm, plugin, allow ##################### diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index 762f5d84..49325887 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ #include #include +#include using namespace Hyprutils::String; using namespace Hyprutils::OS; #include @@ -53,6 +55,28 @@ using namespace Hyprutils::OS; #include "../render/Renderer.hpp" #include "../render/OpenGL.hpp" +#if defined(__DragonFly__) || defined(__FreeBSD__) +#include +#define CRED_T xucred +#define CRED_LVL SOL_LOCAL +#define CRED_OPT LOCAL_PEERCRED +#define CRED_PID cr_pid +#elif defined(__NetBSD__) +#define CRED_T unpcbid +#define CRED_LVL SOL_LOCAL +#define CRED_OPT LOCAL_PEEREID +#define CRED_PID unp_pid +#else +#if defined(__OpenBSD__) +#define CRED_T sockpeercred +#else +#define CRED_T ucred +#endif +#define CRED_LVL SOL_SOCKET +#define CRED_OPT SO_PEERCRED +#define CRED_PID pid +#endif + static void trimTrailingComma(std::string& str) { if (!str.empty() && str.back() == ',') str.pop_back(); @@ -1480,10 +1504,18 @@ static std::string dispatchPlugin(eHyprCtlOutputFormat format, std::string reque if (vars.size() < 3) return "not enough args"; - const auto PLUGIN = g_pPluginSystem->loadPlugin(PATH); + g_pHyprCtl->m_currentRequestParams.pendingPromise = CPromise::make([PATH](SP> resolver) { + g_pPluginSystem->loadPlugin(PATH)->then([resolver, PATH](SP> result) { + if (result->hasError()) { + resolver->reject(result->error()); + return; + } - if (!PLUGIN) - return "error in loading plugin, last error: " + g_pPluginSystem->m_szLastError; + resolver->resolve("ok"); + }); + }); + + return "ok"; } else if (OPERATION == "unload") { if (vars.size() < 3) return "not enough args"; @@ -1712,9 +1744,10 @@ void CHyprCtl::unregisterCommand(const SP& cmd) { } std::string CHyprCtl::getReply(std::string request) { - auto format = eHyprCtlOutputFormat::FORMAT_NORMAL; - bool reloadAll = false; - m_currentRequestParams = {}; + auto format = eHyprCtlOutputFormat::FORMAT_NORMAL; + bool reloadAll = false; + m_currentRequestParams.all = false; + m_currentRequestParams.sysInfoConfig = false; // process flags for non-batch requests if (!request.starts_with("[[BATCH]]") && request.contains("/")) { @@ -1867,6 +1900,16 @@ static int hyprCtlFDTick(int fd, uint32_t mask, void* data) { std::array readBuffer; + // try to get creds + CRED_T creds; + uint32_t len = sizeof(creds); + if (getsockopt(ACCEPTEDCONNECTION, CRED_LVL, CRED_OPT, &creds, &len) == -1) + Debug::log(ERR, "Hyprctl: failed to get peer creds"); + else { + g_pHyprCtl->m_currentRequestParams.pid = creds.CRED_PID; + Debug::log(LOG, "Hyprctl: new connection from pid {}", creds.CRED_PID); + } + // pollfd pollfds[1] = { { @@ -1903,18 +1946,34 @@ static int hyprCtlFDTick(int fd, uint32_t mask, void* data) { reply = "Err: " + std::string(e.what()); } - successWrite(ACCEPTEDCONNECTION, reply); + if (g_pHyprCtl->m_currentRequestParams.pendingPromise) { + // we have a promise pending + g_pHyprCtl->m_currentRequestParams.pendingPromise->then([ACCEPTEDCONNECTION, request](SP> result) { + const auto RES = result->hasError() ? result->error() : result->result(); + successWrite(ACCEPTEDCONNECTION, RES); - if (isFollowUpRollingLogRequest(request)) { - Debug::log(LOG, "Followup rollinglog request received. Starting thread to write to socket."); - Debug::SRollingLogFollow::get().startFor(ACCEPTEDCONNECTION); - runWritingDebugLogThread(ACCEPTEDCONNECTION); - Debug::log(LOG, Debug::SRollingLogFollow::get().debugInfo()); - } else - close(ACCEPTEDCONNECTION); + // No rollinglog or ensureMonitor here. These are only for plugins for now. - if (g_pConfigManager->m_wantsMonitorReload) - g_pConfigManager->ensureMonitorStatus(); + close(ACCEPTEDCONNECTION); + }); + + g_pHyprCtl->m_currentRequestParams.pendingPromise.reset(); + } else { + successWrite(ACCEPTEDCONNECTION, reply); + + if (isFollowUpRollingLogRequest(request)) { + Debug::log(LOG, "Followup rollinglog request received. Starting thread to write to socket."); + Debug::SRollingLogFollow::get().startFor(ACCEPTEDCONNECTION); + runWritingDebugLogThread(ACCEPTEDCONNECTION); + Debug::log(LOG, Debug::SRollingLogFollow::get().debugInfo()); + } else + close(ACCEPTEDCONNECTION); + + if (g_pConfigManager->m_wantsMonitorReload) + g_pConfigManager->ensureMonitorStatus(); + + g_pHyprCtl->m_currentRequestParams.pid = 0; + } return 0; } diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp index a62dd665..b4f3d690 100644 --- a/src/debug/HyprCtl.hpp +++ b/src/debug/HyprCtl.hpp @@ -2,8 +2,10 @@ #include #include "../helpers/MiscFunctions.hpp" +#include "../helpers/defer/Promise.hpp" #include "../desktop/Window.hpp" #include +#include #include // exposed for main.cpp @@ -23,8 +25,10 @@ class CHyprCtl { Hyprutils::OS::CFileDescriptor m_socketFD; struct { - bool all = false; - bool sysInfoConfig = false; + bool all = false; + bool sysInfoConfig = false; + pid_t pid = 0; + SP> pendingPromise; } m_currentRequestParams; static std::string getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format); diff --git a/src/managers/permissions/DynamicPermissionManager.cpp b/src/managers/permissions/DynamicPermissionManager.cpp index c23f1fdb..e111343e 100644 --- a/src/managers/permissions/DynamicPermissionManager.cpp +++ b/src/managers/permissions/DynamicPermissionManager.cpp @@ -7,6 +7,9 @@ #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" +#include +using namespace Hyprutils::String; + #if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) #include #endif @@ -64,10 +67,7 @@ static const char* permissionToHumanString(eDynamicPermissionType type) { return "error"; } -static std::expected binaryNameForWlClient(wl_client* client) { - pid_t pid = 0; - wl_client_get_credentials(client, &pid, nullptr, nullptr); - +static std::expected binaryNameForPid(pid_t pid) { if (pid <= 0) return std::unexpected("No pid for client"); @@ -102,6 +102,13 @@ static std::expected binaryNameForWlClient(wl_client* return fullPath; } +static std::expected binaryNameForWlClient(wl_client* client) { + pid_t pid = 0; + wl_client_get_credentials(client, &pid, nullptr, nullptr); + + return binaryNameForPid(pid); +} + void CDynamicPermissionManager::clearConfigPermissions() { std::erase_if(m_rules, [](const auto& e) { return e->m_source == PERMISSION_RULE_SOURCE_CONFIG; }); } @@ -183,22 +190,99 @@ eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionMode(wl_c return PERMISSION_RULE_ALLOW_MODE_PENDING; } -void CDynamicPermissionManager::askForPermission(wl_client* client, const std::string& binaryPath, eDynamicPermissionType type) { - auto rule = m_rules.emplace_back(SP(new CDynamicPermissionRule(client, type, PERMISSION_RULE_ALLOW_MODE_PENDING))); +eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionModeWithString(pid_t pid, const std::string& str, eDynamicPermissionType permission) { + static auto PPERM = CConfigValue("ecosystem:enforce_permissions"); + + if (*PPERM == 0) + return PERMISSION_RULE_ALLOW_MODE_ALLOW; + + const auto LOOKUP = binaryNameForPid(pid); + + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: checking permission {} for key {} (binary {})", permissionToString(permission), str, + LOOKUP.has_value() ? LOOKUP.value() : "lookup failed: " + LOOKUP.error()); + + // first, check if we have the client + perm combo in our cache. + auto it = std::ranges::find_if(m_rules, [str, permission, pid](const auto& e) { return e->m_keyString == str && pid && pid == e->m_pid && e->m_type == permission; }); + if (it == m_rules.end()) { + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission not cached, checking key"); + + it = std::ranges::find_if(m_rules, [key = str, permission, &LOOKUP](const auto& e) { + if (e->m_type != permission) + return false; // wrong perm + + if (!e->m_binaryRegex) + return false; // no regex + + // regex match + if (RE2::FullMatch(key, *e->m_binaryRegex) || (LOOKUP.has_value() && RE2::FullMatch(LOOKUP.value(), *e->m_binaryRegex))) + return true; + + return false; + }); + + if (it == m_rules.end()) + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: no rule for key"); + else { + if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) { + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed by config rule"); + return PERMISSION_RULE_ALLOW_MODE_ALLOW; + } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) { + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied by config rule"); + return PERMISSION_RULE_ALLOW_MODE_DENY; + } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) { + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending by config rule"); + return PERMISSION_RULE_ALLOW_MODE_PENDING; + } else + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission ask by config rule"); + } + + } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_ALLOW) { + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission allowed before by user"); + return PERMISSION_RULE_ALLOW_MODE_ALLOW; + } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_DENY) { + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission denied before by user"); + return PERMISSION_RULE_ALLOW_MODE_DENY; + } else if ((*it)->m_allowMode == PERMISSION_RULE_ALLOW_MODE_PENDING) { + Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission pending before by user"); + return PERMISSION_RULE_ALLOW_MODE_PENDING; + } + + // if we are here, we need to ask. + askForPermission(nullptr, str, permission, pid); + + return PERMISSION_RULE_ALLOW_MODE_PENDING; +} + +void CDynamicPermissionManager::askForPermission(wl_client* client, const std::string& binaryPath, eDynamicPermissionType type, pid_t pid) { + auto rule = m_rules.emplace_back(SP(new CDynamicPermissionRule(client, type, PERMISSION_RULE_ALLOW_MODE_PENDING))); + + if (!client) + rule->m_keyString = binaryPath; + + rule->m_pid = pid; std::string description = ""; if (binaryPath.empty()) description = std::format("An unknown application (wayland client ID 0x{:x}) is {}.", (uintptr_t)client, permissionToHumanString(type)); - else { + else if (client) { std::string binaryName = binaryPath.contains("/") ? binaryPath.substr(binaryPath.find_last_of('/') + 1) : binaryPath; description = std::format("An application {} ({}) is {}.", binaryName, binaryPath, permissionToHumanString(type)); - } + } else if (pid >= 0) { + if (type == PERMISSION_TYPE_PLUGIN) { + const auto LOOKUP = binaryNameForPid(pid); + description = std::format("An application {} is {}:
{}", LOOKUP.value_or("Unknown"), permissionToHumanString(type), binaryPath); + } else { + const auto LOOKUP = binaryNameForPid(pid); + description = std::format("An application {} ({}) is {}.", LOOKUP.value_or("Unknown"), binaryPath, permissionToHumanString(type)); + } + } else + description = std::format("An application is {}:
{}", permissionToHumanString(type), binaryPath); description += "

Do you want to allow this?"; std::vector options; - if (!binaryPath.empty()) { + if (!binaryPath.empty() && client) { description += "

Hint: you can set persistent rules for these in the Hyprland config file."; options = {"Deny", "Allow and remember app", "Allow once"}; } else @@ -250,7 +334,35 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s } SP> CDynamicPermissionManager::promiseFor(wl_client* client, eDynamicPermissionType permission) { - auto rule = std::ranges::find_if(m_rules, [client, permission](const auto& e) { return e->m_client == client && e->m_type == permission; }); + auto rule = std::ranges::find_if(m_rules, [&client, &permission](const auto& e) { return e->m_client == client && e->m_type == permission; }); + if (rule == m_rules.end()) + return nullptr; + + if (!(*rule)->m_promise) + return nullptr; + + if ((*rule)->m_promiseResolverForExternal) + return nullptr; + + return CPromise::make([rule](SP> r) { (*rule)->m_promiseResolverForExternal = r; }); +} + +SP> CDynamicPermissionManager::promiseFor(const std::string& key, eDynamicPermissionType permission) { + auto rule = std::ranges::find_if(m_rules, [&key, &permission](const auto& e) { return e->m_keyString == key && e->m_type == permission; }); + if (rule == m_rules.end()) + return nullptr; + + if (!(*rule)->m_promise) + return nullptr; + + if ((*rule)->m_promiseResolverForExternal) + return nullptr; + + return CPromise::make([rule](SP> r) { (*rule)->m_promiseResolverForExternal = r; }); +} + +SP> CDynamicPermissionManager::promiseFor(pid_t pid, const std::string& key, eDynamicPermissionType permission) { + auto rule = std::ranges::find_if(m_rules, [&pid, &permission, &key](const auto& e) { return e->m_pid == pid && e->m_keyString == key && e->m_type == permission; }); if (rule == m_rules.end()) return nullptr; diff --git a/src/managers/permissions/DynamicPermissionManager.hpp b/src/managers/permissions/DynamicPermissionManager.hpp index 79dc56a6..f8cfe3ad 100644 --- a/src/managers/permissions/DynamicPermissionManager.hpp +++ b/src/managers/permissions/DynamicPermissionManager.hpp @@ -5,6 +5,7 @@ #include "../../helpers/AsyncDialogBox.hpp" #include #include +#include #include "../../helpers/defer/Promise.hpp" // NOLINTNEXTLINE @@ -56,6 +57,8 @@ class CDynamicPermissionRule { wl_client* const m_client = nullptr; std::string m_binaryPath = ""; UP m_binaryRegex; + std::string m_keyString = ""; + pid_t m_pid = 0; eDynamicPermissionAllowMode m_allowMode = PERMISSION_RULE_ALLOW_MODE_ASK; SP m_dialogBox; // for pending @@ -76,14 +79,19 @@ class CDynamicPermissionManager { // (will continue returning false if the user does not agree, of course.) eDynamicPermissionAllowMode clientPermissionMode(wl_client* client, eDynamicPermissionType permission); + // for plugins for now. Pid 0 means unknown + eDynamicPermissionAllowMode clientPermissionModeWithString(pid_t pid, const std::string& str, eDynamicPermissionType permission); + // get a promise for the result. Returns null if there already was one requested for the client. // Returns null if state is not pending SP> promiseFor(wl_client* client, eDynamicPermissionType permission); + SP> promiseFor(const std::string& str, eDynamicPermissionType permission); + SP> promiseFor(pid_t pid, const std::string& key, eDynamicPermissionType permission); void removeRulesForClient(wl_client* client); private: - void askForPermission(wl_client* client, const std::string& binaryName, eDynamicPermissionType type); + void askForPermission(wl_client* client, const std::string& binaryName, eDynamicPermissionType type, pid_t pid = 0); // std::vector> m_rules; diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index 87b9e8ef..2bed8dbd 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -3,22 +3,75 @@ #include #include #include "../config/ConfigManager.hpp" +#include "../debug/HyprCtl.hpp" #include "../managers/LayoutManager.hpp" #include "../managers/HookSystemManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" +#include "../managers/permissions/DynamicPermissionManager.hpp" +#include "../debug/HyprNotificationOverlay.hpp" CPluginSystem::CPluginSystem() { g_pFunctionHookSystem = makeUnique(); } -CPlugin* CPluginSystem::loadPlugin(const std::string& path) { +SP> CPluginSystem::loadPlugin(const std::string& path) { - m_szLastError = ""; + pid_t pid = 0; + if (g_pHyprCtl->m_currentRequestParams.pid > 0) + pid = g_pHyprCtl->m_currentRequestParams.pid; + + return CPromise::make([path, pid, this](SP> resolver) { + const auto PERM = g_pDynamicPermissionManager->clientPermissionModeWithString(pid, path, PERMISSION_TYPE_PLUGIN); + if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { + Debug::log(LOG, "CPluginSystem: Waiting for user confirmation to load {}", path); + + auto promise = g_pDynamicPermissionManager->promiseFor(pid, path, PERMISSION_TYPE_PLUGIN); + if (!promise) { // already awaiting or something? + resolver->reject("Failed to get a promise for permission"); + return; + } + + promise->then([this, path, resolver](SP> result) { + if (result->hasError()) { + Debug::log(ERR, "CPluginSystem: Error spawning permission prompt"); + resolver->reject("Error spawning permission prompt"); + return; + } + + if (result->result() != PERMISSION_RULE_ALLOW_MODE_ALLOW) { + Debug::log(ERR, "CPluginSystem: Rejecting plugin load of {}, user denied", path); + resolver->reject("user denied"); + return; + } + + Debug::log(LOG, "CPluginSystem: Loading {}, user allowed", path); + + const auto RESULT = loadPluginInternal(path); + if (RESULT.has_value()) + resolver->resolve(RESULT.value()); + else + resolver->reject(RESULT.error()); + }); + return; + } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { + Debug::log(LOG, "CPluginSystem: Rejecting plugin load, permission is disabled"); + resolver->reject("permission is disabled"); + return; + } + + const auto RESULT = loadPluginInternal(path); + if (RESULT.has_value()) + resolver->resolve(RESULT.value()); + else + resolver->reject(RESULT.error()); + }); +} + +std::expected CPluginSystem::loadPluginInternal(const std::string& path) { if (getPluginByPath(path)) { - m_szLastError = "Cannot load a plugin twice!"; Debug::log(ERR, " [PluginSystem] Cannot load a plugin twice!"); - return nullptr; + return std::unexpected("Cannot load a plugin twice!"); } auto* const PLUGIN = m_vLoadedPlugins.emplace_back(makeUnique()).get(); @@ -29,10 +82,9 @@ CPlugin* CPluginSystem::loadPlugin(const std::string& path) { if (!MODULE) { std::string strerr = dlerror(); - m_szLastError = std::format("Plugin {} could not be loaded: {}", path, strerr); Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded: {}", path, strerr); m_vLoadedPlugins.pop_back(); - return nullptr; + return std::unexpected(std::format("Plugin {} could not be loaded: {}", path, strerr)); } PLUGIN->m_pHandle = MODULE; @@ -41,21 +93,19 @@ CPlugin* CPluginSystem::loadPlugin(const std::string& path) { PPLUGIN_INIT_FUNC initFunc = (PPLUGIN_INIT_FUNC)dlsym(MODULE, PLUGIN_INIT_FUNC_STR); if (!apiVerFunc || !initFunc) { - m_szLastError = std::format("Plugin {} could not be loaded: {}", path, "missing apiver/init func"); Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded. (No apiver/init func)", path); dlclose(MODULE); m_vLoadedPlugins.pop_back(); - return nullptr; + return std::unexpected(std::format("Plugin {} could not be loaded: {}", path, "missing apiver/init func")); } const std::string PLUGINAPIVER = apiVerFunc(); if (PLUGINAPIVER != HYPRLAND_API_VERSION) { - m_szLastError = std::format("Plugin {} could not be loaded: {}", path, "API version mismatch"); Debug::log(ERR, " [PluginSystem] Plugin {} could not be loaded. (API version mismatch)", path); dlclose(MODULE); m_vLoadedPlugins.pop_back(); - return nullptr; + return std::unexpected(std::format("Plugin {} could not be loaded: {}", path, "API version mismatch")); } PLUGIN_DESCRIPTION_INFO PLUGINDATA; @@ -70,10 +120,9 @@ CPlugin* CPluginSystem::loadPlugin(const std::string& path) { } } catch (std::exception& e) { m_bAllowConfigVars = false; - m_szLastError = std::format("Plugin {} could not be loaded: plugin crashed/threw in main: {}", path, e.what()); Debug::log(ERR, " [PluginSystem] Plugin {} (Handle {:x}) crashed in init. Unloading.", path, (uintptr_t)MODULE); unloadPlugin(PLUGIN, true); // Plugin could've already hooked/done something - return nullptr; + return std::unexpected(std::format("Plugin {} could not be loaded: plugin crashed/threw in main: {}", path, e.what())); } m_bAllowConfigVars = false; @@ -146,33 +195,39 @@ void CPluginSystem::unloadAllPlugins() { unloadPlugin(p.get(), false); // Unload remaining plugins gracefully } -std::vector CPluginSystem::updateConfigPlugins(const std::vector& plugins, bool& changed) { - std::vector failures; - +void CPluginSystem::updateConfigPlugins(const std::vector& plugins, bool& changed) { // unload all plugins that are no longer present for (auto const& p : m_vLoadedPlugins | std::views::reverse) { - if (p->m_bLoadedWithConfig && std::find(plugins.begin(), plugins.end(), p->path) == plugins.end()) { - Debug::log(LOG, "Unloading plugin {} which is no longer present in config", p->path); - unloadPlugin(p.get(), false); - changed = true; - } + if (!p->m_bLoadedWithConfig || std::ranges::find(plugins, p->path) != plugins.end()) + continue; + + Debug::log(LOG, "Unloading plugin {} which is no longer present in config", p->path); + unloadPlugin(p.get(), false); + changed = true; } // load all new plugins for (auto const& path : plugins) { - if (std::find_if(m_vLoadedPlugins.begin(), m_vLoadedPlugins.end(), [&](const auto& other) { return other->path == path; }) == m_vLoadedPlugins.end()) { - Debug::log(LOG, "Loading plugin {} which is now present in config", path); - const auto plugin = loadPlugin(path); + if (std::ranges::find_if(m_vLoadedPlugins, [&](const auto& other) { return other->path == path; }) != m_vLoadedPlugins.end()) + continue; - if (plugin) { - plugin->m_bLoadedWithConfig = true; - changed = true; - } else - failures.push_back(path); - } + Debug::log(LOG, "Loading plugin {} which is now present in config", path); + + changed = true; + + loadPlugin(path)->then([path](SP> result) { + if (result->hasError()) { + const auto NAME = path.contains('/') ? path.substr(path.find_last_of('/') + 1) : path; + Debug::log(ERR, "CPluginSystem::updateConfigPlugins: failed to load plugin {}: {}", NAME, result->error()); + g_pHyprNotificationOverlay->addNotification(std::format("Failed to load plugin {}: {}", NAME, result->error()), CHyprColor{0, 0, 0, 0}, 5000, ICON_ERROR); + return; + } + + result->result()->m_bLoadedWithConfig = true; + + Debug::log(LOG, "CPluginSystem::updateConfigPlugins: loaded {}", path); + }); } - - return failures; } CPlugin* CPluginSystem::getPluginByPath(const std::string& path) { diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp index 95841bef..ba1dafc0 100644 --- a/src/plugins/PluginSystem.hpp +++ b/src/plugins/PluginSystem.hpp @@ -1,8 +1,10 @@ #pragma once #include "../defines.hpp" +#include "../helpers/defer/Promise.hpp" #include "PluginAPI.hpp" #include +#include class IHyprWindowDecoration; @@ -30,23 +32,24 @@ class CPluginSystem { public: CPluginSystem(); - CPlugin* loadPlugin(const std::string& path); - void unloadPlugin(const CPlugin* plugin, bool eject = false); - void unloadAllPlugins(); - std::vector updateConfigPlugins(const std::vector& plugins, bool& changed); - CPlugin* getPluginByPath(const std::string& path); - CPlugin* getPluginByHandle(HANDLE handle); - std::vector getAllPlugins(); - size_t pluginCount(); - void sigGetPlugins(CPlugin** data, size_t len); + SP> loadPlugin(const std::string& path); + void unloadPlugin(const CPlugin* plugin, bool eject = false); + void unloadAllPlugins(); + void updateConfigPlugins(const std::vector& plugins, bool& changed); + CPlugin* getPluginByPath(const std::string& path); + CPlugin* getPluginByHandle(HANDLE handle); + std::vector getAllPlugins(); + size_t pluginCount(); + void sigGetPlugins(CPlugin** data, size_t len); - bool m_bAllowConfigVars = false; - std::string m_szLastError = ""; + bool m_bAllowConfigVars = false; private: - std::vector> m_vLoadedPlugins; + std::vector> m_vLoadedPlugins; - jmp_buf m_jbPluginFaultJumpBuf; + jmp_buf m_jbPluginFaultJumpBuf; + + std::expected loadPluginInternal(const std::string& path); }; inline UP g_pPluginSystem;