diff --git a/example/hyprland.conf b/example/hyprland.conf index cfe5040b..e21b7844 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -52,6 +52,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/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 #include #include @@ -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(); + Debug::log(LOG, "Creating the DynamicPermissionManager!"); + g_pDynamicPermissionManager = makeUnique(); + Debug::log(LOG, "Creating the ConfigManager!"); g_pConfigManager = makeUnique(); 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 CConfigManager::resetHLConfig() { m_vFailedPluginConfigValues.clear(); finalExecRequests.clear(); + g_pDynamicPermissionManager->clearConfigPermissions(); + // paths m_configPaths.clear(); std::string mainConfigPath = getMainConfigPath(); @@ -2831,6 +2848,32 @@ std::optional CConfigManager::handlePlugin(const std::string& comma return {}; } +std::optional 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& 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 handleBindWS(const std::string&, const std::string&); std::optional handleEnv(const std::string&, const std::string&); std::optional handlePlugin(const std::string&, const std::string&); + std::optional 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 +#include "DynamicPermissionManager.hpp" +#include +#include +#include +#include +#include "../../Compositor.hpp" +#include "../../config/ConfigValue.hpp" + +#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) +#include +#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(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 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(new CDynamicPermissionRule(binaryName, type, mode))); +} + +eDynamicPermissionAllowMode CDynamicPermissionManager::clientPermissionMode(wl_client* client, eDynamicPermissionType permission) { + + static auto PPERM = CConfigValue("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(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 {} ({}) is {}.", binaryName, binaryPath, permissionToHumanString(type)); + } + + description += "

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

Hint: you can set persistent rules for these in the Hyprland config file."; + 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(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 +#include +#include + +// 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 m_binaryRegex; + + eDynamicPermissionAllowMode m_allowMode = PERMISSION_RULE_ALLOW_MODE_ASK; + SP 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> m_rules; +}; + +inline UP 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 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 callback) { - auto TEXTURE = makeShared(pMonitor->output->state->state().buffer); + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(resource->client(), PERMISSION_TYPE_SCREENCOPY); + auto TEXTURE = makeShared(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 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 callback) { } bool CScreencopyFrame::copyShm() { - auto TEXTURE = makeShared(pMonitor->output->state->state().buffer); + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(resource->client(), PERMISSION_TYPE_SCREENCOPY); + auto TEXTURE = makeShared(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 @@ -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 (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()); + 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)); + 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; - 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 (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 m_pScreencopyDeniedTexture; + private: enum eEGLContextVersion : uint8_t { EGL_CONTEXT_GLES_2_0 = 0,