summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2025-04-08 19:39:53 +0200
committerGitHub <noreply@github.com>2025-04-08 19:39:53 +0200
commit260d8e1f71b1dc6f3f354747debdc87d96b9e99f (patch)
tree9e13cd67a6b08e356a8773b093a9c8d9fdf645b0 /src
parent642f394eb3f8fd99a51f4c8279497c458c40ba65 (diff)
Permission Manager: add permission management for screencopy (#9930)
Diffstat (limited to 'src')
-rw-r--r--src/Compositor.cpp5
-rw-r--r--src/config/ConfigManager.cpp43
-rw-r--r--src/config/ConfigManager.hpp1
-rw-r--r--src/config/defaultConfig.hpp14
-rw-r--r--src/debug/CrashReporter.cpp2
-rw-r--r--src/managers/permissions/DynamicPermissionManager.cpp234
-rw-r--r--src/managers/permissions/DynamicPermissionManager.hpp85
-rw-r--r--src/plugins/PluginAPI.cpp2
-rw-r--r--src/protocols/Screencopy.cpp65
-rw-r--r--src/protocols/ToplevelExport.cpp48
-rw-r--r--src/render/OpenGL.cpp2
-rw-r--r--src/render/OpenGL.hpp2
12 files changed, 469 insertions, 34 deletions
diff --git a/src/Compositor.cpp b/src/Compositor.cpp
index 594bd85a..947b6549 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -15,6 +15,7 @@
#include "managers/DonationNagManager.hpp"
#include "managers/ANRManager.hpp"
#include "managers/eventLoop/EventLoopManager.hpp"
+#include "managers/permissions/DynamicPermissionManager.hpp"
#include <algorithm>
#include <aquamarine/output/Output.hpp>
#include <bit>
@@ -570,6 +571,7 @@ void CCompositor::cleanup() {
removeAllSignals();
g_pInputManager.reset();
+ g_pDynamicPermissionManager.reset();
g_pDecorationPositioner.reset();
g_pCursorManager.reset();
g_pPluginSystem.reset();
@@ -624,6 +626,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Debug::log(LOG, "Creating the AnimationManager!");
g_pAnimationManager = makeUnique<CHyprAnimationManager>();
+ Debug::log(LOG, "Creating the DynamicPermissionManager!");
+ g_pDynamicPermissionManager = makeUnique<CDynamicPermissionManager>();
+
Debug::log(LOG, "Creating the ConfigManager!");
g_pConfigManager = makeUnique<CConfigManager>();
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index 0d12e29c..6399372c 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -22,6 +22,7 @@
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../managers/LayoutManager.hpp"
#include "../managers/EventManager.hpp"
+#include "../managers/permissions/DynamicPermissionManager.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
#include "../plugins/PluginSystem.hpp"
@@ -374,6 +375,18 @@ static Hyprlang::CParseResult handlePlugin(const char* c, const char* v) {
return result;
}
+static Hyprlang::CParseResult handlePermission(const char* c, const char* v) {
+ const std::string VALUE = v;
+ const std::string COMMAND = c;
+
+ const auto RESULT = g_pConfigManager->handlePermission(COMMAND, VALUE);
+
+ Hyprlang::CParseResult result;
+ if (RESULT.has_value())
+ result.setError(RESULT.value().c_str());
+ return result;
+}
+
void CConfigManager::registerConfigVar(const char* name, const Hyprlang::INT& val) {
m_configValueNumber++;
m_pConfig->addConfigValue(name, val);
@@ -703,6 +716,7 @@ CConfigManager::CConfigManager() {
registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0});
registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0});
+ registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0});
registerConfigVar("experimental:xx_color_management_v4", Hyprlang::INT{0});
@@ -764,6 +778,7 @@ CConfigManager::CConfigManager() {
m_pConfig->registerHandler(&::handleSubmap, "submap", {false});
m_pConfig->registerHandler(&::handleBlurLS, "blurls", {false});
m_pConfig->registerHandler(&::handlePlugin, "plugin", {false});
+ m_pConfig->registerHandler(&::handlePermission, "permission", {false});
m_pConfig->registerHandler(&::handleEnv, "env", {true});
// pluginza
@@ -946,6 +961,8 @@ std::optional<std::string> CConfigManager::resetHLConfig() {
m_vFailedPluginConfigValues.clear();
finalExecRequests.clear();
+ g_pDynamicPermissionManager->clearConfigPermissions();
+
// paths
m_configPaths.clear();
std::string mainConfigPath = getMainConfigPath();
@@ -2831,6 +2848,32 @@ std::optional<std::string> CConfigManager::handlePlugin(const std::string& comma
return {};
}
+std::optional<std::string> CConfigManager::handlePermission(const std::string& command, const std::string& value) {
+ CVarList data(value);
+
+ eDynamicPermissionType type = PERMISSION_TYPE_UNKNOWN;
+ eDynamicPermissionAllowMode mode = PERMISSION_RULE_ALLOW_MODE_UNKNOWN;
+
+ if (data[1] == "screencopy")
+ type = PERMISSION_TYPE_SCREENCOPY;
+
+ if (data[2] == "ask")
+ mode = PERMISSION_RULE_ALLOW_MODE_ASK;
+ else if (data[2] == "allow")
+ mode = PERMISSION_RULE_ALLOW_MODE_ALLOW;
+ else if (data[2] == "deny")
+ mode = PERMISSION_RULE_ALLOW_MODE_DENY;
+
+ if (type == PERMISSION_TYPE_UNKNOWN)
+ return "unknown permission type";
+ if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN)
+ return "unknown permission allow mode";
+
+ g_pDynamicPermissionManager->addConfigPermissionRule(data[0], type, mode);
+
+ return {};
+}
+
const std::vector<SConfigOptionDescription>& CConfigManager::getAllDescriptions() {
return CONFIG_OPTIONS;
}
diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp
index 4da123c9..50ab798c 100644
--- a/src/config/ConfigManager.hpp
+++ b/src/config/ConfigManager.hpp
@@ -244,6 +244,7 @@ class CConfigManager {
std::optional<std::string> handleBindWS(const std::string&, const std::string&);
std::optional<std::string> handleEnv(const std::string&, const std::string&);
std::optional<std::string> handlePlugin(const std::string&, const std::string&);
+ std::optional<std::string> handlePermission(const std::string&, const std::string&);
std::string configCurrentPath;
diff --git a/src/config/defaultConfig.hpp b/src/config/defaultConfig.hpp
index ff4a579d..01ad1e72 100644
--- a/src/config/defaultConfig.hpp
+++ b/src/config/defaultConfig.hpp
@@ -65,6 +65,20 @@ env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
+###################
+### PERMISSIONS ###
+###################
+
+# See https://wiki.hyprland.org/Configuring/Permissions/
+
+# ecosystem {
+# enforce_permissions = 1
+# }
+
+# permission = /usr/(bin|local/bin)/grim, screencopy, allow
+# permission = /usr/(lib|libexec|lib64)/xdg-desktop-portal-hyprland, screencopy, allow
+
+
#####################
### LOOK AND FEEL ###
#####################
diff --git a/src/debug/CrashReporter.cpp b/src/debug/CrashReporter.cpp
index f6e0b55f..0143b29c 100644
--- a/src/debug/CrashReporter.cpp
+++ b/src/debug/CrashReporter.cpp
@@ -192,7 +192,7 @@ void NCrashReporter::createAndSaveCrash(int sig) {
#endif
};
u_int miblen = sizeof(mib) / sizeof(mib[0]);
- char exe[PATH_MAX] = "";
+ char exe[PATH_MAX] = "/nonexistent";
size_t sz = sizeof(exe);
sysctl(mib, miblen, &exe, &sz, NULL, 0);
const auto FPATH = std::filesystem::canonical(exe);
diff --git a/src/managers/permissions/DynamicPermissionManager.cpp b/src/managers/permissions/DynamicPermissionManager.cpp
new file mode 100644
index 00000000..fe795985
--- /dev/null
+++ b/src/managers/permissions/DynamicPermissionManager.cpp
@@ -0,0 +1,234 @@
+#include <re2/re2.h>
+#include "DynamicPermissionManager.hpp"
+#include <algorithm>
+#include <wayland-server-core.h>
+#include <expected>
+#include <filesystem>
+#include "../../Compositor.hpp"
+#include "../../config/ConfigValue.hpp"
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
+#include <sys/sysctl.h>
+#endif
+
+static void clientDestroyInternal(struct wl_listener* listener, void* data) {
+ SDynamicPermissionRuleDestroyWrapper* wrap = wl_container_of(listener, wrap, listener);
+ CDynamicPermissionRule* rule = wrap->parent;
+ g_pDynamicPermissionManager->removeRulesForClient(rule->client());
+}
+
+CDynamicPermissionRule::CDynamicPermissionRule(const std::string& binaryPathRegex, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode) :
+ m_type(type), m_source(PERMISSION_RULE_SOURCE_CONFIG), m_binaryRegex(makeUnique<re2::RE2>(binaryPathRegex)), m_allowMode(defaultAllowMode) {
+ ;
+}
+
+CDynamicPermissionRule::CDynamicPermissionRule(wl_client* const client, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode) :
+ m_type(type), m_source(PERMISSION_RULE_SOURCE_RUNTIME_USER), m_client(client), m_allowMode(defaultAllowMode) {
+ wl_list_init(&m_destroyWrapper.listener.link);
+ m_destroyWrapper.listener.notify = ::clientDestroyInternal;
+ m_destroyWrapper.parent = this;
+ wl_display_add_destroy_listener(g_pCompositor->m_sWLDisplay, &m_destroyWrapper.listener);
+}
+
+CDynamicPermissionRule::~CDynamicPermissionRule() {
+ if (m_client) {
+ wl_list_remove(&m_destroyWrapper.listener.link);
+ wl_list_init(&m_destroyWrapper.listener.link);
+ }
+
+ if (m_dialogBox && m_dialogBox->isRunning())
+ m_dialogBox->kill();
+}
+
+wl_client* CDynamicPermissionRule::client() const {
+ return m_client;
+}
+
+static const char* permissionToString(eDynamicPermissionType type) {
+ switch (type) {
+ case PERMISSION_TYPE_UNKNOWN: return "PERMISSION_TYPE_UNKNOWN";
+ case PERMISSION_TYPE_SCREENCOPY: return "PERMISSION_TYPE_SCREENCOPY";
+ }
+
+ return "error";
+}
+
+static const char* permissionToHumanString(eDynamicPermissionType type) {
+ switch (type) {
+ case PERMISSION_TYPE_UNKNOWN: return "requesting an unknown permission";
+ case PERMISSION_TYPE_SCREENCOPY: return "trying to capture your screen";
+ }
+
+ 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);
+
+ if (pid <= 0)
+ return std::unexpected("No pid for client");
+
+#if defined(KERN_PROC_PATHNAME)
+ int mib[] = {
+ CTL_KERN,
+#if defined(__NetBSD__)
+ KERN_PROC_ARGS,
+ pid,
+ KERN_PROC_PATHNAME,
+#else
+ KERN_PROC,
+ KERN_PROC_PATHNAME,
+ pid,
+#endif
+ };
+ u_int miblen = sizeof(mib) / sizeof(mib[0]);
+ char exe[PATH_MAX] = "/nonexistent";
+ size_t sz = sizeof(exe);
+ sysctl(mib, miblen, &exe, &sz, NULL, 0);
+ std::string path = exe;
+#else
+ std::string path = std::format("/proc/{}/exe", (uint64_t)pid);
+#endif
+ std::error_code ec;
+
+ std::string fullPath = std::filesystem::canonical(path, ec);
+
+ if (ec)
+ return std::unexpected("canonical failed");
+
+ return fullPath;
+}
+
+void CDynamicPermissionManager::clearConfigPermissions() {
+ std::erase_if(m_rules, [](const auto& e) { return e->m_source == PERMISSION_RULE_SOURCE_CONFIG; });
+}
+
+void CDynamicPermissionManager::addConfigPermissionRule(const std::string& binaryName, eDynamicPermissionType type, eDynamicPermissionAllowMode mode) {
+ m_rules.emplace_back(SP<CDynamicPermissionRule>(new CDynamicPermissionRule(binaryName, type, mode)));
+}
+
+eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionMode(wl_client* client, eDynamicPermissionType permission) {
+
+ static auto PPERM = CConfigValue<Hyprlang::INT>("ecosystem:enforce_permissions");
+
+ if (*PPERM == 0)
+ return PERMISSION_RULE_ALLOW_MODE_ALLOW;
+
+ const auto LOOKUP = binaryNameForWlClient(client);
+
+ Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: checking permission {} for client {:x} (binary {})", permissionToString(permission), (uintptr_t)client,
+ 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, [client, permission](const auto& e) { return e->m_client == client && e->m_type == permission; });
+ if (it == m_rules.end()) {
+ Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: permission not cached, checking binary name");
+
+ if (!LOOKUP.has_value())
+ Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: binary name check failed");
+ else {
+ const auto BINNAME = LOOKUP.value().contains("/") ? LOOKUP.value().substr(LOOKUP.value().find_last_of('/') + 1) : LOOKUP.value();
+ Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: binary path {}, name {}", LOOKUP.value(), BINNAME);
+
+ it = std::ranges::find_if(m_rules, [clientBinaryPath = LOOKUP.value(), permission](const auto& e) {
+ if (e->m_type != permission)
+ return false; // wrong perm
+
+ if (!e->m_binaryPath.empty() && e->m_binaryPath == clientBinaryPath)
+ return true; // matches binary path
+
+ if (!e->m_binaryRegex)
+ return false; // wl_client* rule
+
+ // regex match
+ if (RE2::FullMatch(clientBinaryPath, *e->m_binaryRegex))
+ return true;
+
+ return false;
+ });
+
+ if (it == m_rules.end())
+ Debug::log(TRACE, "CDynamicPermissionManager::clientHasPermission: no rule for binary");
+ 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(client, LOOKUP.value_or(""), permission);
+
+ 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)));
+
+ std::string description = "";
+ if (binaryPath.empty())
+ description = std::format("An unknown application (wayland client ID 0x{:x}) is {}.", (uintptr_t)client, permissionToHumanString(type));
+ else {
+ 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));
+ }
+
+ description += "<br/><br/>Do you want to allow this?";
+
+ std::vector<std::string> options;
+
+ if (!binaryPath.empty()) {
+ 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
+ options = {"Deny", "Allow"};
+
+ rule->m_dialogBox = CAsyncDialogBox::create("Permission request", description, options);
+
+ if (!rule->m_dialogBox) {
+ Debug::log(ERR, "CDynamicPermissionManager::askForPermission: hyprland-qtutils likely missing, cannot ask! Disabling permission control...");
+ rule->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;
+ return;
+ }
+
+ rule->m_dialogBox->open([r = WP<CDynamicPermissionRule>(rule), binaryPath](std::string result) {
+ if (!r)
+ return;
+
+ Debug::log(TRACE, "CDynamicPermissionRule: user returned {}", result);
+
+ if (result.starts_with("Allow once"))
+ r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;
+ else if (result.starts_with("Deny")) {
+ r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_DENY;
+ r->m_binaryPath = binaryPath;
+ } else if (result.starts_with("Allow and remember")) {
+ r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;
+ r->m_binaryPath = binaryPath;
+ } else if (result.starts_with("Allow"))
+ r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW;
+ });
+}
+
+void CDynamicPermissionManager::removeRulesForClient(wl_client* client) {
+ std::erase_if(m_rules, [client](const auto& e) { return e->m_client == client; });
+}
diff --git a/src/managers/permissions/DynamicPermissionManager.hpp b/src/managers/permissions/DynamicPermissionManager.hpp
new file mode 100644
index 00000000..681cbc1c
--- /dev/null
+++ b/src/managers/permissions/DynamicPermissionManager.hpp
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "../../macros.hpp"
+#include "../../helpers/memory/Memory.hpp"
+#include "../../helpers/AsyncDialogBox.hpp"
+#include <vector>
+#include <wayland-server-core.h>
+#include <optional>
+
+// NOLINTNEXTLINE
+namespace re2 {
+ class RE2;
+};
+
+enum eDynamicPermissionType : uint8_t {
+ PERMISSION_TYPE_UNKNOWN = 0,
+ PERMISSION_TYPE_SCREENCOPY,
+};
+
+enum eDynamicPermissionRuleSource : uint8_t {
+ PERMISSION_RULE_SOURCE_UNKNOWN = 0,
+ PERMISSION_RULE_SOURCE_CONFIG,
+ PERMISSION_RULE_SOURCE_RUNTIME_USER,
+};
+
+enum eDynamicPermissionAllowMode : uint8_t {
+ PERMISSION_RULE_ALLOW_MODE_UNKNOWN = 0,
+ PERMISSION_RULE_ALLOW_MODE_DENY,
+ PERMISSION_RULE_ALLOW_MODE_ASK,
+ PERMISSION_RULE_ALLOW_MODE_ALLOW,
+ PERMISSION_RULE_ALLOW_MODE_PENDING, // popup is open
+};
+
+class CDynamicPermissionRule;
+
+struct SDynamicPermissionRuleDestroyWrapper {
+ wl_listener listener;
+ CDynamicPermissionRule* parent = nullptr;
+};
+
+class CDynamicPermissionRule {
+ public:
+ ~CDynamicPermissionRule();
+
+ wl_client* client() const;
+
+ private:
+ // config rule
+ CDynamicPermissionRule(const std::string& binaryPathRegex, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode = PERMISSION_RULE_ALLOW_MODE_ASK);
+ // user rule
+ CDynamicPermissionRule(wl_client* const client, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode = PERMISSION_RULE_ALLOW_MODE_ASK);
+
+ const eDynamicPermissionType m_type = PERMISSION_TYPE_UNKNOWN;
+ const eDynamicPermissionRuleSource m_source = PERMISSION_RULE_SOURCE_UNKNOWN;
+ wl_client* const m_client = nullptr;
+ std::string m_binaryPath = "";
+ UP<re2::RE2> m_binaryRegex;
+
+ eDynamicPermissionAllowMode m_allowMode = PERMISSION_RULE_ALLOW_MODE_ASK;
+ SP<CAsyncDialogBox> m_dialogBox; // for pending
+
+ SDynamicPermissionRuleDestroyWrapper m_destroyWrapper;
+
+ friend class CDynamicPermissionManager;
+};
+
+class CDynamicPermissionManager {
+ public:
+ void clearConfigPermissions();
+ void addConfigPermissionRule(const std::string& binaryPath, eDynamicPermissionType type, eDynamicPermissionAllowMode mode);
+
+ // if the rule is "ask", or missing, will pop up a dialog and return false until the user agrees.
+ // (will continue returning false if the user does not agree, of course.)
+ eDynamicPermissionAllowMode clientPermissionMode(wl_client* client, eDynamicPermissionType permission);
+
+ void removeRulesForClient(wl_client* client);
+
+ private:
+ void askForPermission(wl_client* client, const std::string& binaryName, eDynamicPermissionType type);
+
+ //
+ std::vector<SP<CDynamicPermissionRule>> m_rules;
+};
+
+inline UP<CDynamicPermissionManager> g_pDynamicPermissionManager; \ No newline at end of file
diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp
index 92f043fd..e1c9f39c 100644
--- a/src/plugins/PluginAPI.cpp
+++ b/src/plugins/PluginAPI.cpp
@@ -301,7 +301,7 @@ APICALL std::vector<SFunctionMatch> HyprlandAPI::findFunctionsByName(HANDLE hand
#endif
};
u_int miblen = sizeof(mib) / sizeof(mib[0]);
- char exe[PATH_MAX] = "";
+ char exe[PATH_MAX] = "/nonexistent";
size_t sz = sizeof(exe);
sysctl(mib, miblen, &exe, &sz, NULL, 0);
const auto FPATH = std::filesystem::canonical(exe);
diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp
index c124e6d2..afa61f36 100644
--- a/src/protocols/Screencopy.cpp
+++ b/src/protocols/Screencopy.cpp
@@ -3,6 +3,7 @@
#include "../managers/eventLoop/EventLoopManager.hpp"
#include "../managers/PointerManager.hpp"
#include "../managers/EventManager.hpp"
+#include "../managers/permissions/DynamicPermissionManager.hpp"
#include "../render/Renderer.hpp"
#include "../render/OpenGL.hpp"
#include "../helpers/Monitor.hpp"
@@ -196,9 +197,10 @@ void CScreencopyFrame::share() {
}
void CScreencopyFrame::copyDmabuf(std::function<void(bool)> callback) {
- auto TEXTURE = makeShared<CTexture>(pMonitor->output->state->state().buffer);
+ const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(resource->client(), PERMISSION_TYPE_SCREENCOPY);
+ auto TEXTURE = makeShared<CTexture>(pMonitor->output->state->state().buffer);
- CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};
+ CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};
if (!g_pHyprRenderer->beginRender(pMonitor.lock(), fakeDamage, RENDER_MODE_TO_BUFFER, buffer.buffer, nullptr, true)) {
LOGM(ERR, "Can't copy: failed to begin rendering to dma frame");
@@ -206,14 +208,23 @@ void CScreencopyFrame::copyDmabuf(std::function<void(bool)> callback) {
return;
}
- CBox monbox = CBox{0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y}
- .translate({-box.x, -box.y}) // vvvv kinda ass-backwards but that's how I designed the renderer... sigh.
- .transform(wlTransformToHyprutils(invertTransform(pMonitor->transform)), pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y);
- g_pHyprOpenGL->setMonitorTransformEnabled(true);
- g_pHyprOpenGL->setRenderModifEnabled(false);
- g_pHyprOpenGL->renderTexture(TEXTURE, monbox, 1);
- g_pHyprOpenGL->setRenderModifEnabled(true);
- g_pHyprOpenGL->setMonitorTransformEnabled(false);
+ if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) {
+ CBox monbox = CBox{0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y}
+ .translate({-box.x, -box.y}) // vvvv kinda ass-backwards but that's how I designed the renderer... sigh.
+ .transform(wlTransformToHyprutils(invertTransform(pMonitor->transform)), pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y);
+ g_pHyprOpenGL->setMonitorTransformEnabled(true);
+ g_pHyprOpenGL->setRenderModifEnabled(false);
+ g_pHyprOpenGL->renderTexture(TEXTURE, monbox, 1);
+ g_pHyprOpenGL->setRenderModifEnabled(true);
+ g_pHyprOpenGL->setMonitorTransformEnabled(false);
+ } else if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING)
+ g_pHyprOpenGL->clear(Colors::BLACK);
+ else {
+ g_pHyprOpenGL->clear(Colors::BLACK);
+ CBox texbox =
+ CBox{pMonitor->vecTransformedSize / 2.F, g_pHyprOpenGL->m_pScreencopyDeniedTexture->m_vSize}.translate(-g_pHyprOpenGL->m_pScreencopyDeniedTexture->m_vSize / 2.F);
+ g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pScreencopyDeniedTexture, texbox, 1);
+ }
g_pHyprOpenGL->m_RenderData.blockScreenShader = true;
g_pHyprRenderer->endRender();
@@ -233,9 +244,10 @@ void CScreencopyFrame::copyDmabuf(std::function<void(bool)> callback) {
}
bool CScreencopyFrame::copyShm() {
- auto TEXTURE = makeShared<CTexture>(pMonitor->output->state->state().buffer);
+ const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(resource->client(), PERMISSION_TYPE_SCREENCOPY);
+ auto TEXTURE = makeShared<CTexture>(pMonitor->output->state->state().buffer);
- auto shm = buffer->shm();
+ auto shm = buffer->shm();
auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); // no need for end, cuz it's shm
CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX};
@@ -250,12 +262,21 @@ bool CScreencopyFrame::copyShm() {
return false;
}
- CBox monbox = CBox{0, 0, pMonitor->vecTransformedSize.x, pMonitor->vecTransformedSize.y}.translate({-box.x, -box.y});
- g_pHyprOpenGL->setMonitorTransformEnabled(true);
- g_pHyprOpenGL->setRenderModifEnabled(false);
- g_pHyprOpenGL->renderTexture(TEXTURE, monbox, 1);
- g_pHyprOpenGL->setRenderModifEnabled(true);
- g_pHyprOpenGL->setMonitorTransformEnabled(false);
+ if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) {
+ CBox monbox = CBox{0, 0, pMonitor->vecTransformedSize.x, pMonitor->vecTransformedSize.y}.translate({-box.x, -box.y});
+ g_pHyprOpenGL->setMonitorTransformEnabled(true);
+ g_pHyprOpenGL->setRenderModifEnabled(false);
+ g_pHyprOpenGL->renderTexture(TEXTURE, monbox, 1);
+ g_pHyprOpenGL->setRenderModifEnabled(true);
+ g_pHyprOpenGL->setMonitorTransformEnabled(false);
+ } else if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING)
+ g_pHyprOpenGL->clear(Colors::BLACK);
+ else {
+ g_pHyprOpenGL->clear(Colors::BLACK);
+ CBox texbox =
+ CBox{pMonitor->vecTransformedSize / 2.F, g_pHyprOpenGL->m_pScreencopyDeniedTexture->m_vSize}.translate(-g_pHyprOpenGL->m_pScreencopyDeniedTexture->m_vSize / 2.F);
+ g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pScreencopyDeniedTexture, texbox, 1);
+ }
#ifndef GLES2
glBindFramebuffer(GL_READ_FRAMEBUFFER, fb.getFBID());
@@ -430,6 +451,14 @@ void CScreencopyProtocol::onOutputCommit(PHLMONITOR pMonitor) {
if (!f)
continue;
+ // check permissions
+ const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(f->resource->client(), PERMISSION_TYPE_SCREENCOPY);
+
+ if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING)
+ continue; // pending an answer, don't do anything yet.
+
+ // otherwise share. If it's denied, it will be black.
+
if (!f->pMonitor || !f->buffer) {
framesToRemove.emplace_back(f);
continue;
diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp
index 66e74164..8f18afbc 100644
--- a/src/protocols/ToplevelExport.cpp
+++ b/src/protocols/ToplevelExport.cpp
@@ -8,6 +8,7 @@
#include "../helpers/Format.hpp"
#include "../managers/EventManager.hpp"
#include "../managers/input/InputManager.hpp"
+#include "../managers/permissions/DynamicPermissionManager.hpp"
#include "../render/Renderer.hpp"
#include <algorithm>
@@ -231,7 +232,8 @@ void CToplevelExportFrame::share() {
}
bool CToplevelExportFrame::copyShm(timespec* now) {
- auto shm = buffer->shm();
+ const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(resource->client(), PERMISSION_TYPE_SCREENCOPY);
+ auto shm = buffer->shm();
auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); // no need for end, cuz it's shm
// render the client
@@ -256,12 +258,18 @@ bool CToplevelExportFrame::copyShm(timespec* now) {
g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 1.0));
// render client at 0,0
- g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(pWindow); // block the feedback to avoid spamming the surface if it's visible
- g_pHyprRenderer->renderWindow(pWindow, PMONITOR, now, false, RENDER_PASS_ALL, true, true);
- g_pHyprRenderer->m_bBlockSurfaceFeedback = false;
-
- if (overlayCursor)
- g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - pWindow->m_vRealPosition->value());
+ if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) {
+ g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(pWindow); // block the feedback to avoid spamming the surface if it's visible
+ g_pHyprRenderer->renderWindow(pWindow, PMONITOR, now, false, RENDER_PASS_ALL, true, true);
+ g_pHyprRenderer->m_bBlockSurfaceFeedback = false;
+
+ if (overlayCursor)
+ g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - pWindow->m_vRealPosition->value());
+ } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) {
+ CBox texbox =
+ CBox{PMONITOR->vecTransformedSize / 2.F, g_pHyprOpenGL->m_pScreencopyDeniedTexture->m_vSize}.translate(-g_pHyprOpenGL->m_pScreencopyDeniedTexture->m_vSize / 2.F);
+ g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pScreencopyDeniedTexture, texbox, 1);
+ }
const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format);
if (!PFORMAT) {
@@ -322,6 +330,7 @@ bool CToplevelExportFrame::copyShm(timespec* now) {
}
bool CToplevelExportFrame::copyDmabuf(timespec* now) {
+ const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(resource->client(), PERMISSION_TYPE_SCREENCOPY);
const auto PMONITOR = pWindow->m_pMonitor.lock();
CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX};
@@ -337,13 +346,18 @@ bool CToplevelExportFrame::copyDmabuf(timespec* now) {
return false;
g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 1.0));
-
- g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(pWindow); // block the feedback to avoid spamming the surface if it's visible
- g_pHyprRenderer->renderWindow(pWindow, PMONITOR, now, false, RENDER_PASS_ALL, true, true);
- g_pHyprRenderer->m_bBlockSurfaceFeedback = false;
-
- if (overlayCursor)
- g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - pWindow->m_vRealPosition->value());
+ if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) {
+ g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(pWindow); // block the feedback to avoid spamming the surface if it's visible
+ g_pHyprRenderer->renderWindow(pWindow, PMONITOR, now, false, RENDER_PASS_ALL, true, true);
+ g_pHyprRenderer->m_bBlockSurfaceFeedback = false;
+
+ if (overlayCursor)
+ g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - pWindow->m_vRealPosition->value());
+ } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) {
+ CBox texbox =
+ CBox{PMONITOR->vecTransformedSize / 2.F, g_pHyprOpenGL->m_pScreencopyDeniedTexture->m_vSize}.translate(-g_pHyprOpenGL->m_pScreencopyDeniedTexture->m_vSize / 2.F);
+ g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pScreencopyDeniedTexture, texbox, 1);
+ }
g_pHyprOpenGL->m_RenderData.blockScreenShader = true;
g_pHyprRenderer->endRender();
@@ -417,6 +431,12 @@ void CToplevelExportProtocol::onOutputCommit(PHLMONITOR pMonitor) {
if (!f)
continue;
+ // check permissions
+ const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(f->resource->client(), PERMISSION_TYPE_SCREENCOPY);
+
+ if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING)
+ continue; // pending an answer, don't do anything yet.
+
if (!validMapped(f->pWindow)) {
framesToRemove.emplace_back(f);
continue;
diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp
index 2a3723bd..eaea31be 100644
--- a/src/render/OpenGL.cpp
+++ b/src/render/OpenGL.cpp
@@ -2850,6 +2850,8 @@ void CHyprOpenGLImpl::initAssets() {
"unknown"),
CHyprColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true);
+ m_pScreencopyDeniedTexture = renderText("Permission denied to share screen", Colors::WHITE, 20);
+
ensureBackgroundTexturePresence();
}
diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp
index c3091f15..ab091f24 100644
--- a/src/render/OpenGL.hpp
+++ b/src/render/OpenGL.hpp
@@ -283,6 +283,8 @@ class CHyprOpenGLImpl {
bool EXT_create_context_robustness = false;
} m_sExts;
+ SP<CTexture> m_pScreencopyDeniedTexture;
+
private:
enum eEGLContextVersion : uint8_t {
EGL_CONTEXT_GLES_2_0 = 0,