mirror of
https://github.com/hyprwm/Hyprland.git
synced 2025-05-19 08:30:22 -07:00
helpers: Add an async dialog box impl (#9919)
Adds an async dialog box, way safer than our previous local solution for ANR
This commit is contained in:
parent
e96b8ce4cc
commit
3c128679ee
@ -108,7 +108,7 @@ find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
|
||||
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.8.0)
|
||||
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2)
|
||||
pkg_check_modules(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7)
|
||||
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.5.1)
|
||||
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.6.0)
|
||||
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.1)
|
||||
|
||||
string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION})
|
||||
|
18
flake.lock
generated
18
flake.lock
generated
@ -128,11 +128,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1743549251,
|
||||
"narHash": "sha256-yf+AXt0RkAkCyF6iSnJt6EJAnNG/l6qv70CVzhRP6Bg=",
|
||||
"lastModified": 1743714874,
|
||||
"narHash": "sha256-yt8F7NhMFCFHUHy/lNjH/pjZyIDFNk52Q4tivQ31WFo=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-protocols",
|
||||
"rev": "4ab17ccac08456cb5e00e8bd323de2efd30612be",
|
||||
"rev": "3a5c2bda1c1a4e55cc1330c782547695a93f05b2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -238,11 +238,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1742984269,
|
||||
"narHash": "sha256-uz9FaCIbga/gQ5ZG1Hb4HVVjTWT1qjjCAFlCXiaefxg=",
|
||||
"lastModified": 1743950287,
|
||||
"narHash": "sha256-/6IAEWyb8gC/NKZElxiHChkouiUOrVYNq9YqG0Pzm4Y=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "7248194a2ce0106ae647b70d0526a96dc9d6ad60",
|
||||
"rev": "f2dc70e448b994cef627a157ee340135bd68fbc6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -276,11 +276,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1743583204,
|
||||
"narHash": "sha256-F7n4+KOIfWrwoQjXrL2wD9RhFYLs2/GGe/MQY1sSdlE=",
|
||||
"lastModified": 1743827369,
|
||||
"narHash": "sha256-rpqepOZ8Eo1zg+KJeWoq1HAOgoMCDloqv5r2EAa9TSA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "2c8d3f48d33929642c1c12cd243df4cc7d2ce434",
|
||||
"rev": "42a1c966be226125b48c384171c44c651c236c22",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -35,7 +35,7 @@ aquamarine = dependency('aquamarine', version: '>=0.8.0')
|
||||
hyprcursor = dependency('hyprcursor', version: '>=0.1.7')
|
||||
hyprgraphics = dependency('hyprgraphics', version: '>= 0.1.1')
|
||||
hyprlang = dependency('hyprlang', version: '>= 0.3.2')
|
||||
hyprutils = dependency('hyprutils', version: '>= 0.2.3')
|
||||
hyprutils = dependency('hyprutils', version: '>= 0.6.0')
|
||||
aquamarine_version_list = aquamarine.version().split('.')
|
||||
add_project_arguments(['-DAQUAMARINE_VERSION="@0@"'.format(aquamarine.version())], language: 'cpp')
|
||||
add_project_arguments(['-DAQUAMARINE_VERSION_MAJOR=@0@'.format(aquamarine_version_list.get(0))], language: 'cpp')
|
||||
|
122
src/helpers/AsyncDialogBox.cpp
Normal file
122
src/helpers/AsyncDialogBox.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include "AsyncDialogBox.hpp"
|
||||
#include "./fs/FsUtils.hpp"
|
||||
#include <csignal>
|
||||
#include "../managers/eventLoop/EventLoopManager.hpp"
|
||||
|
||||
using namespace Hyprutils::OS;
|
||||
|
||||
SP<CAsyncDialogBox> CAsyncDialogBox::create(const std::string& title, const std::string& description, std::vector<std::string> buttons) {
|
||||
if (!NFsUtils::executableExistsInPath("hyprland-dialog")) {
|
||||
Debug::log(ERR, "CAsyncDialogBox: cannot create, no hyprland-dialog");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto dialog = SP<CAsyncDialogBox>(new CAsyncDialogBox(title, description, buttons));
|
||||
|
||||
dialog->m_selfWeakReference = dialog;
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
CAsyncDialogBox::CAsyncDialogBox(const std::string& title, const std::string& description, std::vector<std::string> buttons) :
|
||||
m_title(title), m_description(description), m_buttons(buttons) {
|
||||
;
|
||||
}
|
||||
|
||||
static int onFdWrite(int fd, uint32_t mask, void* data) {
|
||||
auto box = (CAsyncDialogBox*)data;
|
||||
|
||||
box->onWrite(fd, mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CAsyncDialogBox::onWrite(int fd, uint32_t mask) {
|
||||
if (mask & WL_EVENT_READABLE) {
|
||||
std::array<char, 1024> buf;
|
||||
int ret = 0;
|
||||
|
||||
// make the FD nonblock for a moment
|
||||
// TODO: can we avoid this without risking a blocking read()?
|
||||
int fdFlags = fcntl(fd, F_GETFL, 0);
|
||||
if (fcntl(fd, F_SETFL, fdFlags | O_NONBLOCK) < 0) {
|
||||
Debug::log(ERR, "CAsyncDialogBox::onWrite: fcntl 1 failed!");
|
||||
return;
|
||||
}
|
||||
|
||||
while ((ret = read(m_pipeReadFd.get(), buf.data(), 1023)) > 0) {
|
||||
m_stdout += std::string_view{(char*)buf.data(), (size_t)ret};
|
||||
}
|
||||
|
||||
// restore the flags (otherwise libwayland wont give us a hangup)
|
||||
if (fcntl(fd, F_SETFL, fdFlags) < 0) {
|
||||
Debug::log(ERR, "CAsyncDialogBox::onWrite: fcntl 2 failed!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) {
|
||||
Debug::log(LOG, "CAsyncDialogBox: dialog {:x} hung up, closed.");
|
||||
|
||||
if (m_onResolution)
|
||||
m_onResolution(m_stdout);
|
||||
|
||||
wl_event_source_remove(m_readEventSource);
|
||||
m_selfReference.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CAsyncDialogBox::open(std::function<void(std::string)> onResolution) {
|
||||
m_onResolution = onResolution;
|
||||
|
||||
std::string buttonsString = "";
|
||||
for (auto& b : m_buttons) {
|
||||
buttonsString += b + ";";
|
||||
}
|
||||
if (!buttonsString.empty())
|
||||
buttonsString.pop_back();
|
||||
|
||||
CProcess proc("hyprland-dialog", std::vector<std::string>{"--title", m_title, "--text", m_description, "--buttons", buttonsString});
|
||||
|
||||
int outPipe[2];
|
||||
if (pipe(outPipe)) {
|
||||
Debug::log(ERR, "CAsyncDialogBox::open: failed to pipe()");
|
||||
return;
|
||||
}
|
||||
|
||||
m_pipeReadFd = CFileDescriptor(outPipe[0]);
|
||||
|
||||
proc.setStdoutFD(outPipe[1]);
|
||||
|
||||
m_readEventSource = wl_event_loop_add_fd(g_pEventLoopManager->m_sWayland.loop, m_pipeReadFd.get(), WL_EVENT_READABLE, ::onFdWrite, this);
|
||||
|
||||
if (!m_readEventSource) {
|
||||
Debug::log(ERR, "CAsyncDialogBox::open: failed to add read fd to loop");
|
||||
return;
|
||||
}
|
||||
|
||||
m_selfReference = m_selfWeakReference.lock();
|
||||
|
||||
m_dialogPid = proc.pid();
|
||||
|
||||
if (!proc.runAsync()) {
|
||||
Debug::log(ERR, "CAsyncDialogBox::open: failed to run async");
|
||||
wl_event_source_remove(m_readEventSource);
|
||||
return;
|
||||
}
|
||||
|
||||
// close the write fd, only the dialog owns it now
|
||||
close(outPipe[1]);
|
||||
}
|
||||
|
||||
void CAsyncDialogBox::kill() {
|
||||
if (m_dialogPid <= 0)
|
||||
return;
|
||||
|
||||
::kill(m_dialogPid, SIGKILL);
|
||||
}
|
||||
|
||||
bool CAsyncDialogBox::isRunning() const {
|
||||
return m_readEventSource;
|
||||
}
|
45
src/helpers/AsyncDialogBox.hpp
Normal file
45
src/helpers/AsyncDialogBox.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "../macros.hpp"
|
||||
#include "./memory/Memory.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
|
||||
struct wl_event_source;
|
||||
|
||||
class CAsyncDialogBox {
|
||||
public:
|
||||
static SP<CAsyncDialogBox> create(const std::string& title, const std::string& description, std::vector<std::string> buttons);
|
||||
|
||||
CAsyncDialogBox(const CAsyncDialogBox&) = delete;
|
||||
CAsyncDialogBox(CAsyncDialogBox&&) = delete;
|
||||
CAsyncDialogBox& operator=(const CAsyncDialogBox&) = delete;
|
||||
CAsyncDialogBox& operator=(CAsyncDialogBox&&) = delete;
|
||||
|
||||
void open(std::function<void(std::string)> onResolution);
|
||||
void kill();
|
||||
bool isRunning() const;
|
||||
|
||||
void onWrite(int fd, uint32_t mask);
|
||||
|
||||
private:
|
||||
CAsyncDialogBox(const std::string& title, const std::string& description, std::vector<std::string> buttons);
|
||||
|
||||
pid_t m_dialogPid = 0;
|
||||
wl_event_source* m_readEventSource = nullptr;
|
||||
std::function<void(std::string)> m_onResolution;
|
||||
Hyprutils::OS::CFileDescriptor m_pipeReadFd;
|
||||
std::string m_stdout = "";
|
||||
|
||||
const std::string m_title;
|
||||
const std::string m_description;
|
||||
const std::vector<std::string> m_buttons;
|
||||
|
||||
// WARNING: cyclic reference. This will be removed once the event source is removed to avoid dangling pointers
|
||||
SP<CAsyncDialogBox> m_selfReference;
|
||||
WP<CAsyncDialogBox> m_selfWeakReference;
|
||||
};
|
@ -39,8 +39,6 @@ CANRManager::CANRManager() {
|
||||
}
|
||||
|
||||
void CANRManager::onTick() {
|
||||
std::erase_if(m_data, [](const auto& e) { return e->isDefunct(); });
|
||||
|
||||
static auto PENABLEANR = CConfigValue<Hyprlang::INT>("misc:enable_anr_dialog");
|
||||
static auto PANRTHRESHOLD = CConfigValue<Hyprlang::INT>("misc:anr_missed_pings");
|
||||
|
||||
@ -68,7 +66,7 @@ void CANRManager::onTick() {
|
||||
continue;
|
||||
|
||||
if (data->missedResponses >= *PANRTHRESHOLD) {
|
||||
if (!data->isThreadRunning() && !data->dialogThreadSaidWait) {
|
||||
if (!data->isRunning() && !data->dialogSaidWait) {
|
||||
data->runDialog("Application Not Responding", firstWindow->m_szTitle, firstWindow->m_szClass, data->getPid());
|
||||
|
||||
for (const auto& w : g_pCompositor->m_vWindows) {
|
||||
@ -81,11 +79,11 @@ void CANRManager::onTick() {
|
||||
*w->m_notRespondingTint = 0.2F;
|
||||
}
|
||||
}
|
||||
} else if (data->isThreadRunning())
|
||||
} else if (data->isRunning())
|
||||
data->killDialog();
|
||||
|
||||
if (data->missedResponses == 0)
|
||||
data->dialogThreadSaidWait = false;
|
||||
data->dialogSaidWait = false;
|
||||
|
||||
data->missedResponses++;
|
||||
|
||||
@ -115,7 +113,7 @@ void CANRManager::onResponse(SP<CXWaylandSurface> pXwaylandSurface) {
|
||||
|
||||
void CANRManager::onResponse(SP<CANRManager::SANRData> data) {
|
||||
data->missedResponses = 0;
|
||||
if (data->isThreadRunning())
|
||||
if (data->isRunning())
|
||||
data->killDialog();
|
||||
}
|
||||
|
||||
@ -158,64 +156,39 @@ CANRManager::SANRData::SANRData(PHLWINDOW pWindow) :
|
||||
}
|
||||
|
||||
CANRManager::SANRData::~SANRData() {
|
||||
if (dialogThread.joinable()) {
|
||||
if (dialogBox && dialogBox->isRunning())
|
||||
killDialog();
|
||||
// dangerous: might lock if the above failed!!
|
||||
dialogThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void CANRManager::SANRData::runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID) {
|
||||
if (!dialogThreadExited)
|
||||
if (dialogBox && dialogBox->isRunning())
|
||||
killDialog();
|
||||
|
||||
// dangerous: might lock if the above failed!!
|
||||
if (dialogThread.joinable())
|
||||
dialogThread.join();
|
||||
dialogBox = CAsyncDialogBox::create(title,
|
||||
std::format("Application {} with class of {} is not responding.\nWhat do you want to do with it?", appName.empty() ? "unknown" : appName,
|
||||
appClass.empty() ? "unknown" : appClass),
|
||||
std::vector<std::string>{"Terminate", "Wait"});
|
||||
|
||||
dialogThreadExited = false;
|
||||
dialogThreadSaidWait = false;
|
||||
dialogThread = std::thread([title, appName, appClass, dialogWmPID, this]() {
|
||||
SP<CProcess> proc = makeShared<CProcess>("hyprland-dialog",
|
||||
std::vector<std::string>{"--title", title, "--text",
|
||||
std::format("Application {} with class of {} is not responding.\nWhat do you want to do with it?",
|
||||
appName.empty() ? "unknown" : appName, appClass.empty() ? "unknown" : appClass),
|
||||
"--buttons", "Terminate;Wait"});
|
||||
|
||||
dialogProc = proc;
|
||||
proc->runSync();
|
||||
|
||||
dialogThreadExited = true;
|
||||
|
||||
if (proc->stdOut().empty())
|
||||
return;
|
||||
|
||||
if (proc->stdOut().starts_with("Terminate"))
|
||||
kill(dialogWmPID, SIGKILL);
|
||||
if (proc->stdOut().starts_with("Wait"))
|
||||
dialogThreadSaidWait = true;
|
||||
dialogBox->open([dialogWmPID, this](std::string result) {
|
||||
if (result.starts_with("Terminate"))
|
||||
::kill(dialogWmPID, SIGKILL);
|
||||
else if (result.starts_with("Wait"))
|
||||
dialogSaidWait = true;
|
||||
else
|
||||
Debug::log(ERR, "CANRManager::SANRData::runDialog: lambda: unrecognized result: {}", result);
|
||||
});
|
||||
}
|
||||
|
||||
bool CANRManager::SANRData::isThreadRunning() {
|
||||
if (dialogThread.native_handle() == 0)
|
||||
return false;
|
||||
if (dialogThreadExited)
|
||||
return false;
|
||||
return pthread_kill(dialogThread.native_handle(), 0) != ESRCH;
|
||||
bool CANRManager::SANRData::isRunning() {
|
||||
return dialogBox && dialogBox->isRunning();
|
||||
}
|
||||
|
||||
void CANRManager::SANRData::killDialog() {
|
||||
if (!dialogProc)
|
||||
if (!dialogBox)
|
||||
return;
|
||||
|
||||
if (!dialogProc->pid()) {
|
||||
Debug::log(ERR, "ANR: cannot kill dialogProc, as it doesn't have a pid.");
|
||||
dialogProc = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
kill(dialogProc->pid(), SIGKILL);
|
||||
dialogBox->kill();
|
||||
dialogBox = nullptr;
|
||||
}
|
||||
|
||||
bool CANRManager::SANRData::fitsWindow(PHLWINDOW pWindow) const {
|
||||
|
@ -7,8 +7,7 @@
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
#include "./eventLoop/EventLoopTimer.hpp"
|
||||
#include "../helpers/signal/Signal.hpp"
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include "../helpers/AsyncDialogBox.hpp"
|
||||
#include <vector>
|
||||
|
||||
class CXDGWMBase;
|
||||
@ -36,13 +35,12 @@ class CANRManager {
|
||||
WP<CXDGWMBase> xdgBase;
|
||||
|
||||
int missedResponses = 0;
|
||||
std::thread dialogThread;
|
||||
SP<Hyprutils::OS::CProcess> dialogProc;
|
||||
std::atomic<bool> dialogThreadExited = false;
|
||||
std::atomic<bool> dialogThreadSaidWait = false;
|
||||
|
||||
bool dialogSaidWait = false;
|
||||
SP<CAsyncDialogBox> dialogBox;
|
||||
|
||||
void runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID);
|
||||
bool isThreadRunning();
|
||||
bool isRunning();
|
||||
void killDialog();
|
||||
bool isDefunct() const;
|
||||
bool fitsWindow(PHLWINDOW pWindow) const;
|
||||
|
@ -68,6 +68,7 @@ class CEventLoopManager {
|
||||
wl_event_source* m_configWatcherInotifySource = nullptr;
|
||||
|
||||
friend class CSyncTimeline;
|
||||
friend class CAsyncDialogBox;
|
||||
};
|
||||
|
||||
inline UP<CEventLoopManager> g_pEventLoopManager;
|
||||
|
Loading…
x
Reference in New Issue
Block a user