summaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/Compositor.cpp5
-rw-r--r--src/config/ConfigManager.cpp1
-rw-r--r--src/desktop/Window.cpp10
-rw-r--r--src/desktop/Window.hpp4
-rw-r--r--src/managers/ANRManager.cpp173
-rw-r--r--src/managers/ANRManager.hpp46
-rw-r--r--src/protocols/XDGShell.cpp10
-rw-r--r--src/protocols/XDGShell.hpp5
-rw-r--r--src/render/OpenGL.cpp16
-rw-r--r--src/render/Renderer.cpp6
10 files changed, 271 insertions, 5 deletions
diff --git a/src/Compositor.cpp b/src/Compositor.cpp
index 5389844f..a35e4216 100644
--- a/src/Compositor.cpp
+++ b/src/Compositor.cpp
@@ -13,6 +13,7 @@
#include "managers/SeatManager.hpp"
#include "managers/VersionKeeperManager.hpp"
#include "managers/DonationNagManager.hpp"
+#include "managers/ANRManager.hpp"
#include "managers/eventLoop/EventLoopManager.hpp"
#include <algorithm>
#include <aquamarine/output/Output.hpp>
@@ -592,6 +593,7 @@ void CCompositor::cleanup() {
g_pEventLoopManager.reset();
g_pVersionKeeperMgr.reset();
g_pDonationNagManager.reset();
+ g_pANRManager.reset();
g_pConfigWatcher.reset();
if (m_pAqBackend)
@@ -694,6 +696,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Debug::log(LOG, "Creating the DonationNag!");
g_pDonationNagManager = makeUnique<CDonationNagManager>();
+ Debug::log(LOG, "Creating the ANRManager!");
+ g_pANRManager = makeUnique<CANRManager>();
+
Debug::log(LOG, "Starting XWayland");
g_pXWayland = makeUnique<CXWayland>(g_pCompositor->m_bWantsXwayland);
} break;
diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp
index dc56d73f..9b547ffb 100644
--- a/src/config/ConfigManager.cpp
+++ b/src/config/ConfigManager.cpp
@@ -430,6 +430,7 @@ CConfigManager::CConfigManager() {
m_pConfig->addConfigValue("misc:disable_xdg_env_checks", Hyprlang::INT{0});
m_pConfig->addConfigValue("misc:disable_hyprland_qtutils_check", Hyprlang::INT{0});
m_pConfig->addConfigValue("misc:lockdead_screen_delay", Hyprlang::INT{1000});
+ m_pConfig->addConfigValue("misc:enable_anr_dialog", Hyprlang::INT{1});
m_pConfig->addConfigValue("group:insert_after_current", Hyprlang::INT{1});
m_pConfig->addConfigValue("group:focus_removed_window", Hyprlang::INT{1});
diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp
index 7b7f954d..7c0693bf 100644
--- a/src/desktop/Window.cpp
+++ b/src/desktop/Window.cpp
@@ -13,6 +13,7 @@
#include "../config/ConfigValue.hpp"
#include "../managers/TokenManager.hpp"
#include "../managers/AnimationManager.hpp"
+#include "../managers/ANRManager.hpp"
#include "../protocols/XDGShell.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../protocols/ContentType.hpp"
@@ -48,6 +49,7 @@ PHLWINDOW CWindow::create(SP<CXWaylandSurface> surface) {
g_pAnimationManager->createAnimation(0.f, pWindow->m_fDimPercent, g_pConfigManager->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_fMovingToWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_fMovingFromWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
+ g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, g_pConfigManager->getAnimationPropertyConfig("fade"), pWindow, AVARDAMAGE_ENTIRE);
pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));
pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));
@@ -71,6 +73,7 @@ PHLWINDOW CWindow::create(SP<CXDGSurfaceResource> resource) {
g_pAnimationManager->createAnimation(0.f, pWindow->m_fDimPercent, g_pConfigManager->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_fMovingToWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE);
g_pAnimationManager->createAnimation(0.f, pWindow->m_fMovingFromWorkspaceAlpha, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), pWindow, AVARDAMAGE_ENTIRE);
+ g_pAnimationManager->createAnimation(0.f, pWindow->m_notRespondingTint, g_pConfigManager->getAnimationPropertyConfig("fade"), pWindow, AVARDAMAGE_ENTIRE);
pWindow->addWindowDeco(makeUnique<CHyprDropShadowDecoration>(pWindow));
pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));
@@ -1796,3 +1799,10 @@ void CWindow::deactivateGroupMembers() {
break;
}
}
+
+bool CWindow::isNotResponding() {
+ if (!m_pXDGSurface)
+ return false;
+
+ return g_pANRManager->isNotResponding(m_pXDGSurface->owner.lock());
+}
diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp
index bc32bab4..50b61050 100644
--- a/src/desktop/Window.hpp
+++ b/src/desktop/Window.hpp
@@ -388,6 +388,9 @@ class CWindow {
// window tags
CTagKeeper m_tags;
+ // ANR
+ PHLANIMVAR<float> m_notRespondingTint;
+
// For the list lookup
bool operator==(const CWindow& rhs) const {
return m_pXDGSurface == rhs.m_pXDGSurface && m_pXWaylandSurface == rhs.m_pXWaylandSurface && m_vPosition == rhs.m_vPosition && m_vSize == rhs.m_vSize &&
@@ -480,6 +483,7 @@ class CWindow {
NContentType::eContentType getContentType();
void setContentType(NContentType::eContentType contentType);
void deactivateGroupMembers();
+ bool isNotResponding();
CBox getWindowMainSurfaceBox() const {
return {m_vRealPosition->value().x, m_vRealPosition->value().y, m_vRealSize->value().x, m_vRealSize->value().y};
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;
+}
diff --git a/src/managers/ANRManager.hpp b/src/managers/ANRManager.hpp
new file mode 100644
index 00000000..7d67e872
--- /dev/null
+++ b/src/managers/ANRManager.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "../helpers/memory/Memory.hpp"
+#include "../desktop/DesktopTypes.hpp"
+#include <chrono>
+#include <hyprutils/os/Process.hpp>
+#include <hyprutils/os/FileDescriptor.hpp>
+#include <map>
+#include "./eventLoop/EventLoopTimer.hpp"
+#include "../helpers/signal/Signal.hpp"
+#include <atomic>
+#include <thread>
+
+class CXDGWMBase;
+
+class CANRManager {
+ public:
+ CANRManager();
+
+ void onResponse(SP<CXDGWMBase> wmBase);
+ bool isNotResponding(SP<CXDGWMBase> wmBase);
+
+ private:
+ bool m_active = false;
+ SP<CEventLoopTimer> m_timer;
+
+ void onTick();
+
+ struct SANRData {
+ ~SANRData();
+
+ int missedResponses = 0;
+ std::thread dialogThread;
+ SP<Hyprutils::OS::CProcess> dialogProc;
+ std::atomic<bool> dialogThreadExited = false;
+ std::atomic<bool> dialogThreadSaidWait = false;
+
+ void runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID);
+ bool isThreadRunning();
+ void killDialog() const;
+ };
+
+ std::map<WP<CXDGWMBase>, SP<SANRData>> m_data;
+};
+
+inline UP<CANRManager> g_pANRManager; \ No newline at end of file
diff --git a/src/protocols/XDGShell.cpp b/src/protocols/XDGShell.cpp
index f5d1a8fa..9424a9d6 100644
--- a/src/protocols/XDGShell.cpp
+++ b/src/protocols/XDGShell.cpp
@@ -3,6 +3,7 @@
#include <algorithm>
#include "../Compositor.hpp"
#include "../managers/SeatManager.hpp"
+#include "../managers/ANRManager.hpp"
#include "../helpers/Monitor.hpp"
#include "core/Seat.hpp"
#include "core/Compositor.hpp"
@@ -740,6 +741,11 @@ CXDGWMBase::CXDGWMBase(SP<CXdgWmBase> resource_) : resource(resource_) {
LOGM(LOG, "New xdg_surface at {:x}", (uintptr_t)RESOURCE.get());
});
+
+ resource->setPong([this](CXdgWmBase* r, uint32_t serial) {
+ g_pANRManager->onResponse(self.lock());
+ events.pong.emit();
+ });
}
bool CXDGWMBase::good() {
@@ -750,6 +756,10 @@ wl_client* CXDGWMBase::client() {
return pClient;
}
+void CXDGWMBase::ping() {
+ resource->sendPing(1337);
+}
+
CXDGShellProtocol::CXDGShellProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
grab = makeShared<CSeatGrab>();
grab->keyboard = true;
diff --git a/src/protocols/XDGShell.hpp b/src/protocols/XDGShell.hpp
index 4c2b8100..b46e0236 100644
--- a/src/protocols/XDGShell.hpp
+++ b/src/protocols/XDGShell.hpp
@@ -241,12 +241,17 @@ class CXDGWMBase {
bool good();
wl_client* client();
+ void ping();
std::vector<WP<CXDGPositionerResource>> positioners;
std::vector<WP<CXDGSurfaceResource>> surfaces;
WP<CXDGWMBase> self;
+ struct {
+ CSignal pong;
+ } events;
+
private:
SP<CXdgWmBase> resource;
wl_client* pClient = nullptr;
diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp
index 3b641c36..1a296b65 100644
--- a/src/render/OpenGL.cpp
+++ b/src/render/OpenGL.cpp
@@ -1396,12 +1396,18 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CB
glUniform1f(shader->roundingPower, roundingPower);
if (allowDim && m_RenderData.currentWindow) {
- glUniform1i(shader->applyTint, 1);
- const auto DIM = m_RenderData.currentWindow->m_fDimPercent->value();
- glUniform3f(shader->tint, 1.f - DIM, 1.f - DIM, 1.f - DIM);
- } else {
+ if (m_RenderData.currentWindow->m_notRespondingTint->value() > 0) {
+ const auto DIM = m_RenderData.currentWindow->m_notRespondingTint->value();
+ glUniform1i(shader->applyTint, 1);
+ glUniform3f(shader->tint, 1.f - DIM, 1.f - DIM, 1.f - DIM);
+ } else if (m_RenderData.currentWindow->m_fDimPercent->value() > 0) {
+ glUniform1i(shader->applyTint, 1);
+ const auto DIM = m_RenderData.currentWindow->m_fDimPercent->value();
+ glUniform3f(shader->tint, 1.f - DIM, 1.f - DIM, 1.f - DIM);
+ } else
+ glUniform1i(shader->applyTint, 0);
+ } else
glUniform1i(shader->applyTint, 0);
- }
}
const float verts[] = {
diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp
index b61f1c3e..f01a0d12 100644
--- a/src/render/Renderer.cpp
+++ b/src/render/Renderer.cpp
@@ -473,6 +473,12 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, timespe
if (ignorePosition) {
renderdata.pos.x = pMonitor->vecPosition.x;
renderdata.pos.y = pMonitor->vecPosition.y;
+ } else {
+ const bool ANR = pWindow->isNotResponding();
+ if (ANR && pWindow->m_notRespondingTint->goal() != 0.2F)
+ *pWindow->m_notRespondingTint = 0.2F;
+ else if (!ANR && pWindow->m_notRespondingTint->goal() != 0.F)
+ *pWindow->m_notRespondingTint = 0.F;
}
if (standalone)