Changeset View
Changeset View
Standalone View
Standalone View
autotests/integration/stacking_order_test.cpp
Show All 17 Lines | |||||
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | *********************************************************************/ | 19 | *********************************************************************/ | ||
20 | 20 | | |||
21 | #include "kwin_wayland_test.h" | 21 | #include "kwin_wayland_test.h" | ||
22 | 22 | | |||
23 | #include "abstract_client.h" | 23 | #include "abstract_client.h" | ||
24 | #include "atoms.h" | 24 | #include "atoms.h" | ||
25 | #include "client.h" | 25 | #include "client.h" | ||
26 | #include "deleted.h" | ||||
26 | #include "main.h" | 27 | #include "main.h" | ||
27 | #include "platform.h" | 28 | #include "platform.h" | ||
28 | #include "shell_client.h" | 29 | #include "shell_client.h" | ||
29 | #include "wayland_server.h" | 30 | #include "wayland_server.h" | ||
30 | #include "workspace.h" | 31 | #include "workspace.h" | ||
31 | 32 | | |||
32 | #include <KWayland/Client/compositor.h> | 33 | #include <KWayland/Client/compositor.h> | ||
33 | #include <KWayland/Client/shell.h> | 34 | #include <KWayland/Client/shell.h> | ||
Show All 12 Lines | |||||
46 | 47 | | |||
47 | private Q_SLOTS: | 48 | private Q_SLOTS: | ||
48 | void initTestCase(); | 49 | void initTestCase(); | ||
49 | void init(); | 50 | void init(); | ||
50 | void cleanup(); | 51 | void cleanup(); | ||
51 | 52 | | |||
52 | void testTransientIsAboveParent(); | 53 | void testTransientIsAboveParent(); | ||
53 | void testRaiseTransient(); | 54 | void testRaiseTransient(); | ||
55 | void testDeletedTransient(); | ||||
54 | 56 | | |||
55 | void testGroupTransientIsAboveWindowGroup(); | 57 | void testGroupTransientIsAboveWindowGroup(); | ||
56 | void testRaiseGroupTransient(); | 58 | void testRaiseGroupTransient(); | ||
59 | void testDeletedGroupTransient(); | ||||
57 | void testDontKeepAboveNonModalDialogGroupTransients(); | 60 | void testDontKeepAboveNonModalDialogGroupTransients(); | ||
58 | 61 | | |||
59 | }; | 62 | }; | ||
60 | 63 | | |||
61 | void StackingOrderTest::initTestCase() | 64 | void StackingOrderTest::initTestCase() | ||
62 | { | 65 | { | ||
63 | qRegisterMetaType<KWin::AbstractClient *>(); | 66 | qRegisterMetaType<KWin::AbstractClient *>(); | ||
67 | qRegisterMetaType<KWin::Deleted *>(); | ||||
64 | qRegisterMetaType<KWin::ShellClient *>(); | 68 | qRegisterMetaType<KWin::ShellClient *>(); | ||
65 | 69 | | |||
66 | QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); | 70 | QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); | ||
67 | QVERIFY(workspaceCreatedSpy.isValid()); | 71 | QVERIFY(workspaceCreatedSpy.isValid()); | ||
68 | kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); | 72 | kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); | ||
69 | QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); | 73 | QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); | ||
70 | 74 | | |||
71 | kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); | 75 | kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); | ||
▲ Show 20 Lines • Show All 125 Lines • ▼ Show 20 Line(s) | 136 | { | |||
197 | // If we activate the transient, the parent should be raised too. | 201 | // If we activate the transient, the parent should be raised too. | ||
198 | workspace()->activateClient(transient); | 202 | workspace()->activateClient(transient); | ||
199 | QTRY_VERIFY(!parent->isActive()); | 203 | QTRY_VERIFY(!parent->isActive()); | ||
200 | QTRY_VERIFY(transient->isActive()); | 204 | QTRY_VERIFY(transient->isActive()); | ||
201 | QTRY_VERIFY(!anotherClient->isActive()); | 205 | QTRY_VERIFY(!anotherClient->isActive()); | ||
202 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{anotherClient, parent, transient})); | 206 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{anotherClient, parent, transient})); | ||
203 | } | 207 | } | ||
204 | 208 | | |||
209 | struct WindowUnrefDeleter | ||||
210 | { | ||||
211 | static inline void cleanup(Deleted *d) { | ||||
212 | if (d != nullptr) { | ||||
213 | d->unrefWindow(); | ||||
214 | } | ||||
215 | } | ||||
216 | }; | ||||
217 | | ||||
218 | void StackingOrderTest::testDeletedTransient() | ||||
219 | { | ||||
220 | // This test verifies that deleted transients are kept above their | ||||
221 | // old parents. | ||||
222 | | ||||
223 | // Create the parent. | ||||
224 | KWayland::Client::Surface *parentSurface = | ||||
225 | Test::createSurface(Test::waylandCompositor()); | ||||
226 | QVERIFY(parentSurface); | ||||
227 | KWayland::Client::ShellSurface *parentShellSurface = | ||||
228 | Test::createShellSurface(parentSurface, parentSurface); | ||||
229 | QVERIFY(parentShellSurface); | ||||
230 | ShellClient *parent = Test::renderAndWaitForShown(parentSurface, QSize(256, 256), Qt::blue); | ||||
231 | QVERIFY(parent); | ||||
232 | QVERIFY(parent->isActive()); | ||||
233 | QVERIFY(!parent->isTransient()); | ||||
234 | | ||||
235 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{parent})); | ||||
236 | | ||||
237 | // Create the first transient. | ||||
238 | KWayland::Client::Surface *transient1Surface = | ||||
239 | Test::createSurface(Test::waylandCompositor()); | ||||
240 | QVERIFY(transient1Surface); | ||||
241 | KWayland::Client::ShellSurface *transient1ShellSurface = | ||||
242 | Test::createShellSurface(transient1Surface, transient1Surface); | ||||
243 | QVERIFY(transient1ShellSurface); | ||||
244 | transient1ShellSurface->setTransient(parentSurface, QPoint(0, 0)); | ||||
245 | ShellClient *transient1 = Test::renderAndWaitForShown( | ||||
246 | transient1Surface, QSize(128, 128), Qt::red); | ||||
247 | QVERIFY(transient1); | ||||
248 | QTRY_VERIFY(transient1->isActive()); | ||||
249 | QVERIFY(transient1->isTransient()); | ||||
250 | QCOMPARE(transient1->transientFor(), parent); | ||||
251 | | ||||
252 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{parent, transient1})); | ||||
253 | | ||||
254 | // Create the second transient. | ||||
255 | KWayland::Client::Surface *transient2Surface = | ||||
256 | Test::createSurface(Test::waylandCompositor()); | ||||
257 | QVERIFY(transient2Surface); | ||||
258 | KWayland::Client::ShellSurface *transient2ShellSurface = | ||||
259 | Test::createShellSurface(transient2Surface, transient2Surface); | ||||
260 | QVERIFY(transient2ShellSurface); | ||||
261 | transient2ShellSurface->setTransient(transient1Surface, QPoint(0, 0)); | ||||
262 | ShellClient *transient2 = Test::renderAndWaitForShown( | ||||
263 | transient2Surface, QSize(128, 128), Qt::red); | ||||
264 | QVERIFY(transient2); | ||||
265 | QTRY_VERIFY(transient2->isActive()); | ||||
266 | QVERIFY(transient2->isTransient()); | ||||
267 | QCOMPARE(transient2->transientFor(), transient1); | ||||
268 | | ||||
269 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{parent, transient1, transient2})); | ||||
270 | | ||||
271 | // Activate the parent, both transients have to be above it. | ||||
272 | workspace()->activateClient(parent); | ||||
273 | QTRY_VERIFY(parent->isActive()); | ||||
274 | QTRY_VERIFY(!transient1->isActive()); | ||||
275 | QTRY_VERIFY(!transient2->isActive()); | ||||
276 | | ||||
277 | // Close the top-most transient. | ||||
278 | connect(transient2, &ShellClient::windowClosed, this, | ||||
279 | [](Toplevel *toplevel, Deleted *deleted) { | ||||
280 | Q_UNUSED(toplevel) | ||||
281 | deleted->refWindow(); | ||||
282 | } | ||||
283 | ); | ||||
284 | | ||||
285 | QSignalSpy windowClosedSpy(transient2, &ShellClient::windowClosed); | ||||
286 | QVERIFY(windowClosedSpy.isValid()); | ||||
287 | delete transient2ShellSurface; | ||||
288 | delete transient2Surface; | ||||
289 | QVERIFY(windowClosedSpy.wait()); | ||||
290 | | ||||
291 | QScopedPointer<Deleted, WindowUnrefDeleter> deletedTransient( | ||||
292 | windowClosedSpy.first().at(1).value<Deleted *>()); | ||||
293 | QVERIFY(deletedTransient.data()); | ||||
294 | | ||||
295 | // The deleted transient still has to be above its old parent (transient1). | ||||
296 | QTRY_VERIFY(parent->isActive()); | ||||
297 | QTRY_VERIFY(!transient1->isActive()); | ||||
298 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{parent, transient1, deletedTransient.data()})); | ||||
299 | } | ||||
300 | | ||||
205 | static xcb_window_t createGroupWindow(xcb_connection_t *conn, | 301 | static xcb_window_t createGroupWindow(xcb_connection_t *conn, | ||
206 | const QRect &geometry, | 302 | const QRect &geometry, | ||
207 | xcb_window_t leaderWid = XCB_WINDOW_NONE) | 303 | xcb_window_t leaderWid = XCB_WINDOW_NONE) | ||
208 | { | 304 | { | ||
209 | xcb_window_t wid = xcb_generate_id(conn); | 305 | xcb_window_t wid = xcb_generate_id(conn); | ||
210 | xcb_create_window( | 306 | xcb_create_window( | ||
211 | conn, // c | 307 | conn, // c | ||
212 | XCB_COPY_FROM_PARENT, // depth | 308 | XCB_COPY_FROM_PARENT, // depth | ||
▲ Show 20 Lines • Show All 283 Lines • ▼ Show 20 Line(s) | 470 | { | |||
496 | QTRY_VERIFY(anotherClient->isActive()); | 592 | QTRY_VERIFY(anotherClient->isActive()); | ||
497 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{member1, leader, member2, transient, anotherClient})); | 593 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{member1, leader, member2, transient, anotherClient})); | ||
498 | 594 | | |||
499 | workspace()->activateClient(transient); | 595 | workspace()->activateClient(transient); | ||
500 | QTRY_VERIFY(transient->isActive()); | 596 | QTRY_VERIFY(transient->isActive()); | ||
501 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{member1, leader, member2, anotherClient, transient})); | 597 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{member1, leader, member2, anotherClient, transient})); | ||
502 | } | 598 | } | ||
503 | 599 | | |||
600 | void StackingOrderTest::testDeletedGroupTransient() | ||||
601 | { | ||||
602 | // This test verifies that deleted group transients are kept above their | ||||
603 | // old window groups. | ||||
604 | | ||||
605 | const QRect geometry = QRect(0, 0, 128, 128); | ||||
606 | | ||||
607 | QScopedPointer<xcb_connection_t, XcbConnectionDeleter> conn( | ||||
608 | xcb_connect(nullptr, nullptr)); | ||||
609 | | ||||
610 | QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); | ||||
611 | QVERIFY(windowCreatedSpy.isValid()); | ||||
612 | | ||||
613 | // Create the group leader. | ||||
614 | xcb_window_t leaderWid = createGroupWindow(conn.data(), geometry); | ||||
615 | xcb_map_window(conn.data(), leaderWid); | ||||
616 | xcb_flush(conn.data()); | ||||
617 | | ||||
618 | QVERIFY(windowCreatedSpy.wait()); | ||||
619 | Client *leader = windowCreatedSpy.first().first().value<Client *>(); | ||||
620 | QVERIFY(leader); | ||||
621 | QVERIFY(leader->isActive()); | ||||
622 | QCOMPARE(leader->windowId(), leaderWid); | ||||
623 | QVERIFY(!leader->isTransient()); | ||||
624 | | ||||
625 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{leader})); | ||||
626 | | ||||
627 | // Create another group member. | ||||
628 | windowCreatedSpy.clear(); | ||||
629 | xcb_window_t member1Wid = createGroupWindow(conn.data(), geometry, leaderWid); | ||||
630 | xcb_map_window(conn.data(), member1Wid); | ||||
631 | xcb_flush(conn.data()); | ||||
632 | | ||||
633 | QVERIFY(windowCreatedSpy.wait()); | ||||
634 | Client *member1 = windowCreatedSpy.first().first().value<Client *>(); | ||||
635 | QVERIFY(member1); | ||||
636 | QVERIFY(member1->isActive()); | ||||
637 | QCOMPARE(member1->windowId(), member1Wid); | ||||
638 | QCOMPARE(member1->group(), leader->group()); | ||||
639 | QVERIFY(!member1->isTransient()); | ||||
640 | | ||||
641 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{leader, member1})); | ||||
642 | | ||||
643 | // Create yet another group member. | ||||
644 | windowCreatedSpy.clear(); | ||||
645 | xcb_window_t member2Wid = createGroupWindow(conn.data(), geometry, leaderWid); | ||||
646 | xcb_map_window(conn.data(), member2Wid); | ||||
647 | xcb_flush(conn.data()); | ||||
648 | | ||||
649 | QVERIFY(windowCreatedSpy.wait()); | ||||
650 | Client *member2 = windowCreatedSpy.first().first().value<Client *>(); | ||||
651 | QVERIFY(member2); | ||||
652 | QVERIFY(member2->isActive()); | ||||
653 | QCOMPARE(member2->windowId(), member2Wid); | ||||
654 | QCOMPARE(member2->group(), leader->group()); | ||||
655 | QVERIFY(!member2->isTransient()); | ||||
656 | | ||||
657 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{leader, member1, member2})); | ||||
658 | | ||||
659 | // Create a group transient. | ||||
660 | windowCreatedSpy.clear(); | ||||
661 | xcb_window_t transientWid = createGroupWindow(conn.data(), geometry, leaderWid); | ||||
662 | xcb_icccm_set_wm_transient_for(conn.data(), transientWid, rootWindow()); | ||||
663 | | ||||
664 | // Currently, we have some weird bug workaround: if a group transient | ||||
665 | // is a non-modal dialog, then it won't be kept above its window group. | ||||
666 | // We need to explicitly specify window type, otherwise the window type | ||||
667 | // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient | ||||
668 | // for before (the EWMH spec says to do that). | ||||
669 | xcb_atom_t net_wm_window_type = Xcb::Atom( | ||||
670 | QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.data()); | ||||
671 | xcb_atom_t net_wm_window_type_normal = Xcb::Atom( | ||||
672 | QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.data()); | ||||
673 | xcb_change_property( | ||||
674 | conn.data(), // c | ||||
675 | XCB_PROP_MODE_REPLACE, // mode | ||||
676 | transientWid, // window | ||||
677 | net_wm_window_type, // property | ||||
678 | XCB_ATOM_ATOM, // type | ||||
679 | 32, // format | ||||
680 | 1, // data_len | ||||
681 | &net_wm_window_type_normal // data | ||||
682 | ); | ||||
683 | | ||||
684 | xcb_map_window(conn.data(), transientWid); | ||||
685 | xcb_flush(conn.data()); | ||||
686 | | ||||
687 | QVERIFY(windowCreatedSpy.wait()); | ||||
688 | Client *transient = windowCreatedSpy.first().first().value<Client *>(); | ||||
689 | QVERIFY(transient); | ||||
690 | QVERIFY(transient->isActive()); | ||||
691 | QCOMPARE(transient->windowId(), transientWid); | ||||
692 | QCOMPARE(transient->group(), leader->group()); | ||||
693 | QVERIFY(transient->isTransient()); | ||||
694 | QVERIFY(transient->groupTransient()); | ||||
695 | QVERIFY(!transient->isDialog()); // See above why | ||||
696 | | ||||
697 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{leader, member1, member2, transient})); | ||||
698 | | ||||
699 | // Unmap the transient. | ||||
700 | connect(transient, &Client::windowClosed, this, | ||||
701 | [](Toplevel *toplevel, Deleted *deleted) { | ||||
702 | Q_UNUSED(toplevel) | ||||
703 | deleted->refWindow(); | ||||
704 | } | ||||
705 | ); | ||||
706 | | ||||
707 | QSignalSpy windowClosedSpy(transient, &Client::windowClosed); | ||||
708 | QVERIFY(windowClosedSpy.isValid()); | ||||
709 | xcb_unmap_window(conn.data(), transientWid); | ||||
710 | xcb_flush(conn.data()); | ||||
711 | QVERIFY(windowClosedSpy.wait()); | ||||
712 | | ||||
713 | QScopedPointer<Deleted, WindowUnrefDeleter> deletedTransient( | ||||
714 | windowClosedSpy.first().at(1).value<Deleted *>()); | ||||
715 | QVERIFY(deletedTransient.data()); | ||||
716 | | ||||
717 | // The transient has to be above each member of the window group. | ||||
718 | QCOMPARE(workspace()->stackingOrder(), (ToplevelList{leader, member1, member2, deletedTransient.data()})); | ||||
719 | } | ||||
720 | | ||||
504 | void StackingOrderTest::testDontKeepAboveNonModalDialogGroupTransients() | 721 | void StackingOrderTest::testDontKeepAboveNonModalDialogGroupTransients() | ||
505 | { | 722 | { | ||
506 | // Bug 76026 | 723 | // Bug 76026 | ||
507 | 724 | | |||
508 | const QRect geometry = QRect(0, 0, 128, 128); | 725 | const QRect geometry = QRect(0, 0, 128, 128); | ||
509 | 726 | | |||
510 | QScopedPointer<xcb_connection_t, XcbConnectionDeleter> conn( | 727 | QScopedPointer<xcb_connection_t, XcbConnectionDeleter> conn( | ||
511 | xcb_connect(nullptr, nullptr)); | 728 | xcb_connect(nullptr, nullptr)); | ||
▲ Show 20 Lines • Show All 89 Lines • Show Last 20 Lines |