File indexing completed on 2025-03-23 13:48:05
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"