diff --git a/assets/install/lockdead.png b/assets/install/lockdead.png index f8bae2255f9..44545c88f0d 100644 Binary files a/assets/install/lockdead.png and b/assets/install/lockdead.png differ diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 872456a6d39..9d17779832c 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -2906,6 +2906,8 @@ std::optional CConfigManager::handlePermission(const std::string& c if (data[1] == "screencopy") type = PERMISSION_TYPE_SCREENCOPY; + else if (data[1] == "plugin") + type = PERMISSION_TYPE_PLUGIN; if (data[2] == "ask") mode = PERMISSION_RULE_ALLOW_MODE_ASK; diff --git a/src/helpers/AsyncDialogBox.cpp b/src/helpers/AsyncDialogBox.cpp index 73bae8f6281..86eb9e1f03c 100644 --- a/src/helpers/AsyncDialogBox.cpp +++ b/src/helpers/AsyncDialogBox.cpp @@ -59,8 +59,7 @@ void CAsyncDialogBox::onWrite(int fd, uint32_t mask) { if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { Debug::log(LOG, "CAsyncDialogBox: dialog {:x} hung up, closed."); - if (m_onResolution) - m_onResolution(m_stdout); + m_promiseResolver->resolve(m_stdout); wl_event_source_remove(m_readEventSource); m_selfReference.reset(); @@ -68,9 +67,7 @@ void CAsyncDialogBox::onWrite(int fd, uint32_t mask) { } } -void CAsyncDialogBox::open(std::function onResolution) { - m_onResolution = onResolution; - +SP> CAsyncDialogBox::open() { std::string buttonsString = ""; for (auto& b : m_buttons) { buttonsString += b + ";"; @@ -83,7 +80,7 @@ void CAsyncDialogBox::open(std::function onResolution) { int outPipe[2]; if (pipe(outPipe)) { Debug::log(ERR, "CAsyncDialogBox::open: failed to pipe()"); - return; + return nullptr; } m_pipeReadFd = CFileDescriptor(outPipe[0]); @@ -94,7 +91,7 @@ void CAsyncDialogBox::open(std::function onResolution) { if (!m_readEventSource) { Debug::log(ERR, "CAsyncDialogBox::open: failed to add read fd to loop"); - return; + return nullptr; } m_selfReference = m_selfWeakReference.lock(); @@ -102,13 +99,17 @@ void CAsyncDialogBox::open(std::function onResolution) { if (!proc.runAsync()) { Debug::log(ERR, "CAsyncDialogBox::open: failed to run async"); wl_event_source_remove(m_readEventSource); - return; + return nullptr; } m_dialogPid = proc.pid(); // close the write fd, only the dialog owns it now close(outPipe[1]); + + auto promise = CPromise::make([this](SP> r) { m_promiseResolver = r; }); + + return promise; } void CAsyncDialogBox::kill() { diff --git a/src/helpers/AsyncDialogBox.hpp b/src/helpers/AsyncDialogBox.hpp index d3cc242c572..0cb95846bbf 100644 --- a/src/helpers/AsyncDialogBox.hpp +++ b/src/helpers/AsyncDialogBox.hpp @@ -2,6 +2,7 @@ #include "../macros.hpp" #include "./memory/Memory.hpp" +#include "./defer/Promise.hpp" #include #include @@ -15,29 +16,30 @@ class CAsyncDialogBox { public: static SP create(const std::string& title, const std::string& description, std::vector buttons); - CAsyncDialogBox(const CAsyncDialogBox&) = delete; - CAsyncDialogBox(CAsyncDialogBox&&) = delete; - CAsyncDialogBox& operator=(const CAsyncDialogBox&) = delete; - CAsyncDialogBox& operator=(CAsyncDialogBox&&) = delete; + CAsyncDialogBox(const CAsyncDialogBox&) = delete; + CAsyncDialogBox(CAsyncDialogBox&&) = delete; + CAsyncDialogBox& operator=(const CAsyncDialogBox&) = delete; + CAsyncDialogBox& operator=(CAsyncDialogBox&&) = delete; - void open(std::function onResolution); - void kill(); - bool isRunning() const; + SP> open(); + void kill(); + bool isRunning() const; - void onWrite(int fd, uint32_t mask); + void onWrite(int fd, uint32_t mask); private: CAsyncDialogBox(const std::string& title, const std::string& description, std::vector buttons); - pid_t m_dialogPid = 0; - wl_event_source* m_readEventSource = nullptr; - std::function m_onResolution; - Hyprutils::OS::CFileDescriptor m_pipeReadFd; - std::string m_stdout = ""; + pid_t m_dialogPid = 0; + wl_event_source* m_readEventSource = nullptr; + Hyprutils::OS::CFileDescriptor m_pipeReadFd; + std::string m_stdout = ""; - const std::string m_title; - const std::string m_description; - const std::vector m_buttons; + const std::string m_title; + const std::string m_description; + const std::vector m_buttons; + + SP> m_promiseResolver; // WARNING: cyclic reference. This will be removed once the event source is removed to avoid dangling pointers SP m_selfReference; diff --git a/src/helpers/defer/Promise.hpp b/src/helpers/defer/Promise.hpp new file mode 100644 index 00000000000..53399bc2cc4 --- /dev/null +++ b/src/helpers/defer/Promise.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include "../memory/Memory.hpp" + +// TODO: move into hyprutils + +template +class CPromise; +template +class CPromiseResult; + +template +class CPromiseResolver { + public: + CPromiseResolver(const CPromiseResolver&) = delete; + CPromiseResolver(CPromiseResolver&&) = delete; + CPromiseResolver& operator=(const CPromiseResolver&) = delete; + CPromiseResolver& operator=(CPromiseResolver&&) = delete; + + void resolve(T value) { + if (m_promise->m_result) + return; + + m_promise->m_result = CPromiseResult::result(value); + + if (!m_promise->m_then) + return; + + m_promise->m_then(m_promise->m_result); + } + + void reject(const std::string& reason) { + if (m_promise->m_result) + return; + + m_promise->m_result = CPromiseResult::err(reason); + + if (!m_promise->m_then) + return; + + m_promise->m_then(m_promise->m_result); + } + + private: + CPromiseResolver(SP> promise) : m_promise(promise) {} + + SP> m_promise; + + friend class CPromise; +}; + +template +class CPromiseResult { + public: + bool hasError() { + return m_hasError; + } + + T result() { + return m_result; + } + + std::string error() { + return m_error; + } + + private: + static SP> result(T result) { + auto p = SP>(new CPromiseResult()); + p->m_result = result; + return p; + } + + static SP> err(std::string reason) { + auto p = SP>(new CPromiseResult()); + p->m_error = reason; + p->m_hasError = true; + return p; + } + + T m_result = {}; + std::string m_error = {}; + bool m_hasError = false; + + friend class CPromiseResolver; +}; + +template +class CPromise { + public: + CPromise(const CPromise&) = delete; + CPromise(CPromise&&) = delete; + CPromise& operator=(const CPromise&) = delete; + CPromise& operator=(CPromise&&) = delete; + + static SP make(const std::function>)>& fn) { + auto sp = SP>(new CPromise()); + fn(SP>(new CPromiseResolver(sp))); + return sp; + } + + void then(std::function>)>&& fn) { + m_then = std::move(fn); + if (m_result) + m_then(m_result); + } + + private: + CPromise() = default; + + const std::function>)> m_fn; + std::function>)> m_then; + SP> m_result; + + friend class CPromiseResult; + friend class CPromiseResolver; +}; \ No newline at end of file diff --git a/src/managers/ANRManager.cpp b/src/managers/ANRManager.cpp index 69d127a79ec..48b9a6db0bf 100644 --- a/src/managers/ANRManager.cpp +++ b/src/managers/ANRManager.cpp @@ -169,7 +169,14 @@ void CANRManager::SANRData::runDialog(const std::string& title, const std::strin appClass.empty() ? "unknown" : appClass), std::vector{"Terminate", "Wait"}); - dialogBox->open([dialogWmPID, this](std::string result) { + dialogBox->open()->then([dialogWmPID, this](SP> r) { + if (r->hasError()) { + Debug::log(ERR, "CANRManager::SANRData::runDialog: error spawning dialog"); + return; + } + + const auto& result = r->result(); + if (result.starts_with("Terminate")) ::kill(dialogWmPID, SIGKILL); else if (result.starts_with("Wait")) diff --git a/src/managers/permissions/DynamicPermissionManager.cpp b/src/managers/permissions/DynamicPermissionManager.cpp index cf1fd1a0288..c23f1fdbe4e 100644 --- a/src/managers/permissions/DynamicPermissionManager.cpp +++ b/src/managers/permissions/DynamicPermissionManager.cpp @@ -48,6 +48,7 @@ static const char* permissionToString(eDynamicPermissionType type) { switch (type) { case PERMISSION_TYPE_UNKNOWN: return "PERMISSION_TYPE_UNKNOWN"; case PERMISSION_TYPE_SCREENCOPY: return "PERMISSION_TYPE_SCREENCOPY"; + case PERMISSION_TYPE_PLUGIN: return "PERMISSION_TYPE_PLUGIN"; } return "error"; @@ -57,6 +58,7 @@ 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"; + case PERMISSION_TYPE_PLUGIN: return "trying to load a plugin"; } return "error"; @@ -210,10 +212,22 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s return; } - rule->m_dialogBox->open([r = WP(rule), binaryPath](std::string result) { + rule->m_promise = rule->m_dialogBox->open(); + rule->m_promise->then([r = WP(rule), binaryPath](SP> pr) { if (!r) return; + if (pr->hasError()) { + // not reachable for now + Debug::log(TRACE, "CDynamicPermissionRule: error spawning dialog box"); + if (r->m_promiseResolverForExternal) + r->m_promiseResolverForExternal->reject("error spawning dialog box"); + r->m_promiseResolverForExternal.reset(); + return; + } + + const std::string& result = pr->result(); + Debug::log(TRACE, "CDynamicPermissionRule: user returned {}", result); if (result.starts_with("Allow once")) @@ -226,9 +240,29 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s r->m_binaryPath = binaryPath; } else if (result.starts_with("Allow")) r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_ALLOW; + + if (r->m_promiseResolverForExternal) + r->m_promiseResolverForExternal->resolve(r->m_allowMode); + + r->m_promise.reset(); + r->m_promiseResolverForExternal.reset(); }); } +SP> CDynamicPermissionManager::promiseFor(wl_client* client, eDynamicPermissionType permission) { + auto rule = std::ranges::find_if(m_rules, [client, permission](const auto& e) { return e->m_client == client && e->m_type == permission; }); + if (rule == m_rules.end()) + return nullptr; + + if (!(*rule)->m_promise) + return nullptr; + + if ((*rule)->m_promiseResolverForExternal) + return nullptr; + + return CPromise::make([rule](SP> r) { (*rule)->m_promiseResolverForExternal = r; }); +} + 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 index 681cbc1c4e2..90b4f99132c 100644 --- a/src/managers/permissions/DynamicPermissionManager.hpp +++ b/src/managers/permissions/DynamicPermissionManager.hpp @@ -5,7 +5,7 @@ #include "../../helpers/AsyncDialogBox.hpp" #include #include -#include +#include "../../helpers/defer/Promise.hpp" // NOLINTNEXTLINE namespace re2 { @@ -15,6 +15,7 @@ namespace re2 { enum eDynamicPermissionType : uint8_t { PERMISSION_TYPE_UNKNOWN = 0, PERMISSION_TYPE_SCREENCOPY, + PERMISSION_TYPE_PLUGIN, }; enum eDynamicPermissionRuleSource : uint8_t { @@ -50,16 +51,18 @@ class CDynamicPermissionRule { // 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; + 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 + eDynamicPermissionAllowMode m_allowMode = PERMISSION_RULE_ALLOW_MODE_ASK; + SP m_dialogBox; // for pending + SP> m_promise; // for pending + SP> m_promiseResolverForExternal; // for external promise - SDynamicPermissionRuleDestroyWrapper m_destroyWrapper; + SDynamicPermissionRuleDestroyWrapper m_destroyWrapper; friend class CDynamicPermissionManager; }; @@ -73,6 +76,10 @@ class CDynamicPermissionManager { // (will continue returning false if the user does not agree, of course.) eDynamicPermissionAllowMode clientPermissionMode(wl_client* client, eDynamicPermissionType permission); + // get a promise for the result. Returns null if there already was one requested for the client. + // Returns null if state is not pending + SP> promiseFor(wl_client* client, eDynamicPermissionType permission); + void removeRulesForClient(wl_client* client); private: