KWin
Loading...
Searching...
No Matches
stacking_order_test.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "kwin_wayland_test.h"
11
12#include "atoms.h"
13#include "main.h"
14#include "wayland_server.h"
15#include "window.h"
16#include "workspace.h"
17#include "x11window.h"
18
19#include <KWayland/Client/compositor.h>
20#include <KWayland/Client/surface.h>
21
22#include <xcb/xcb.h>
23#include <xcb/xcb_icccm.h>
24
25using namespace KWin;
26
27static const QString s_socketName = QStringLiteral("wayland_test_kwin_stacking_order-0");
28
29class StackingOrderTest : public QObject
30{
31 Q_OBJECT
32
33private Q_SLOTS:
34 void initTestCase();
35 void init();
36 void cleanup();
37
38 void testTransientIsAboveParent();
39 void testRaiseTransient();
40 void testDeletedTransient();
41
42 void testGroupTransientIsAboveWindowGroup();
43 void testRaiseGroupTransient();
44 void testDeletedGroupTransient();
45 void testDontKeepAboveNonModalDialogGroupTransients();
46
47 void testKeepAbove();
48 void testKeepBelow();
49
50 void testPreserveRelativeWindowStacking();
51};
52
53void StackingOrderTest::initTestCase()
54{
55 qRegisterMetaType<KWin::Window *>();
56
57 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
58 QVERIFY(waylandServer()->init(s_socketName));
60 QRect(0, 0, 1280, 1024),
61 QRect(1280, 0, 1280, 1024),
62 });
63
64 kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
65
66 kwinApp()->start();
67 QVERIFY(applicationStartedSpy.wait());
68}
69
70void StackingOrderTest::init()
71{
73}
74
75void StackingOrderTest::cleanup()
76{
78}
79
80void StackingOrderTest::testTransientIsAboveParent()
81{
82 // This test verifies that transients are always above their parents.
83
84 // Create the parent.
85 std::unique_ptr<KWayland::Client::Surface> parentSurface = Test::createSurface();
86 QVERIFY(parentSurface);
87 std::unique_ptr<Test::XdgToplevel> parentShellSurface(Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get()));
88 QVERIFY(parentShellSurface);
89 Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue);
90 QVERIFY(parent);
91 QVERIFY(parent->isActive());
92 QVERIFY(!parent->isTransient());
93
94 // Initially, the stacking order should contain only the parent window.
95 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent}));
96
97 // Create the transient.
98 std::unique_ptr<KWayland::Client::Surface> transientSurface = Test::createSurface();
99 QVERIFY(transientSurface);
100 std::unique_ptr<Test::XdgToplevel> transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get(), transientSurface.get()));
101 QVERIFY(transientShellSurface);
102 transientShellSurface->set_parent(parentShellSurface->object());
103 Window *transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(128, 128), Qt::red);
104 QVERIFY(transient);
105 QVERIFY(transient->isActive());
106 QVERIFY(transient->isTransient());
107
108 // The transient should be above the parent.
109 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient}));
110
111 // The transient still stays above the parent if we activate the latter.
112 workspace()->activateWindow(parent);
113 QTRY_VERIFY(parent->isActive());
114 QTRY_VERIFY(!transient->isActive());
115 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient}));
116}
117
118void StackingOrderTest::testRaiseTransient()
119{
120 // This test verifies that both the parent and the transient will be
121 // raised if either one of them is activated.
122
123 // Create the parent.
124 std::unique_ptr<KWayland::Client::Surface> parentSurface = Test::createSurface();
125 QVERIFY(parentSurface);
126 std::unique_ptr<Test::XdgToplevel> parentShellSurface(Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get()));
127 QVERIFY(parentShellSurface);
128 Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue);
129 QVERIFY(parent);
130 QVERIFY(parent->isActive());
131 QVERIFY(!parent->isTransient());
132
133 // Initially, the stacking order should contain only the parent window.
134 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent}));
135
136 // Create the transient.
137 std::unique_ptr<KWayland::Client::Surface> transientSurface = Test::createSurface();
138 QVERIFY(transientSurface);
139 std::unique_ptr<Test::XdgToplevel> transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get(), transientSurface.get()));
140 QVERIFY(transientShellSurface);
141 transientShellSurface->set_parent(parentShellSurface->object());
142 Window *transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(128, 128), Qt::red);
143 QVERIFY(transient);
144 QTRY_VERIFY(transient->isActive());
145 QVERIFY(transient->isTransient());
146
147 // The transient should be above the parent.
148 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient}));
149
150 // Create a window that doesn't have any relationship to the parent or the transient.
151 std::unique_ptr<KWayland::Client::Surface> anotherSurface = Test::createSurface();
152 QVERIFY(anotherSurface);
153 std::unique_ptr<Test::XdgToplevel> anotherShellSurface(Test::createXdgToplevelSurface(anotherSurface.get(), anotherSurface.get()));
154 QVERIFY(anotherShellSurface);
155 Window *anotherWindow = Test::renderAndWaitForShown(anotherSurface.get(), QSize(128, 128), Qt::green);
156 QVERIFY(anotherWindow);
157 QVERIFY(anotherWindow->isActive());
158 QVERIFY(!anotherWindow->isTransient());
159
160 // The newly created surface has to be above both the parent and the transient.
161 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient, anotherWindow}));
162
163 // If we activate the parent, the transient should be raised too.
164 workspace()->activateWindow(parent);
165 QTRY_VERIFY(parent->isActive());
166 QTRY_VERIFY(!transient->isActive());
167 QTRY_VERIFY(!anotherWindow->isActive());
168 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{anotherWindow, parent, transient}));
169
170 // Go back to the initial setup.
171 workspace()->activateWindow(anotherWindow);
172 QTRY_VERIFY(!parent->isActive());
173 QTRY_VERIFY(!transient->isActive());
174 QTRY_VERIFY(anotherWindow->isActive());
175 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient, anotherWindow}));
176
177 // If we activate the transient, the parent should be raised too.
178 workspace()->activateWindow(transient);
179 QTRY_VERIFY(!parent->isActive());
180 QTRY_VERIFY(transient->isActive());
181 QTRY_VERIFY(!anotherWindow->isActive());
182 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{anotherWindow, parent, transient}));
183}
184
186{
188 {
189 if (d != nullptr) {
190 d->unref();
191 }
192 }
193};
194
195void StackingOrderTest::testDeletedTransient()
196{
197 // This test verifies that deleted transients are kept above their
198 // old parents.
199
200 // Create the parent.
201 std::unique_ptr<KWayland::Client::Surface> parentSurface = Test::createSurface();
202 QVERIFY(parentSurface);
203 std::unique_ptr<Test::XdgToplevel>
204 parentShellSurface(Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get()));
205 QVERIFY(parentShellSurface);
206 Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue);
207 QVERIFY(parent);
208 QVERIFY(parent->isActive());
209 QVERIFY(!parent->isTransient());
210
211 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent}));
212
213 // Create the first transient.
214 std::unique_ptr<KWayland::Client::Surface> transient1Surface = Test::createSurface();
215 QVERIFY(transient1Surface);
216 std::unique_ptr<Test::XdgToplevel> transient1ShellSurface(Test::createXdgToplevelSurface(transient1Surface.get(), transient1Surface.get()));
217 QVERIFY(transient1ShellSurface);
218 transient1ShellSurface->set_parent(parentShellSurface->object());
219 Window *transient1 = Test::renderAndWaitForShown(transient1Surface.get(), QSize(128, 128), Qt::red);
220 QVERIFY(transient1);
221 QTRY_VERIFY(transient1->isActive());
222 QVERIFY(transient1->isTransient());
223 QCOMPARE(transient1->transientFor(), parent);
224
225 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient1}));
226
227 // Create the second transient.
228 std::unique_ptr<KWayland::Client::Surface> transient2Surface = Test::createSurface();
229 QVERIFY(transient2Surface);
230 std::unique_ptr<Test::XdgToplevel> transient2ShellSurface(Test::createXdgToplevelSurface(transient2Surface.get(), transient2Surface.get()));
231 QVERIFY(transient2ShellSurface);
232 transient2ShellSurface->set_parent(transient1ShellSurface->object());
233 Window *transient2 = Test::renderAndWaitForShown(transient2Surface.get(), QSize(128, 128), Qt::red);
234 QVERIFY(transient2);
235 QTRY_VERIFY(transient2->isActive());
236 QVERIFY(transient2->isTransient());
237 QCOMPARE(transient2->transientFor(), transient1);
238
239 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient1, transient2}));
240
241 // Activate the parent, both transients have to be above it.
242 workspace()->activateWindow(parent);
243 QTRY_VERIFY(parent->isActive());
244 QTRY_VERIFY(!transient1->isActive());
245 QTRY_VERIFY(!transient2->isActive());
246
247 // Close the top-most transient.
248 connect(transient2, &Window::closed, transient2, &Window::ref);
249 auto cleanup = qScopeGuard([transient2]() {
250 transient2->unref();
251 });
252
253 QSignalSpy windowClosedSpy(transient2, &Window::closed);
254 transient2ShellSurface.reset();
255 transient2Surface.reset();
256 QVERIFY(windowClosedSpy.wait());
257
258 // The deleted transient still has to be above its old parent (transient1).
259 QTRY_VERIFY(parent->isActive());
260 QTRY_VERIFY(!transient1->isActive());
261 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{parent, transient1, transient2}));
262}
263
264static xcb_window_t createGroupWindow(xcb_connection_t *conn,
265 const QRect &geometry,
266 xcb_window_t leaderWid = XCB_WINDOW_NONE)
267{
268 xcb_window_t wid = xcb_generate_id(conn);
269 xcb_create_window(
270 conn, // c
271 XCB_COPY_FROM_PARENT, // depth
272 wid, // wid
273 rootWindow(), // parent
274 geometry.x(), // x
275 geometry.y(), // y
276 geometry.width(), // width
277 geometry.height(), // height
278 0, // border_width
279 XCB_WINDOW_CLASS_INPUT_OUTPUT, // _class
280 XCB_COPY_FROM_PARENT, // visual
281 0, // value_mask
282 nullptr // value_list
283 );
284
285 xcb_size_hints_t sizeHints = {};
286 xcb_icccm_size_hints_set_position(&sizeHints, 1, geometry.x(), geometry.y());
287 xcb_icccm_size_hints_set_size(&sizeHints, 1, geometry.width(), geometry.height());
288 xcb_icccm_set_wm_normal_hints(conn, wid, &sizeHints);
289
290 if (leaderWid == XCB_WINDOW_NONE) {
291 leaderWid = wid;
292 }
293
294 xcb_change_property(
295 conn, // c
296 XCB_PROP_MODE_REPLACE, // mode
297 wid, // window
298 atoms->wm_client_leader, // property
299 XCB_ATOM_WINDOW, // type
300 32, // format
301 1, // data_len
302 &leaderWid // data
303 );
304
305 return wid;
306}
307
308void StackingOrderTest::testGroupTransientIsAboveWindowGroup()
309{
310 // This test verifies that group transients are always above other
311 // window group members.
312
313 const QRect geometry = QRect(0, 0, 128, 128);
314
316
317 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
318
319 // Create the group leader.
320 xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry);
321 xcb_map_window(conn.get(), leaderWid);
322 xcb_flush(conn.get());
323
324 QVERIFY(windowCreatedSpy.wait());
325 X11Window *leader = windowCreatedSpy.first().first().value<X11Window *>();
326 QVERIFY(leader);
327 QVERIFY(leader->isActive());
328 QCOMPARE(leader->window(), leaderWid);
329 QVERIFY(!leader->isTransient());
330
331 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader}));
332
333 // Create another group member.
334 windowCreatedSpy.clear();
335 xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid);
336 xcb_map_window(conn.get(), member1Wid);
337 xcb_flush(conn.get());
338
339 QVERIFY(windowCreatedSpy.wait());
340 X11Window *member1 = windowCreatedSpy.first().first().value<X11Window *>();
341 QVERIFY(member1);
342 QVERIFY(member1->isActive());
343 QCOMPARE(member1->window(), member1Wid);
344 QCOMPARE(member1->group(), leader->group());
345 QVERIFY(!member1->isTransient());
346
347 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1}));
348
349 // Create yet another group member.
350 windowCreatedSpy.clear();
351 xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid);
352 xcb_map_window(conn.get(), member2Wid);
353 xcb_flush(conn.get());
354
355 QVERIFY(windowCreatedSpy.wait());
356 X11Window *member2 = windowCreatedSpy.first().first().value<X11Window *>();
357 QVERIFY(member2);
358 QVERIFY(member2->isActive());
359 QCOMPARE(member2->window(), member2Wid);
360 QCOMPARE(member2->group(), leader->group());
361 QVERIFY(!member2->isTransient());
362
363 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2}));
364
365 // Create a group transient.
366 windowCreatedSpy.clear();
367 xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid);
368 xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow());
369
370 // Currently, we have some weird bug workaround: if a group transient
371 // is a non-modal dialog, then it won't be kept above its window group.
372 // We need to explicitly specify window type, otherwise the window type
373 // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient
374 // for before (the EWMH spec says to do that).
375 xcb_atom_t net_wm_window_type = Xcb::Atom(
376 QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get());
377 xcb_atom_t net_wm_window_type_normal = Xcb::Atom(
378 QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get());
379 xcb_change_property(
380 conn.get(), // c
381 XCB_PROP_MODE_REPLACE, // mode
382 transientWid, // window
383 net_wm_window_type, // property
384 XCB_ATOM_ATOM, // type
385 32, // format
386 1, // data_len
387 &net_wm_window_type_normal // data
388 );
389
390 xcb_map_window(conn.get(), transientWid);
391 xcb_flush(conn.get());
392
393 QVERIFY(windowCreatedSpy.wait());
394 X11Window *transient = windowCreatedSpy.first().first().value<X11Window *>();
395 QVERIFY(transient);
396 QVERIFY(transient->isActive());
397 QCOMPARE(transient->window(), transientWid);
398 QCOMPARE(transient->group(), leader->group());
399 QVERIFY(transient->isTransient());
400 QVERIFY(transient->groupTransient());
401 QVERIFY(!transient->isDialog()); // See above why
402
403 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
404
405 // If we activate any member of the window group, the transient will be above it.
406 workspace()->activateWindow(leader);
407 QTRY_VERIFY(leader->isActive());
408 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, member2, leader, transient}));
409
410 workspace()->activateWindow(member1);
411 QTRY_VERIFY(member1->isActive());
412 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member2, leader, member1, transient}));
413
414 workspace()->activateWindow(member2);
415 QTRY_VERIFY(member2->isActive());
416 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
417
418 workspace()->activateWindow(transient);
419 QTRY_VERIFY(transient->isActive());
420 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
421}
422
423void StackingOrderTest::testRaiseGroupTransient()
424{
425 const QRect geometry = QRect(0, 0, 128, 128);
426
428
429 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
430
431 // Create the group leader.
432 xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry);
433 xcb_map_window(conn.get(), leaderWid);
434 xcb_flush(conn.get());
435
436 QVERIFY(windowCreatedSpy.wait());
437 X11Window *leader = windowCreatedSpy.first().first().value<X11Window *>();
438 QVERIFY(leader);
439 QVERIFY(leader->isActive());
440 QCOMPARE(leader->window(), leaderWid);
441 QVERIFY(!leader->isTransient());
442
443 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader}));
444
445 // Create another group member.
446 windowCreatedSpy.clear();
447 xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid);
448 xcb_map_window(conn.get(), member1Wid);
449 xcb_flush(conn.get());
450
451 QVERIFY(windowCreatedSpy.wait());
452 X11Window *member1 = windowCreatedSpy.first().first().value<X11Window *>();
453 QVERIFY(member1);
454 QVERIFY(member1->isActive());
455 QCOMPARE(member1->window(), member1Wid);
456 QCOMPARE(member1->group(), leader->group());
457 QVERIFY(!member1->isTransient());
458
459 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1}));
460
461 // Create yet another group member.
462 windowCreatedSpy.clear();
463 xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid);
464 xcb_map_window(conn.get(), member2Wid);
465 xcb_flush(conn.get());
466
467 QVERIFY(windowCreatedSpy.wait());
468 X11Window *member2 = windowCreatedSpy.first().first().value<X11Window *>();
469 QVERIFY(member2);
470 QVERIFY(member2->isActive());
471 QCOMPARE(member2->window(), member2Wid);
472 QCOMPARE(member2->group(), leader->group());
473 QVERIFY(!member2->isTransient());
474
475 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2}));
476
477 // Create a group transient.
478 windowCreatedSpy.clear();
479 xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid);
480 xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow());
481
482 // Currently, we have some weird bug workaround: if a group transient
483 // is a non-modal dialog, then it won't be kept above its window group.
484 // We need to explicitly specify window type, otherwise the window type
485 // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient
486 // for before (the EWMH spec says to do that).
487 xcb_atom_t net_wm_window_type = Xcb::Atom(
488 QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get());
489 xcb_atom_t net_wm_window_type_normal = Xcb::Atom(
490 QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get());
491 xcb_change_property(
492 conn.get(), // c
493 XCB_PROP_MODE_REPLACE, // mode
494 transientWid, // window
495 net_wm_window_type, // property
496 XCB_ATOM_ATOM, // type
497 32, // format
498 1, // data_len
499 &net_wm_window_type_normal // data
500 );
501
502 xcb_map_window(conn.get(), transientWid);
503 xcb_flush(conn.get());
504
505 QVERIFY(windowCreatedSpy.wait());
506 X11Window *transient = windowCreatedSpy.first().first().value<X11Window *>();
507 QVERIFY(transient);
508 QVERIFY(transient->isActive());
509 QCOMPARE(transient->window(), transientWid);
510 QCOMPARE(transient->group(), leader->group());
511 QVERIFY(transient->isTransient());
512 QVERIFY(transient->groupTransient());
513 QVERIFY(!transient->isDialog()); // See above why
514
515 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
516
517 // Create a Wayland window that is not a member of the window group.
518 std::unique_ptr<KWayland::Client::Surface> anotherSurface = Test::createSurface();
519 QVERIFY(anotherSurface);
520 std::unique_ptr<Test::XdgToplevel> anotherShellSurface(Test::createXdgToplevelSurface(anotherSurface.get(), anotherSurface.get()));
521 QVERIFY(anotherShellSurface);
522 Window *anotherWindow = Test::renderAndWaitForShown(anotherSurface.get(), QSize(128, 128), Qt::green);
523 QVERIFY(anotherWindow);
524 QVERIFY(anotherWindow->isActive());
525 QVERIFY(!anotherWindow->isTransient());
526
527 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient, anotherWindow}));
528
529 // If we activate the leader, then only it and the transient have to be raised.
530 workspace()->activateWindow(leader);
531 QTRY_VERIFY(leader->isActive());
532 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, member2, anotherWindow, leader, transient}));
533
534 // If another member of the window group is activated, then the transient will
535 // be above that member and the leader.
536 workspace()->activateWindow(member2);
537 QTRY_VERIFY(member2->isActive());
538 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, anotherWindow, leader, member2, transient}));
539
540 // FIXME: If we activate the transient, only it will be raised.
541 workspace()->activateWindow(anotherWindow);
542 QTRY_VERIFY(anotherWindow->isActive());
543 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, leader, member2, transient, anotherWindow}));
544
545 workspace()->activateWindow(transient);
546 QTRY_VERIFY(transient->isActive());
547 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, leader, member2, anotherWindow, transient}));
548}
549
550void StackingOrderTest::testDeletedGroupTransient()
551{
552 // This test verifies that deleted group transients are kept above their
553 // old window groups.
554
555 const QRect geometry = QRect(0, 0, 128, 128);
556
558
559 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
560
561 // Create the group leader.
562 xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry);
563 xcb_map_window(conn.get(), leaderWid);
564 xcb_flush(conn.get());
565
566 QVERIFY(windowCreatedSpy.wait());
567 X11Window *leader = windowCreatedSpy.first().first().value<X11Window *>();
568 QVERIFY(leader);
569 QVERIFY(leader->isActive());
570 QCOMPARE(leader->window(), leaderWid);
571 QVERIFY(!leader->isTransient());
572
573 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader}));
574
575 // Create another group member.
576 windowCreatedSpy.clear();
577 xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid);
578 xcb_map_window(conn.get(), member1Wid);
579 xcb_flush(conn.get());
580
581 QVERIFY(windowCreatedSpy.wait());
582 X11Window *member1 = windowCreatedSpy.first().first().value<X11Window *>();
583 QVERIFY(member1);
584 QVERIFY(member1->isActive());
585 QCOMPARE(member1->window(), member1Wid);
586 QCOMPARE(member1->group(), leader->group());
587 QVERIFY(!member1->isTransient());
588
589 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1}));
590
591 // Create yet another group member.
592 windowCreatedSpy.clear();
593 xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid);
594 xcb_map_window(conn.get(), member2Wid);
595 xcb_flush(conn.get());
596
597 QVERIFY(windowCreatedSpy.wait());
598 X11Window *member2 = windowCreatedSpy.first().first().value<X11Window *>();
599 QVERIFY(member2);
600 QVERIFY(member2->isActive());
601 QCOMPARE(member2->window(), member2Wid);
602 QCOMPARE(member2->group(), leader->group());
603 QVERIFY(!member2->isTransient());
604
605 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2}));
606
607 // Create a group transient.
608 windowCreatedSpy.clear();
609 xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid);
610 xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow());
611
612 // Currently, we have some weird bug workaround: if a group transient
613 // is a non-modal dialog, then it won't be kept above its window group.
614 // We need to explicitly specify window type, otherwise the window type
615 // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient
616 // for before (the EWMH spec says to do that).
617 xcb_atom_t net_wm_window_type = Xcb::Atom(
618 QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get());
619 xcb_atom_t net_wm_window_type_normal = Xcb::Atom(
620 QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get());
621 xcb_change_property(
622 conn.get(), // c
623 XCB_PROP_MODE_REPLACE, // mode
624 transientWid, // window
625 net_wm_window_type, // property
626 XCB_ATOM_ATOM, // type
627 32, // format
628 1, // data_len
629 &net_wm_window_type_normal // data
630 );
631
632 xcb_map_window(conn.get(), transientWid);
633 xcb_flush(conn.get());
634
635 QVERIFY(windowCreatedSpy.wait());
636 X11Window *transient = windowCreatedSpy.first().first().value<X11Window *>();
637 QVERIFY(transient);
638 QVERIFY(transient->isActive());
639 QCOMPARE(transient->window(), transientWid);
640 QCOMPARE(transient->group(), leader->group());
641 QVERIFY(transient->isTransient());
642 QVERIFY(transient->groupTransient());
643 QVERIFY(!transient->isDialog()); // See above why
644
645 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
646
647 // Unmap the transient.
648 connect(transient, &Window::closed, transient, &Window::ref);
649 auto cleanup = qScopeGuard([transient]() {
650 transient->unref();
651 });
652
653 QSignalSpy windowClosedSpy(transient, &X11Window::closed);
654 xcb_unmap_window(conn.get(), transientWid);
655 xcb_flush(conn.get());
656 QVERIFY(windowClosedSpy.wait());
657
658 // The transient has to be above each member of the window group.
659 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
660}
661
662void StackingOrderTest::testDontKeepAboveNonModalDialogGroupTransients()
663{
664 // Bug 76026
665
666 const QRect geometry = QRect(0, 0, 128, 128);
667
669
670 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
671
672 // Create the group leader.
673 xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry);
674 xcb_map_window(conn.get(), leaderWid);
675 xcb_flush(conn.get());
676
677 QVERIFY(windowCreatedSpy.wait());
678 X11Window *leader = windowCreatedSpy.first().first().value<X11Window *>();
679 QVERIFY(leader);
680 QVERIFY(leader->isActive());
681 QCOMPARE(leader->window(), leaderWid);
682 QVERIFY(!leader->isTransient());
683
684 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader}));
685
686 // Create another group member.
687 windowCreatedSpy.clear();
688 xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid);
689 xcb_map_window(conn.get(), member1Wid);
690 xcb_flush(conn.get());
691
692 QVERIFY(windowCreatedSpy.wait());
693 X11Window *member1 = windowCreatedSpy.first().first().value<X11Window *>();
694 QVERIFY(member1);
695 QVERIFY(member1->isActive());
696 QCOMPARE(member1->window(), member1Wid);
697 QCOMPARE(member1->group(), leader->group());
698 QVERIFY(!member1->isTransient());
699
700 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1}));
701
702 // Create yet another group member.
703 windowCreatedSpy.clear();
704 xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid);
705 xcb_map_window(conn.get(), member2Wid);
706 xcb_flush(conn.get());
707
708 QVERIFY(windowCreatedSpy.wait());
709 X11Window *member2 = windowCreatedSpy.first().first().value<X11Window *>();
710 QVERIFY(member2);
711 QVERIFY(member2->isActive());
712 QCOMPARE(member2->window(), member2Wid);
713 QCOMPARE(member2->group(), leader->group());
714 QVERIFY(!member2->isTransient());
715
716 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2}));
717
718 // Create a group transient.
719 windowCreatedSpy.clear();
720 xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid);
721 xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow());
722 xcb_map_window(conn.get(), transientWid);
723 xcb_flush(conn.get());
724
725 QVERIFY(windowCreatedSpy.wait());
726 X11Window *transient = windowCreatedSpy.first().first().value<X11Window *>();
727 QVERIFY(transient);
728 QVERIFY(transient->isActive());
729 QCOMPARE(transient->window(), transientWid);
730 QCOMPARE(transient->group(), leader->group());
731 QVERIFY(transient->isTransient());
732 QVERIFY(transient->groupTransient());
733 QVERIFY(transient->isDialog());
734 QVERIFY(!transient->isModal());
735
736 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
737
738 workspace()->activateWindow(leader);
739 QTRY_VERIFY(leader->isActive());
740 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member1, member2, transient, leader}));
741
742 workspace()->activateWindow(member1);
743 QTRY_VERIFY(member1->isActive());
744 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{member2, transient, leader, member1}));
745
746 workspace()->activateWindow(member2);
747 QTRY_VERIFY(member2->isActive());
748 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{transient, leader, member1, member2}));
749
750 workspace()->activateWindow(transient);
751 QTRY_VERIFY(transient->isActive());
752 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{leader, member1, member2, transient}));
753}
754
755void StackingOrderTest::testKeepAbove()
756{
757 // This test verifies that "keep-above" windows are kept above other windows.
758
759 // Create the first window.
760 std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
761 QVERIFY(surface1);
762 std::unique_ptr<Test::XdgToplevel> shellSurface1(Test::createXdgToplevelSurface(surface1.get(), surface1.get()));
763 QVERIFY(shellSurface1);
764 Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(128, 128), Qt::green);
765 QVERIFY(window1);
766 QVERIFY(window1->isActive());
767 QVERIFY(!window1->keepAbove());
768
769 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1}));
770
771 // Create the second window.
772 std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
773 QVERIFY(surface2);
774 std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get(), surface2.get()));
775 QVERIFY(shellSurface2);
776 Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(128, 128), Qt::green);
777 QVERIFY(window2);
778 QVERIFY(window2->isActive());
779 QVERIFY(!window2->keepAbove());
780
781 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1, window2}));
782
783 // Go to the initial test position.
784 workspace()->activateWindow(window1);
785 QTRY_VERIFY(window1->isActive());
786 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window2, window1}));
787
788 // Set the "keep-above" flag on the window2, it should go above other windows.
789 {
791 window2->setKeepAbove(true);
792 }
793
794 QVERIFY(window2->keepAbove());
795 QVERIFY(!window2->isActive());
796 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1, window2}));
797}
798
799void StackingOrderTest::testKeepBelow()
800{
801 // This test verifies that "keep-below" windows are kept below other windows.
802
803 // Create the first window.
804 std::unique_ptr<KWayland::Client::Surface> surface1 = Test::createSurface();
805 QVERIFY(surface1);
806 std::unique_ptr<Test::XdgToplevel> shellSurface1(Test::createXdgToplevelSurface(surface1.get(), surface1.get()));
807 QVERIFY(shellSurface1);
808 Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(128, 128), Qt::green);
809 QVERIFY(window1);
810 QVERIFY(window1->isActive());
811 QVERIFY(!window1->keepBelow());
812
813 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1}));
814
815 // Create the second window.
816 std::unique_ptr<KWayland::Client::Surface> surface2 = Test::createSurface();
817 QVERIFY(surface2);
818 std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get(), surface2.get()));
819 QVERIFY(shellSurface2);
820 Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(128, 128), Qt::green);
821 QVERIFY(window2);
822 QVERIFY(window2->isActive());
823 QVERIFY(!window2->keepBelow());
824
825 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window1, window2}));
826
827 // Set the "keep-below" flag on the window2, it should go below other windows.
828 {
830 window2->setKeepBelow(true);
831 }
832
833 QVERIFY(window2->isActive());
834 QVERIFY(window2->keepBelow());
835 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{window2, window1}));
836}
837
838void StackingOrderTest::testPreserveRelativeWindowStacking()
839{
840 // This test verifies that raising a window doesn't affect the order of transient windows that are constrained
841 // to be above it, see BUG: 477262
842
843 const int windowsQuantity = 5;
844
845 std::unique_ptr<KWayland::Client::Surface> surfaces[windowsQuantity];
846 std::unique_ptr<Test::XdgToplevel> shellSurfaces[windowsQuantity];
847 Window *windows[windowsQuantity];
848
849 // Create 5 windows.
850 for (int i = 0; i < windowsQuantity; i++) {
851 surfaces[i] = Test::createSurface();
852 QVERIFY(surfaces[i]);
853 shellSurfaces[i] = std::unique_ptr<Test::XdgToplevel>(Test::createXdgToplevelSurface(surfaces[i].get(), surfaces[i].get()));
854 QVERIFY(shellSurfaces[i]);
855 }
856
857 // link them into the following hierarchy:
858 // * 0 - parent to all
859 // * 1, 2, 3 - children of 0
860 // * 4 - child of 3
861 shellSurfaces[1]->set_parent(shellSurfaces[0]->object());
862 shellSurfaces[2]->set_parent(shellSurfaces[0]->object());
863 shellSurfaces[3]->set_parent(shellSurfaces[0]->object());
864 shellSurfaces[4]->set_parent(shellSurfaces[3]->object());
865
866 for (int i = 0; i < windowsQuantity; i++) {
867 windows[i] = Test::renderAndWaitForShown(surfaces[i].get(), QSize(128, 128), Qt::green);
868 QVERIFY(windows[i]);
869 }
870
871 // verify initial windows order
872 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{windows[0], windows[1], windows[2], windows[3], windows[4]}));
873
874 // activate parent
875 workspace()->activateWindow(windows[0]);
876 // verify that order hasn't changed
877 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{windows[0], windows[1], windows[2], windows[3], windows[4]}));
878
879 // change stacking order
880 workspace()->activateWindow(windows[2]);
881 workspace()->activateWindow(windows[1]);
882 // verify order
883 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{windows[0], windows[3], windows[4], windows[2], windows[1]}));
884
885 // activate parent
886 workspace()->activateWindow(windows[0]);
887 // verify that order hasn't changed
888 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{windows[0], windows[3], windows[4], windows[2], windows[1]}));
889
890 // activate child 3
891 workspace()->activateWindow(windows[3]);
892 // verify that both child 3 and 4 have been raised
893 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{windows[0], windows[2], windows[1], windows[3], windows[4]}));
894
895 // activate parent
896 workspace()->activateWindow(windows[0]);
897 // verify that order hasn't changed
898 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{windows[0], windows[2], windows[1], windows[3], windows[4]}));
899
900 // yet another check - add KeepAbove attribute to parent window (see BUG: 477262)
901 windows[0]->setKeepAbove(true);
902 // verify that order hasn't changed
903 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{windows[0], windows[2], windows[1], windows[3], windows[4]}));
904 // verify that child windows can still be restacked freely
905 workspace()->activateWindow(windows[1]);
906 workspace()->activateWindow(windows[2]);
907 QCOMPARE(workspace()->stackingOrder(), (QList<Window *>{windows[0], windows[3], windows[4], windows[1], windows[2]}));
908}
909
911#include "stacking_order_test.moc"
Xcb::Atom wm_client_leader
Definition atoms.h:30
bool isDialog() const
Definition window.h:1952
void unref()
Definition window.cpp:118
bool isModal() const
Definition window.cpp:2285
bool isActive() const
Definition window.h:882
void activateWindow(Window *window, bool force=false)
void windowAdded(KWin::Window *)
bool isTransient() const override
Definition x11window.h:560
xcb_window_t window() const
const Group * group() const override
Definition x11window.h:565
bool groupTransient() const override
Definition x11window.h:555
#define WAYLANDTEST_MAIN(TestObject)
Window * renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format=QImage::Format_ARGB32, int timeout=5000)
void destroyWaylandConnection()
void setOutputConfig(const QList< QRect > &geometries)
bool setupWaylandConnection(AdditionalWaylandInterfaces flags=AdditionalWaylandInterfaces())
XcbConnectionPtr createX11Connection()
std::unique_ptr< KWayland::Client::Surface > createSurface()
XdgToplevel * createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent=nullptr)
std::unique_ptr< xcb_connection_t, XcbConnectionDeleter > XcbConnectionPtr
KWIN_EXPORT xcb_window_t rootWindow()
Definition xcb.h:24
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
KWIN_EXPORT Atoms * atoms
Definition main.cpp:74