1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 #include "kwin_wayland_test.h"
10 #include "atoms.h"
11 #include "x11client.h"
12 #include "composite.h"
13 #include "effects.h"
14 #include "effectloader.h"
15 #include "cursor.h"
16 #include "deleted.h"
17 #include "platform.h"
18 #include "screens.h"
19 #include "wayland_server.h"
20 #include "workspace.h"
21 
22 #include <KWayland/Client/surface.h>
23 
24 #include <netwm.h>
25 #include <xcb/xcb_icccm.h>
26 
27 using namespace KWin;
28 using namespace KWayland::Client;
29 static const QString s_socketName = QStringLiteral("wayland_test_x11_client-0");
30 
31 class X11ClientTest : public QObject
32 {
33 Q_OBJECT
34 private Q_SLOTS:
35     void initTestCase();
36     void init();
37     void cleanup();
38 
39     void testMinimumSize();
40     void testMaximumSize();
41     void testResizeIncrements();
42     void testResizeIncrementsNoBaseSize();
43     void testTrimCaption_data();
44     void testTrimCaption();
45     void testFullscreenLayerWithActiveWaylandWindow();
46     void testFocusInWithWaylandLastActiveWindow();
47     void testX11WindowId();
48     void testCaptionChanges();
49     void testCaptionWmName();
50     void testCaptionMultipleWindows();
51     void testFullscreenWindowGroups();
52     void testActivateFocusedWindow();
53     void testReentrantMoveResize();
54 };
55 
initTestCase()56 void X11ClientTest::initTestCase()
57 {
58     qRegisterMetaType<KWin::Deleted*>();
59     qRegisterMetaType<KWin::AbstractClient*>();
60     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
61     QVERIFY(applicationStartedSpy.isValid());
62     kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
63     QVERIFY(waylandServer()->init(s_socketName));
64     kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
65 
66     kwinApp()->start();
67     QVERIFY(applicationStartedSpy.wait());
68     QVERIFY(KWin::Compositor::self());
69     Test::initWaylandWorkspace();
70 }
71 
init()72 void X11ClientTest::init()
73 {
74     QVERIFY(Test::setupWaylandConnection());
75 }
76 
cleanup()77 void X11ClientTest::cleanup()
78 {
79     Test::destroyWaylandConnection();
80 }
81 
82 struct XcbConnectionDeleter
83 {
cleanupXcbConnectionDeleter84     static inline void cleanup(xcb_connection_t *pointer)
85     {
86         xcb_disconnect(pointer);
87     }
88 };
89 
testMinimumSize()90 void X11ClientTest::testMinimumSize()
91 {
92     // This test verifies that the minimum size constraint is correctly applied.
93 
94     // Create an xcb window.
95     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
96     QVERIFY(!xcb_connection_has_error(c.data()));
97     const QRect windowGeometry(0, 0, 100, 200);
98     xcb_window_t w = xcb_generate_id(c.data());
99     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
100                       windowGeometry.x(),
101                       windowGeometry.y(),
102                       windowGeometry.width(),
103                       windowGeometry.height(),
104                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
105     xcb_size_hints_t hints;
106     memset(&hints, 0, sizeof(hints));
107     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
108     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
109     xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height());
110     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
111     xcb_map_window(c.data(), w);
112     xcb_flush(c.data());
113 
114     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
115     QVERIFY(windowCreatedSpy.isValid());
116     QVERIFY(windowCreatedSpy.wait());
117     X11Client *client = windowCreatedSpy.last().first().value<X11Client *>();
118     QVERIFY(client);
119     QVERIFY(client->isDecorated());
120 
121     QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
122     QVERIFY(clientStartMoveResizedSpy.isValid());
123     QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
124     QVERIFY(clientStepUserMovedResizedSpy.isValid());
125     QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
126     QVERIFY(clientFinishUserMovedResizedSpy.isValid());
127 
128     // Begin resize.
129     QCOMPARE(workspace()->moveResizeClient(), nullptr);
130     QVERIFY(!client->isInteractiveResize());
131     workspace()->slotWindowResize();
132     QCOMPARE(workspace()->moveResizeClient(), client);
133     QCOMPARE(clientStartMoveResizedSpy.count(), 1);
134     QVERIFY(client->isInteractiveResize());
135 
136     const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
137 
138     client->keyPressEvent(Qt::Key_Left);
139     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
140     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
141     QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
142     QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
143     QCOMPARE(client->clientSize().width(), 100);
144 
145     client->keyPressEvent(Qt::Key_Right);
146     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
147     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
148     QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
149     QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
150     QCOMPARE(client->clientSize().width(), 100);
151 
152     client->keyPressEvent(Qt::Key_Right);
153     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
154     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
155     QVERIFY(clientStepUserMovedResizedSpy.wait());
156     QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
157     QCOMPARE(client->clientSize().width(), 108);
158 
159     client->keyPressEvent(Qt::Key_Up);
160     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
161     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, -8));
162     QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
163     QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
164     QCOMPARE(client->clientSize().height(), 200);
165 
166     client->keyPressEvent(Qt::Key_Down);
167     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
168     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
169     QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
170     QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
171     QCOMPARE(client->clientSize().height(), 200);
172 
173     client->keyPressEvent(Qt::Key_Down);
174     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
175     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
176     QVERIFY(clientStepUserMovedResizedSpy.wait());
177     QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
178     QCOMPARE(client->clientSize().height(), 208);
179 
180     // Finish the resize operation.
181     QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
182     client->keyPressEvent(Qt::Key_Enter);
183     QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
184     QCOMPARE(workspace()->moveResizeClient(), nullptr);
185     QVERIFY(!client->isInteractiveResize());
186 
187     // Destroy the window.
188     QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
189     QVERIFY(windowClosedSpy.isValid());
190     xcb_unmap_window(c.data(), w);
191     xcb_destroy_window(c.data(), w);
192     xcb_flush(c.data());
193     QVERIFY(windowClosedSpy.wait());
194     c.reset();
195 }
196 
testMaximumSize()197 void X11ClientTest::testMaximumSize()
198 {
199     // This test verifies that the maximum size constraint is correctly applied.
200 
201     // Create an xcb window.
202     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
203     QVERIFY(!xcb_connection_has_error(c.data()));
204     const QRect windowGeometry(0, 0, 100, 200);
205     xcb_window_t w = xcb_generate_id(c.data());
206     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
207                       windowGeometry.x(),
208                       windowGeometry.y(),
209                       windowGeometry.width(),
210                       windowGeometry.height(),
211                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
212     xcb_size_hints_t hints;
213     memset(&hints, 0, sizeof(hints));
214     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
215     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
216     xcb_icccm_size_hints_set_max_size(&hints, windowGeometry.width(), windowGeometry.height());
217     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
218     xcb_map_window(c.data(), w);
219     xcb_flush(c.data());
220 
221     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
222     QVERIFY(windowCreatedSpy.isValid());
223     QVERIFY(windowCreatedSpy.wait());
224     X11Client *client = windowCreatedSpy.last().first().value<X11Client *>();
225     QVERIFY(client);
226     QVERIFY(client->isDecorated());
227 
228     QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
229     QVERIFY(clientStartMoveResizedSpy.isValid());
230     QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
231     QVERIFY(clientStepUserMovedResizedSpy.isValid());
232     QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
233     QVERIFY(clientFinishUserMovedResizedSpy.isValid());
234 
235     // Begin resize.
236     QCOMPARE(workspace()->moveResizeClient(), nullptr);
237     QVERIFY(!client->isInteractiveResize());
238     workspace()->slotWindowResize();
239     QCOMPARE(workspace()->moveResizeClient(), client);
240     QCOMPARE(clientStartMoveResizedSpy.count(), 1);
241     QVERIFY(client->isInteractiveResize());
242 
243     const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
244 
245     client->keyPressEvent(Qt::Key_Right);
246     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
247     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
248     QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
249     QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
250     QCOMPARE(client->clientSize().width(), 100);
251 
252     client->keyPressEvent(Qt::Key_Left);
253     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
254     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
255     QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
256     QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
257     QCOMPARE(client->clientSize().width(), 100);
258 
259     client->keyPressEvent(Qt::Key_Left);
260     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
261     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
262     QVERIFY(clientStepUserMovedResizedSpy.wait());
263     QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
264     QCOMPARE(client->clientSize().width(), 92);
265 
266     client->keyPressEvent(Qt::Key_Down);
267     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
268     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 8));
269     QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
270     QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
271     QCOMPARE(client->clientSize().height(), 200);
272 
273     client->keyPressEvent(Qt::Key_Up);
274     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
275     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
276     QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
277     QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
278     QCOMPARE(client->clientSize().height(), 200);
279 
280     client->keyPressEvent(Qt::Key_Up);
281     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
282     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, -8));
283     QVERIFY(clientStepUserMovedResizedSpy.wait());
284     QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
285     QCOMPARE(client->clientSize().height(), 192);
286 
287     // Finish the resize operation.
288     QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
289     client->keyPressEvent(Qt::Key_Enter);
290     QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
291     QCOMPARE(workspace()->moveResizeClient(), nullptr);
292     QVERIFY(!client->isInteractiveResize());
293 
294     // Destroy the window.
295     QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
296     QVERIFY(windowClosedSpy.isValid());
297     xcb_unmap_window(c.data(), w);
298     xcb_destroy_window(c.data(), w);
299     xcb_flush(c.data());
300     QVERIFY(windowClosedSpy.wait());
301     c.reset();
302 }
303 
testResizeIncrements()304 void X11ClientTest::testResizeIncrements()
305 {
306     // This test verifies that the resize increments constraint is correctly applied.
307 
308     // Create an xcb window.
309     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
310     QVERIFY(!xcb_connection_has_error(c.data()));
311     const QRect windowGeometry(0, 0, 100, 200);
312     xcb_window_t w = xcb_generate_id(c.data());
313     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
314                       windowGeometry.x(),
315                       windowGeometry.y(),
316                       windowGeometry.width(),
317                       windowGeometry.height(),
318                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
319     xcb_size_hints_t hints;
320     memset(&hints, 0, sizeof(hints));
321     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
322     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
323     xcb_icccm_size_hints_set_base_size(&hints, windowGeometry.width(), windowGeometry.height());
324     xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5);
325     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
326     xcb_map_window(c.data(), w);
327     xcb_flush(c.data());
328 
329     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
330     QVERIFY(windowCreatedSpy.isValid());
331     QVERIFY(windowCreatedSpy.wait());
332     X11Client *client = windowCreatedSpy.last().first().value<X11Client *>();
333     QVERIFY(client);
334     QVERIFY(client->isDecorated());
335 
336     QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
337     QVERIFY(clientStartMoveResizedSpy.isValid());
338     QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
339     QVERIFY(clientStepUserMovedResizedSpy.isValid());
340     QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
341     QVERIFY(clientFinishUserMovedResizedSpy.isValid());
342 
343     // Begin resize.
344     QCOMPARE(workspace()->moveResizeClient(), nullptr);
345     QVERIFY(!client->isInteractiveResize());
346     workspace()->slotWindowResize();
347     QCOMPARE(workspace()->moveResizeClient(), client);
348     QCOMPARE(clientStartMoveResizedSpy.count(), 1);
349     QVERIFY(client->isInteractiveResize());
350 
351     const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
352 
353     client->keyPressEvent(Qt::Key_Right);
354     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
355     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
356     QVERIFY(clientStepUserMovedResizedSpy.wait());
357     QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
358     QCOMPARE(client->clientSize(), QSize(106, 200));
359 
360     client->keyPressEvent(Qt::Key_Down);
361     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
362     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
363     QVERIFY(clientStepUserMovedResizedSpy.wait());
364     QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
365     QCOMPARE(client->clientSize(), QSize(106, 205));
366 
367     // Finish the resize operation.
368     QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
369     client->keyPressEvent(Qt::Key_Enter);
370     QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
371     QCOMPARE(workspace()->moveResizeClient(), nullptr);
372     QVERIFY(!client->isInteractiveResize());
373 
374     // Destroy the window.
375     QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
376     QVERIFY(windowClosedSpy.isValid());
377     xcb_unmap_window(c.data(), w);
378     xcb_destroy_window(c.data(), w);
379     xcb_flush(c.data());
380     QVERIFY(windowClosedSpy.wait());
381     c.reset();
382 }
383 
testResizeIncrementsNoBaseSize()384 void X11ClientTest::testResizeIncrementsNoBaseSize()
385 {
386     // Create an xcb window.
387     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
388     QVERIFY(!xcb_connection_has_error(c.data()));
389     const QRect windowGeometry(0, 0, 100, 200);
390     xcb_window_t w = xcb_generate_id(c.data());
391     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
392                       windowGeometry.x(),
393                       windowGeometry.y(),
394                       windowGeometry.width(),
395                       windowGeometry.height(),
396                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
397     xcb_size_hints_t hints;
398     memset(&hints, 0, sizeof(hints));
399     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
400     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
401     xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height());
402     xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5);
403     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
404     xcb_map_window(c.data(), w);
405     xcb_flush(c.data());
406 
407     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
408     QVERIFY(windowCreatedSpy.isValid());
409     QVERIFY(windowCreatedSpy.wait());
410     X11Client *client = windowCreatedSpy.last().first().value<X11Client *>();
411     QVERIFY(client);
412     QVERIFY(client->isDecorated());
413 
414     QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
415     QVERIFY(clientStartMoveResizedSpy.isValid());
416     QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
417     QVERIFY(clientStepUserMovedResizedSpy.isValid());
418     QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
419     QVERIFY(clientFinishUserMovedResizedSpy.isValid());
420 
421     // Begin resize.
422     QCOMPARE(workspace()->moveResizeClient(), nullptr);
423     QVERIFY(!client->isInteractiveResize());
424     workspace()->slotWindowResize();
425     QCOMPARE(workspace()->moveResizeClient(), client);
426     QCOMPARE(clientStartMoveResizedSpy.count(), 1);
427     QVERIFY(client->isInteractiveResize());
428 
429     const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
430 
431     client->keyPressEvent(Qt::Key_Right);
432     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
433     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
434     QVERIFY(clientStepUserMovedResizedSpy.wait());
435     QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
436     QCOMPARE(client->clientSize(), QSize(106, 200));
437 
438     client->keyPressEvent(Qt::Key_Down);
439     client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
440     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
441     QVERIFY(clientStepUserMovedResizedSpy.wait());
442     QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
443     QCOMPARE(client->clientSize(), QSize(106, 205));
444 
445     // Finish the resize operation.
446     QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
447     client->keyPressEvent(Qt::Key_Enter);
448     QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
449     QCOMPARE(workspace()->moveResizeClient(), nullptr);
450     QVERIFY(!client->isInteractiveResize());
451 
452     // Destroy the window.
453     QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
454     QVERIFY(windowClosedSpy.isValid());
455     xcb_unmap_window(c.data(), w);
456     xcb_destroy_window(c.data(), w);
457     xcb_flush(c.data());
458     QVERIFY(windowClosedSpy.wait());
459     c.reset();
460 }
461 
testTrimCaption_data()462 void X11ClientTest::testTrimCaption_data()
463 {
464     QTest::addColumn<QByteArray>("originalTitle");
465     QTest::addColumn<QByteArray>("expectedTitle");
466 
467     QTest::newRow("simplified")
468         << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")
469         << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben? – Marlies Hübner - Mozilla Firefox");
470 
471     QTest::newRow("with emojis")
472         << QByteArrayLiteral("\bTesting non\302\255printable:\177, emoij:\360\237\230\203, non-characters:\357\277\276")
473         << QByteArrayLiteral("Testing nonprintable:, emoij:\360\237\230\203, non-characters:");
474 }
475 
testTrimCaption()476 void X11ClientTest::testTrimCaption()
477 {
478     // this test verifies that caption is properly trimmed
479 
480     // create an xcb window
481     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
482     QVERIFY(!xcb_connection_has_error(c.data()));
483     const QRect windowGeometry(0, 0, 100, 200);
484     xcb_window_t w = xcb_generate_id(c.data());
485     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
486                       windowGeometry.x(),
487                       windowGeometry.y(),
488                       windowGeometry.width(),
489                       windowGeometry.height(),
490                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
491     xcb_size_hints_t hints;
492     memset(&hints, 0, sizeof(hints));
493     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
494     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
495     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
496     NETWinInfo winInfo(c.data(), w, rootWindow(), NET::Properties(), NET::Properties2());
497     QFETCH(QByteArray, originalTitle);
498     winInfo.setName(originalTitle);
499     xcb_map_window(c.data(), w);
500     xcb_flush(c.data());
501 
502     // we should get a client for it
503     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
504     QVERIFY(windowCreatedSpy.isValid());
505     QVERIFY(windowCreatedSpy.wait());
506     X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
507     QVERIFY(client);
508     QCOMPARE(client->window(), w);
509     QFETCH(QByteArray, expectedTitle);
510     QCOMPARE(client->caption(), QString::fromUtf8(expectedTitle));
511 
512     // and destroy the window again
513     xcb_unmap_window(c.data(), w);
514     xcb_flush(c.data());
515 
516     QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
517     QVERIFY(windowClosedSpy.isValid());
518     QVERIFY(windowClosedSpy.wait());
519     xcb_destroy_window(c.data(), w);
520     c.reset();
521 }
522 
testFullscreenLayerWithActiveWaylandWindow()523 void X11ClientTest::testFullscreenLayerWithActiveWaylandWindow()
524 {
525     // this test verifies that an X11 fullscreen window does not stay in the active layer
526     // when a Wayland window is active, see BUG: 375759
527     QCOMPARE(screens()->count(), 1);
528 
529     // first create an X11 window
530     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
531     QVERIFY(!xcb_connection_has_error(c.data()));
532     const QRect windowGeometry(0, 0, 100, 200);
533     xcb_window_t w = xcb_generate_id(c.data());
534     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
535                       windowGeometry.x(),
536                       windowGeometry.y(),
537                       windowGeometry.width(),
538                       windowGeometry.height(),
539                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
540     xcb_size_hints_t hints;
541     memset(&hints, 0, sizeof(hints));
542     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
543     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
544     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
545     xcb_map_window(c.data(), w);
546     xcb_flush(c.data());
547 
548     // we should get a client for it
549     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
550     QVERIFY(windowCreatedSpy.isValid());
551     QVERIFY(windowCreatedSpy.wait());
552     X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
553     QVERIFY(client);
554     QCOMPARE(client->window(), w);
555     QVERIFY(!client->isFullScreen());
556     QVERIFY(client->isActive());
557     QCOMPARE(client->layer(), NormalLayer);
558 
559     workspace()->slotWindowFullScreen();
560     QVERIFY(client->isFullScreen());
561     QCOMPARE(client->layer(), ActiveLayer);
562     QCOMPARE(workspace()->stackingOrder().last(), client);
563 
564     // now let's open a Wayland window
565     QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
566     QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
567     auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
568     QVERIFY(waylandClient);
569     QVERIFY(waylandClient->isActive());
570     QCOMPARE(waylandClient->layer(), NormalLayer);
571     QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
572     QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
573     QCOMPARE(client->layer(), NormalLayer);
574 
575     // now activate fullscreen again
576     workspace()->activateClient(client);
577     QTRY_VERIFY(client->isActive());
578     QCOMPARE(client->layer(), ActiveLayer);
579     QCOMPARE(workspace()->stackingOrder().last(), client);
580     QCOMPARE(workspace()->stackingOrder().last(), client);
581 
582     // activate wayland window again
583     workspace()->activateClient(waylandClient);
584     QTRY_VERIFY(waylandClient->isActive());
585     QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
586     QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
587 
588     // back to x window
589     workspace()->activateClient(client);
590     QTRY_VERIFY(client->isActive());
591     // remove fullscreen
592     QVERIFY(client->isFullScreen());
593     workspace()->slotWindowFullScreen();
594     QVERIFY(!client->isFullScreen());
595     // and fullscreen again
596     workspace()->slotWindowFullScreen();
597     QVERIFY(client->isFullScreen());
598     QCOMPARE(workspace()->stackingOrder().last(), client);
599     QCOMPARE(workspace()->stackingOrder().last(), client);
600 
601     // activate wayland window again
602     workspace()->activateClient(waylandClient);
603     QTRY_VERIFY(waylandClient->isActive());
604     QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
605     QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
606 
607     // back to X11 window
608     workspace()->activateClient(client);
609     QTRY_VERIFY(client->isActive());
610     // remove fullscreen
611     QVERIFY(client->isFullScreen());
612     workspace()->slotWindowFullScreen();
613     QVERIFY(!client->isFullScreen());
614     // and fullscreen through X API
615     NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
616     info.setState(NET::FullScreen, NET::FullScreen);
617     NETRootInfo rootInfo(c.data(), NET::Properties());
618     rootInfo.setActiveWindow(w, NET::FromApplication, XCB_CURRENT_TIME, XCB_WINDOW_NONE);
619     xcb_flush(c.data());
620     QTRY_VERIFY(client->isFullScreen());
621     QCOMPARE(workspace()->stackingOrder().last(), client);
622     QCOMPARE(workspace()->stackingOrder().last(), client);
623 
624     // activate wayland window again
625     workspace()->activateClient(waylandClient);
626     QTRY_VERIFY(waylandClient->isActive());
627     QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
628     QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
629     QCOMPARE(client->layer(), NormalLayer);
630 
631     // close the window
632     shellSurface.reset();
633     surface.reset();
634     QVERIFY(Test::waitForWindowDestroyed(waylandClient));
635     QTRY_VERIFY(client->isActive());
636     QCOMPARE(client->layer(), ActiveLayer);
637 
638     // and destroy the window again
639     xcb_unmap_window(c.data(), w);
640     xcb_flush(c.data());
641 }
642 
testFocusInWithWaylandLastActiveWindow()643 void X11ClientTest::testFocusInWithWaylandLastActiveWindow()
644 {
645     // this test verifies that Workspace::allowClientActivation does not crash if last client was a Wayland client
646 
647     // create an X11 window
648     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
649     QVERIFY(!xcb_connection_has_error(c.data()));
650     const QRect windowGeometry(0, 0, 100, 200);
651     xcb_window_t w = xcb_generate_id(c.data());
652     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
653                       windowGeometry.x(),
654                       windowGeometry.y(),
655                       windowGeometry.width(),
656                       windowGeometry.height(),
657                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
658     xcb_size_hints_t hints;
659     memset(&hints, 0, sizeof(hints));
660     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
661     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
662     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
663     xcb_map_window(c.data(), w);
664     xcb_flush(c.data());
665 
666     // we should get a client for it
667     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
668     QVERIFY(windowCreatedSpy.isValid());
669     QVERIFY(windowCreatedSpy.wait());
670     X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
671     QVERIFY(client);
672     QCOMPARE(client->window(), w);
673     QVERIFY(client->isActive());
674 
675     // create Wayland window
676     QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
677     QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
678     auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
679     QVERIFY(waylandClient);
680     QVERIFY(waylandClient->isActive());
681     // activate no window
682     workspace()->setActiveClient(nullptr);
683     QVERIFY(!waylandClient->isActive());
684     QVERIFY(!workspace()->activeClient());
685     // and close Wayland window again
686     shellSurface.reset();
687     surface.reset();
688     QVERIFY(Test::waitForWindowDestroyed(waylandClient));
689 
690     // and try to activate the x11 client through X11 api
691     const auto cookie = xcb_set_input_focus_checked(c.data(), XCB_INPUT_FOCUS_NONE, w, XCB_CURRENT_TIME);
692     auto error = xcb_request_check(c.data(), cookie);
693     QVERIFY(!error);
694     // this accesses last_active_client on trying to activate
695     QTRY_VERIFY(client->isActive());
696 
697     // and destroy the window again
698     xcb_unmap_window(c.data(), w);
699     xcb_flush(c.data());
700 }
701 
testX11WindowId()702 void X11ClientTest::testX11WindowId()
703 {
704     // create an X11 window
705     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
706     QVERIFY(!xcb_connection_has_error(c.data()));
707     const QRect windowGeometry(0, 0, 100, 200);
708     xcb_window_t w = xcb_generate_id(c.data());
709     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
710                       windowGeometry.x(),
711                       windowGeometry.y(),
712                       windowGeometry.width(),
713                       windowGeometry.height(),
714                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
715     xcb_size_hints_t hints;
716     memset(&hints, 0, sizeof(hints));
717     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
718     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
719     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
720     xcb_map_window(c.data(), w);
721     xcb_flush(c.data());
722 
723     // we should get a client for it
724     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
725     QVERIFY(windowCreatedSpy.isValid());
726     QVERIFY(windowCreatedSpy.wait());
727     X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
728     QVERIFY(client);
729     QCOMPARE(client->window(), w);
730     QVERIFY(client->isActive());
731     QCOMPARE(client->window(), w);
732     QCOMPARE(client->internalId().isNull(), false);
733     const auto uuid = client->internalId();
734     QUuid deletedUuid;
735     QCOMPARE(deletedUuid.isNull(), true);
736 
737     connect(client, &X11Client::windowClosed, this, [&deletedUuid] (Toplevel *, Deleted *d) { deletedUuid = d->internalId(); });
738 
739 
740     NETRootInfo rootInfo(c.data(), NET::WMAllProperties);
741     QCOMPARE(rootInfo.activeWindow(), client->window());
742 
743     // activate a wayland window
744     QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
745     QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
746     auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
747     QVERIFY(waylandClient);
748     QVERIFY(waylandClient->isActive());
749     xcb_flush(kwinApp()->x11Connection());
750 
751     NETRootInfo rootInfo2(c.data(), NET::WMAllProperties);
752     QCOMPARE(rootInfo2.activeWindow(), 0u);
753 
754     // back to X11 client
755     shellSurface.reset();
756     surface.reset();
757     QVERIFY(Test::waitForWindowDestroyed(waylandClient));
758 
759     QTRY_VERIFY(client->isActive());
760     NETRootInfo rootInfo3(c.data(), NET::WMAllProperties);
761     QCOMPARE(rootInfo3.activeWindow(), client->window());
762 
763     // and destroy the window again
764     xcb_unmap_window(c.data(), w);
765     xcb_flush(c.data());
766     QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
767     QVERIFY(windowClosedSpy.isValid());
768     QVERIFY(windowClosedSpy.wait());
769 
770     QCOMPARE(deletedUuid.isNull(), false);
771     QCOMPARE(deletedUuid, uuid);
772 }
773 
testCaptionChanges()774 void X11ClientTest::testCaptionChanges()
775 {
776     // verifies that caption is updated correctly when the X11 window updates it
777     // BUG: 383444
778     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
779     QVERIFY(!xcb_connection_has_error(c.data()));
780     const QRect windowGeometry(0, 0, 100, 200);
781     xcb_window_t w = xcb_generate_id(c.data());
782     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
783                       windowGeometry.x(),
784                       windowGeometry.y(),
785                       windowGeometry.width(),
786                       windowGeometry.height(),
787                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
788     xcb_size_hints_t hints;
789     memset(&hints, 0, sizeof(hints));
790     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
791     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
792     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
793     NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
794     info.setName("foo");
795     xcb_map_window(c.data(), w);
796     xcb_flush(c.data());
797 
798     // we should get a client for it
799     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
800     QVERIFY(windowCreatedSpy.isValid());
801     QVERIFY(windowCreatedSpy.wait());
802     X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
803     QVERIFY(client);
804     QCOMPARE(client->window(), w);
805     QCOMPARE(client->caption(), QStringLiteral("foo"));
806 
807     QSignalSpy captionChangedSpy(client, &X11Client::captionChanged);
808     QVERIFY(captionChangedSpy.isValid());
809     info.setName("bar");
810     xcb_flush(c.data());
811     QVERIFY(captionChangedSpy.wait());
812     QCOMPARE(client->caption(), QStringLiteral("bar"));
813 
814     // and destroy the window again
815     QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
816     QVERIFY(windowClosedSpy.isValid());
817     xcb_unmap_window(c.data(), w);
818     xcb_flush(c.data());
819     QVERIFY(windowClosedSpy.wait());
820     xcb_destroy_window(c.data(), w);
821     c.reset();
822 }
823 
testCaptionWmName()824 void X11ClientTest::testCaptionWmName()
825 {
826     // this test verifies that a caption set through WM_NAME is read correctly
827 
828     // open glxgears as that one only uses WM_NAME
829     QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded);
830     QVERIFY(clientAddedSpy.isValid());
831 
832     QProcess glxgears;
833     glxgears.setProgram(QStringLiteral("glxgears"));
834     glxgears.start();
835     QVERIFY(glxgears.waitForStarted());
836 
837     QVERIFY(clientAddedSpy.wait());
838     QCOMPARE(clientAddedSpy.count(), 1);
839     QCOMPARE(workspace()->clientList().count(), 1);
840     X11Client *glxgearsClient = workspace()->clientList().first();
841     QCOMPARE(glxgearsClient->caption(), QStringLiteral("glxgears"));
842 
843     glxgears.terminate();
844     QVERIFY(glxgears.waitForFinished());
845 }
846 
testCaptionMultipleWindows()847 void X11ClientTest::testCaptionMultipleWindows()
848 {
849     // BUG 384760
850     // create first window
851     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
852     QVERIFY(!xcb_connection_has_error(c.data()));
853     const QRect windowGeometry(0, 0, 100, 200);
854     xcb_window_t w = xcb_generate_id(c.data());
855     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
856                       windowGeometry.x(),
857                       windowGeometry.y(),
858                       windowGeometry.width(),
859                       windowGeometry.height(),
860                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
861     xcb_size_hints_t hints;
862     memset(&hints, 0, sizeof(hints));
863     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
864     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
865     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
866     NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
867     info.setName("foo");
868     xcb_map_window(c.data(), w);
869     xcb_flush(c.data());
870 
871     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
872     QVERIFY(windowCreatedSpy.isValid());
873     QVERIFY(windowCreatedSpy.wait());
874     X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
875     QVERIFY(client);
876     QCOMPARE(client->window(), w);
877     QCOMPARE(client->caption(), QStringLiteral("foo"));
878 
879     // create second window with same caption
880     xcb_window_t w2 = xcb_generate_id(c.data());
881     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(),
882                       windowGeometry.x(),
883                       windowGeometry.y(),
884                       windowGeometry.width(),
885                       windowGeometry.height(),
886                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
887     xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints);
888     NETWinInfo info2(c.data(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
889     info2.setName("foo");
890     info2.setIconName("foo");
891     xcb_map_window(c.data(), w2);
892     xcb_flush(c.data());
893 
894     windowCreatedSpy.clear();
895     QVERIFY(windowCreatedSpy.wait());
896     X11Client *client2 = windowCreatedSpy.first().first().value<X11Client *>();
897     QVERIFY(client2);
898     QCOMPARE(client2->window(), w2);
899     QCOMPARE(client2->caption(), QStringLiteral("foo <2>\u200E"));
900     NETWinInfo info3(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2());
901     QCOMPARE(QByteArray(info3.visibleName()), QByteArrayLiteral("foo <2>\u200E"));
902     QCOMPARE(QByteArray(info3.visibleIconName()), QByteArrayLiteral("foo <2>\u200E"));
903 
904     QSignalSpy captionChangedSpy(client2, &X11Client::captionChanged);
905     QVERIFY(captionChangedSpy.isValid());
906 
907     NETWinInfo info4(c.data(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
908     info4.setName("foobar");
909     info4.setIconName("foobar");
910     xcb_map_window(c.data(), w2);
911     xcb_flush(c.data());
912 
913     QVERIFY(captionChangedSpy.wait());
914     QCOMPARE(client2->caption(), QStringLiteral("foobar"));
915     NETWinInfo info5(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2());
916     QCOMPARE(QByteArray(info5.visibleName()), QByteArray());
917     QTRY_COMPARE(QByteArray(info5.visibleIconName()), QByteArray());
918 }
919 
920 
testFullscreenWindowGroups()921 void X11ClientTest::testFullscreenWindowGroups()
922 {
923     // this test creates an X11 window and puts it to full screen
924     // then a second window is created which is in the same window group
925     // BUG: 388310
926 
927     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
928     QVERIFY(!xcb_connection_has_error(c.data()));
929     const QRect windowGeometry(0, 0, 100, 200);
930     xcb_window_t w = xcb_generate_id(c.data());
931     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
932                       windowGeometry.x(),
933                       windowGeometry.y(),
934                       windowGeometry.width(),
935                       windowGeometry.height(),
936                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
937     xcb_size_hints_t hints;
938     memset(&hints, 0, sizeof(hints));
939     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
940     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
941     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
942     xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
943     xcb_map_window(c.data(), w);
944     xcb_flush(c.data());
945 
946     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
947     QVERIFY(windowCreatedSpy.isValid());
948     QVERIFY(windowCreatedSpy.wait());
949     X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
950     QVERIFY(client);
951     QCOMPARE(client->window(), w);
952     QCOMPARE(client->isActive(), true);
953 
954     QCOMPARE(client->isFullScreen(), false);
955     QCOMPARE(client->layer(), NormalLayer);
956     workspace()->slotWindowFullScreen();
957     QCOMPARE(client->isFullScreen(), true);
958     QCOMPARE(client->layer(), ActiveLayer);
959 
960     // now let's create a second window
961     windowCreatedSpy.clear();
962     xcb_window_t w2 = xcb_generate_id(c.data());
963     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(),
964                       windowGeometry.x(),
965                       windowGeometry.y(),
966                       windowGeometry.width(),
967                       windowGeometry.height(),
968                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
969     xcb_size_hints_t hints2;
970     memset(&hints2, 0, sizeof(hints2));
971     xcb_icccm_size_hints_set_position(&hints2, 1, windowGeometry.x(), windowGeometry.y());
972     xcb_icccm_size_hints_set_size(&hints2, 1, windowGeometry.width(), windowGeometry.height());
973     xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints2);
974     xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w2, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
975     xcb_map_window(c.data(), w2);
976     xcb_flush(c.data());
977 
978     QVERIFY(windowCreatedSpy.wait());
979     X11Client *client2 = windowCreatedSpy.first().first().value<X11Client *>();
980     QVERIFY(client2);
981     QVERIFY(client != client2);
982     QCOMPARE(client2->window(), w2);
983     QCOMPARE(client2->isActive(), true);
984     QCOMPARE(client2->group(), client->group());
985     // first client should be moved back to normal layer
986     QCOMPARE(client->isActive(), false);
987     QCOMPARE(client->isFullScreen(), true);
988     QCOMPARE(client->layer(), NormalLayer);
989 
990     // activating the fullscreen window again, should move it to active layer
991     workspace()->activateClient(client);
992     QTRY_COMPARE(client->layer(), ActiveLayer);
993 }
994 
testActivateFocusedWindow()995 void X11ClientTest::testActivateFocusedWindow()
996 {
997     // The window manager may call XSetInputFocus() on a window that already has focus, in which
998     // case no FocusIn event will be generated and the window won't be marked as active. This test
999     // verifies that we handle that subtle case properly.
1000 
1001     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> connection(xcb_connect(nullptr, nullptr));
1002     QVERIFY(!xcb_connection_has_error(connection.data()));
1003 
1004     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
1005     QVERIFY(windowCreatedSpy.isValid());
1006 
1007     const QRect windowGeometry(0, 0, 100, 200);
1008     xcb_size_hints_t hints;
1009     memset(&hints, 0, sizeof(hints));
1010     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
1011     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
1012 
1013     // Create the first test window.
1014     const xcb_window_t window1 = xcb_generate_id(connection.data());
1015     xcb_create_window(connection.data(), XCB_COPY_FROM_PARENT, window1, rootWindow(),
1016                       windowGeometry.x(), windowGeometry.y(),
1017                       windowGeometry.width(), windowGeometry.height(),
1018                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
1019     xcb_icccm_set_wm_normal_hints(connection.data(), window1, &hints);
1020     xcb_change_property(connection.data(), XCB_PROP_MODE_REPLACE, window1,
1021                         atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &window1);
1022     xcb_map_window(connection.data(), window1);
1023     xcb_flush(connection.data());
1024     QVERIFY(windowCreatedSpy.wait());
1025     X11Client *client1 = windowCreatedSpy.first().first().value<X11Client *>();
1026     QVERIFY(client1);
1027     QCOMPARE(client1->window(), window1);
1028     QCOMPARE(client1->isActive(), true);
1029 
1030     // Create the second test window.
1031     const xcb_window_t window2 = xcb_generate_id(connection.data());
1032     xcb_create_window(connection.data(), XCB_COPY_FROM_PARENT, window2, rootWindow(),
1033                       windowGeometry.x(), windowGeometry.y(),
1034                       windowGeometry.width(), windowGeometry.height(),
1035                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
1036     xcb_icccm_set_wm_normal_hints(connection.data(), window2, &hints);
1037     xcb_change_property(connection.data(), XCB_PROP_MODE_REPLACE, window2,
1038                         atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &window2);
1039     xcb_map_window(connection.data(), window2);
1040     xcb_flush(connection.data());
1041     QVERIFY(windowCreatedSpy.wait());
1042     X11Client *client2 = windowCreatedSpy.last().first().value<X11Client *>();
1043     QVERIFY(client2);
1044     QCOMPARE(client2->window(), window2);
1045     QCOMPARE(client2->isActive(), true);
1046 
1047     // When the second test window is destroyed, the window manager will attempt to activate the
1048     // next client in the focus chain, which is the first window.
1049     xcb_set_input_focus(connection.data(), XCB_INPUT_FOCUS_POINTER_ROOT, window1, XCB_CURRENT_TIME);
1050     xcb_destroy_window(connection.data(), window2);
1051     xcb_flush(connection.data());
1052     QVERIFY(Test::waitForWindowDestroyed(client2));
1053     QVERIFY(client1->isActive());
1054 
1055     // Destroy the first test window.
1056     xcb_destroy_window(connection.data(), window1);
1057     xcb_flush(connection.data());
1058     QVERIFY(Test::waitForWindowDestroyed(client1));
1059 }
1060 
testReentrantMoveResize()1061 void X11ClientTest::testReentrantMoveResize()
1062 {
1063     // This test verifies that calling moveResize() from a slot connected directly
1064     // to the frameGeometryChanged() signal won't cause an infinite recursion.
1065 
1066     // Create a test window.
1067     QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
1068     QVERIFY(!xcb_connection_has_error(c.data()));
1069     const QRect windowGeometry(0, 0, 100, 200);
1070     xcb_window_t w = xcb_generate_id(c.data());
1071     xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
1072                       windowGeometry.x(),
1073                       windowGeometry.y(),
1074                       windowGeometry.width(),
1075                       windowGeometry.height(),
1076                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
1077     xcb_size_hints_t hints;
1078     memset(&hints, 0, sizeof(hints));
1079     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
1080     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
1081     xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
1082     xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
1083     xcb_map_window(c.data(), w);
1084     xcb_flush(c.data());
1085 
1086     QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
1087     QVERIFY(windowCreatedSpy.isValid());
1088     QVERIFY(windowCreatedSpy.wait());
1089     X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
1090     QVERIFY(client);
1091     QCOMPARE(client->pos(), QPoint(0, 0));
1092 
1093     // Let's pretend that there is a script that really wants the client to be at (100, 100).
1094     connect(client, &AbstractClient::frameGeometryChanged, this, [client]() {
1095         client->moveResize(QRect(QPoint(100, 100), client->size()));
1096     });
1097 
1098     // Trigger the lambda above.
1099     client->move(QPoint(40, 50));
1100 
1101     // Eventually, the client will end up at (100, 100).
1102     QCOMPARE(client->pos(), QPoint(100, 100));
1103 
1104     // Destroy the test window.
1105     xcb_destroy_window(c.data(), w);
1106     xcb_flush(c.data());
1107     QVERIFY(Test::waitForWindowDestroyed(client));
1108 }
1109 
1110 WAYLANDTEST_MAIN(X11ClientTest)
1111 #include "x11_client_test.moc"
1112