async: add Promise and use it for AsyncDialogBox

This commit is contained in:
Vaxry 2025-04-27 00:03:32 +01:00
parent 4f868a1f3c
commit 0302bfdc22
No known key found for this signature in database
GPG Key ID: 665806380871D640
7 changed files with 208 additions and 36 deletions

View File

@ -2906,6 +2906,8 @@ std::optional<std::string> CConfigManager::handlePermission(const std::string& c
if (data[1] == "screencopy") if (data[1] == "screencopy")
type = PERMISSION_TYPE_SCREENCOPY; type = PERMISSION_TYPE_SCREENCOPY;
else if (data[1] == "plugin")
type = PERMISSION_TYPE_PLUGIN;
if (data[2] == "ask") if (data[2] == "ask")
mode = PERMISSION_RULE_ALLOW_MODE_ASK; mode = PERMISSION_RULE_ALLOW_MODE_ASK;

View File

@ -59,8 +59,7 @@ void CAsyncDialogBox::onWrite(int fd, uint32_t mask) {
if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) {
Debug::log(LOG, "CAsyncDialogBox: dialog {:x} hung up, closed."); Debug::log(LOG, "CAsyncDialogBox: dialog {:x} hung up, closed.");
if (m_onResolution) m_promiseResolver->resolve(m_stdout);
m_onResolution(m_stdout);
wl_event_source_remove(m_readEventSource); wl_event_source_remove(m_readEventSource);
m_selfReference.reset(); m_selfReference.reset();
@ -68,9 +67,7 @@ void CAsyncDialogBox::onWrite(int fd, uint32_t mask) {
} }
} }
void CAsyncDialogBox::open(std::function<void(std::string)> onResolution) { SP<CPromise<std::string>> CAsyncDialogBox::open() {
m_onResolution = onResolution;
std::string buttonsString = ""; std::string buttonsString = "";
for (auto& b : m_buttons) { for (auto& b : m_buttons) {
buttonsString += b + ";"; buttonsString += b + ";";
@ -83,7 +80,7 @@ void CAsyncDialogBox::open(std::function<void(std::string)> onResolution) {
int outPipe[2]; int outPipe[2];
if (pipe(outPipe)) { if (pipe(outPipe)) {
Debug::log(ERR, "CAsyncDialogBox::open: failed to pipe()"); Debug::log(ERR, "CAsyncDialogBox::open: failed to pipe()");
return; return nullptr;
} }
m_pipeReadFd = CFileDescriptor(outPipe[0]); m_pipeReadFd = CFileDescriptor(outPipe[0]);
@ -94,7 +91,7 @@ void CAsyncDialogBox::open(std::function<void(std::string)> onResolution) {
if (!m_readEventSource) { if (!m_readEventSource) {
Debug::log(ERR, "CAsyncDialogBox::open: failed to add read fd to loop"); Debug::log(ERR, "CAsyncDialogBox::open: failed to add read fd to loop");
return; return nullptr;
} }
m_selfReference = m_selfWeakReference.lock(); m_selfReference = m_selfWeakReference.lock();
@ -102,13 +99,17 @@ void CAsyncDialogBox::open(std::function<void(std::string)> onResolution) {
if (!proc.runAsync()) { if (!proc.runAsync()) {
Debug::log(ERR, "CAsyncDialogBox::open: failed to run async"); Debug::log(ERR, "CAsyncDialogBox::open: failed to run async");
wl_event_source_remove(m_readEventSource); wl_event_source_remove(m_readEventSource);
return; return nullptr;
} }
m_dialogPid = proc.pid(); m_dialogPid = proc.pid();
// close the write fd, only the dialog owns it now // close the write fd, only the dialog owns it now
close(outPipe[1]); close(outPipe[1]);
auto promise = CPromise<std::string>::make([this](SP<CPromiseResolver<std::string>> r) { m_promiseResolver = r; });
return promise;
} }
void CAsyncDialogBox::kill() { void CAsyncDialogBox::kill() {

View File

@ -2,6 +2,7 @@
#include "../macros.hpp" #include "../macros.hpp"
#include "./memory/Memory.hpp" #include "./memory/Memory.hpp"
#include "./defer/Promise.hpp"
#include <vector> #include <vector>
#include <functional> #include <functional>
@ -15,29 +16,30 @@ class CAsyncDialogBox {
public: public:
static SP<CAsyncDialogBox> create(const std::string& title, const std::string& description, std::vector<std::string> buttons); static SP<CAsyncDialogBox> create(const std::string& title, const std::string& description, std::vector<std::string> buttons);
CAsyncDialogBox(const CAsyncDialogBox&) = delete; CAsyncDialogBox(const CAsyncDialogBox&) = delete;
CAsyncDialogBox(CAsyncDialogBox&&) = delete; CAsyncDialogBox(CAsyncDialogBox&&) = delete;
CAsyncDialogBox& operator=(const CAsyncDialogBox&) = delete; CAsyncDialogBox& operator=(const CAsyncDialogBox&) = delete;
CAsyncDialogBox& operator=(CAsyncDialogBox&&) = delete; CAsyncDialogBox& operator=(CAsyncDialogBox&&) = delete;
void open(std::function<void(std::string)> onResolution); SP<CPromise<std::string>> open();
void kill(); void kill();
bool isRunning() const; bool isRunning() const;
void onWrite(int fd, uint32_t mask); void onWrite(int fd, uint32_t mask);
private: private:
CAsyncDialogBox(const std::string& title, const std::string& description, std::vector<std::string> buttons); CAsyncDialogBox(const std::string& title, const std::string& description, std::vector<std::string> buttons);
pid_t m_dialogPid = 0; pid_t m_dialogPid = 0;
wl_event_source* m_readEventSource = nullptr; wl_event_source* m_readEventSource = nullptr;
std::function<void(std::string)> m_onResolution; Hyprutils::OS::CFileDescriptor m_pipeReadFd;
Hyprutils::OS::CFileDescriptor m_pipeReadFd; std::string m_stdout = "";
std::string m_stdout = "";
const std::string m_title; const std::string m_title;
const std::string m_description; const std::string m_description;
const std::vector<std::string> m_buttons; const std::vector<std::string> m_buttons;
SP<CPromiseResolver<std::string>> m_promiseResolver;
// WARNING: cyclic reference. This will be removed once the event source is removed to avoid dangling pointers // WARNING: cyclic reference. This will be removed once the event source is removed to avoid dangling pointers
SP<CAsyncDialogBox> m_selfReference; SP<CAsyncDialogBox> m_selfReference;

View File

@ -0,0 +1,119 @@
#pragma once
#include <functional>
#include <string>
#include "../memory/Memory.hpp"
// TODO: move into hyprutils
template <typename T>
class CPromise;
template <typename T>
class CPromiseResult;
template <typename T>
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<T>::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<T>::err(reason);
if (!m_promise->m_then)
return;
m_promise->m_then(m_promise->m_result);
}
private:
CPromiseResolver(SP<CPromise<T>> promise) : m_promise(promise) {}
SP<CPromise<T>> m_promise;
friend class CPromise<T>;
};
template <typename T>
class CPromiseResult {
public:
bool hasError() {
return m_hasError;
}
T result() {
return m_result;
}
std::string error() {
return m_error;
}
private:
static SP<CPromiseResult<T>> result(T result) {
auto p = SP<CPromiseResult<T>>(new CPromiseResult<T>());
p->m_result = result;
return p;
}
static SP<CPromiseResult<T>> err(std::string reason) {
auto p = SP<CPromiseResult<T>>(new CPromiseResult<T>());
p->m_error = reason;
p->m_hasError = true;
return p;
}
T m_result = {};
std::string m_error = {};
bool m_hasError = false;
friend class CPromiseResolver<T>;
};
template <typename T>
class CPromise {
public:
CPromise(const CPromise&) = delete;
CPromise(CPromise&&) = delete;
CPromise& operator=(const CPromise&) = delete;
CPromise& operator=(CPromise&&) = delete;
static SP<CPromise> make(const std::function<void(SP<CPromiseResolver<T>>)>& fn) {
auto sp = SP<CPromise<T>>(new CPromise<T>());
fn(SP<CPromiseResolver<T>>(new CPromiseResolver<T>(sp)));
return sp;
}
void then(std::function<void(SP<CPromiseResult<T>>)>&& fn) {
m_then = std::move(fn);
if (m_result)
m_then(m_result);
}
private:
CPromise() = default;
const std::function<void(SP<CPromiseResult<T>>)> m_fn;
std::function<void(SP<CPromiseResult<T>>)> m_then;
SP<CPromiseResult<T>> m_result;
friend class CPromiseResult<T>;
friend class CPromiseResolver<T>;
};

View File

@ -169,7 +169,14 @@ void CANRManager::SANRData::runDialog(const std::string& title, const std::strin
appClass.empty() ? "unknown" : appClass), appClass.empty() ? "unknown" : appClass),
std::vector<std::string>{"Terminate", "Wait"}); std::vector<std::string>{"Terminate", "Wait"});
dialogBox->open([dialogWmPID, this](std::string result) { dialogBox->open()->then([dialogWmPID, this](SP<CPromiseResult<std::string>> r) {
if (r->hasError()) {
Debug::log(ERR, "CANRManager::SANRData::runDialog: error spawning dialog");
return;
}
const auto& result = r->result();
if (result.starts_with("Terminate")) if (result.starts_with("Terminate"))
::kill(dialogWmPID, SIGKILL); ::kill(dialogWmPID, SIGKILL);
else if (result.starts_with("Wait")) else if (result.starts_with("Wait"))

View File

@ -48,6 +48,7 @@ static const char* permissionToString(eDynamicPermissionType type) {
switch (type) { switch (type) {
case PERMISSION_TYPE_UNKNOWN: return "PERMISSION_TYPE_UNKNOWN"; case PERMISSION_TYPE_UNKNOWN: return "PERMISSION_TYPE_UNKNOWN";
case PERMISSION_TYPE_SCREENCOPY: return "PERMISSION_TYPE_SCREENCOPY"; case PERMISSION_TYPE_SCREENCOPY: return "PERMISSION_TYPE_SCREENCOPY";
case PERMISSION_TYPE_PLUGIN: return "PERMISSION_TYPE_PLUGIN";
} }
return "error"; return "error";
@ -57,6 +58,7 @@ static const char* permissionToHumanString(eDynamicPermissionType type) {
switch (type) { switch (type) {
case PERMISSION_TYPE_UNKNOWN: return "requesting an unknown permission"; case PERMISSION_TYPE_UNKNOWN: return "requesting an unknown permission";
case PERMISSION_TYPE_SCREENCOPY: return "trying to capture your screen"; case PERMISSION_TYPE_SCREENCOPY: return "trying to capture your screen";
case PERMISSION_TYPE_PLUGIN: return "trying to load a plugin";
} }
return "error"; return "error";
@ -210,10 +212,22 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s
return; return;
} }
rule->m_dialogBox->open([r = WP<CDynamicPermissionRule>(rule), binaryPath](std::string result) { rule->m_promise = rule->m_dialogBox->open();
rule->m_promise->then([r = WP<CDynamicPermissionRule>(rule), binaryPath](SP<CPromiseResult<std::string>> pr) {
if (!r) if (!r)
return; 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); Debug::log(TRACE, "CDynamicPermissionRule: user returned {}", result);
if (result.starts_with("Allow once")) if (result.starts_with("Allow once"))
@ -226,9 +240,29 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s
r->m_binaryPath = binaryPath; r->m_binaryPath = binaryPath;
} else if (result.starts_with("Allow")) } else if (result.starts_with("Allow"))
r->m_allowMode = PERMISSION_RULE_ALLOW_MODE_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<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; });
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; });
}
void CDynamicPermissionManager::removeRulesForClient(wl_client* client) { void CDynamicPermissionManager::removeRulesForClient(wl_client* client) {
std::erase_if(m_rules, [client](const auto& e) { return e->m_client == client; }); std::erase_if(m_rules, [client](const auto& e) { return e->m_client == client; });
} }

View File

@ -5,7 +5,7 @@
#include "../../helpers/AsyncDialogBox.hpp" #include "../../helpers/AsyncDialogBox.hpp"
#include <vector> #include <vector>
#include <wayland-server-core.h> #include <wayland-server-core.h>
#include <optional> #include "../../helpers/defer/Promise.hpp"
// NOLINTNEXTLINE // NOLINTNEXTLINE
namespace re2 { namespace re2 {
@ -15,6 +15,7 @@ namespace re2 {
enum eDynamicPermissionType : uint8_t { enum eDynamicPermissionType : uint8_t {
PERMISSION_TYPE_UNKNOWN = 0, PERMISSION_TYPE_UNKNOWN = 0,
PERMISSION_TYPE_SCREENCOPY, PERMISSION_TYPE_SCREENCOPY,
PERMISSION_TYPE_PLUGIN,
}; };
enum eDynamicPermissionRuleSource : uint8_t { enum eDynamicPermissionRuleSource : uint8_t {
@ -50,16 +51,18 @@ class CDynamicPermissionRule {
// user rule // user rule
CDynamicPermissionRule(wl_client* const client, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode = PERMISSION_RULE_ALLOW_MODE_ASK); CDynamicPermissionRule(wl_client* const client, eDynamicPermissionType type, eDynamicPermissionAllowMode defaultAllowMode = PERMISSION_RULE_ALLOW_MODE_ASK);
const eDynamicPermissionType m_type = PERMISSION_TYPE_UNKNOWN; const eDynamicPermissionType m_type = PERMISSION_TYPE_UNKNOWN;
const eDynamicPermissionRuleSource m_source = PERMISSION_RULE_SOURCE_UNKNOWN; const eDynamicPermissionRuleSource m_source = PERMISSION_RULE_SOURCE_UNKNOWN;
wl_client* const m_client = nullptr; wl_client* const m_client = nullptr;
std::string m_binaryPath = ""; std::string m_binaryPath = "";
UP<re2::RE2> m_binaryRegex; UP<re2::RE2> m_binaryRegex;
eDynamicPermissionAllowMode m_allowMode = PERMISSION_RULE_ALLOW_MODE_ASK; eDynamicPermissionAllowMode m_allowMode = PERMISSION_RULE_ALLOW_MODE_ASK;
SP<CAsyncDialogBox> m_dialogBox; // for pending SP<CAsyncDialogBox> m_dialogBox; // for pending
SP<CPromise<std::string>> m_promise; // for pending
SP<CPromiseResolver<eDynamicPermissionAllowMode>> m_promiseResolverForExternal; // for external promise
SDynamicPermissionRuleDestroyWrapper m_destroyWrapper; SDynamicPermissionRuleDestroyWrapper m_destroyWrapper;
friend class CDynamicPermissionManager; friend class CDynamicPermissionManager;
}; };
@ -73,7 +76,11 @@ class CDynamicPermissionManager {
// (will continue returning false if the user does not agree, of course.) // (will continue returning false if the user does not agree, of course.)
eDynamicPermissionAllowMode clientPermissionMode(wl_client* client, eDynamicPermissionType permission); eDynamicPermissionAllowMode clientPermissionMode(wl_client* client, eDynamicPermissionType permission);
void removeRulesForClient(wl_client* client); // 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);
void removeRulesForClient(wl_client* client);
private: private:
void askForPermission(wl_client* client, const std::string& binaryName, eDynamicPermissionType type); void askForPermission(wl_client* client, const std::string& binaryName, eDynamicPermissionType type);