From f4b148df1e2d8edc96bd878a4cfde32ca6515ac8 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Fri, 21 Feb 2025 21:26:53 +0100 Subject: [PATCH] anr: add xwayland support (#9456) Adds XWayland support to ANR dialogs --- src/desktop/Window.cpp | 5 +- src/managers/ANRManager.cpp | 145 +++++++++++++++++++++++++++--------- src/managers/ANRManager.hpp | 22 +++++- src/xwayland/XSurface.cpp | 27 +++++++ src/xwayland/XSurface.hpp | 12 +-- src/xwayland/XWM.cpp | 30 +++++--- src/xwayland/XWayland.hpp | 1 + 7 files changed, 184 insertions(+), 58 deletions(-) diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp index 7c0693bf4..6404c3857 100644 --- a/src/desktop/Window.cpp +++ b/src/desktop/Window.cpp @@ -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()); } diff --git a/src/managers/ANRManager.cpp b/src/managers/ANRManager.cpp index 3793f27f2..ebf02001f 100644 --- a/src/managers/ANRManager.cpp +++ b/src/managers/ANRManager.cpp @@ -7,6 +7,7 @@ #include "../protocols/XDGShell.hpp" #include "./eventLoop/EventLoopManager.hpp" #include "../config/ConfigValue.hpp" +#include "../xwayland/XSurface.hpp" using namespace Hyprutils::OS; @@ -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(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(); + m_data.emplace_back(makeShared(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("misc:enable_anr_dialog"); @@ -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++; @@ -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; @@ -95,22 +88,81 @@ void CANRManager::onTick() { data->missedResponses++; - wmBase->ping(); + data->ping(); } m_timer->updateTimeout(TIMER_TIMEOUT); } void CANRManager::onResponse(SP 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 pXwaylandSurface) { + const auto DATA = dataFor(pXwaylandSurface); + + if (!DATA) + return; + + onResponse(DATA); +} + +void CANRManager::onResponse(SP 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 data) { + return data->missedResponses > 1; +} + +SP 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::dataFor(SP 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::dataFor(SP 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{}) { + ; +} + +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(); @@ -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 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"}); + 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.empty() ? "unknown" : appName, appClass.empty() ? "unknown" : appClass), + "--buttons", "Terminate;Wait"}); dialogProc = proc; proc->runSync(); @@ -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::isNotResponding(SP wmBase) { - if (!m_data.contains(wmBase)) - return false; - return m_data[wmBase]->missedResponses > 1; +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; +} + +void CANRManager::SANRData::ping() { + if (xdgBase) { + xdgBase->ping(); + return; + } + + if (xwaylandSurface) + xwaylandSurface->ping(); } diff --git a/src/managers/ANRManager.hpp b/src/managers/ANRManager.hpp index 7d67e8726..263197765 100644 --- a/src/managers/ANRManager.hpp +++ b/src/managers/ANRManager.hpp @@ -5,20 +5,22 @@ #include #include #include -#include #include "./eventLoop/EventLoopTimer.hpp" #include "../helpers/signal/Signal.hpp" #include #include +#include class CXDGWMBase; +class CXWaylandSurface; class CANRManager { public: CANRManager(); void onResponse(SP wmBase); - bool isNotResponding(SP wmBase); + void onResponse(SP xwaylandSurface); + bool isNotResponding(PHLWINDOW pWindow); private: bool m_active = false; @@ -27,8 +29,12 @@ class CANRManager { void onTick(); struct SANRData { + SANRData(PHLWINDOW pWindow); ~SANRData(); + WP xwaylandSurface; + WP xdgBase; + int missedResponses = 0; std::thread dialogThread; SP dialogProc; @@ -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, SP> m_data; + void onResponse(SP data); + bool isNotResponding(SP data); + SP dataFor(PHLWINDOW pWindow); + SP dataFor(SP wmBase); + SP dataFor(SP pXwaylandSurface); + + std::vector> m_data; }; inline UP g_pANRManager; \ No newline at end of file diff --git a/src/xwayland/XSurface.cpp b/src/xwayland/XSurface.cpp index 91f20be52..b297c29c9 100644 --- a/src/xwayland/XSurface.cpp +++ b/src/xwayland/XSurface.cpp @@ -2,6 +2,7 @@ #include "XWayland.hpp" #include "../protocols/XWaylandShell.hpp" #include "../protocols/core/Compositor.hpp" +#include "../managers/ANRManager.hpp" #ifndef NO_XWAYLAND @@ -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) { @@ -297,4 +320,8 @@ void CXWaylandSurface::setWithdrawn(bool withdrawn) { ; } +void CXWaylandSurface::ping() { + ; +} + #endif diff --git a/src/xwayland/XSurface.hpp b/src/xwayland/XSurface.hpp index 55e96078c..53777c33f 100644 --- a/src/xwayland/XSurface.hpp +++ b/src/xwayland/XSurface.hpp @@ -69,10 +69,11 @@ class CXWaylandSurface { std::optional 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; @@ -88,7 +89,7 @@ class CXWaylandSurface { UP hints; UP sizeHints; - std::vector atoms; + std::vector atoms, protocols; std::string role = ""; bool transient = false; @@ -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); diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp index 0899261a6..4ef0389ee 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -15,6 +15,7 @@ #include "../protocols/core/Seat.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/SeatManager.hpp" +#include "../managers/ANRManager.hpp" #include "../protocols/XWaylandShell.hpp" #include "../protocols/core/Compositor.hpp" using namespace Hyprutils::OS; @@ -283,6 +284,16 @@ void CXWM::readProp(SP XSURF, uint32_t atom, xcb_get_property_ XSURF->sizeHints->max_height = -1; } } + } else if (atom == HYPRATOMS["WM_PROTOCOLS"]) { + if (reply->type == XCB_ATOM_ATOM) { + auto atoms = (xcb_atom_t*)xcb_get_property_value(reply); + std::vector vec; + vec.reserve(reply->value_len); + for (size_t i = 0; i < reply->value_len; ++i) { + vec.emplace_back(atoms[i]); + } + XSURF->protocols = vec; + } } else { Debug::log(TRACE, "[xwm] Unhandled prop {} -> {}", atom, propName); return; @@ -315,16 +326,14 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { if (!XSURF) return; - std::string propName = "?"; - for (auto const& ha : HYPRATOMS) { - if (ha.second != e->type) - continue; + std::string propName = getAtomName(e->type); - propName = ha.first; - break; - } - - if (e->type == HYPRATOMS["WL_SURFACE_ID"]) { + if (e->type == HYPRATOMS["WM_PROTOCOLS"]) { + if (e->data.data32[1] == XSURF->lastPingSeq && e->data.data32[0] == HYPRATOMS["_NET_WM_PING"]) { + g_pANRManager->onResponse(XSURF); + return; + } + } else if (e->type == HYPRATOMS["WL_SURFACE_ID"]) { if (XSURF->surface) { Debug::log(WARN, "[xwm] Re-assignment of WL_SURFACE_ID"); dissociate(XSURF); @@ -1056,9 +1065,10 @@ void CXWM::onNewResource(SP resource) { } void CXWM::readWindowData(SP surf) { - const std::array interestingProps = { + const std::array interestingProps = { XCB_ATOM_WM_CLASS, XCB_ATOM_WM_NAME, XCB_ATOM_WM_TRANSIENT_FOR, HYPRATOMS["WM_HINTS"], HYPRATOMS["_NET_WM_STATE"], HYPRATOMS["_NET_WM_NAME"], HYPRATOMS["_NET_WM_WINDOW_TYPE"], HYPRATOMS["WM_NORMAL_HINTS"], + HYPRATOMS["WM_PROTOCOLS"], }; for (size_t i = 0; i < interestingProps.size(); i++) { diff --git a/src/xwayland/XWayland.hpp b/src/xwayland/XWayland.hpp index af8d957c0..9202f19ee 100644 --- a/src/xwayland/XWayland.hpp +++ b/src/xwayland/XWayland.hpp @@ -83,6 +83,7 @@ inline std::unordered_map HYPRATOMS = { HYPRATOM("_NET_WORKAREA"), HYPRATOM("_NET_WM_ICON"), HYPRATOM("_NET_WM_CM_S0"), + HYPRATOM("_NET_WM_PING"), HYPRATOM("WM_PROTOCOLS"), HYPRATOM("WM_HINTS"), HYPRATOM("WM_DELETE_WINDOW"),