Skip to content

Commit

Permalink
anr: add xwayland support (#9456)
Browse files Browse the repository at this point in the history
Adds XWayland support to ANR dialogs
  • Loading branch information
vaxerski authored Feb 21, 2025
1 parent 0e24f9c commit f4b148d
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 57 deletions.
5 changes: 1 addition & 4 deletions src/desktop/Window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1801,8 +1801,5 @@ void CWindow::deactivateGroupMembers() {
}

bool CWindow::isNotResponding() {
if (!m_pXDGSurface)
return false;

return g_pANRManager->isNotResponding(m_pXDGSurface->owner.lock());
return g_pANRManager->isNotResponding(m_pSelf.lock());
}
143 changes: 108 additions & 35 deletions src/managers/ANRManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "../protocols/XDGShell.hpp"
#include "./eventLoop/EventLoopManager.hpp"
#include "../config/ConfigValue.hpp"
#include "../xwayland/XSurface.hpp"

using namespace Hyprutils::OS;

Expand All @@ -26,25 +27,19 @@ CANRManager::CANRManager() {
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)
for (const auto& d : m_data) {
if (d->fitsWindow(window))
return;
}

m_data[window->m_pXDGSurface->owner] = makeShared<SANRData>();
m_data.emplace_back(makeShared<SANRData>(window));
});

m_timer->updateTimeout(TIMER_TIMEOUT);
}

void CANRManager::onTick() {
std::erase_if(m_data, [](const auto& e) { return e.first.expired(); });
std::erase_if(m_data, [](const auto& e) { return e->isDefunct(); });

static auto PENABLEANR = CConfigValue<Hyprlang::INT>("misc:enable_anr_dialog");

Expand All @@ -53,14 +48,14 @@ void CANRManager::onTick() {
return;
}

for (auto& [wmBase, data] : m_data) {
for (auto& 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)
if (!w->m_bIsMapped)
continue;

if (w->m_pXDGSurface->owner != wmBase)
if (!data->fitsWindow(w))
continue;

count++;
Expand All @@ -73,15 +68,13 @@ void CANRManager::onTick() {

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);
data->runDialog("Application Not Responding", firstWindow->m_szTitle, firstWindow->m_szClass, data->getPid());

for (const auto& w : g_pCompositor->m_vWindows) {
if (!w->m_bIsMapped || w->m_bIsX11 || !w->m_pXDGSurface)
if (!w->m_bIsMapped)
continue;

if (w->m_pXDGSurface->owner != wmBase)
if (!data->fitsWindow(w))
continue;

*w->m_notRespondingTint = 0.2F;
Expand All @@ -95,22 +88,81 @@ void CANRManager::onTick() {

data->missedResponses++;

wmBase->ping();
data->ping();
}

m_timer->updateTimeout(TIMER_TIMEOUT);
}

void CANRManager::onResponse(SP<CXDGWMBase> wmBase) {
if (!m_data.contains(wmBase))
const auto DATA = dataFor(wmBase);

if (!DATA)
return;

auto& data = m_data.at(wmBase);
onResponse(DATA);
}

void CANRManager::onResponse(SP<CXWaylandSurface> pXwaylandSurface) {
const auto DATA = dataFor(pXwaylandSurface);

if (!DATA)
return;

onResponse(DATA);
}

void CANRManager::onResponse(SP<CANRManager::SANRData> data) {
data->missedResponses = 0;
if (data->isThreadRunning())
data->killDialog();
}

bool CANRManager::isNotResponding(PHLWINDOW pWindow) {
const auto DATA = dataFor(pWindow);

if (!DATA)
return false;

return isNotResponding(DATA);
}

bool CANRManager::isNotResponding(SP<CANRManager::SANRData> data) {
return data->missedResponses > 1;
}

SP<CANRManager::SANRData> CANRManager::dataFor(PHLWINDOW pWindow) {
auto it = m_data.end();
if (pWindow->m_pXWaylandSurface)
it = std::ranges::find_if(m_data, [&pWindow](const auto& data) { return data->xwaylandSurface && data->xwaylandSurface == pWindow->m_pXWaylandSurface; });
else if (pWindow->m_pXDGSurface)
it = std::ranges::find_if(m_data, [&pWindow](const auto& data) { return data->xdgBase && data->xdgBase == pWindow->m_pXDGSurface->owner; });
return it == m_data.end() ? nullptr : *it;
}

SP<CANRManager::SANRData> CANRManager::dataFor(SP<CXDGWMBase> wmBase) {
auto it = std::ranges::find_if(m_data, [&wmBase](const auto& data) { return data->xdgBase && data->xdgBase == wmBase; });
return it == m_data.end() ? nullptr : *it;
}

SP<CANRManager::SANRData> CANRManager::dataFor(SP<CXWaylandSurface> pXwaylandSurface) {
auto it = std::ranges::find_if(m_data, [&pXwaylandSurface](const auto& data) { return data->xwaylandSurface && data->xwaylandSurface == pXwaylandSurface; });
return it == m_data.end() ? nullptr : *it;
}

CANRManager::SANRData::SANRData(PHLWINDOW pWindow) :
xwaylandSurface(pWindow->m_pXWaylandSurface), xdgBase(pWindow->m_pXDGSurface ? pWindow->m_pXDGSurface->owner : WP<CXDGWMBase>{}) {
;
}

CANRManager::SANRData::~SANRData() {
if (dialogThread.joinable()) {
killDialog();
// dangerous: might lock if the above failed!!
dialogThread.join();
}
}

void CANRManager::SANRData::runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID) {
if (!dialogThreadExited)
killDialog();
Expand All @@ -122,11 +174,11 @@ void CANRManager::SANRData::runDialog(const std::string& title, const std::strin
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"});
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.empty() ? "unknown" : appName, appClass.empty() ? "unknown" : appClass),
"--buttons", "Terminate;Wait"});

dialogProc = proc;
proc->runSync();
Expand Down Expand Up @@ -163,16 +215,37 @@ void CANRManager::SANRData::killDialog() const {
kill(dialogProc->pid(), SIGKILL);
}

CANRManager::SANRData::~SANRData() {
if (dialogThread.joinable()) {
killDialog();
// dangerous: might lock if the above failed!!
dialogThread.join();
bool CANRManager::SANRData::fitsWindow(PHLWINDOW pWindow) const {
if (pWindow->m_pXWaylandSurface)
return pWindow->m_pXWaylandSurface == xwaylandSurface;
else if (pWindow->m_pXDGSurface)
return pWindow->m_pXDGSurface->owner == xdgBase && xdgBase;
return false;
}

bool CANRManager::SANRData::isDefunct() const {
return xdgBase.expired() && xwaylandSurface.expired();
}

pid_t CANRManager::SANRData::getPid() const {
if (xdgBase) {
pid_t pid = 0;
wl_client_get_credentials(xdgBase->client(), &pid, nullptr, nullptr);
return pid;
}

if (xwaylandSurface)
return xwaylandSurface->pid;

return 0;
}

bool CANRManager::isNotResponding(SP<CXDGWMBase> wmBase) {
if (!m_data.contains(wmBase))
return false;
return m_data[wmBase]->missedResponses > 1;
void CANRManager::SANRData::ping() {
if (xdgBase) {
xdgBase->ping();
return;
}

if (xwaylandSurface)
xwaylandSurface->ping();
}
22 changes: 19 additions & 3 deletions src/managers/ANRManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@
#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>
#include <vector>

class CXDGWMBase;
class CXWaylandSurface;

class CANRManager {
public:
CANRManager();

void onResponse(SP<CXDGWMBase> wmBase);
bool isNotResponding(SP<CXDGWMBase> wmBase);
void onResponse(SP<CXWaylandSurface> xwaylandSurface);
bool isNotResponding(PHLWINDOW pWindow);

private:
bool m_active = false;
Expand All @@ -27,8 +29,12 @@ class CANRManager {
void onTick();

struct SANRData {
SANRData(PHLWINDOW pWindow);
~SANRData();

WP<CXWaylandSurface> xwaylandSurface;
WP<CXDGWMBase> xdgBase;

int missedResponses = 0;
std::thread dialogThread;
SP<Hyprutils::OS::CProcess> dialogProc;
Expand All @@ -38,9 +44,19 @@ class CANRManager {
void runDialog(const std::string& title, const std::string& appName, const std::string appClass, pid_t dialogWmPID);
bool isThreadRunning();
void killDialog() const;
bool isDefunct() const;
bool fitsWindow(PHLWINDOW pWindow) const;
pid_t getPid() const;
void ping();
};

std::map<WP<CXDGWMBase>, SP<SANRData>> m_data;
void onResponse(SP<SANRData> data);
bool isNotResponding(SP<SANRData> data);
SP<SANRData> dataFor(PHLWINDOW pWindow);
SP<SANRData> dataFor(SP<CXDGWMBase> wmBase);
SP<SANRData> dataFor(SP<CXWaylandSurface> pXwaylandSurface);

std::vector<SP<SANRData>> m_data;
};

inline UP<CANRManager> g_pANRManager;
27 changes: 27 additions & 0 deletions src/xwayland/XSurface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "XWayland.hpp"
#include "../protocols/XWaylandShell.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../managers/ANRManager.hpp"

#ifndef NO_XWAYLAND

Expand Down Expand Up @@ -243,6 +244,28 @@ void CXWaylandSurface::setWithdrawn(bool withdrawn_) {
xcb_change_property(g_pXWayland->pWM->connection, XCB_PROP_MODE_REPLACE, xID, HYPRATOMS["WM_STATE"], HYPRATOMS["WM_STATE"], 32, props.size(), props.data());
}

void CXWaylandSurface::ping() {
bool supportsPing = std::ranges::find(protocols, HYPRATOMS["_NET_WM_PING"]) != protocols.end();

if (!supportsPing) {
Debug::log(TRACE, "CXWaylandSurface: XID {} does not support ping, just sending an instant reply", xID);
g_pANRManager->onResponse(self.lock());
return;
}

timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);

xcb_client_message_data_t msg = {};
msg.data32[0] = HYPRATOMS["_NET_WM_PING"];
msg.data32[1] = now.tv_sec * 1000 + now.tv_nsec / 1000000;
msg.data32[2] = xID;

lastPingSeq = msg.data32[1];

g_pXWayland->pWM->sendWMMessage(self.lock(), &msg, XCB_EVENT_MASK_PROPERTY_CHANGE);
}

#else

CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : xID(xID_), geometry(geometry_), overrideRedirect(OR) {
Expand Down Expand Up @@ -297,4 +320,8 @@ void CXWaylandSurface::setWithdrawn(bool withdrawn) {
;
}

void CXWaylandSurface::ping() {
;
}

#endif
12 changes: 7 additions & 5 deletions src/xwayland/XSurface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ class CXWaylandSurface {
std::optional<bool> requestsMinimize;
} state;

uint32_t xID = 0;
uint64_t wlID = 0;
uint64_t wlSerial = 0;
pid_t pid = 0;
uint32_t xID = 0;
uint64_t wlID = 0;
uint64_t wlSerial = 0;
uint32_t lastPingSeq = 0;
pid_t pid = 0;
CBox geometry;
bool overrideRedirect = false;
bool withdrawn = false;
Expand All @@ -88,7 +89,7 @@ class CXWaylandSurface {

UP<xcb_icccm_wm_hints_t> hints;
UP<xcb_size_hints_t> sizeHints;
std::vector<uint32_t> atoms;
std::vector<uint32_t> atoms, protocols;
std::string role = "";
bool transient = false;

Expand All @@ -99,6 +100,7 @@ class CXWaylandSurface {
void setMinimized(bool mz);
void restackToTop();
void close();
void ping();

private:
CXWaylandSurface(uint32_t xID, CBox geometry, bool OR);
Expand Down
Loading

0 comments on commit f4b148d

Please sign in to comment.