x11_client_test.cpp 47 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
    SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
6

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
7
8
    SPDX-License-Identifier: GPL-2.0-or-later
*/
9
#include "kwin_wayland_test.h"
10
#include "atoms.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
11
#include "x11client.h"
12
13
14
15
#include "composite.h"
#include "effects.h"
#include "effectloader.h"
#include "cursor.h"
16
#include "deleted.h"
17
#include "platform.h"
18
#include "screens.h"
19
20
21
#include "wayland_server.h"
#include "workspace.h"

22
23
#include <KWayland/Client/surface.h>

24
25
26
27
#include <netwm.h>
#include <xcb/xcb_icccm.h>

using namespace KWin;
28
using namespace KWayland::Client;
29
30
31
32
33
34
35
36
37
38
static const QString s_socketName = QStringLiteral("wayland_test_x11_client-0");

class X11ClientTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
    void initTestCase();
    void init();
    void cleanup();

39
40
41
42
    void testMinimumSize();
    void testMaximumSize();
    void testResizeIncrements();
    void testResizeIncrementsNoBaseSize();
43
44
    void testTrimCaption_data();
    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 testReentrantSetFrameGeometry();
54
55
56
57
};

void X11ClientTest::initTestCase()
{
58
    qRegisterMetaType<KWin::Deleted*>();
59
    qRegisterMetaType<KWin::AbstractClient*>();
60
61
    QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
    QVERIFY(applicationStartedSpy.isValid());
62
63
    kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
    QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit()));
64
    kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
65
66

    kwinApp()->start();
67
    QVERIFY(applicationStartedSpy.wait());
68
69
    QVERIFY(KWin::Compositor::self());
    waylandServer()->initWorkspace();
70
71
72
73
}

void X11ClientTest::init()
{
74
    QVERIFY(Test::setupWaylandConnection());
75
76
77
78
}

void X11ClientTest::cleanup()
{
79
    Test::destroyWaylandConnection();
80
81
82
83
84
85
86
87
88
89
}

struct XcbConnectionDeleter
{
    static inline void cleanup(xcb_connection_t *pointer)
    {
        xcb_disconnect(pointer);
    }
};

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
void X11ClientTest::testMinimumSize()
{
    // This test verifies that the minimum size constraint is correctly applied.

    // Create an xcb window.
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
    X11Client *client = windowCreatedSpy.last().first().value<X11Client *>();
    QVERIFY(client);
    QVERIFY(client->isDecorated());

    QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
    QVERIFY(clientStartMoveResizedSpy.isValid());
    QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
    QVERIFY(clientStepUserMovedResizedSpy.isValid());
    QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
    QVERIFY(clientFinishUserMovedResizedSpy.isValid());

    // Begin resize.
    QCOMPARE(workspace()->moveResizeClient(), nullptr);
    QVERIFY(!client->isResize());
    workspace()->slotWindowResize();
    QCOMPARE(workspace()->moveResizeClient(), client);
    QCOMPARE(clientStartMoveResizedSpy.count(), 1);
    QVERIFY(client->isResize());

136
    const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
137
138

    client->keyPressEvent(Qt::Key_Left);
139
140
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
141
142
143
144
145
    QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
    QCOMPARE(client->clientSize().width(), 100);

    client->keyPressEvent(Qt::Key_Right);
146
147
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
148
149
150
151
152
    QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
    QCOMPARE(client->clientSize().width(), 100);

    client->keyPressEvent(Qt::Key_Right);
153
154
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
155
156
157
158
159
    QVERIFY(clientStepUserMovedResizedSpy.wait());
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
    QCOMPARE(client->clientSize().width(), 108);

    client->keyPressEvent(Qt::Key_Up);
160
161
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, -8));
162
163
164
165
166
    QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
    QCOMPARE(client->clientSize().height(), 200);

    client->keyPressEvent(Qt::Key_Down);
167
168
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
169
170
171
172
173
    QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
    QCOMPARE(client->clientSize().height(), 200);

    client->keyPressEvent(Qt::Key_Down);
174
175
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    QVERIFY(clientStepUserMovedResizedSpy.wait());
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
    QCOMPARE(client->clientSize().height(), 208);

    // Finish the resize operation.
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
    client->keyPressEvent(Qt::Key_Enter);
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
    QCOMPARE(workspace()->moveResizeClient(), nullptr);
    QVERIFY(!client->isResize());

    // Destroy the window.
    QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
    QVERIFY(windowClosedSpy.isValid());
    xcb_unmap_window(c.data(), w);
    xcb_destroy_window(c.data(), w);
    xcb_flush(c.data());
    QVERIFY(windowClosedSpy.wait());
    c.reset();
}

void X11ClientTest::testMaximumSize()
{
    // This test verifies that the maximum size constraint is correctly applied.

    // Create an xcb window.
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_size_hints_set_max_size(&hints, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
    X11Client *client = windowCreatedSpy.last().first().value<X11Client *>();
    QVERIFY(client);
    QVERIFY(client->isDecorated());

    QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
    QVERIFY(clientStartMoveResizedSpy.isValid());
    QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
    QVERIFY(clientStepUserMovedResizedSpy.isValid());
    QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
    QVERIFY(clientFinishUserMovedResizedSpy.isValid());

    // Begin resize.
    QCOMPARE(workspace()->moveResizeClient(), nullptr);
    QVERIFY(!client->isResize());
    workspace()->slotWindowResize();
    QCOMPARE(workspace()->moveResizeClient(), client);
    QCOMPARE(clientStartMoveResizedSpy.count(), 1);
    QVERIFY(client->isResize());

243
    const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
244
245

    client->keyPressEvent(Qt::Key_Right);
246
247
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
248
249
250
251
252
    QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
    QCOMPARE(client->clientSize().width(), 100);

    client->keyPressEvent(Qt::Key_Left);
253
254
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
255
256
257
258
259
    QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
    QCOMPARE(client->clientSize().width(), 100);

    client->keyPressEvent(Qt::Key_Left);
260
261
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
262
263
264
265
266
    QVERIFY(clientStepUserMovedResizedSpy.wait());
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
    QCOMPARE(client->clientSize().width(), 92);

    client->keyPressEvent(Qt::Key_Down);
267
268
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 8));
269
270
271
272
273
    QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
    QCOMPARE(client->clientSize().height(), 200);

    client->keyPressEvent(Qt::Key_Up);
274
275
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
276
277
278
279
280
    QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
    QCOMPARE(client->clientSize().height(), 200);

    client->keyPressEvent(Qt::Key_Up);
281
282
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, -8));
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
    QVERIFY(clientStepUserMovedResizedSpy.wait());
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
    QCOMPARE(client->clientSize().height(), 192);

    // Finish the resize operation.
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
    client->keyPressEvent(Qt::Key_Enter);
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
    QCOMPARE(workspace()->moveResizeClient(), nullptr);
    QVERIFY(!client->isResize());

    // Destroy the window.
    QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
    QVERIFY(windowClosedSpy.isValid());
    xcb_unmap_window(c.data(), w);
    xcb_destroy_window(c.data(), w);
    xcb_flush(c.data());
    QVERIFY(windowClosedSpy.wait());
    c.reset();
}

void X11ClientTest::testResizeIncrements()
{
    // This test verifies that the resize increments constraint is correctly applied.

    // Create an xcb window.
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_size_hints_set_base_size(&hints, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5);
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
    X11Client *client = windowCreatedSpy.last().first().value<X11Client *>();
    QVERIFY(client);
    QVERIFY(client->isDecorated());

    QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
    QVERIFY(clientStartMoveResizedSpy.isValid());
    QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
    QVERIFY(clientStepUserMovedResizedSpy.isValid());
    QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
    QVERIFY(clientFinishUserMovedResizedSpy.isValid());

    // Begin resize.
    QCOMPARE(workspace()->moveResizeClient(), nullptr);
    QVERIFY(!client->isResize());
    workspace()->slotWindowResize();
    QCOMPARE(workspace()->moveResizeClient(), client);
    QCOMPARE(clientStartMoveResizedSpy.count(), 1);
    QVERIFY(client->isResize());

351
    const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
352
353

    client->keyPressEvent(Qt::Key_Right);
354
355
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
356
357
358
359
360
    QVERIFY(clientStepUserMovedResizedSpy.wait());
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
    QCOMPARE(client->clientSize(), QSize(106, 200));

    client->keyPressEvent(Qt::Key_Down);
361
362
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
    QVERIFY(clientStepUserMovedResizedSpy.wait());
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
    QCOMPARE(client->clientSize(), QSize(106, 205));

    // Finish the resize operation.
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
    client->keyPressEvent(Qt::Key_Enter);
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
    QCOMPARE(workspace()->moveResizeClient(), nullptr);
    QVERIFY(!client->isResize());

    // Destroy the window.
    QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
    QVERIFY(windowClosedSpy.isValid());
    xcb_unmap_window(c.data(), w);
    xcb_destroy_window(c.data(), w);
    xcb_flush(c.data());
    QVERIFY(windowClosedSpy.wait());
    c.reset();
}

void X11ClientTest::testResizeIncrementsNoBaseSize()
{
    // Create an xcb window.
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5);
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
    X11Client *client = windowCreatedSpy.last().first().value<X11Client *>();
    QVERIFY(client);
    QVERIFY(client->isDecorated());

    QSignalSpy clientStartMoveResizedSpy(client, &AbstractClient::clientStartUserMovedResized);
    QVERIFY(clientStartMoveResizedSpy.isValid());
    QSignalSpy clientStepUserMovedResizedSpy(client, &AbstractClient::clientStepUserMovedResized);
    QVERIFY(clientStepUserMovedResizedSpy.isValid());
    QSignalSpy clientFinishUserMovedResizedSpy(client, &AbstractClient::clientFinishUserMovedResized);
    QVERIFY(clientFinishUserMovedResizedSpy.isValid());

    // Begin resize.
    QCOMPARE(workspace()->moveResizeClient(), nullptr);
    QVERIFY(!client->isResize());
    workspace()->slotWindowResize();
    QCOMPARE(workspace()->moveResizeClient(), client);
    QCOMPARE(clientStartMoveResizedSpy.count(), 1);
    QVERIFY(client->isResize());

429
    const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
430
431

    client->keyPressEvent(Qt::Key_Right);
432
433
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
434
435
436
437
438
    QVERIFY(clientStepUserMovedResizedSpy.wait());
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
    QCOMPARE(client->clientSize(), QSize(106, 200));

    client->keyPressEvent(Qt::Key_Down);
439
440
    client->updateMoveResize(KWin::Cursors::self()->mouse()->pos());
    QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
    QVERIFY(clientStepUserMovedResizedSpy.wait());
    QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
    QCOMPARE(client->clientSize(), QSize(106, 205));

    // Finish the resize operation.
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
    client->keyPressEvent(Qt::Key_Enter);
    QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
    QCOMPARE(workspace()->moveResizeClient(), nullptr);
    QVERIFY(!client->isResize());

    // Destroy the window.
    QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
    QVERIFY(windowClosedSpy.isValid());
    xcb_unmap_window(c.data(), w);
    xcb_destroy_window(c.data(), w);
    xcb_flush(c.data());
    QVERIFY(windowClosedSpy.wait());
    c.reset();
}

462
463
464
465
466
467
468
469
470
471
472
473
474
void X11ClientTest::testTrimCaption_data()
{
    QTest::addColumn<QByteArray>("originalTitle");
    QTest::addColumn<QByteArray>("expectedTitle");

    QTest::newRow("simplified")
        << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")
        << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben? – Marlies Hübner - Mozilla Firefox");

    QTest::newRow("with emojis")
        << QByteArrayLiteral("\bTesting non\302\255printable:\177, emoij:\360\237\230\203, non-characters:\357\277\276")
        << QByteArrayLiteral("Testing nonprintable:, emoij:\360\237\230\203, non-characters:");
}
475

476
void X11ClientTest::testTrimCaption()
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
{
    // this test verifies that caption is properly trimmed

    // create an xcb window
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    NETWinInfo winInfo(c.data(), w, rootWindow(), NET::Properties(), NET::Properties2());
497
498
    QFETCH(QByteArray, originalTitle);
    winInfo.setName(originalTitle);
499
500
501
502
503
504
505
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    // we should get a client for it
    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
506
    X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
507
508
    QVERIFY(client);
    QCOMPARE(client->window(), w);
509
510
    QFETCH(QByteArray, expectedTitle);
    QCOMPARE(client->caption(), QString::fromUtf8(expectedTitle));
511
512
513
514
515

    // and destroy the window again
    xcb_unmap_window(c.data(), w);
    xcb_flush(c.data());

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
516
    QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
517
518
519
520
521
522
    QVERIFY(windowClosedSpy.isValid());
    QVERIFY(windowClosedSpy.wait());
    xcb_destroy_window(c.data(), w);
    c.reset();
}

523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
void X11ClientTest::testFullscreenLayerWithActiveWaylandWindow()
{
    // this test verifies that an X11 fullscreen window does not stay in the active layer
    // when a Wayland window is active, see BUG: 375759
    QCOMPARE(screens()->count(), 1);

    // first create an X11 window
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    // we should get a client for it
    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
552
    X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
553
554
555
556
557
558
559
560
561
562
563
564
565
    QVERIFY(client);
    QCOMPARE(client->window(), w);
    QVERIFY(!client->isFullScreen());
    QVERIFY(client->isActive());
    QCOMPARE(client->layer(), NormalLayer);

    workspace()->slotWindowFullScreen();
    QVERIFY(client->isFullScreen());
    QCOMPARE(client->layer(), ActiveLayer);
    QCOMPARE(workspace()->stackingOrder().last(), client);

    // now let's open a Wayland window
    QScopedPointer<Surface> surface(Test::createSurface());
566
    QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
    auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
    QVERIFY(waylandClient);
    QVERIFY(waylandClient->isActive());
    QCOMPARE(waylandClient->layer(), NormalLayer);
    QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
    QCOMPARE(workspace()->xStackingOrder().last(), waylandClient);
    QCOMPARE(client->layer(), NormalLayer);

    // now activate fullscreen again
    workspace()->activateClient(client);
    QTRY_VERIFY(client->isActive());
    QCOMPARE(client->layer(), ActiveLayer);
    QCOMPARE(workspace()->stackingOrder().last(), client);
    QCOMPARE(workspace()->xStackingOrder().last(), client);

    // activate wayland window again
    workspace()->activateClient(waylandClient);
    QTRY_VERIFY(waylandClient->isActive());
    QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
    QCOMPARE(workspace()->xStackingOrder().last(), waylandClient);

588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
    // back to x window
    workspace()->activateClient(client);
    QTRY_VERIFY(client->isActive());
    // remove fullscreen
    QVERIFY(client->isFullScreen());
    workspace()->slotWindowFullScreen();
    QVERIFY(!client->isFullScreen());
    // and fullscreen again
    workspace()->slotWindowFullScreen();
    QVERIFY(client->isFullScreen());
    QCOMPARE(workspace()->stackingOrder().last(), client);
    QCOMPARE(workspace()->xStackingOrder().last(), client);

    // activate wayland window again
    workspace()->activateClient(waylandClient);
    QTRY_VERIFY(waylandClient->isActive());
    QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
    QCOMPARE(workspace()->xStackingOrder().last(), waylandClient);

    // back to X11 window
    workspace()->activateClient(client);
    QTRY_VERIFY(client->isActive());
    // remove fullscreen
    QVERIFY(client->isFullScreen());
    workspace()->slotWindowFullScreen();
    QVERIFY(!client->isFullScreen());
    // and fullscreen through X API
    NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
    info.setState(NET::FullScreen, NET::FullScreen);
    NETRootInfo rootInfo(c.data(), NET::Properties());
    rootInfo.setActiveWindow(w, NET::FromApplication, XCB_CURRENT_TIME, XCB_WINDOW_NONE);
    xcb_flush(c.data());
    QTRY_VERIFY(client->isFullScreen());
    QCOMPARE(workspace()->stackingOrder().last(), client);
    QCOMPARE(workspace()->xStackingOrder().last(), client);

    // activate wayland window again
    workspace()->activateClient(waylandClient);
    QTRY_VERIFY(waylandClient->isActive());
    QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
    QCOMPARE(workspace()->xStackingOrder().last(), waylandClient);
    QCOMPARE(client->layer(), NormalLayer);

631
632
633
634
635
636
637
638
639
640
641
642
    // close the window
    shellSurface.reset();
    surface.reset();
    QVERIFY(Test::waitForWindowDestroyed(waylandClient));
    QTRY_VERIFY(client->isActive());
    QCOMPARE(client->layer(), ActiveLayer);

    // and destroy the window again
    xcb_unmap_window(c.data(), w);
    xcb_flush(c.data());
}

643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
void X11ClientTest::testFocusInWithWaylandLastActiveWindow()
{
    // this test verifies that Workspace::allowClientActivation does not crash if last client was a Wayland client

    // create an X11 window
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    // we should get a client for it
    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
670
    X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
671
672
673
674
675
676
    QVERIFY(client);
    QCOMPARE(client->window(), w);
    QVERIFY(client->isActive());

    // create Wayland window
    QScopedPointer<Surface> surface(Test::createSurface());
677
    QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
    auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
    QVERIFY(waylandClient);
    QVERIFY(waylandClient->isActive());
    // activate no window
    workspace()->setActiveClient(nullptr);
    QVERIFY(!waylandClient->isActive());
    QVERIFY(!workspace()->activeClient());
    // and close Wayland window again
    shellSurface.reset();
    surface.reset();
    QVERIFY(Test::waitForWindowDestroyed(waylandClient));

    // and try to activate the x11 client through X11 api
    const auto cookie = xcb_set_input_focus_checked(c.data(), XCB_INPUT_FOCUS_NONE, w, XCB_CURRENT_TIME);
    auto error = xcb_request_check(c.data(), cookie);
    QVERIFY(!error);
    // this accesses last_active_client on trying to activate
    QTRY_VERIFY(client->isActive());

    // and destroy the window again
    xcb_unmap_window(c.data(), w);
    xcb_flush(c.data());
}

702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
void X11ClientTest::testX11WindowId()
{
    // create an X11 window
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    // we should get a client for it
    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
727
    X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
728
729
730
731
    QVERIFY(client);
    QCOMPARE(client->windowId(), w);
    QVERIFY(client->isActive());
    QCOMPARE(client->window(), w);
732
733
734
735
736
    QCOMPARE(client->internalId().isNull(), false);
    const auto uuid = client->internalId();
    QUuid deletedUuid;
    QCOMPARE(deletedUuid.isNull(), true);

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
737
    connect(client, &X11Client::windowClosed, this, [&deletedUuid] (Toplevel *, Deleted *d) { deletedUuid = d->internalId(); });
738

739
740
741
742
743
744

    NETRootInfo rootInfo(c.data(), NET::WMAllProperties);
    QCOMPARE(rootInfo.activeWindow(), client->window());

    // activate a wayland window
    QScopedPointer<Surface> surface(Test::createSurface());
745
    QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
    auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
    QVERIFY(waylandClient);
    QVERIFY(waylandClient->isActive());
    xcb_flush(kwinApp()->x11Connection());

    NETRootInfo rootInfo2(c.data(), NET::WMAllProperties);
    QCOMPARE(rootInfo2.activeWindow(), 0u);

    // back to X11 client
    shellSurface.reset();
    surface.reset();
    QVERIFY(Test::waitForWindowDestroyed(waylandClient));

    QTRY_VERIFY(client->isActive());
    NETRootInfo rootInfo3(c.data(), NET::WMAllProperties);
    QCOMPARE(rootInfo3.activeWindow(), client->window());

    // and destroy the window again
    xcb_unmap_window(c.data(), w);
    xcb_flush(c.data());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
766
    QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
767
768
769
770
771
    QVERIFY(windowClosedSpy.isValid());
    QVERIFY(windowClosedSpy.wait());

    QCOMPARE(deletedUuid.isNull(), false);
    QCOMPARE(deletedUuid, uuid);
772
773
}

774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
void X11ClientTest::testCaptionChanges()
{
    // verifies that caption is updated correctly when the X11 window updates it
    // BUG: 383444
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
    info.setName("foo");
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    // we should get a client for it
    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
802
    X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
803
804
805
806
    QVERIFY(client);
    QCOMPARE(client->windowId(), w);
    QCOMPARE(client->caption(), QStringLiteral("foo"));

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
807
    QSignalSpy captionChangedSpy(client, &X11Client::captionChanged);
808
809
810
811
812
813
814
    QVERIFY(captionChangedSpy.isValid());
    info.setName("bar");
    xcb_flush(c.data());
    QVERIFY(captionChangedSpy.wait());
    QCOMPARE(client->caption(), QStringLiteral("bar"));

    // and destroy the window again
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
815
    QSignalSpy windowClosedSpy(client, &X11Client::windowClosed);
816
817
818
819
820
821
822
823
    QVERIFY(windowClosedSpy.isValid());
    xcb_unmap_window(c.data(), w);
    xcb_flush(c.data());
    QVERIFY(windowClosedSpy.wait());
    xcb_destroy_window(c.data(), w);
    c.reset();
}

824
825
826
827
828
829
830
831
832
void X11ClientTest::testCaptionWmName()
{
    // this test verifies that a caption set through WM_NAME is read correctly

    // open glxgears as that one only uses WM_NAME
    QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(clientAddedSpy.isValid());

    QProcess glxgears;
833
834
    glxgears.setProgram(QStringLiteral("glxgears"));
    glxgears.start();
835
836
837
838
839
    QVERIFY(glxgears.waitForStarted());

    QVERIFY(clientAddedSpy.wait());
    QCOMPARE(clientAddedSpy.count(), 1);
    QCOMPARE(workspace()->clientList().count(), 1);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
840
    X11Client *glxgearsClient = workspace()->clientList().first();
841
842
843
844
845
846
    QCOMPARE(glxgearsClient->caption(), QStringLiteral("glxgears"));

    glxgears.terminate();
    QVERIFY(glxgears.waitForFinished());
}

847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
void X11ClientTest::testCaptionMultipleWindows()
{
    // BUG 384760
    // create first window
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
    info.setName("foo");
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
874
    X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
    QVERIFY(client);
    QCOMPARE(client->windowId(), w);
    QCOMPARE(client->caption(), QStringLiteral("foo"));

    // create second window with same caption
    xcb_window_t w2 = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints);
    NETWinInfo info2(c.data(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
    info2.setName("foo");
    info2.setIconName("foo");
    xcb_map_window(c.data(), w2);
    xcb_flush(c.data());

    windowCreatedSpy.clear();
    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
896
    X11Client *client2 = windowCreatedSpy.first().first().value<X11Client *>();
897
898
899
900
901
902
903
    QVERIFY(client2);
    QCOMPARE(client2->windowId(), w2);
    QCOMPARE(client2->caption(), QStringLiteral("foo <2>\u200E"));
    NETWinInfo info3(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2());
    QCOMPARE(QByteArray(info3.visibleName()), QByteArrayLiteral("foo <2>\u200E"));
    QCOMPARE(QByteArray(info3.visibleIconName()), QByteArrayLiteral("foo <2>\u200E"));

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
904
    QSignalSpy captionChangedSpy(client2, &X11Client::captionChanged);
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
    QVERIFY(captionChangedSpy.isValid());

    NETWinInfo info4(c.data(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
    info4.setName("foobar");
    info4.setIconName("foobar");
    xcb_map_window(c.data(), w2);
    xcb_flush(c.data());

    QVERIFY(captionChangedSpy.wait());
    QCOMPARE(client2->caption(), QStringLiteral("foobar"));
    NETWinInfo info5(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2());
    QCOMPARE(QByteArray(info5.visibleName()), QByteArray());
    QTRY_COMPARE(QByteArray(info5.visibleIconName()), QByteArray());
}

920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948

void X11ClientTest::testFullscreenWindowGroups()
{
    // this test creates an X11 window and puts it to full screen
    // then a second window is created which is in the same window group
    // BUG: 388310

    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
949
    X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
    QVERIFY(client);
    QCOMPARE(client->windowId(), w);
    QCOMPARE(client->isActive(), true);

    QCOMPARE(client->isFullScreen(), false);
    QCOMPARE(client->layer(), NormalLayer);
    workspace()->slotWindowFullScreen();
    QCOMPARE(client->isFullScreen(), true);
    QCOMPARE(client->layer(), ActiveLayer);

    // now let's create a second window
    windowCreatedSpy.clear();
    xcb_window_t w2 = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints2;
    memset(&hints2, 0, sizeof(hints2));
    xcb_icccm_size_hints_set_position(&hints2, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints2, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints2);
    xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w2, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
    xcb_map_window(c.data(), w2);
    xcb_flush(c.data());

    QVERIFY(windowCreatedSpy.wait());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
979
    X11Client *client2 = windowCreatedSpy.first().first().value<X11Client *>();
980
981
982
983
984
985
986
987
988
    QVERIFY(client2);
    QVERIFY(client != client2);
    QCOMPARE(client2->windowId(), w2);
    QCOMPARE(client2->isActive(), true);
    QCOMPARE(client2->group(), client->group());
    // first client should be moved back to normal layer
    QCOMPARE(client->isActive(), false);
    QCOMPARE(client->isFullScreen(), true);
    QCOMPARE(client->layer(), NormalLayer);
989
990
991
992

    // activating the fullscreen window again, should move it to active layer
    workspace()->activateClient(client);
    QTRY_COMPARE(client->layer(), ActiveLayer);
993
994
}

995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
void X11ClientTest::testActivateFocusedWindow()
{
    // The window manager may call XSetInputFocus() on a window that already has focus, in which
    // case no FocusIn event will be generated and the window won't be marked as active. This test
    // verifies that we handle that subtle case properly.

    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> connection(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(connection.data()));

    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());

    const QRect windowGeometry(0, 0, 100, 200);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());

    // Create the first test window.
    const xcb_window_t window1 = xcb_generate_id(connection.data());
    xcb_create_window(connection.data(), XCB_COPY_FROM_PARENT, window1, rootWindow(),
                      windowGeometry.x(), windowGeometry.y(),
                      windowGeometry.width(), windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_icccm_set_wm_normal_hints(connection.data(), window1, &hints);
    xcb_change_property(connection.data(), XCB_PROP_MODE_REPLACE, window1,
                        atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &window1);
    xcb_map_window(connection.data(), window1);
    xcb_flush(connection.data());
    QVERIFY(windowCreatedSpy.wait());
    X11Client *client1 = windowCreatedSpy.first().first().value<X11Client *>();
    QVERIFY(client1);
    QCOMPARE(client1->windowId(), window1);
    QCOMPARE(client1->isActive(), true);

    // Create the second test window.
    const xcb_window_t window2 = xcb_generate_id(connection.data());
    xcb_create_window(connection.data(), XCB_COPY_FROM_PARENT, window2, rootWindow(),
                      windowGeometry.x(), windowGeometry.y(),
                      windowGeometry.width(), windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_icccm_set_wm_normal_hints(connection.data(), window2, &hints);
    xcb_change_property(connection.data(), XCB_PROP_MODE_REPLACE, window2,
                        atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &window2);
    xcb_map_window(connection.data(), window2);
    xcb_flush(connection.data());
    QVERIFY(windowCreatedSpy.wait());
    X11Client *client2 = windowCreatedSpy.last().first().value<X11Client *>();
    QVERIFY(client2);
    QCOMPARE(client2->windowId(), window2);
    QCOMPARE(client2->isActive(), true);

    // When the second test window is destroyed, the window manager will attempt to activate the
    // next client in the focus chain, which is the first window.
    xcb_set_input_focus(connection.data(), XCB_INPUT_FOCUS_POINTER_ROOT, window1, XCB_CURRENT_TIME);
    xcb_destroy_window(connection.data(), window2);
    xcb_flush(connection.data());
    QVERIFY(Test::waitForWindowDestroyed(client2));
    QVERIFY(client1->isActive());

    // Destroy the first test window.
    xcb_destroy_window(connection.data(), window1);
    xcb_flush(connection.data());
    QVERIFY(Test::waitForWindowDestroyed(client1));
}

1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
void X11ClientTest::testReentrantSetFrameGeometry()
{
    // This test verifies that calling setFrameGeometry() from a slot connected directly
    // to the frameGeometryChanged() signal won't cause an infinite recursion.

    // Create a test window.
    QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
    QVERIFY(!xcb_connection_has_error(c.data()));
    const QRect windowGeometry(0, 0, 100, 200);
    xcb_window_t w = xcb_generate_id(c.data());
    xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
                      windowGeometry.x(),
                      windowGeometry.y(),
                      windowGeometry.width(),
                      windowGeometry.height(),
                      0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
    xcb_size_hints_t hints;
    memset(&hints, 0, sizeof(hints));
    xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
    xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
    xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
    xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
    xcb_map_window(c.data(), w);
    xcb_flush(c.data());

    QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
    QVERIFY(windowCreatedSpy.isValid());
    QVERIFY(windowCreatedSpy.wait());
    X11Client *client = windowCreatedSpy.first().first().value<X11Client *>();
    QVERIFY(client);
    QCOMPARE(client->pos(), QPoint(0, 0));

    // Let's pretend that there is a script that really wants the client to be at (100, 100).
    connect(client, &AbstractClient::frameGeometryChanged, this, [client]() {
        client->setFrameGeometry(QRect(QPoint(100, 100), client->size()));
    });

    // Trigger the lambda above.
    client->move(QPoint(40, 50));

    // Eventually, the client will end up at (100, 100).
    QCOMPARE(client->pos(), QPoint(100, 100));

    // Destroy the test window.
    xcb_destroy_window(c.data(), w);
    xcb_flush(c.data());
    QVERIFY(Test::waitForWindowDestroyed(client));
}

1110
1111
WAYLANDTEST_MAIN(X11ClientTest)
#include "x11_client_test.moc"