File indexing completed on 2024-11-10 04:56:06
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"