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.
This commit is contained in:
Vaxry 2025-02-18 15:10:40 +00:00
parent 3352317ca8
commit fb8eaba83f
11 changed files with 272 additions and 6 deletions

View File

@ -104,7 +104,7 @@ find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION})
pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.4.5) pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.4.5)
pkg_check_modules(hyprlang_dep REQUIRED IMPORTED_TARGET hyprlang>=0.3.2) 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(hyprcursor_dep REQUIRED IMPORTED_TARGET hyprcursor>=0.1.7)
pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.5.0) pkg_check_modules(hyprutils_dep REQUIRED IMPORTED_TARGET hyprutils>=0.5.1)
pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.1) pkg_check_modules(hyprgraphics_dep REQUIRED IMPORTED_TARGET hyprgraphics>=0.1.1)
string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION}) string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION})

View File

@ -13,6 +13,7 @@
#include "managers/SeatManager.hpp" #include "managers/SeatManager.hpp"
#include "managers/VersionKeeperManager.hpp" #include "managers/VersionKeeperManager.hpp"
#include "managers/DonationNagManager.hpp" #include "managers/DonationNagManager.hpp"
#include "managers/ANRManager.hpp"
#include "managers/eventLoop/EventLoopManager.hpp" #include "managers/eventLoop/EventLoopManager.hpp"
#include <algorithm> #include <algorithm>
#include <aquamarine/output/Output.hpp> #include <aquamarine/output/Output.hpp>
@ -592,6 +593,7 @@ void CCompositor::cleanup() {
g_pEventLoopManager.reset(); g_pEventLoopManager.reset();
g_pVersionKeeperMgr.reset(); g_pVersionKeeperMgr.reset();
g_pDonationNagManager.reset(); g_pDonationNagManager.reset();
g_pANRManager.reset();
g_pConfigWatcher.reset(); g_pConfigWatcher.reset();
if (m_pAqBackend) if (m_pAqBackend)
@ -694,6 +696,9 @@ void CCompositor::initManagers(eManagersInitStage stage) {
Debug::log(LOG, "Creating the DonationNag!"); Debug::log(LOG, "Creating the DonationNag!");
g_pDonationNagManager = makeUnique<CDonationNagManager>(); g_pDonationNagManager = makeUnique<CDonationNagManager>();
Debug::log(LOG, "Creating the ANRManager!");
g_pANRManager = makeUnique<CANRManager>();
Debug::log(LOG, "Starting XWayland"); Debug::log(LOG, "Starting XWayland");
g_pXWayland = makeUnique<CXWayland>(g_pCompositor->m_bWantsXwayland); g_pXWayland = makeUnique<CXWayland>(g_pCompositor->m_bWantsXwayland);
} break; } break;

View File

@ -430,6 +430,7 @@ CConfigManager::CConfigManager() {
m_pConfig->addConfigValue("misc:disable_xdg_env_checks", Hyprlang::INT{0}); 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:disable_hyprland_qtutils_check", Hyprlang::INT{0});
m_pConfig->addConfigValue("misc:lockdead_screen_delay", Hyprlang::INT{1000}); 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:insert_after_current", Hyprlang::INT{1});
m_pConfig->addConfigValue("group:focus_removed_window", Hyprlang::INT{1}); m_pConfig->addConfigValue("group:focus_removed_window", Hyprlang::INT{1});

View File

@ -13,6 +13,7 @@
#include "../config/ConfigValue.hpp" #include "../config/ConfigValue.hpp"
#include "../managers/TokenManager.hpp" #include "../managers/TokenManager.hpp"
#include "../managers/AnimationManager.hpp" #include "../managers/AnimationManager.hpp"
#include "../managers/ANRManager.hpp"
#include "../protocols/XDGShell.hpp" #include "../protocols/XDGShell.hpp"
#include "../protocols/core/Compositor.hpp" #include "../protocols/core/Compositor.hpp"
#include "../protocols/ContentType.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_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_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_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<CHyprDropShadowDecoration>(pWindow));
pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(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_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_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_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<CHyprDropShadowDecoration>(pWindow));
pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow)); pWindow->addWindowDeco(makeUnique<CHyprBorderDecoration>(pWindow));
@ -1796,3 +1799,10 @@ void CWindow::deactivateGroupMembers() {
break; break;
} }
} }
bool CWindow::isNotResponding() {
if (!m_pXDGSurface)
return false;
return g_pANRManager->isNotResponding(m_pXDGSurface->owner.lock());
}

View File

@ -388,6 +388,9 @@ class CWindow {
// window tags // window tags
CTagKeeper m_tags; CTagKeeper m_tags;
// ANR
PHLANIMVAR<float> m_notRespondingTint;
// For the list lookup // For the list lookup
bool operator==(const CWindow& rhs) const { 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 && 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(); NContentType::eContentType getContentType();
void setContentType(NContentType::eContentType contentType); void setContentType(NContentType::eContentType contentType);
void deactivateGroupMembers(); void deactivateGroupMembers();
bool isNotResponding();
CBox getWindowMainSurfaceBox() const { CBox getWindowMainSurfaceBox() const {
return {m_vRealPosition->value().x, m_vRealPosition->value().y, m_vRealSize->value().x, m_vRealSize->value().y}; return {m_vRealPosition->value().x, m_vRealPosition->value().y, m_vRealSize->value().x, m_vRealSize->value().y};

173
src/managers/ANRManager.cpp Normal file
View File

@ -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;
}

View File

@ -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;

View File

@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include "../Compositor.hpp" #include "../Compositor.hpp"
#include "../managers/SeatManager.hpp" #include "../managers/SeatManager.hpp"
#include "../managers/ANRManager.hpp"
#include "../helpers/Monitor.hpp" #include "../helpers/Monitor.hpp"
#include "core/Seat.hpp" #include "core/Seat.hpp"
#include "core/Compositor.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()); 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() { bool CXDGWMBase::good() {
@ -750,6 +756,10 @@ wl_client* CXDGWMBase::client() {
return pClient; 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) { CXDGShellProtocol::CXDGShellProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
grab = makeShared<CSeatGrab>(); grab = makeShared<CSeatGrab>();
grab->keyboard = true; grab->keyboard = true;

View File

@ -241,12 +241,17 @@ class CXDGWMBase {
bool good(); bool good();
wl_client* client(); wl_client* client();
void ping();
std::vector<WP<CXDGPositionerResource>> positioners; std::vector<WP<CXDGPositionerResource>> positioners;
std::vector<WP<CXDGSurfaceResource>> surfaces; std::vector<WP<CXDGSurfaceResource>> surfaces;
WP<CXDGWMBase> self; WP<CXDGWMBase> self;
struct {
CSignal pong;
} events;
private: private:
SP<CXdgWmBase> resource; SP<CXdgWmBase> resource;
wl_client* pClient = nullptr; wl_client* pClient = nullptr;

View File

@ -1396,12 +1396,18 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CB
glUniform1f(shader->roundingPower, roundingPower); glUniform1f(shader->roundingPower, roundingPower);
if (allowDim && m_RenderData.currentWindow) { if (allowDim && m_RenderData.currentWindow) {
glUniform1i(shader->applyTint, 1); if (m_RenderData.currentWindow->m_notRespondingTint->value() > 0) {
const auto DIM = m_RenderData.currentWindow->m_fDimPercent->value(); const auto DIM = m_RenderData.currentWindow->m_notRespondingTint->value();
glUniform3f(shader->tint, 1.f - DIM, 1.f - DIM, 1.f - DIM); glUniform1i(shader->applyTint, 1);
} else { 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); glUniform1i(shader->applyTint, 0);
}
} }
const float verts[] = { const float verts[] = {

View File

@ -473,6 +473,12 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, timespe
if (ignorePosition) { if (ignorePosition) {
renderdata.pos.x = pMonitor->vecPosition.x; renderdata.pos.x = pMonitor->vecPosition.x;
renderdata.pos.y = pMonitor->vecPosition.y; 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) if (standalone)