mirror of
https://github.com/hyprwm/Hyprland.git
synced 2025-05-12 23:00:36 +01:00
permissions: add perms for plugin loading (#10184)
Adds permission management for loading plugins --------- Co-authored-by: Jan Beich <jbeich@FreeBSD.org>
This commit is contained in:
parent
2118440488
commit
5bd7ff884d
12 changed files with 416 additions and 91 deletions
|
@ -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
|
||||
|
||||
|
||||
#####################
|
||||
|
|
85
hyprpm/src/core/HyprlandSocket.cpp
Normal file
85
hyprpm/src/core/HyprlandSocket.cpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
#include "HyprlandSocket.hpp"
|
||||
#include <pwd.h>
|
||||
#include <sys/socket.h>
|
||||
#include "../helpers/StringUtils.hpp"
|
||||
#include <print>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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;
|
||||
}
|
7
hyprpm/src/core/HyprlandSocket.hpp
Normal file
7
hyprpm/src/core/HyprlandSocket.hpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace NHyprlandSocket {
|
||||
std::string send(const std::string& cmd);
|
||||
};
|
|
@ -4,6 +4,7 @@
|
|||
#include "../progress/CProgressBar.hpp"
|
||||
#include "Manifest.hpp"
|
||||
#include "DataState.hpp"
|
||||
#include "HyprlandSocket.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
@ -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<glz::json_t::array_t>(execAndGet("hyprctl plugins list -j"));
|
||||
const auto json = glz::read_json<glz::json_t::array_t>(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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
#####################
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <sys/poll.h>
|
||||
#include <filesystem>
|
||||
#include <ranges>
|
||||
#include <sys/eventfd.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
@ -24,6 +25,7 @@
|
|||
#include <numeric>
|
||||
|
||||
#include <hyprutils/string/String.hpp>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
using namespace Hyprutils::String;
|
||||
using namespace Hyprutils::OS;
|
||||
#include <aquamarine/input/Input.hpp>
|
||||
|
@ -53,6 +55,28 @@ using namespace Hyprutils::OS;
|
|||
#include "../render/Renderer.hpp"
|
||||
#include "../render/OpenGL.hpp"
|
||||
|
||||
#if defined(__DragonFly__) || defined(__FreeBSD__)
|
||||
#include <sys/ucred.h>
|
||||
#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<std::string>::make([PATH](SP<CPromiseResolver<std::string>> resolver) {
|
||||
g_pPluginSystem->loadPlugin(PATH)->then([resolver, PATH](SP<CPromiseResult<CPlugin*>> 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<SHyprCtlCommand>& 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<char, 1024> 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<CPromiseResult<std::string>> 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;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
#include <fstream>
|
||||
#include "../helpers/MiscFunctions.hpp"
|
||||
#include "../helpers/defer/Promise.hpp"
|
||||
#include "../desktop/Window.hpp"
|
||||
#include <functional>
|
||||
#include <sys/types.h>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
|
||||
// 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<CPromise<std::string>> pendingPromise;
|
||||
} m_currentRequestParams;
|
||||
|
||||
static std::string getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format);
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
#include "../../Compositor.hpp"
|
||||
#include "../../config/ConfigValue.hpp"
|
||||
|
||||
#include <hyprutils/string/String.hpp>
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
@ -64,10 +67,7 @@ static const char* permissionToHumanString(eDynamicPermissionType type) {
|
|||
return "error";
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> binaryNameForWlClient(wl_client* client) {
|
||||
pid_t pid = 0;
|
||||
wl_client_get_credentials(client, &pid, nullptr, nullptr);
|
||||
|
||||
static std::expected<std::string, std::string> binaryNameForPid(pid_t pid) {
|
||||
if (pid <= 0)
|
||||
return std::unexpected("No pid for client");
|
||||
|
||||
|
@ -102,6 +102,13 @@ static std::expected<std::string, std::string> binaryNameForWlClient(wl_client*
|
|||
return fullPath;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> 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<CDynamicPermissionRule>(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<Hyprlang::INT>("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<CDynamicPermissionRule>(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 <b>{}</b> ({}) is {}.", binaryName, binaryPath, permissionToHumanString(type));
|
||||
}
|
||||
} else if (pid >= 0) {
|
||||
if (type == PERMISSION_TYPE_PLUGIN) {
|
||||
const auto LOOKUP = binaryNameForPid(pid);
|
||||
description = std::format("An application <b>{}</b> is {}:<br/><b>{}</b>", LOOKUP.value_or("Unknown"), permissionToHumanString(type), binaryPath);
|
||||
} else {
|
||||
const auto LOOKUP = binaryNameForPid(pid);
|
||||
description = std::format("An application <b>{}</b> ({}) is {}.", LOOKUP.value_or("Unknown"), binaryPath, permissionToHumanString(type));
|
||||
}
|
||||
} else
|
||||
description = std::format("An application is {}:<br/><b>{}</b>", permissionToHumanString(type), binaryPath);
|
||||
|
||||
description += "<br/><br/>Do you want to allow this?";
|
||||
|
||||
std::vector<std::string> options;
|
||||
|
||||
if (!binaryPath.empty()) {
|
||||
if (!binaryPath.empty() && client) {
|
||||
description += "<br/><br/><i>Hint: you can set persistent rules for these in the Hyprland config file.</i>";
|
||||
options = {"Deny", "Allow and remember app", "Allow once"};
|
||||
} else
|
||||
|
@ -250,7 +334,35 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s
|
|||
}
|
||||
|
||||
SP<CPromise<eDynamicPermissionAllowMode>> 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<eDynamicPermissionAllowMode>::make([rule](SP<CPromiseResolver<eDynamicPermissionAllowMode>> r) { (*rule)->m_promiseResolverForExternal = r; });
|
||||
}
|
||||
|
||||
SP<CPromise<eDynamicPermissionAllowMode>> 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<eDynamicPermissionAllowMode>::make([rule](SP<CPromiseResolver<eDynamicPermissionAllowMode>> r) { (*rule)->m_promiseResolverForExternal = r; });
|
||||
}
|
||||
|
||||
SP<CPromise<eDynamicPermissionAllowMode>> 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;
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "../../helpers/AsyncDialogBox.hpp"
|
||||
#include <vector>
|
||||
#include <wayland-server-core.h>
|
||||
#include <sys/types.h>
|
||||
#include "../../helpers/defer/Promise.hpp"
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
|
@ -56,6 +57,8 @@ class CDynamicPermissionRule {
|
|||
wl_client* const m_client = nullptr;
|
||||
std::string m_binaryPath = "";
|
||||
UP<re2::RE2> m_binaryRegex;
|
||||
std::string m_keyString = "";
|
||||
pid_t m_pid = 0;
|
||||
|
||||
eDynamicPermissionAllowMode m_allowMode = PERMISSION_RULE_ALLOW_MODE_ASK;
|
||||
SP<CAsyncDialogBox> 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<CPromise<eDynamicPermissionAllowMode>> promiseFor(wl_client* client, eDynamicPermissionType permission);
|
||||
SP<CPromise<eDynamicPermissionAllowMode>> promiseFor(const std::string& str, eDynamicPermissionType permission);
|
||||
SP<CPromise<eDynamicPermissionAllowMode>> 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<SP<CDynamicPermissionRule>> m_rules;
|
||||
|
|
|
@ -3,22 +3,75 @@
|
|||
#include <dlfcn.h>
|
||||
#include <ranges>
|
||||
#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<CHookSystem>();
|
||||
}
|
||||
|
||||
CPlugin* CPluginSystem::loadPlugin(const std::string& path) {
|
||||
SP<CPromise<CPlugin*>> 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<CPlugin*>::make([path, pid, this](SP<CPromiseResolver<CPlugin*>> 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<CPromiseResult<eDynamicPermissionAllowMode>> 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<CPlugin*, std::string> 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<CPlugin>()).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<std::string> CPluginSystem::updateConfigPlugins(const std::vector<std::string>& plugins, bool& changed) {
|
||||
std::vector<std::string> failures;
|
||||
|
||||
void CPluginSystem::updateConfigPlugins(const std::vector<std::string>& 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<CPromiseResult<CPlugin*>> 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) {
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "../helpers/defer/Promise.hpp"
|
||||
#include "PluginAPI.hpp"
|
||||
#include <csetjmp>
|
||||
#include <expected>
|
||||
|
||||
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<std::string> updateConfigPlugins(const std::vector<std::string>& plugins, bool& changed);
|
||||
CPlugin* getPluginByPath(const std::string& path);
|
||||
CPlugin* getPluginByHandle(HANDLE handle);
|
||||
std::vector<CPlugin*> getAllPlugins();
|
||||
size_t pluginCount();
|
||||
void sigGetPlugins(CPlugin** data, size_t len);
|
||||
SP<CPromise<CPlugin*>> loadPlugin(const std::string& path);
|
||||
void unloadPlugin(const CPlugin* plugin, bool eject = false);
|
||||
void unloadAllPlugins();
|
||||
void updateConfigPlugins(const std::vector<std::string>& plugins, bool& changed);
|
||||
CPlugin* getPluginByPath(const std::string& path);
|
||||
CPlugin* getPluginByHandle(HANDLE handle);
|
||||
std::vector<CPlugin*> 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<UP<CPlugin>> m_vLoadedPlugins;
|
||||
std::vector<UP<CPlugin>> m_vLoadedPlugins;
|
||||
|
||||
jmp_buf m_jbPluginFaultJumpBuf;
|
||||
jmp_buf m_jbPluginFaultJumpBuf;
|
||||
|
||||
std::expected<CPlugin*, std::string> loadPluginInternal(const std::string& path);
|
||||
};
|
||||
|
||||
inline UP<CPluginSystem> g_pPluginSystem;
|
||||
|
|
Loading…
Reference in a new issue