-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
11 changed files
with
272 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
fb8eaba
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we handle cases where we cannot grab the application name and class?
fb8eaba
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e.g. we could put "unnamed" as a placeholder, if fetching fails
fb8eaba
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could
fb8eaba
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alright ill add it to #9436 then