From 0cb62920a7ab0b199754c941046ae86e3a1c368d Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Wed, 18 Mar 2026 02:34:06 -0700 Subject: [PATCH] hyprland/focus_grab: handle destruction of tracked windows --- changelog/next.md | 1 + src/core/platformmenu.cpp | 5 +- src/core/popupanchor.cpp | 5 +- src/wayland/hyprland/focus_grab/qml.cpp | 99 +++++++++++---------- src/wayland/hyprland/focus_grab/qml.hpp | 4 +- src/wayland/hyprland/surface/qml.cpp | 9 +- src/wayland/idle_inhibit/inhibitor.cpp | 19 +--- src/wayland/shortcuts_inhibit/inhibitor.cpp | 10 +-- src/wayland/toplevel_management/qml.cpp | 9 +- src/window/proxywindow.cpp | 6 ++ src/window/proxywindow.hpp | 2 + 11 files changed, 67 insertions(+), 102 deletions(-) diff --git a/changelog/next.md b/changelog/next.md index cceb79e..a8981b9 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -60,6 +60,7 @@ set shell id. - Desktop action order is now preserved. - Fixed partial socket reads in greetd and hyprland on slow machines. - Worked around Qt bug causing crashes when plugging and unplugging monitors. +- Fixed HyprlandFocusGrab crashing if windows were destroyed after being passed to it. ## Packaging Changes diff --git a/src/core/platformmenu.cpp b/src/core/platformmenu.cpp index 427dde0..d8901e2 100644 --- a/src/core/platformmenu.cpp +++ b/src/core/platformmenu.cpp @@ -18,7 +18,6 @@ #include #include "../window/proxywindow.hpp" -#include "../window/windowinterface.hpp" #include "iconprovider.hpp" #include "model.hpp" #include "platformmenu_p.hpp" @@ -91,10 +90,8 @@ bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relati } else if (parentWindow == nullptr) { qCritical() << "Cannot display PlatformMenuEntry with null parent window."; return false; - } else if (auto* proxy = qobject_cast(parentWindow)) { + } else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) { window = proxy->backingWindow(); - } else if (auto* interface = qobject_cast(parentWindow)) { - window = interface->proxyWindow()->backingWindow(); } else { qCritical() << "PlatformMenuEntry.display() must be called with a window."; return false; diff --git a/src/core/popupanchor.cpp b/src/core/popupanchor.cpp index 151dd5d..ca817c9 100644 --- a/src/core/popupanchor.cpp +++ b/src/core/popupanchor.cpp @@ -11,7 +11,6 @@ #include #include "../window/proxywindow.hpp" -#include "../window/windowinterface.hpp" #include "types.hpp" bool PopupAnchorState::operator==(const PopupAnchorState& other) const { @@ -40,10 +39,8 @@ void PopupAnchor::setWindowInternal(QObject* window) { } if (window) { - if (auto* proxy = qobject_cast(window)) { + if (auto* proxy = ProxyWindowBase::forObject(window)) { this->bProxyWindow = proxy; - } else if (auto* interface = qobject_cast(window)) { - this->bProxyWindow = interface->proxyWindow(); } else { qWarning() << "Tried to set popup anchor window to" << window << "which is not a quickshell window."; diff --git a/src/wayland/hyprland/focus_grab/qml.cpp b/src/wayland/hyprland/focus_grab/qml.cpp index e26a75a..cf1ac24 100644 --- a/src/wayland/hyprland/focus_grab/qml.cpp +++ b/src/wayland/hyprland/focus_grab/qml.cpp @@ -9,7 +9,6 @@ #include #include "../../../window/proxywindow.hpp" -#include "../../../window/windowinterface.hpp" #include "grab.hpp" #include "manager.hpp" @@ -38,8 +37,51 @@ QObjectList HyprlandFocusGrab::windows() const { return this->windowObjects; } void HyprlandFocusGrab::setWindows(QObjectList windows) { if (windows == this->windowObjects) return; + if (this->grab) this->grab->startTransaction(); + + for (auto* obj: this->windowObjects) { + if (windows.contains(obj)) continue; + QObject::disconnect(obj, nullptr, this, nullptr); + + auto* proxy = ProxyWindowBase::forObject(obj); + if (!proxy) continue; + + QObject::disconnect(proxy, nullptr, this, nullptr); + + if (this->grab && proxy->backingWindow()) { + this->grab->removeWindow(proxy->backingWindow()); + } + } + + for (auto it = windows.begin(); it != windows.end();) { + auto* proxy = ProxyWindowBase::forObject(*it); + if (!proxy) { + it = windows.erase(it); + continue; + } + + if (this->windowObjects.contains(*it)) { + ++it; + continue; + } + + QObject::connect(*it, &QObject::destroyed, this, &HyprlandFocusGrab::onObjectDestroyed); + QObject::connect( + proxy, + &ProxyWindowBase::windowConnected, + this, + &HyprlandFocusGrab::onProxyConnected + ); + + if (this->grab && proxy->backingWindow()) { + this->grab->addWindow(proxy->backingWindow()); + } + + ++it; + } + + if (this->grab) this->grab->completeTransaction(); this->windowObjects = std::move(windows); - this->syncWindows(); emit this->windowsChanged(); } @@ -75,59 +117,18 @@ void HyprlandFocusGrab::tryActivate() { QObject::connect(this->grab, &FocusGrab::cleared, this, &HyprlandFocusGrab::onGrabCleared); this->grab->startTransaction(); - for (auto* proxy: this->trackedProxies) { - if (proxy->backingWindow() != nullptr) { + for (auto* obj: this->windowObjects) { + auto* proxy = ProxyWindowBase::forObject(obj); + if (proxy && proxy->backingWindow()) { this->grab->addWindow(proxy->backingWindow()); } } this->grab->completeTransaction(); } -void HyprlandFocusGrab::syncWindows() { - auto newProxy = QList(); - for (auto* windowObject: this->windowObjects) { - auto* proxyWindow = qobject_cast(windowObject); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(windowObject)) { - proxyWindow = iface->proxyWindow(); - } - } - - if (proxyWindow != nullptr) { - newProxy.push_back(proxyWindow); - } - } - - if (this->grab) this->grab->startTransaction(); - - for (auto* oldWindow: this->trackedProxies) { - if (!newProxy.contains(oldWindow)) { - QObject::disconnect(oldWindow, nullptr, this, nullptr); - - if (this->grab != nullptr && oldWindow->backingWindow() != nullptr) { - this->grab->removeWindow(oldWindow->backingWindow()); - } - } - } - - for (auto* newProxy: newProxy) { - if (!this->trackedProxies.contains(newProxy)) { - QObject::connect( - newProxy, - &ProxyWindowBase::windowConnected, - this, - &HyprlandFocusGrab::onProxyConnected - ); - - if (this->grab != nullptr && newProxy->backingWindow() != nullptr) { - this->grab->addWindow(newProxy->backingWindow()); - } - } - } - - this->trackedProxies = newProxy; - if (this->grab) this->grab->completeTransaction(); +void HyprlandFocusGrab::onObjectDestroyed(QObject* object) { + this->windowObjects.removeOne(object); + emit this->windowsChanged(); } } // namespace qs::hyprland diff --git a/src/wayland/hyprland/focus_grab/qml.hpp b/src/wayland/hyprland/focus_grab/qml.hpp index 705b0d3..97a10de 100644 --- a/src/wayland/hyprland/focus_grab/qml.hpp +++ b/src/wayland/hyprland/focus_grab/qml.hpp @@ -96,15 +96,13 @@ private slots: void onGrabActivated(); void onGrabCleared(); void onProxyConnected(); + void onObjectDestroyed(QObject* object); private: void tryActivate(); - void syncWindows(); bool targetActive = false; QObjectList windowObjects; - QList trackedProxies; - QList trackedWindows; focus_grab::FocusGrab* grab = nullptr; }; diff --git a/src/wayland/hyprland/surface/qml.cpp b/src/wayland/hyprland/surface/qml.cpp index c4f7d67..4575842 100644 --- a/src/wayland/hyprland/surface/qml.cpp +++ b/src/wayland/hyprland/surface/qml.cpp @@ -14,7 +14,6 @@ #include "../../../core/region.hpp" #include "../../../window/proxywindow.hpp" -#include "../../../window/windowinterface.hpp" #include "manager.hpp" #include "surface.hpp" @@ -23,13 +22,7 @@ using QtWaylandClient::QWaylandWindow; namespace qs::hyprland::surface { HyprlandWindow* HyprlandWindow::qmlAttachedProperties(QObject* object) { - auto* proxyWindow = qobject_cast(object); - - if (!proxyWindow) { - if (auto* iface = qobject_cast(object)) { - proxyWindow = iface->proxyWindow(); - } - } + auto* proxyWindow = ProxyWindowBase::forObject(object); if (!proxyWindow) return nullptr; return new HyprlandWindow(proxyWindow); diff --git a/src/wayland/idle_inhibit/inhibitor.cpp b/src/wayland/idle_inhibit/inhibitor.cpp index efeeae1..bfea7a0 100644 --- a/src/wayland/idle_inhibit/inhibitor.cpp +++ b/src/wayland/idle_inhibit/inhibitor.cpp @@ -6,7 +6,6 @@ #include #include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" #include "proto.hpp" namespace qs::wayland::idle_inhibit { @@ -25,27 +24,13 @@ QObject* IdleInhibitor::window() const { return this->bWindowObject; } void IdleInhibitor::setWindow(QObject* window) { if (window == this->bWindowObject) return; - auto* proxyWindow = qobject_cast(window); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - + auto* proxyWindow = ProxyWindowBase::forObject(window); this->bWindowObject = proxyWindow ? window : nullptr; } void IdleInhibitor::boundWindowChanged() { auto* window = this->bBoundWindow.value(); - auto* proxyWindow = qobject_cast(window); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - + auto* proxyWindow = ProxyWindowBase::forObject(window); if (proxyWindow == this->proxyWindow) return; if (this->mWaylandWindow) { diff --git a/src/wayland/shortcuts_inhibit/inhibitor.cpp b/src/wayland/shortcuts_inhibit/inhibitor.cpp index 2fca9bc..a91d5e2 100644 --- a/src/wayland/shortcuts_inhibit/inhibitor.cpp +++ b/src/wayland/shortcuts_inhibit/inhibitor.cpp @@ -9,7 +9,6 @@ #include #include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" #include "proto.hpp" namespace qs::wayland::shortcuts_inhibit { @@ -48,14 +47,7 @@ ShortcutInhibitor::~ShortcutInhibitor() { void ShortcutInhibitor::onBoundWindowChanged() { auto* window = this->bBoundWindow.value(); - auto* proxyWindow = qobject_cast(window); - - if (!proxyWindow) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - + auto* proxyWindow = ProxyWindowBase::forObject(window); if (proxyWindow == this->proxyWindow) return; if (this->proxyWindow) { diff --git a/src/wayland/toplevel_management/qml.cpp b/src/wayland/toplevel_management/qml.cpp index 6a1d96b..cb53381 100644 --- a/src/wayland/toplevel_management/qml.cpp +++ b/src/wayland/toplevel_management/qml.cpp @@ -9,7 +9,6 @@ #include "../../core/qmlscreen.hpp" #include "../../core/util.hpp" #include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" #include "../output_tracking.hpp" #include "handle.hpp" #include "manager.hpp" @@ -73,13 +72,7 @@ void Toplevel::fullscreenOn(QuickshellScreenInfo* screen) { } void Toplevel::setRectangle(QObject* window, QRect rect) { - auto* proxyWindow = qobject_cast(window); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } + auto* proxyWindow = ProxyWindowBase::forObject(window); if (proxyWindow != this->rectWindow) { if (this->rectWindow != nullptr) { diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index 62126bd..8a20dfa 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -57,6 +57,12 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent) ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(true); } +ProxyWindowBase* ProxyWindowBase::forObject(QObject* obj) { + if (auto* proxy = qobject_cast(obj)) return proxy; + if (auto* iface = qobject_cast(obj)) return iface->proxyWindow(); + return nullptr; +} + void ProxyWindowBase::onReload(QObject* oldInstance) { if (this->mVisible) this->window = this->retrieveWindow(oldInstance); auto wasVisible = this->window != nullptr && this->window->isVisible(); diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index aec821e..9ff66c4 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -66,6 +66,8 @@ public: explicit ProxyWindowBase(QObject* parent = nullptr); ~ProxyWindowBase() override; + static ProxyWindowBase* forObject(QObject* obj); + ProxyWindowBase(ProxyWindowBase&) = delete; ProxyWindowBase(ProxyWindowBase&&) = delete; void operator=(ProxyWindowBase&) = delete;