diff --git a/autotests/integration/internal_window.cpp b/autotests/integration/internal_window.cpp index 37ea57c519..849de26f3e 100644 --- a/autotests/integration/internal_window.cpp +++ b/autotests/integration/internal_window.cpp @@ -9,6 +9,7 @@ #include "kwin_wayland_test.h" #include "platform.h" #include "cursor.h" +#include "deleted.h" #include "effects.h" #include "internal_client.h" #include "screens.h" @@ -65,6 +66,7 @@ private Q_SLOTS: void testChangeWindowType(); void testEffectWindow(); void testReentrantSetFrameGeometry(); + void testDismissPopup(); }; class HelperWindow : public QRasterWindow @@ -173,6 +175,7 @@ void HelperWindow::keyReleaseEvent(QKeyEvent *event) void InternalWindowTest::initTestCase() { qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); QVERIFY(applicationStartedSpy.isValid()); @@ -809,6 +812,41 @@ void InternalWindowTest::testReentrantSetFrameGeometry() QCOMPARE(client->pos(), QPoint(100, 100)); } +void InternalWindowTest::testDismissPopup() +{ + // This test verifies that a popup window created by the compositor will be dismissed + // when user clicks another window. + + // Create a toplevel window. + QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded); + QVERIFY(clientAddedSpy.isValid()); + HelperWindow clientToplevel; + clientToplevel.setGeometry(0, 0, 100, 100); + clientToplevel.show(); + QTRY_COMPARE(clientAddedSpy.count(), 1); + auto serverToplevel = clientAddedSpy.last().first().value(); + QVERIFY(serverToplevel); + + // Create a popup window. + QRasterWindow clientPopup; + clientPopup.setFlag(Qt::Popup); + clientPopup.setTransientParent(&clientToplevel); + clientPopup.setGeometry(0, 0, 50, 50); + clientPopup.show(); + QTRY_COMPARE(clientAddedSpy.count(), 2); + auto serverPopup = clientAddedSpy.last().first().value(); + QVERIFY(serverPopup); + + // Click somewhere outside the popup window. + QSignalSpy popupClosedSpy(serverPopup, &InternalClient::windowClosed); + quint32 timestamp = 0; + kwinApp()->platform()->pointerMotion(QPointF(serverPopup->x() + serverPopup->width() + 1, + serverPopup->y() + serverPopup->height() + 1), + timestamp++); + kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++); + QTRY_COMPARE(popupClosedSpy.count(), 1); +} + } WAYLANDTEST_MAIN(KWin::InternalWindowTest) diff --git a/input.cpp b/input.cpp index b5abb378df..70ef4b26bf 100644 --- a/input.cpp +++ b/input.cpp @@ -2560,10 +2560,44 @@ Toplevel *InputRedirection::findToplevel(const QPoint &pos) return u; } } + if (Toplevel *window = findInternal(pos)) { + return window; + } } return findManagedToplevel(pos); } +Toplevel *InputRedirection::findInternal(const QPoint &pos) const +{ + const QList &internalClients = workspace()->internalClients(); + if (internalClients.isEmpty()) { + return nullptr; + } + + auto it = internalClients.end(); + do { + --it; + QWindow *w = (*it)->internalWindow(); + if (!w || !w->isVisible()) { + continue; + } + if (!(*it)->frameGeometry().contains(pos)) { + continue; + } + // check input mask + const QRegion mask = w->mask().translated(w->geometry().topLeft()); + if (!mask.isEmpty() && !mask.contains(pos)) { + continue; + } + if (w->property("outputOnly").toBool()) { + continue; + } + return *it; + } while (it != internalClients.begin()); + + return nullptr; +} + Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos) { if (!Workspace::self()) { @@ -2785,16 +2819,8 @@ void InputDeviceHandler::update() } Toplevel *toplevel = nullptr; - QWindow *internalWindow = nullptr; - if (positionValid()) { - const auto pos = position().toPoint(); - internalWindow = findInternalWindow(pos); - if (internalWindow) { - toplevel = workspace()->findInternal(internalWindow); - } else { - toplevel = input()->findToplevel(pos); - } + toplevel = input()->findToplevel(position().toPoint()); } // Always set the toplevel at the position of the input device. setAt(toplevel); @@ -2803,11 +2829,12 @@ void InputDeviceHandler::update() return; } - if (internalWindow) { - if (m_focus.internalWindow != internalWindow) { + if (auto client = qobject_cast(toplevel)) { + QWindow *handle = client->internalWindow(); + if (m_focus.internalWindow != handle) { // changed internal window updateDecoration(); - updateInternalWindow(internalWindow); + updateInternalWindow(handle); updateFocus(); } else if (updateDecoration()) { // went onto or off from decoration, update focus @@ -2850,39 +2877,4 @@ QWindow *InputDeviceHandler::internalWindow() const return m_focus.internalWindow; } -QWindow* InputDeviceHandler::findInternalWindow(const QPoint &pos) const -{ - if (waylandServer()->isScreenLocked()) { - return nullptr; - } - - const QList &internalClients = workspace()->internalClients(); - if (internalClients.isEmpty()) { - return nullptr; - } - - auto it = internalClients.end(); - do { - --it; - QWindow *w = (*it)->internalWindow(); - if (!w || !w->isVisible()) { - continue; - } - if (!(*it)->frameGeometry().contains(pos)) { - continue; - } - // check input mask - const QRegion mask = w->mask().translated(w->geometry().topLeft()); - if (!mask.isEmpty() && !mask.contains(pos)) { - continue; - } - if (w->property("outputOnly").toBool()) { - continue; - } - return w; - } while (it != internalClients.begin()); - - return nullptr; -} - } // namespace diff --git a/input.h b/input.h index 42e86d1370..4db1d7dd24 100644 --- a/input.h +++ b/input.h @@ -316,6 +316,7 @@ private: void reconfigure(); void setupInputFilters(); void installInputEventFilter(InputEventFilter *filter); + Toplevel *findInternal(const QPoint &pos) const; KeyboardInputRedirection *m_keyboard; PointerInputRedirection *m_pointer; TabletInputRedirection *m_tablet; @@ -497,8 +498,6 @@ private: bool updateDecoration(); void updateInternalWindow(QWindow *window); - QWindow* findInternalWindow(const QPoint &pos) const; - struct { QPointer at; QMetaObject::Connection surfaceCreatedConnection; diff --git a/internal_client.cpp b/internal_client.cpp index a965da804d..a01da4b529 100644 --- a/internal_client.cpp +++ b/internal_client.cpp @@ -14,6 +14,7 @@ #include +#include #include #include @@ -388,6 +389,18 @@ void InternalClient::destroyClient() delete this; } +bool InternalClient::hasPopupGrab() const +{ + return !m_internalWindow->flags().testFlag(Qt::WindowTransparentForInput) && + m_internalWindow->flags().testFlag(Qt::Popup) && + !m_internalWindow->flags().testFlag(Qt::ToolTip); +} + +void InternalClient::popupDone() +{ + m_internalWindow->hide(); +} + void InternalClient::present(const QSharedPointer fbo) { Q_ASSERT(m_internalImage.isNull()); @@ -438,8 +451,15 @@ bool InternalClient::acceptsFocus() const bool InternalClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const { Q_UNUSED(checks) - - return qobject_cast(other) != nullptr; + const InternalClient *otherInternal = qobject_cast(other); + if (!otherInternal) { + return false; + } + if (otherInternal == this) { + return true; + } + return otherInternal->internalWindow()->isAncestorOf(internalWindow()) || + internalWindow()->isAncestorOf(otherInternal->internalWindow()); } void InternalClient::doMove(int x, int y) diff --git a/internal_client.h b/internal_client.h index 034afd0dfd..187c78f32b 100644 --- a/internal_client.h +++ b/internal_client.h @@ -63,6 +63,8 @@ public: void setNoBorder(bool set) override; void updateDecoration(bool check_workspace_pos, bool force = false) override; void destroyClient() override; + bool hasPopupGrab() const override; + void popupDone() override; void present(const QSharedPointer fbo); void present(const QImage &image, const QRegion &damage); diff --git a/popup_input_filter.cpp b/popup_input_filter.cpp index cf3a2a7f73..828a7cc851 100644 --- a/popup_input_filter.cpp +++ b/popup_input_filter.cpp @@ -7,6 +7,7 @@ #include "popup_input_filter.h" #include "abstract_client.h" #include "deleted.h" +#include "internal_client.h" #include "workspace.h" #include @@ -18,6 +19,7 @@ PopupInputFilter::PopupInputFilter() : QObject() { connect(workspace(), &Workspace::clientAdded, this, &PopupInputFilter::handleClientAdded); + connect(workspace(), &Workspace::internalClientAdded, this, &PopupInputFilter::handleClientAdded); } void PopupInputFilter::handleClientAdded(Toplevel *client)