mirror of
https://github.com/hyprwm/Hyprland.git
synced 2025-05-19 08:30:22 -07:00
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:
parent
3352317ca8
commit
fb8eaba83f
@ -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})
|
||||
|
@ -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;
|
||||
|
@ -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});
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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};
|
||||
|
173
src/managers/ANRManager.cpp
Normal file
173
src/managers/ANRManager.cpp
Normal 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;
|
||||
}
|
46
src/managers/ANRManager.hpp
Normal file
46
src/managers/ANRManager.hpp
Normal 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;
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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[] = {
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user