Warning, file /plasma/kwin/autotests/integration/stacking_order_test.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "kwin_wayland_test.h"
0011 
0012 #include "atoms.h"
0013 #include "core/outputbackend.h"
0014 #include "deleted.h"
0015 #include "main.h"
0016 #include "wayland_server.h"
0017 #include "window.h"
0018 #include "workspace.h"
0019 #include "x11window.h"
0020 
0021 #include <KWayland/Client/compositor.h>
0022 #include <KWayland/Client/surface.h>
0023 
0024 #include <xcb/xcb.h>
0025 #include <xcb/xcb_icccm.h>
0026 
0027 using namespace KWin;
0028 
0029 static const QString s_socketName = QStringLiteral("wayland_test_kwin_stacking_order-0");
0030 
0031 class StackingOrderTest : public QObject
0032 {
0033     Q_OBJECT
0034 
0035 private Q_SLOTS:
0036     void initTestCase();
0037     void init();
0038     void cleanup();
0039 
0040     void testTransientIsAboveParent();
0041     void testRaiseTransient();
0042     void testDeletedTransient();
0043 
0044     void testGroupTransientIsAboveWindowGroup();
0045     void testRaiseGroupTransient();
0046     void testDeletedGroupTransient();
0047     void testDontKeepAboveNonModalDialogGroupTransients();
0048 
0049     void testKeepAbove();
0050     void testKeepBelow();
0051 };
0052 
0053 void StackingOrderTest::initTestCase()
0054 {
0055     qRegisterMetaType<KWin::Window *>();
0056     qRegisterMetaType<KWin::Deleted *>();
0057 
0058     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0059     QVERIFY(waylandServer()->init(s_socketName));
0060     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0061 
0062     kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
0063 
0064     kwinApp()->start();
0065     QVERIFY(applicationStartedSpy.wait());
0066 }
0067 
0068 void StackingOrderTest::init()
0069 {
0070     QVERIFY(Test::setupWaylandConnection());
0071 }
0072 
0073 void StackingOrderTest::cleanup()
0074 {
0075     Test::destroyWaylandConnection();
0076 }
0077 
0078 void StackingOrderTest::testTransientIsAboveParent()
0079 {
0080     // This test verifies that transients are always above their parents.
0081 
0082     // Create the parent.
0083     std::unique_ptr<KWayland::Client::Surface> parentSurface = Test::createSurface();
0084     QVERIFY(parentSurface);
0085     Test::XdgToplevel *parentShellSurface = Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get());
0086     QVERIFY(parentShellSurface);
0087     Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue);
0088     QVERIFY(parent);
0089     QVERIFY(parent->isActive());
0090     QVERIFY(!parent->isTransient());
0091 
0092     // Initially, the stacking order should contain only the parent window.
0093     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent}));
0094 
0095     // Create the transient.
0096     std::unique_ptr<KWayland::Client::Surface> transientSurface = Test::createSurface();
0097     QVERIFY(transientSurface);
0098     Test::XdgToplevel *transientShellSurface = Test::createXdgToplevelSurface(transientSurface.get(), transientSurface.get());
0099     QVERIFY(transientShellSurface);
0100     transientShellSurface->set_parent(parentShellSurface->object());
0101     Window *transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(128, 128), Qt::red);
0102     QVERIFY(transient);
0103     QVERIFY(transient->isActive());
0104     QVERIFY(transient->isTransient());
0105 
0106     // The transient should be above the parent.
0107     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient}));
0108 
0109     // The transient still stays above the parent if we activate the latter.
0110     workspace()->activateWindow(parent);
0111     QTRY_VERIFY(parent->isActive());
0112     QTRY_VERIFY(!transient->isActive());
0113     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient}));
0114 }
0115 
0116 void StackingOrderTest::testRaiseTransient()
0117 {
0118     // This test verifies that both the parent and the transient will be
0119     // raised if either one of them is activated.
0120 
0121     // Create the parent.
0122     std::unique_ptr<KWayland::Client::Surface> parentSurface = Test::createSurface();
0123     QVERIFY(parentSurface);
0124     Test::XdgToplevel *parentShellSurface = Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get());
0125     QVERIFY(parentShellSurface);
0126     Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue);
0127     QVERIFY(parent);
0128     QVERIFY(parent->isActive());
0129     QVERIFY(!parent->isTransient());
0130 
0131     // Initially, the stacking order should contain only the parent window.
0132     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent}));
0133 
0134     // Create the transient.
0135     std::unique_ptr<KWayland::Client::Surface> transientSurface = Test::createSurface();
0136     QVERIFY(transientSurface);
0137     Test::XdgToplevel *transientShellSurface = Test::createXdgToplevelSurface(transientSurface.get(), transientSurface.get());
0138     QVERIFY(transientShellSurface);
0139     transientShellSurface->set_parent(parentShellSurface->object());
0140     Window *transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(128, 128), Qt::red);
0141     QVERIFY(transient);
0142     QTRY_VERIFY(transient->isActive());
0143     QVERIFY(transient->isTransient());
0144 
0145     // The transient should be above the parent.
0146     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient}));
0147 
0148     // Create a window that doesn't have any relationship to the parent or the transient.
0149     std::unique_ptr<KWayland::Client::Surface> anotherSurface = Test::createSurface();
0150     QVERIFY(anotherSurface);
0151     Test::XdgToplevel *anotherShellSurface = Test::createXdgToplevelSurface(anotherSurface.get(), anotherSurface.get());
0152     QVERIFY(anotherShellSurface);
0153     Window *anotherWindow = Test::renderAndWaitForShown(anotherSurface.get(), QSize(128, 128), Qt::green);
0154     QVERIFY(anotherWindow);
0155     QVERIFY(anotherWindow->isActive());
0156     QVERIFY(!anotherWindow->isTransient());
0157 
0158     // The newly created surface has to be above both the parent and the transient.
0159     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient, anotherWindow}));
0160 
0161     // If we activate the parent, the transient should be raised too.
0162     workspace()->activateWindow(parent);
0163     QTRY_VERIFY(parent->isActive());
0164     QTRY_VERIFY(!transient->isActive());
0165     QTRY_VERIFY(!anotherWindow->isActive());
0166     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{anotherWindow, parent, transient}));
0167 
0168     // Go back to the initial setup.
0169     workspace()->activateWindow(anotherWindow);
0170     QTRY_VERIFY(!parent->isActive());
0171     QTRY_VERIFY(!transient->isActive());
0172     QTRY_VERIFY(anotherWindow->isActive());
0173     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient, anotherWindow}));
0174 
0175     // If we activate the transient, the parent should be raised too.
0176     workspace()->activateWindow(transient);
0177     QTRY_VERIFY(!parent->isActive());
0178     QTRY_VERIFY(transient->isActive());
0179     QTRY_VERIFY(!anotherWindow->isActive());
0180     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{anotherWindow, parent, transient}));
0181 }
0182 
0183 struct WindowUnrefDeleter
0184 {
0185     void operator()(Deleted *d)
0186     {
0187         if (d != nullptr) {
0188             d->unrefWindow();
0189         }
0190     }
0191 };
0192 
0193 void StackingOrderTest::testDeletedTransient()
0194 {
0195     // This test verifies that deleted transients are kept above their
0196     // old parents.
0197 
0198     // Create the parent.
0199     std::unique_ptr<KWayland::Client::Surface> parentSurface = Test::createSurface();
0200     QVERIFY(parentSurface);
0201     Test::XdgToplevel *parentShellSurface =
0202         Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get());
0203     QVERIFY(parentShellSurface);
0204     Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue);
0205     QVERIFY(parent);
0206     QVERIFY(parent->isActive());
0207     QVERIFY(!parent->isTransient());
0208 
0209     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent}));
0210 
0211     // Create the first transient.
0212     std::unique_ptr<KWayland::Client::Surface> transient1Surface = Test::createSurface();
0213     QVERIFY(transient1Surface);
0214     Test::XdgToplevel *transient1ShellSurface = Test::createXdgToplevelSurface(transient1Surface.get(), transient1Surface.get());
0215     QVERIFY(transient1ShellSurface);
0216     transient1ShellSurface->set_parent(parentShellSurface->object());
0217     Window *transient1 = Test::renderAndWaitForShown(transient1Surface.get(), QSize(128, 128), Qt::red);
0218     QVERIFY(transient1);
0219     QTRY_VERIFY(transient1->isActive());
0220     QVERIFY(transient1->isTransient());
0221     QCOMPARE(transient1->transientFor(), parent);
0222 
0223     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient1}));
0224 
0225     // Create the second transient.
0226     std::unique_ptr<KWayland::Client::Surface> transient2Surface = Test::createSurface();
0227     QVERIFY(transient2Surface);
0228     Test::XdgToplevel *transient2ShellSurface = Test::createXdgToplevelSurface(transient2Surface.get(), transient2Surface.get());
0229     QVERIFY(transient2ShellSurface);
0230     transient2ShellSurface->set_parent(transient1ShellSurface->object());
0231     Window *transient2 = Test::renderAndWaitForShown(transient2Surface.get(), QSize(128, 128), Qt::red);
0232     QVERIFY(transient2);
0233     QTRY_VERIFY(transient2->isActive());
0234     QVERIFY(transient2->isTransient());
0235     QCOMPARE(transient2->transientFor(), transient1);
0236 
0237     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient1, transient2}));
0238 
0239     // Activate the parent, both transients have to be above it.
0240     workspace()->activateWindow(parent);
0241     QTRY_VERIFY(parent->isActive());
0242     QTRY_VERIFY(!transient1->isActive());
0243     QTRY_VERIFY(!transient2->isActive());
0244 
0245     // Close the top-most transient.
0246     connect(transient2, &Window::windowClosed, this, [](Window *original, Deleted *deleted) {
0247         deleted->refWindow();
0248     });
0249 
0250     QSignalSpy windowClosedSpy(transient2, &Window::windowClosed);
0251     delete transient2ShellSurface;
0252     transient2Surface.reset();
0253     QVERIFY(windowClosedSpy.wait());
0254 
0255     std::unique_ptr<Deleted, WindowUnrefDeleter> deletedTransient(
0256         windowClosedSpy.first().at(1).value<Deleted *>());
0257     QVERIFY(deletedTransient.get());
0258 
0259     // The deleted transient still has to be above its old parent (transient1).
0260     QTRY_VERIFY(parent->isActive());
0261     QTRY_VERIFY(!transient1->isActive());
0262     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient1, deletedTransient.get()}));
0263 }
0264 
0265 static xcb_window_t createGroupWindow(xcb_connection_t *conn,
0266                                       const QRect &geometry,
0267                                       xcb_window_t leaderWid = XCB_WINDOW_NONE)
0268 {
0269     xcb_window_t wid = xcb_generate_id(conn);
0270     xcb_create_window(
0271         conn, // c
0272         XCB_COPY_FROM_PARENT, // depth
0273         wid, // wid
0274         rootWindow(), // parent
0275         geometry.x(), // x
0276         geometry.y(), // y
0277         geometry.width(), // width
0278         geometry.height(), // height
0279         0, // border_width
0280         XCB_WINDOW_CLASS_INPUT_OUTPUT, // _class
0281         XCB_COPY_FROM_PARENT, // visual
0282         0, // value_mask
0283         nullptr // value_list
0284     );
0285 
0286     xcb_size_hints_t sizeHints = {};
0287     xcb_icccm_size_hints_set_position(&sizeHints, 1, geometry.x(), geometry.y());
0288     xcb_icccm_size_hints_set_size(&sizeHints, 1, geometry.width(), geometry.height());
0289     xcb_icccm_set_wm_normal_hints(conn, wid, &sizeHints);
0290 
0291     if (leaderWid == XCB_WINDOW_NONE) {
0292         leaderWid = wid;
0293     }
0294 
0295     xcb_change_property(
0296         conn, // c
0297         XCB_PROP_MODE_REPLACE, // mode
0298         wid, // window
0299         atoms->wm_client_leader, // property
0300         XCB_ATOM_WINDOW, // type
0301         32, // format
0302         1, // data_len
0303         &leaderWid // data
0304     );
0305 
0306     return wid;
0307 }
0308 
0309 struct XcbConnectionDeleter
0310 {
0311     void operator()(xcb_connection_t *c)
0312     {
0313         xcb_disconnect(c);
0314     }
0315 };
0316 
0317 void StackingOrderTest::testGroupTransientIsAboveWindowGroup()
0318 {
0319     // This test verifies that group transients are always above other
0320     // window group members.
0321 
0322     const QRect geometry = QRect(0, 0, 128, 128);
0323 
0324     std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> conn(
0325         xcb_connect(nullptr, nullptr));
0326 
0327     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0328 
0329     // Create the group leader.
0330     xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry);
0331     xcb_map_window(conn.get(), leaderWid);
0332     xcb_flush(conn.get());
0333 
0334     QVERIFY(windowCreatedSpy.wait());
0335     X11Window *leader = windowCreatedSpy.first().first().value<X11Window *>();
0336     QVERIFY(leader);
0337     QVERIFY(leader->isActive());
0338     QCOMPARE(leader->window(), leaderWid);
0339     QVERIFY(!leader->isTransient());
0340 
0341     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader}));
0342 
0343     // Create another group member.
0344     windowCreatedSpy.clear();
0345     xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid);
0346     xcb_map_window(conn.get(), member1Wid);
0347     xcb_flush(conn.get());
0348 
0349     QVERIFY(windowCreatedSpy.wait());
0350     X11Window *member1 = windowCreatedSpy.first().first().value<X11Window *>();
0351     QVERIFY(member1);
0352     QVERIFY(member1->isActive());
0353     QCOMPARE(member1->window(), member1Wid);
0354     QCOMPARE(member1->group(), leader->group());
0355     QVERIFY(!member1->isTransient());
0356 
0357     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1}));
0358 
0359     // Create yet another group member.
0360     windowCreatedSpy.clear();
0361     xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid);
0362     xcb_map_window(conn.get(), member2Wid);
0363     xcb_flush(conn.get());
0364 
0365     QVERIFY(windowCreatedSpy.wait());
0366     X11Window *member2 = windowCreatedSpy.first().first().value<X11Window *>();
0367     QVERIFY(member2);
0368     QVERIFY(member2->isActive());
0369     QCOMPARE(member2->window(), member2Wid);
0370     QCOMPARE(member2->group(), leader->group());
0371     QVERIFY(!member2->isTransient());
0372 
0373     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2}));
0374 
0375     // Create a group transient.
0376     windowCreatedSpy.clear();
0377     xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid);
0378     xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow());
0379 
0380     // Currently, we have some weird bug workaround: if a group transient
0381     // is a non-modal dialog, then it won't be kept above its window group.
0382     // We need to explicitly specify window type, otherwise the window type
0383     // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient
0384     // for before (the EWMH spec says to do that).
0385     xcb_atom_t net_wm_window_type = Xcb::Atom(
0386         QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get());
0387     xcb_atom_t net_wm_window_type_normal = Xcb::Atom(
0388         QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get());
0389     xcb_change_property(
0390         conn.get(), // c
0391         XCB_PROP_MODE_REPLACE, // mode
0392         transientWid, // window
0393         net_wm_window_type, // property
0394         XCB_ATOM_ATOM, // type
0395         32, // format
0396         1, // data_len
0397         &net_wm_window_type_normal // data
0398     );
0399 
0400     xcb_map_window(conn.get(), transientWid);
0401     xcb_flush(conn.get());
0402 
0403     QVERIFY(windowCreatedSpy.wait());
0404     X11Window *transient = windowCreatedSpy.first().first().value<X11Window *>();
0405     QVERIFY(transient);
0406     QVERIFY(transient->isActive());
0407     QCOMPARE(transient->window(), transientWid);
0408     QCOMPARE(transient->group(), leader->group());
0409     QVERIFY(transient->isTransient());
0410     QVERIFY(transient->groupTransient());
0411     QVERIFY(!transient->isDialog()); // See above why
0412 
0413     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
0414 
0415     // If we activate any member of the window group, the transient will be above it.
0416     workspace()->activateWindow(leader);
0417     QTRY_VERIFY(leader->isActive());
0418     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, member2, leader, transient}));
0419 
0420     workspace()->activateWindow(member1);
0421     QTRY_VERIFY(member1->isActive());
0422     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member2, leader, member1, transient}));
0423 
0424     workspace()->activateWindow(member2);
0425     QTRY_VERIFY(member2->isActive());
0426     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
0427 
0428     workspace()->activateWindow(transient);
0429     QTRY_VERIFY(transient->isActive());
0430     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
0431 }
0432 
0433 void StackingOrderTest::testRaiseGroupTransient()
0434 {
0435     const QRect geometry = QRect(0, 0, 128, 128);
0436 
0437     std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> conn(
0438         xcb_connect(nullptr, nullptr));
0439 
0440     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0441 
0442     // Create the group leader.
0443     xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry);
0444     xcb_map_window(conn.get(), leaderWid);
0445     xcb_flush(conn.get());
0446 
0447     QVERIFY(windowCreatedSpy.wait());
0448     X11Window *leader = windowCreatedSpy.first().first().value<X11Window *>();
0449     QVERIFY(leader);
0450     QVERIFY(leader->isActive());
0451     QCOMPARE(leader->window(), leaderWid);
0452     QVERIFY(!leader->isTransient());
0453 
0454     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader}));
0455 
0456     // Create another group member.
0457     windowCreatedSpy.clear();
0458     xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid);
0459     xcb_map_window(conn.get(), member1Wid);
0460     xcb_flush(conn.get());
0461 
0462     QVERIFY(windowCreatedSpy.wait());
0463     X11Window *member1 = windowCreatedSpy.first().first().value<X11Window *>();
0464     QVERIFY(member1);
0465     QVERIFY(member1->isActive());
0466     QCOMPARE(member1->window(), member1Wid);
0467     QCOMPARE(member1->group(), leader->group());
0468     QVERIFY(!member1->isTransient());
0469 
0470     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1}));
0471 
0472     // Create yet another group member.
0473     windowCreatedSpy.clear();
0474     xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid);
0475     xcb_map_window(conn.get(), member2Wid);
0476     xcb_flush(conn.get());
0477 
0478     QVERIFY(windowCreatedSpy.wait());
0479     X11Window *member2 = windowCreatedSpy.first().first().value<X11Window *>();
0480     QVERIFY(member2);
0481     QVERIFY(member2->isActive());
0482     QCOMPARE(member2->window(), member2Wid);
0483     QCOMPARE(member2->group(), leader->group());
0484     QVERIFY(!member2->isTransient());
0485 
0486     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2}));
0487 
0488     // Create a group transient.
0489     windowCreatedSpy.clear();
0490     xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid);
0491     xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow());
0492 
0493     // Currently, we have some weird bug workaround: if a group transient
0494     // is a non-modal dialog, then it won't be kept above its window group.
0495     // We need to explicitly specify window type, otherwise the window type
0496     // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient
0497     // for before (the EWMH spec says to do that).
0498     xcb_atom_t net_wm_window_type = Xcb::Atom(
0499         QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get());
0500     xcb_atom_t net_wm_window_type_normal = Xcb::Atom(
0501         QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get());
0502     xcb_change_property(
0503         conn.get(), // c
0504         XCB_PROP_MODE_REPLACE, // mode
0505         transientWid, // window
0506         net_wm_window_type, // property
0507         XCB_ATOM_ATOM, // type
0508         32, // format
0509         1, // data_len
0510         &net_wm_window_type_normal // data
0511     );
0512 
0513     xcb_map_window(conn.get(), transientWid);
0514     xcb_flush(conn.get());
0515 
0516     QVERIFY(windowCreatedSpy.wait());
0517     X11Window *transient = windowCreatedSpy.first().first().value<X11Window *>();
0518     QVERIFY(transient);
0519     QVERIFY(transient->isActive());
0520     QCOMPARE(transient->window(), transientWid);
0521     QCOMPARE(transient->group(), leader->group());
0522     QVERIFY(transient->isTransient());
0523     QVERIFY(transient->groupTransient());
0524     QVERIFY(!transient->isDialog()); // See above why
0525 
0526     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
0527 
0528     // Create a Wayland window that is not a member of the window group.
0529     std::unique_ptr<KWayland::Client::Surface> anotherSurface = Test::createSurface();
0530     QVERIFY(anotherSurface);
0531     Test::XdgToplevel *anotherShellSurface = Test::createXdgToplevelSurface(anotherSurface.get(), anotherSurface.get());
0532     QVERIFY(anotherShellSurface);
0533     Window *anotherWindow = Test::renderAndWaitForShown(anotherSurface.get(), QSize(128, 128), Qt::green);
0534     QVERIFY(anotherWindow);
0535     QVERIFY(anotherWindow->isActive());
0536     QVERIFY(!anotherWindow->isTransient());
0537 
0538     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient, anotherWindow}));
0539 
0540     // If we activate the leader, then only it and the transient have to be raised.
0541     workspace()->activateWindow(leader);
0542     QTRY_VERIFY(leader->isActive());
0543     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, member2, anotherWindow, leader, transient}));
0544 
0545     // If another member of the window group is activated, then the transient will
0546     // be above that member and the leader.
0547     workspace()->activateWindow(member2);
0548     QTRY_VERIFY(member2->isActive());
0549     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, anotherWindow, leader, member2, transient}));
0550 
0551     // FIXME: If we activate the transient, only it will be raised.
0552     workspace()->activateWindow(anotherWindow);
0553     QTRY_VERIFY(anotherWindow->isActive());
0554     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, leader, member2, transient, anotherWindow}));
0555 
0556     workspace()->activateWindow(transient);
0557     QTRY_VERIFY(transient->isActive());
0558     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, leader, member2, anotherWindow, transient}));
0559 }
0560 
0561 void StackingOrderTest::testDeletedGroupTransient()
0562 {
0563     // This test verifies that deleted group transients are kept above their
0564     // old window groups.
0565 
0566     const QRect geometry = QRect(0, 0, 128, 128);
0567 
0568     std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> conn(
0569         xcb_connect(nullptr, nullptr));
0570 
0571     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0572 
0573     // Create the group leader.
0574     xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry);
0575     xcb_map_window(conn.get(), leaderWid);
0576     xcb_flush(conn.get());
0577 
0578     QVERIFY(windowCreatedSpy.wait());
0579     X11Window *leader = windowCreatedSpy.first().first().value<X11Window *>();
0580     QVERIFY(leader);
0581     QVERIFY(leader->isActive());
0582     QCOMPARE(leader->window(), leaderWid);
0583     QVERIFY(!leader->isTransient());
0584 
0585     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader}));
0586 
0587     // Create another group member.
0588     windowCreatedSpy.clear();
0589     xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid);
0590     xcb_map_window(conn.get(), member1Wid);
0591     xcb_flush(conn.get());
0592 
0593     QVERIFY(windowCreatedSpy.wait());
0594     X11Window *member1 = windowCreatedSpy.first().first().value<X11Window *>();
0595     QVERIFY(member1);
0596     QVERIFY(member1->isActive());
0597     QCOMPARE(member1->window(), member1Wid);
0598     QCOMPARE(member1->group(), leader->group());
0599     QVERIFY(!member1->isTransient());
0600 
0601     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1}));
0602 
0603     // Create yet another group member.
0604     windowCreatedSpy.clear();
0605     xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid);
0606     xcb_map_window(conn.get(), member2Wid);
0607     xcb_flush(conn.get());
0608 
0609     QVERIFY(windowCreatedSpy.wait());
0610     X11Window *member2 = windowCreatedSpy.first().first().value<X11Window *>();
0611     QVERIFY(member2);
0612     QVERIFY(member2->isActive());
0613     QCOMPARE(member2->window(), member2Wid);
0614     QCOMPARE(member2->group(), leader->group());
0615     QVERIFY(!member2->isTransient());
0616 
0617     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2}));
0618 
0619     // Create a group transient.
0620     windowCreatedSpy.clear();
0621     xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid);
0622     xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow());
0623 
0624     // Currently, we have some weird bug workaround: if a group transient
0625     // is a non-modal dialog, then it won't be kept above its window group.
0626     // We need to explicitly specify window type, otherwise the window type
0627     // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient
0628     // for before (the EWMH spec says to do that).
0629     xcb_atom_t net_wm_window_type = Xcb::Atom(
0630         QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get());
0631     xcb_atom_t net_wm_window_type_normal = Xcb::Atom(
0632         QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get());
0633     xcb_change_property(
0634         conn.get(), // c
0635         XCB_PROP_MODE_REPLACE, // mode
0636         transientWid, // window
0637         net_wm_window_type, // property
0638         XCB_ATOM_ATOM, // type
0639         32, // format
0640         1, // data_len
0641         &net_wm_window_type_normal // data
0642     );
0643 
0644     xcb_map_window(conn.get(), transientWid);
0645     xcb_flush(conn.get());
0646 
0647     QVERIFY(windowCreatedSpy.wait());
0648     X11Window *transient = windowCreatedSpy.first().first().value<X11Window *>();
0649     QVERIFY(transient);
0650     QVERIFY(transient->isActive());
0651     QCOMPARE(transient->window(), transientWid);
0652     QCOMPARE(transient->group(), leader->group());
0653     QVERIFY(transient->isTransient());
0654     QVERIFY(transient->groupTransient());
0655     QVERIFY(!transient->isDialog()); // See above why
0656 
0657     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
0658 
0659     // Unmap the transient.
0660     connect(transient, &X11Window::windowClosed, this, [](Window *original, Deleted *deleted) {
0661         deleted->refWindow();
0662     });
0663 
0664     QSignalSpy windowClosedSpy(transient, &X11Window::windowClosed);
0665     xcb_unmap_window(conn.get(), transientWid);
0666     xcb_flush(conn.get());
0667     QVERIFY(windowClosedSpy.wait());
0668 
0669     std::unique_ptr<Deleted, WindowUnrefDeleter> deletedTransient(
0670         windowClosedSpy.first().at(1).value<Deleted *>());
0671     QVERIFY(deletedTransient.get());
0672 
0673     // The transient has to be above each member of the window group.
0674     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, deletedTransient.get()}));
0675 }
0676 
0677 void StackingOrderTest::testDontKeepAboveNonModalDialogGroupTransients()
0678 {
0679     // Bug 76026
0680 
0681     const QRect geometry = QRect(0, 0, 128, 128);
0682 
0683     std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> conn(
0684         xcb_connect(nullptr, nullptr));
0685 
0686     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0687 
0688     // Create the group leader.
0689     xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry);
0690     xcb_map_window(conn.get(), leaderWid);
0691     xcb_flush(conn.get());
0692 
0693     QVERIFY(windowCreatedSpy.wait());
0694     X11Window *leader = windowCreatedSpy.first().first().value<X11Window *>();
0695     QVERIFY(leader);
0696     QVERIFY(leader->isActive());
0697     QCOMPARE(leader->window(), leaderWid);
0698     QVERIFY(!leader->isTransient());
0699 
0700     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader}));
0701 
0702     // Create another group member.
0703     windowCreatedSpy.clear();
0704     xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid);
0705     xcb_map_window(conn.get(), member1Wid);
0706     xcb_flush(conn.get());
0707 
0708     QVERIFY(windowCreatedSpy.wait());
0709     X11Window *member1 = windowCreatedSpy.first().first().value<X11Window *>();
0710     QVERIFY(member1);
0711     QVERIFY(member1->isActive());
0712     QCOMPARE(member1->window(), member1Wid);
0713     QCOMPARE(member1->group(), leader->group());
0714     QVERIFY(!member1->isTransient());
0715 
0716     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1}));
0717 
0718     // Create yet another group member.
0719     windowCreatedSpy.clear();
0720     xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid);
0721     xcb_map_window(conn.get(), member2Wid);
0722     xcb_flush(conn.get());
0723 
0724     QVERIFY(windowCreatedSpy.wait());
0725     X11Window *member2 = windowCreatedSpy.first().first().value<X11Window *>();
0726     QVERIFY(member2);
0727     QVERIFY(member2->isActive());
0728     QCOMPARE(member2->window(), member2Wid);
0729     QCOMPARE(member2->group(), leader->group());
0730     QVERIFY(!member2->isTransient());
0731 
0732     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2}));
0733 
0734     // Create a group transient.
0735     windowCreatedSpy.clear();
0736     xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid);
0737     xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow());
0738     xcb_map_window(conn.get(), transientWid);
0739     xcb_flush(conn.get());
0740 
0741     QVERIFY(windowCreatedSpy.wait());
0742     X11Window *transient = windowCreatedSpy.first().first().value<X11Window *>();
0743     QVERIFY(transient);
0744     QVERIFY(transient->isActive());
0745     QCOMPARE(transient->window(), transientWid);
0746     QCOMPARE(transient->group(), leader->group());
0747     QVERIFY(transient->isTransient());
0748     QVERIFY(transient->groupTransient());
0749     QVERIFY(transient->isDialog());
0750     QVERIFY(!transient->isModal());
0751 
0752     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
0753 
0754     workspace()->activateWindow(leader);
0755     QTRY_VERIFY(leader->isActive());
0756     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, member2, transient, leader}));
0757 
0758     workspace()->activateWindow(member1);
0759     QTRY_VERIFY(member1->isActive());
0760     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member2, transient, leader, member1}));
0761 
0762     workspace()->activateWindow(member2);
0763     QTRY_VERIFY(member2->isActive());
0764     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{transient, leader, member1, member2}));
0765 
0766     workspace()->activateWindow(transient);
0767     QTRY_VERIFY(transient->isActive());
0768     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
0769 }
0770 
0771 void StackingOrderTest::testKeepAbove()
0772 {
0773     // This test verifies that "keep-above" windows are kept above other windows.
0774 
0775     // Create the first window.
0776     std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
0777     QVERIFY(surface1);
0778     Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
0779     QVERIFY(shellSurface1);
0780     Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(128, 128), Qt::green);
0781     QVERIFY(window1);
0782     QVERIFY(window1->isActive());
0783     QVERIFY(!window1->keepAbove());
0784 
0785     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1}));
0786 
0787     // Create the second window.
0788     std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
0789     QVERIFY(surface2);
0790     Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
0791     QVERIFY(shellSurface2);
0792     Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(128, 128), Qt::green);
0793     QVERIFY(window2);
0794     QVERIFY(window2->isActive());
0795     QVERIFY(!window2->keepAbove());
0796 
0797     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1, window2}));
0798 
0799     // Go to the initial test position.
0800     workspace()->activateWindow(window1);
0801     QTRY_VERIFY(window1->isActive());
0802     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window2, window1}));
0803 
0804     // Set the "keep-above" flag on the window2, it should go above other windows.
0805     {
0806         StackingUpdatesBlocker blocker(workspace());
0807         window2->setKeepAbove(true);
0808     }
0809 
0810     QVERIFY(window2->keepAbove());
0811     QVERIFY(!window2->isActive());
0812     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1, window2}));
0813 }
0814 
0815 void StackingOrderTest::testKeepBelow()
0816 {
0817     // This test verifies that "keep-below" windows are kept below other windows.
0818 
0819     // Create the first window.
0820     std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
0821     QVERIFY(surface1);
0822     Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get());
0823     QVERIFY(shellSurface1);
0824     Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(128, 128), Qt::green);
0825     QVERIFY(window1);
0826     QVERIFY(window1->isActive());
0827     QVERIFY(!window1->keepBelow());
0828 
0829     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1}));
0830 
0831     // Create the second window.
0832     std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
0833     QVERIFY(surface2);
0834     Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get());
0835     QVERIFY(shellSurface2);
0836     Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(128, 128), Qt::green);
0837     QVERIFY(window2);
0838     QVERIFY(window2->isActive());
0839     QVERIFY(!window2->keepBelow());
0840 
0841     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1, window2}));
0842 
0843     // Set the "keep-below" flag on the window2, it should go below other windows.
0844     {
0845         StackingUpdatesBlocker blocker(workspace());
0846         window2->setKeepBelow(true);
0847     }
0848 
0849     QVERIFY(window2->isActive());
0850     QVERIFY(window2->keepBelow());
0851     QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window2, window1}));
0852 }
0853 
0854 WAYLANDTEST_MAIN(StackingOrderTest)
0855 #include "stacking_order_test.moc"