diff --git a/CMakeLists.txt b/CMakeLists.txt index a98f697d..22bc7498 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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(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.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) string(REPLACE "." ";" AQ_VERSION_LIST ${aquamarine_dep_VERSION}) 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 #include @@ -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(); + Debug::log(LOG, "Creating the ANRManager!"); + g_pANRManager = makeUnique(); + Debug::log(LOG, "Starting XWayland"); g_pXWayland = makeUnique(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 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(pWindow)); pWindow->addWindowDeco(makeUnique(pWindow)); @@ -71,6 +73,7 @@ PHLWINDOW CWindow::create(SP 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(pWindow)); pWindow->addWindowDeco(makeUnique(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 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(TIMER_TIMEOUT, [this](SP 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(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(); + }); + + m_timer->updateTimeout(TIMER_TIMEOUT); +} + +void CANRManager::onTick() { + std::erase_if(m_data, [](const auto& e) { return e.first.expired(); }); + + static auto PENABLEANR = CConfigValue("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 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 proc = + makeShared("hyprland-dialog", + std::vector{"--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 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 +#include +#include +#include +#include "./eventLoop/EventLoopTimer.hpp" +#include "../helpers/signal/Signal.hpp" +#include +#include + +class CXDGWMBase; + +class CANRManager { + public: + CANRManager(); + + void onResponse(SP wmBase); + bool isNotResponding(SP wmBase); + + private: + bool m_active = false; + SP m_timer; + + void onTick(); + + struct SANRData { + ~SANRData(); + + int missedResponses = 0; + std::thread dialogThread; + SP dialogProc; + std::atomic dialogThreadExited = false; + std::atomic 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, SP> m_data; +}; + +inline UP 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 #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 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(); 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> positioners; std::vector> surfaces; WP self; + struct { + CSignal pong; + } events; + private: SP 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 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)