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