summaryrefslogtreecommitdiff
path: root/src/managers/ANRManager.cpp
diff options
context:
space:
mode:
authorVaxry <vaxry@vaxry.net>2025-02-18 15:10:40 +0000
committerVaxry <vaxry@vaxry.net>2025-02-18 15:13:56 +0000
commitfb8eaba83f2d114fb346730e789a559ccc366887 (patch)
treed4fe951c24311577ccd9c950f17eb92527b4bd5a /src/managers/ANRManager.cpp
parent3352317ca827dfb2dfd222274dfaf5941c78c086 (diff)
core: add an ANR dialog
for xdg-shell, we can ping the wm_base, and thus render an ANR dialog if an app dies for XWayland, there probably is a similar method, but I don't know about it and don't care.
Diffstat (limited to 'src/managers/ANRManager.cpp')
-rw-r--r--src/managers/ANRManager.cpp173
1 files changed, 173 insertions, 0 deletions
diff --git a/src/managers/ANRManager.cpp b/src/managers/ANRManager.cpp
new file mode 100644
index 00000000..e2b276ee
--- /dev/null
+++ b/src/managers/ANRManager.cpp
@@ -0,0 +1,173 @@
+#include "ANRManager.hpp"
+#include "../helpers/fs/FsUtils.hpp"
+#include "../debug/Log.hpp"
+#include "../macros.hpp"
+#include "HookSystemManager.hpp"
+#include "../Compositor.hpp"
+#include "../protocols/XDGShell.hpp"
+#include "./eventLoop/EventLoopManager.hpp"
+#include "../config/ConfigValue.hpp"
+
+using namespace Hyprutils::OS;
+
+static constexpr auto TIMER_TIMEOUT = std::chrono::milliseconds(1500);
+
+CANRManager::CANRManager() {
+ if (!NFsUtils::executableExistsInPath("hyprland-dialog")) {
+ Debug::log(ERR, "hyprland-dialog missing from PATH, cannot start ANRManager");
+ return;
+ }
+
+ m_timer = makeShared<CEventLoopTimer>(TIMER_TIMEOUT, [this](SP<CEventLoopTimer> self, void* data) { onTick(); }, this);
+ g_pEventLoopManager->addTimer(m_timer);
+
+ m_active = true;
+
+ static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) {
+ auto window = std::any_cast<PHLWINDOW>(data);
+
+ if (window->m_bIsX11)
+ return;
+
+ for (const auto& w : g_pCompositor->m_vWindows) {
+ if (w->m_bIsX11 || w == window || !w->m_pXDGSurface)
+ continue;
+
+ if (w->m_pXDGSurface->owner == window->m_pXDGSurface->owner)
+ return;
+ }
+
+ m_data[window->m_pXDGSurface->owner] = makeShared<SANRData>();
+ });
+
+ m_timer->updateTimeout(TIMER_TIMEOUT);
+}
+
+void CANRManager::onTick() {
+ std::erase_if(m_data, [](const auto& e) { return e.first.expired(); });
+
+ static auto PENABLEANR = CConfigValue<Hyprlang::INT>("misc:enable_anr_dialog");
+
+ if (!*PENABLEANR) {
+ m_timer->updateTimeout(TIMER_TIMEOUT * 10);
+ return;
+ }
+
+ for (auto& [wmBase, data] : m_data) {
+ PHLWINDOW firstWindow;
+ int count = 0;
+ for (const auto& w : g_pCompositor->m_vWindows) {
+ if (!w->m_bIsMapped || w->m_bIsX11 || !w->m_pXDGSurface)
+ continue;
+
+ if (w->m_pXDGSurface->owner != wmBase)
+ continue;
+
+ count++;
+ if (!firstWindow)
+ firstWindow = w;
+ }
+
+ if (count == 0)
+ continue;
+
+ if (data->missedResponses > 0) {
+ if (!data->isThreadRunning() && !data->dialogThreadSaidWait) {
+ pid_t pid = 0;
+ wl_client_get_credentials(wmBase->client(), &pid, nullptr, nullptr);
+ data->runDialog("Application Not Responding", firstWindow->m_szTitle, firstWindow->m_szClass, pid);
+
+ for (const auto& w : g_pCompositor->m_vWindows) {
+ if (!w->m_bIsMapped || w->m_bIsX11 || !w->m_pXDGSurface)
+ continue;
+
+ if (w->m_pXDGSurface->owner != wmBase)
+ continue;
+
+ *w->m_notRespondingTint = 0.2F;
+ }
+ }
+ } else if (data->isThreadRunning())
+ data->killDialog();
+
+ if (data->missedResponses == 0)
+ data->dialogThreadSaidWait = false;
+
+ data->missedResponses++;
+
+ wmBase->ping();
+ }
+
+ m_timer->updateTimeout(TIMER_TIMEOUT);
+}
+
+void CANRManager::onResponse(SP<CXDGWMBase> wmBase) {
+ if (!m_data.contains(wmBase))
+ return;
+
+ auto& data = m_data.at(wmBase);
+ data->missedResponses = 0;
+ if (data->isThreadRunning())
+ data->killDialog();
+}
+
+void CANRManager::SANRData::runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID) {
+ if (!dialogThreadExited)
+ killDialog();
+
+ // dangerous: might lock if the above failed!!
+ if (dialogThread.joinable())
+ dialogThread.join();
+
+ 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, 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;
+ });
+}
+
+bool CANRManager::SANRData::isThreadRunning() {
+ if (dialogThread.native_handle() == 0)
+ return false;
+ if (dialogThreadExited)
+ return false;
+ return pthread_kill(dialogThread.native_handle(), 0) != ESRCH;
+}
+
+void CANRManager::SANRData::killDialog() const {
+ if (!dialogProc)
+ return;
+
+ kill(dialogProc->pid(), SIGKILL);
+}
+
+CANRManager::SANRData::~SANRData() {
+ if (dialogThread.joinable()) {
+ killDialog();
+ // dangerous: might lock if the above failed!!
+ dialogThread.join();
+ }
+}
+
+bool CANRManager::isNotResponding(SP<CXDGWMBase> wmBase) {
+ if (!m_data.contains(wmBase))
+ return false;
+ return m_data[wmBase]->missedResponses > 1;
+}