File indexing completed on 2024-05-12 05:30:42

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