File indexing completed on 2024-05-12 05:30:46

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "kwin_wayland_test.h"
0010 
0011 #include "atoms.h"
0012 #include "compositor.h"
0013 #include "cursor.h"
0014 #include "effect/effectloader.h"
0015 #include "wayland_server.h"
0016 #include "workspace.h"
0017 #include "x11window.h"
0018 
0019 #include <KWayland/Client/surface.h>
0020 
0021 #include <netwm.h>
0022 #include <xcb/xcb_icccm.h>
0023 
0024 using namespace KWin;
0025 static const QString s_socketName = QStringLiteral("wayland_test_x11_window-0");
0026 
0027 class X11WindowTest : public QObject
0028 {
0029     Q_OBJECT
0030 private Q_SLOTS:
0031     void initTestCase_data();
0032     void initTestCase();
0033     void init();
0034     void cleanup();
0035 
0036     void testMinimumSize();
0037     void testMaximumSize();
0038     void testResizeIncrements();
0039     void testResizeIncrementsNoBaseSize();
0040     void testTrimCaption_data();
0041     void testTrimCaption();
0042     void testFullscreenLayerWithActiveWaylandWindow();
0043     void testFocusInWithWaylandLastActiveWindow();
0044     void testCaptionChanges();
0045     void testCaptionWmName();
0046     void testFullscreenWindowGroups();
0047     void testActivateFocusedWindow();
0048     void testReentrantMoveResize();
0049 };
0050 
0051 void X11WindowTest::initTestCase_data()
0052 {
0053     QTest::addColumn<qreal>("scale");
0054     QTest::newRow("normal") << 1.0;
0055     QTest::newRow("scaled2x") << 2.0;
0056 }
0057 
0058 void X11WindowTest::initTestCase()
0059 {
0060     qRegisterMetaType<KWin::Window *>();
0061     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0062     QVERIFY(waylandServer()->init(s_socketName));
0063     Test::setOutputConfig({QRect(0, 0, 1280, 1024)});
0064     kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
0065 
0066     kwinApp()->start();
0067     QVERIFY(applicationStartedSpy.wait());
0068     QVERIFY(KWin::Compositor::self());
0069 }
0070 
0071 void X11WindowTest::init()
0072 {
0073     QVERIFY(Test::setupWaylandConnection());
0074 }
0075 
0076 void X11WindowTest::cleanup()
0077 {
0078     Test::destroyWaylandConnection();
0079 }
0080 
0081 void X11WindowTest::testMinimumSize()
0082 {
0083     // This test verifies that the minimum size constraint is correctly applied.
0084 
0085     QFETCH_GLOBAL(qreal, scale);
0086     kwinApp()->setXwaylandScale(scale);
0087 
0088     // Create an xcb window.
0089     Test::XcbConnectionPtr c = Test::createX11Connection();
0090     QVERIFY(!xcb_connection_has_error(c.get()));
0091     const QRect windowGeometry(0, 0, 100, 200);
0092     xcb_window_t windowId = xcb_generate_id(c.get());
0093     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0094                       windowGeometry.x(),
0095                       windowGeometry.y(),
0096                       windowGeometry.width(),
0097                       windowGeometry.height(),
0098                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0099     xcb_size_hints_t hints;
0100     memset(&hints, 0, sizeof(hints));
0101     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0102     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0103     xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height());
0104     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0105     xcb_map_window(c.get(), windowId);
0106     xcb_flush(c.get());
0107 
0108     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0109     QVERIFY(windowCreatedSpy.wait());
0110     X11Window *window = windowCreatedSpy.last().first().value<X11Window *>();
0111     QVERIFY(window);
0112     QVERIFY(window->isDecorated());
0113 
0114     QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
0115     QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
0116     QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
0117     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0118 
0119     // Begin resize.
0120     QCOMPARE(workspace()->moveResizeWindow(), nullptr);
0121     QVERIFY(!window->isInteractiveResize());
0122     workspace()->slotWindowResize();
0123     QCOMPARE(workspace()->moveResizeWindow(), window);
0124     QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
0125     QVERIFY(window->isInteractiveResize());
0126 
0127     const QPointF cursorPos = KWin::Cursors::self()->mouse()->pos();
0128 
0129     window->keyPressEvent(Qt::Key_Left);
0130     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0131     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
0132     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
0133     QVERIFY(!frameGeometryChangedSpy.wait(10));
0134     QCOMPARE(window->clientSize().width(), 100 / scale);
0135 
0136     window->keyPressEvent(Qt::Key_Right);
0137     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0138     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
0139     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
0140     QVERIFY(!frameGeometryChangedSpy.wait(10));
0141     QCOMPARE(window->clientSize().width(), 100 / scale);
0142 
0143     window->keyPressEvent(Qt::Key_Right);
0144     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0145     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
0146     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
0147     QVERIFY(frameGeometryChangedSpy.wait());
0148     // whilst X11 window size goes through scale, the increment is a logical value kwin side
0149     QCOMPARE(window->clientSize().width(), 100 / scale + 8);
0150 
0151     window->keyPressEvent(Qt::Key_Up);
0152     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0153     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, -8));
0154     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
0155     QVERIFY(!frameGeometryChangedSpy.wait(10));
0156     QCOMPARE(window->clientSize().height(), 200 / scale);
0157 
0158     window->keyPressEvent(Qt::Key_Down);
0159     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0160     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
0161     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
0162     QVERIFY(!frameGeometryChangedSpy.wait(10));
0163     QCOMPARE(window->clientSize().height(), 200 / scale);
0164 
0165     window->keyPressEvent(Qt::Key_Down);
0166     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0167     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
0168     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2);
0169     QVERIFY(frameGeometryChangedSpy.wait());
0170     QCOMPARE(window->clientSize().height(), 200 / scale + 8);
0171 
0172     // Finish the resize operation.
0173     QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 0);
0174     window->keyPressEvent(Qt::Key_Enter);
0175     QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
0176     QCOMPARE(workspace()->moveResizeWindow(), nullptr);
0177     QVERIFY(!window->isInteractiveResize());
0178 
0179     // Destroy the window.
0180     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0181     xcb_unmap_window(c.get(), windowId);
0182     xcb_destroy_window(c.get(), windowId);
0183     xcb_flush(c.get());
0184     QVERIFY(windowClosedSpy.wait());
0185     c.reset();
0186 }
0187 
0188 void X11WindowTest::testMaximumSize()
0189 {
0190     // This test verifies that the maximum size constraint is correctly applied.
0191     QFETCH_GLOBAL(qreal, scale);
0192     kwinApp()->setXwaylandScale(scale);
0193 
0194     // Create an xcb window.
0195     Test::XcbConnectionPtr c = Test::createX11Connection();
0196     QVERIFY(!xcb_connection_has_error(c.get()));
0197     const QRect windowGeometry(0, 0, 100, 200);
0198     xcb_window_t windowId = xcb_generate_id(c.get());
0199     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0200                       windowGeometry.x(),
0201                       windowGeometry.y(),
0202                       windowGeometry.width(),
0203                       windowGeometry.height(),
0204                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0205     xcb_size_hints_t hints;
0206     memset(&hints, 0, sizeof(hints));
0207     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0208     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0209     xcb_icccm_size_hints_set_max_size(&hints, windowGeometry.width(), windowGeometry.height());
0210     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0211     xcb_map_window(c.get(), windowId);
0212     xcb_flush(c.get());
0213 
0214     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0215     QVERIFY(windowCreatedSpy.wait());
0216     X11Window *window = windowCreatedSpy.last().first().value<X11Window *>();
0217     QVERIFY(window);
0218     QVERIFY(window->isDecorated());
0219 
0220     QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
0221     QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
0222     QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
0223     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0224 
0225     // Begin resize.
0226     QCOMPARE(workspace()->moveResizeWindow(), nullptr);
0227     QVERIFY(!window->isInteractiveResize());
0228     workspace()->slotWindowResize();
0229     QCOMPARE(workspace()->moveResizeWindow(), window);
0230     QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
0231     QVERIFY(window->isInteractiveResize());
0232 
0233     const QPointF cursorPos = KWin::Cursors::self()->mouse()->pos();
0234 
0235     window->keyPressEvent(Qt::Key_Right);
0236     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0237     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
0238     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
0239     QVERIFY(!frameGeometryChangedSpy.wait(10));
0240     QCOMPARE(window->clientSize().width(), 100 / scale);
0241 
0242     window->keyPressEvent(Qt::Key_Left);
0243     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0244     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
0245     QVERIFY(!interactiveMoveResizeSteppedSpy.wait(10));
0246     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
0247     QCOMPARE(window->clientSize().width(), 100 / scale);
0248 
0249     window->keyPressEvent(Qt::Key_Left);
0250     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0251     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
0252     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
0253     QVERIFY(frameGeometryChangedSpy.wait());
0254     QCOMPARE(window->clientSize().width(), 100 / scale - 8);
0255 
0256     window->keyPressEvent(Qt::Key_Down);
0257     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0258     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 8));
0259     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
0260     QVERIFY(!frameGeometryChangedSpy.wait(10));
0261     QCOMPARE(window->clientSize().height(), 200 / scale);
0262 
0263     window->keyPressEvent(Qt::Key_Up);
0264     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0265     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
0266     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
0267     QVERIFY(!frameGeometryChangedSpy.wait(10));
0268     QCOMPARE(window->clientSize().height(), 200 / scale);
0269 
0270     window->keyPressEvent(Qt::Key_Up);
0271     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0272     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, -8));
0273     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2);
0274     QVERIFY(frameGeometryChangedSpy.wait());
0275     QCOMPARE(window->clientSize().height(), 200 / scale - 8);
0276 
0277     // Finish the resize operation.
0278     QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 0);
0279     window->keyPressEvent(Qt::Key_Enter);
0280     QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
0281     QCOMPARE(workspace()->moveResizeWindow(), nullptr);
0282     QVERIFY(!window->isInteractiveResize());
0283 
0284     // Destroy the window.
0285     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0286     xcb_unmap_window(c.get(), windowId);
0287     xcb_destroy_window(c.get(), windowId);
0288     xcb_flush(c.get());
0289     QVERIFY(windowClosedSpy.wait());
0290     c.reset();
0291 }
0292 
0293 void X11WindowTest::testResizeIncrements()
0294 {
0295     // This test verifies that the resize increments constraint is correctly applied.
0296     QFETCH_GLOBAL(qreal, scale);
0297     kwinApp()->setXwaylandScale(scale);
0298 
0299     // Create an xcb window.
0300     Test::XcbConnectionPtr c = Test::createX11Connection();
0301     QVERIFY(!xcb_connection_has_error(c.get()));
0302     const QRect windowGeometry(0, 0, 100, 200);
0303     xcb_window_t windowId = xcb_generate_id(c.get());
0304     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0305                       windowGeometry.x(),
0306                       windowGeometry.y(),
0307                       windowGeometry.width(),
0308                       windowGeometry.height(),
0309                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0310     xcb_size_hints_t hints;
0311     memset(&hints, 0, sizeof(hints));
0312     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0313     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0314     xcb_icccm_size_hints_set_base_size(&hints, windowGeometry.width(), windowGeometry.height());
0315     xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5);
0316     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0317     xcb_map_window(c.get(), windowId);
0318     xcb_flush(c.get());
0319 
0320     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0321     QVERIFY(windowCreatedSpy.wait());
0322     X11Window *window = windowCreatedSpy.last().first().value<X11Window *>();
0323     QVERIFY(window);
0324     QVERIFY(window->isDecorated());
0325 
0326     QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
0327     QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
0328     QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
0329     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0330 
0331     // Begin resize.
0332     QCOMPARE(workspace()->moveResizeWindow(), nullptr);
0333     QVERIFY(!window->isInteractiveResize());
0334     workspace()->slotWindowResize();
0335     QCOMPARE(workspace()->moveResizeWindow(), window);
0336     QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
0337     QVERIFY(window->isInteractiveResize());
0338 
0339     const QPointF cursorPos = KWin::Cursors::self()->mouse()->pos();
0340 
0341     window->keyPressEvent(Qt::Key_Right);
0342     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0343     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
0344     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
0345     QVERIFY(frameGeometryChangedSpy.wait());
0346 
0347     //  100 + 8 logical pixels, rounded to resize increments. This will differ on scale
0348     const qreal horizontalResizeInc = 3 / scale;
0349     const qreal verticalResizeInc = 5 / scale;
0350     const qreal expectedHorizontalResizeInc = std::floor(8. / horizontalResizeInc) * horizontalResizeInc;
0351     const qreal expectedVerticalResizeInc = std::floor(8. / verticalResizeInc) * verticalResizeInc;
0352 
0353     QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, 0));
0354 
0355     window->keyPressEvent(Qt::Key_Down);
0356     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0357     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
0358     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2);
0359     QVERIFY(frameGeometryChangedSpy.wait());
0360     QCOMPARE(window->clientSize(), QSize(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, expectedVerticalResizeInc));
0361 
0362     // Finish the resize operation.
0363     QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 0);
0364     window->keyPressEvent(Qt::Key_Enter);
0365     QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
0366     QCOMPARE(workspace()->moveResizeWindow(), nullptr);
0367     QVERIFY(!window->isInteractiveResize());
0368 
0369     // Destroy the window.
0370     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0371     xcb_unmap_window(c.get(), windowId);
0372     xcb_destroy_window(c.get(), windowId);
0373     xcb_flush(c.get());
0374     QVERIFY(windowClosedSpy.wait());
0375     c.reset();
0376 }
0377 
0378 void X11WindowTest::testResizeIncrementsNoBaseSize()
0379 {
0380     QFETCH_GLOBAL(qreal, scale);
0381     kwinApp()->setXwaylandScale(scale);
0382 
0383     // Create an xcb window.
0384     Test::XcbConnectionPtr c = Test::createX11Connection();
0385     QVERIFY(!xcb_connection_has_error(c.get()));
0386     const QRect windowGeometry(0, 0, 100, 200);
0387     xcb_window_t windowId = xcb_generate_id(c.get());
0388     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0389                       windowGeometry.x(),
0390                       windowGeometry.y(),
0391                       windowGeometry.width(),
0392                       windowGeometry.height(),
0393                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0394     xcb_size_hints_t hints;
0395     memset(&hints, 0, sizeof(hints));
0396     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0397     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0398     xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height());
0399     xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5);
0400     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0401     xcb_map_window(c.get(), windowId);
0402     xcb_flush(c.get());
0403 
0404     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0405     QVERIFY(windowCreatedSpy.wait());
0406     X11Window *window = windowCreatedSpy.last().first().value<X11Window *>();
0407     QVERIFY(window);
0408     QVERIFY(window->isDecorated());
0409 
0410     QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
0411     QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
0412     QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
0413     QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
0414 
0415     // Begin resize.
0416     QCOMPARE(workspace()->moveResizeWindow(), nullptr);
0417     QVERIFY(!window->isInteractiveResize());
0418     workspace()->slotWindowResize();
0419     QCOMPARE(workspace()->moveResizeWindow(), window);
0420     QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
0421     QVERIFY(window->isInteractiveResize());
0422 
0423     const QPointF cursorPos = KWin::Cursors::self()->mouse()->pos();
0424 
0425     window->keyPressEvent(Qt::Key_Right);
0426     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0427     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
0428     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
0429     QVERIFY(frameGeometryChangedSpy.wait());
0430 
0431     //  100 + 8 pixels, rounded to resize increments. This will differ on scale
0432     const qreal horizontalResizeInc = 3 / scale;
0433     const qreal verticalResizeInc = 5 / scale;
0434     const qreal expectedHorizontalResizeInc = std::floor(8. / horizontalResizeInc) * horizontalResizeInc;
0435     const qreal expectedVerticalResizeInc = std::floor(8. / verticalResizeInc) * verticalResizeInc;
0436 
0437     QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, 0));
0438 
0439     window->keyPressEvent(Qt::Key_Down);
0440     window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
0441     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
0442     QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2);
0443     QVERIFY(frameGeometryChangedSpy.wait());
0444     QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, expectedVerticalResizeInc));
0445 
0446     // Finish the resize operation.
0447     QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 0);
0448     window->keyPressEvent(Qt::Key_Enter);
0449     QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
0450     QCOMPARE(workspace()->moveResizeWindow(), nullptr);
0451     QVERIFY(!window->isInteractiveResize());
0452 
0453     // Destroy the window.
0454     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0455     xcb_unmap_window(c.get(), windowId);
0456     xcb_destroy_window(c.get(), windowId);
0457     xcb_flush(c.get());
0458     QVERIFY(windowClosedSpy.wait());
0459     c.reset();
0460 }
0461 
0462 void X11WindowTest::testTrimCaption_data()
0463 {
0464     QFETCH_GLOBAL(qreal, scale);
0465     kwinApp()->setXwaylandScale(scale);
0466 
0467     QTest::addColumn<QByteArray>("originalTitle");
0468     QTest::addColumn<QByteArray>("expectedTitle");
0469 
0470     QTest::newRow("simplified")
0471         << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")
0472         << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben? – Marlies Hübner - Mozilla Firefox");
0473 
0474     QTest::newRow("with emojis")
0475         << QByteArrayLiteral("\bTesting non\302\255printable:\177, emoij:\360\237\230\203, non-characters:\357\277\276")
0476         << QByteArrayLiteral("Testing nonprintable:, emoij:\360\237\230\203, non-characters:");
0477 }
0478 
0479 void X11WindowTest::testTrimCaption()
0480 {
0481     QFETCH_GLOBAL(qreal, scale);
0482     kwinApp()->setXwaylandScale(scale);
0483 
0484     // this test verifies that caption is properly trimmed
0485 
0486     // create an xcb window
0487     Test::XcbConnectionPtr c = Test::createX11Connection();
0488     QVERIFY(!xcb_connection_has_error(c.get()));
0489     const QRect windowGeometry(0, 0, 100, 200);
0490     xcb_window_t windowId = xcb_generate_id(c.get());
0491     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0492                       windowGeometry.x(),
0493                       windowGeometry.y(),
0494                       windowGeometry.width(),
0495                       windowGeometry.height(),
0496                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0497     xcb_size_hints_t hints;
0498     memset(&hints, 0, sizeof(hints));
0499     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0500     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0501     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0502     NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2());
0503     QFETCH(QByteArray, originalTitle);
0504     winInfo.setName(originalTitle);
0505     xcb_map_window(c.get(), windowId);
0506     xcb_flush(c.get());
0507 
0508     // we should get a window for it
0509     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0510     QVERIFY(windowCreatedSpy.wait());
0511     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0512     QVERIFY(window);
0513     QCOMPARE(window->window(), windowId);
0514     QFETCH(QByteArray, expectedTitle);
0515     QCOMPARE(window->caption(), QString::fromUtf8(expectedTitle));
0516 
0517     // and destroy the window again
0518     xcb_unmap_window(c.get(), windowId);
0519     xcb_flush(c.get());
0520 
0521     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0522     QVERIFY(windowClosedSpy.wait());
0523     xcb_destroy_window(c.get(), windowId);
0524     c.reset();
0525 }
0526 
0527 void X11WindowTest::testFullscreenLayerWithActiveWaylandWindow()
0528 {
0529     QFETCH_GLOBAL(qreal, scale);
0530     kwinApp()->setXwaylandScale(scale);
0531 
0532     // this test verifies that an X11 fullscreen window does not stay in the active layer
0533     // when a Wayland window is active, see BUG: 375759
0534     QCOMPARE(workspace()->outputs().count(), 1);
0535 
0536     // first create an X11 window
0537     Test::XcbConnectionPtr c = Test::createX11Connection();
0538     QVERIFY(!xcb_connection_has_error(c.get()));
0539     const QRect windowGeometry(0, 0, 100, 200);
0540     xcb_window_t windowId = xcb_generate_id(c.get());
0541     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0542                       windowGeometry.x(),
0543                       windowGeometry.y(),
0544                       windowGeometry.width(),
0545                       windowGeometry.height(),
0546                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0547     xcb_size_hints_t hints;
0548     memset(&hints, 0, sizeof(hints));
0549     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0550     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0551     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0552     xcb_map_window(c.get(), windowId);
0553     xcb_flush(c.get());
0554 
0555     // we should get a window for it
0556     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0557     QVERIFY(windowCreatedSpy.wait());
0558     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0559     QVERIFY(window);
0560     QCOMPARE(window->window(), windowId);
0561     QVERIFY(!window->isFullScreen());
0562     QVERIFY(window->isActive());
0563     QCOMPARE(window->layer(), NormalLayer);
0564 
0565     workspace()->slotWindowFullScreen();
0566     QVERIFY(window->isFullScreen());
0567     QCOMPARE(window->layer(), ActiveLayer);
0568     QCOMPARE(workspace()->stackingOrder().last(), window);
0569 
0570     // now let's open a Wayland window
0571     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0572     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0573     auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0574     QVERIFY(waylandWindow);
0575     QVERIFY(waylandWindow->isActive());
0576     QCOMPARE(waylandWindow->layer(), NormalLayer);
0577     QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
0578     QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
0579     QCOMPARE(window->layer(), NormalLayer);
0580 
0581     // now activate fullscreen again
0582     workspace()->activateWindow(window);
0583     QTRY_VERIFY(window->isActive());
0584     QCOMPARE(window->layer(), ActiveLayer);
0585     QCOMPARE(workspace()->stackingOrder().last(), window);
0586     QCOMPARE(workspace()->stackingOrder().last(), window);
0587 
0588     // activate wayland window again
0589     workspace()->activateWindow(waylandWindow);
0590     QTRY_VERIFY(waylandWindow->isActive());
0591     QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
0592     QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
0593 
0594     // back to x window
0595     workspace()->activateWindow(window);
0596     QTRY_VERIFY(window->isActive());
0597     // remove fullscreen
0598     QVERIFY(window->isFullScreen());
0599     workspace()->slotWindowFullScreen();
0600     QVERIFY(!window->isFullScreen());
0601     // and fullscreen again
0602     workspace()->slotWindowFullScreen();
0603     QVERIFY(window->isFullScreen());
0604     QCOMPARE(workspace()->stackingOrder().last(), window);
0605     QCOMPARE(workspace()->stackingOrder().last(), window);
0606 
0607     // activate wayland window again
0608     workspace()->activateWindow(waylandWindow);
0609     QTRY_VERIFY(waylandWindow->isActive());
0610     QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
0611     QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
0612 
0613     // back to X11 window
0614     workspace()->activateWindow(window);
0615     QTRY_VERIFY(window->isActive());
0616     // remove fullscreen
0617     QVERIFY(window->isFullScreen());
0618     workspace()->slotWindowFullScreen();
0619     QVERIFY(!window->isFullScreen());
0620     // and fullscreen through X API
0621     NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
0622     info.setState(NET::FullScreen, NET::FullScreen);
0623     NETRootInfo rootInfo(c.get(), NET::Properties());
0624     rootInfo.setActiveWindow(windowId, NET::FromApplication, XCB_CURRENT_TIME, XCB_WINDOW_NONE);
0625     xcb_flush(c.get());
0626     QTRY_VERIFY(window->isFullScreen());
0627     QCOMPARE(workspace()->stackingOrder().last(), window);
0628     QCOMPARE(workspace()->stackingOrder().last(), window);
0629 
0630     // activate wayland window again
0631     workspace()->activateWindow(waylandWindow);
0632     QTRY_VERIFY(waylandWindow->isActive());
0633     QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
0634     QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
0635     QCOMPARE(window->layer(), NormalLayer);
0636 
0637     // close the window
0638     shellSurface.reset();
0639     surface.reset();
0640     QVERIFY(Test::waitForWindowClosed(waylandWindow));
0641     QTRY_VERIFY(window->isActive());
0642     QCOMPARE(window->layer(), ActiveLayer);
0643 
0644     // and destroy the window again
0645     xcb_unmap_window(c.get(), windowId);
0646     xcb_flush(c.get());
0647 }
0648 
0649 void X11WindowTest::testFocusInWithWaylandLastActiveWindow()
0650 {
0651     // this test verifies that Workspace::allowWindowActivation does not crash if last client was a Wayland client
0652     QFETCH_GLOBAL(qreal, scale);
0653     kwinApp()->setXwaylandScale(scale);
0654 
0655     // create an X11 window
0656     Test::XcbConnectionPtr c = Test::createX11Connection();
0657     QVERIFY(!xcb_connection_has_error(c.get()));
0658     const QRect windowGeometry(0, 0, 100, 200);
0659     xcb_window_t windowId = xcb_generate_id(c.get());
0660     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0661                       windowGeometry.x(),
0662                       windowGeometry.y(),
0663                       windowGeometry.width(),
0664                       windowGeometry.height(),
0665                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0666     xcb_size_hints_t hints;
0667     memset(&hints, 0, sizeof(hints));
0668     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0669     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0670     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0671     xcb_map_window(c.get(), windowId);
0672     xcb_flush(c.get());
0673 
0674     // we should get a window for it
0675     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0676     QVERIFY(windowCreatedSpy.wait());
0677     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0678     QVERIFY(window);
0679     QCOMPARE(window->window(), windowId);
0680     QVERIFY(window->isActive());
0681 
0682     // create Wayland window
0683     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0684     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0685     auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
0686     QVERIFY(waylandWindow);
0687     QVERIFY(waylandWindow->isActive());
0688     // activate no window
0689     workspace()->setActiveWindow(nullptr);
0690     QVERIFY(!waylandWindow->isActive());
0691     QVERIFY(!workspace()->activeWindow());
0692     // and close Wayland window again
0693     shellSurface.reset();
0694     surface.reset();
0695     QVERIFY(Test::waitForWindowClosed(waylandWindow));
0696 
0697     // and try to activate the x11 window through X11 api
0698     const auto cookie = xcb_set_input_focus_checked(c.get(), XCB_INPUT_FOCUS_NONE, windowId, XCB_CURRENT_TIME);
0699     auto error = xcb_request_check(c.get(), cookie);
0700     QVERIFY(!error);
0701     // this accesses m_lastActiveWindow on trying to activate
0702     QTRY_VERIFY(window->isActive());
0703 
0704     // and destroy the window again
0705     xcb_unmap_window(c.get(), windowId);
0706     xcb_flush(c.get());
0707 }
0708 
0709 void X11WindowTest::testCaptionChanges()
0710 {
0711     QFETCH_GLOBAL(qreal, scale);
0712     kwinApp()->setXwaylandScale(scale);
0713 
0714     // verifies that caption is updated correctly when the X11 window updates it
0715     // BUG: 383444
0716     Test::XcbConnectionPtr c = Test::createX11Connection();
0717     QVERIFY(!xcb_connection_has_error(c.get()));
0718     const QRect windowGeometry(0, 0, 100, 200);
0719     xcb_window_t windowId = xcb_generate_id(c.get());
0720     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0721                       windowGeometry.x(),
0722                       windowGeometry.y(),
0723                       windowGeometry.width(),
0724                       windowGeometry.height(),
0725                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0726     xcb_size_hints_t hints;
0727     memset(&hints, 0, sizeof(hints));
0728     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0729     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0730     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0731     NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
0732     info.setName("foo");
0733     xcb_map_window(c.get(), windowId);
0734     xcb_flush(c.get());
0735 
0736     // we should get a window for it
0737     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0738     QVERIFY(windowCreatedSpy.wait());
0739     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0740     QVERIFY(window);
0741     QCOMPARE(window->window(), windowId);
0742     QCOMPARE(window->caption(), QStringLiteral("foo"));
0743 
0744     QSignalSpy captionChangedSpy(window, &X11Window::captionChanged);
0745     info.setName("bar");
0746     xcb_flush(c.get());
0747     QVERIFY(captionChangedSpy.wait());
0748     QCOMPARE(window->caption(), QStringLiteral("bar"));
0749 
0750     // and destroy the window again
0751     QSignalSpy windowClosedSpy(window, &X11Window::closed);
0752     xcb_unmap_window(c.get(), windowId);
0753     xcb_flush(c.get());
0754     QVERIFY(windowClosedSpy.wait());
0755     xcb_destroy_window(c.get(), windowId);
0756     c.reset();
0757 }
0758 
0759 void X11WindowTest::testCaptionWmName()
0760 {
0761     QFETCH_GLOBAL(qreal, scale);
0762     kwinApp()->setXwaylandScale(scale);
0763 
0764     // this test verifies that a caption set through WM_NAME is read correctly
0765 
0766     // open glxgears as that one only uses WM_NAME
0767     QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
0768 
0769     QProcess glxgears;
0770     glxgears.setProgram(QStringLiteral("glxgears"));
0771     glxgears.start();
0772     QVERIFY(glxgears.waitForStarted());
0773 
0774     QVERIFY(windowAddedSpy.wait());
0775     QCOMPARE(windowAddedSpy.count(), 1);
0776     QCOMPARE(workspace()->windows().count(), 1);
0777     Window *glxgearsWindow = workspace()->windows().first();
0778     QCOMPARE(glxgearsWindow->caption(), QStringLiteral("glxgears"));
0779 
0780     glxgears.terminate();
0781     QVERIFY(glxgears.waitForFinished());
0782 }
0783 
0784 void X11WindowTest::testFullscreenWindowGroups()
0785 {
0786     // this test creates an X11 window and puts it to full screen
0787     // then a second window is created which is in the same window group
0788     // BUG: 388310
0789 
0790     QFETCH_GLOBAL(qreal, scale);
0791     kwinApp()->setXwaylandScale(scale);
0792 
0793     Test::XcbConnectionPtr c = Test::createX11Connection();
0794     QVERIFY(!xcb_connection_has_error(c.get()));
0795     const QRect windowGeometry(0, 0, 100, 200);
0796     xcb_window_t windowId = xcb_generate_id(c.get());
0797     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0798                       windowGeometry.x(),
0799                       windowGeometry.y(),
0800                       windowGeometry.width(),
0801                       windowGeometry.height(),
0802                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0803     xcb_size_hints_t hints;
0804     memset(&hints, 0, sizeof(hints));
0805     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0806     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0807     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0808     xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
0809     xcb_map_window(c.get(), windowId);
0810     xcb_flush(c.get());
0811 
0812     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0813     QVERIFY(windowCreatedSpy.wait());
0814     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0815     QVERIFY(window);
0816     QCOMPARE(window->window(), windowId);
0817     QCOMPARE(window->isActive(), true);
0818 
0819     QCOMPARE(window->isFullScreen(), false);
0820     QCOMPARE(window->layer(), NormalLayer);
0821     workspace()->slotWindowFullScreen();
0822     QCOMPARE(window->isFullScreen(), true);
0823     QCOMPARE(window->layer(), ActiveLayer);
0824 
0825     // now let's create a second window
0826     windowCreatedSpy.clear();
0827     xcb_window_t w2 = xcb_generate_id(c.get());
0828     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, w2, rootWindow(),
0829                       windowGeometry.x(),
0830                       windowGeometry.y(),
0831                       windowGeometry.width(),
0832                       windowGeometry.height(),
0833                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0834     xcb_size_hints_t hints2;
0835     memset(&hints2, 0, sizeof(hints2));
0836     xcb_icccm_size_hints_set_position(&hints2, 1, windowGeometry.x(), windowGeometry.y());
0837     xcb_icccm_size_hints_set_size(&hints2, 1, windowGeometry.width(), windowGeometry.height());
0838     xcb_icccm_set_wm_normal_hints(c.get(), w2, &hints2);
0839     xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, w2, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
0840     xcb_map_window(c.get(), w2);
0841     xcb_flush(c.get());
0842 
0843     QVERIFY(windowCreatedSpy.wait());
0844     X11Window *window2 = windowCreatedSpy.first().first().value<X11Window *>();
0845     QVERIFY(window2);
0846     QVERIFY(window != window2);
0847     QCOMPARE(window2->window(), w2);
0848     QCOMPARE(window2->isActive(), true);
0849     QCOMPARE(window2->group(), window->group());
0850     // first window should be moved back to normal layer
0851     QCOMPARE(window->isActive(), false);
0852     QCOMPARE(window->isFullScreen(), true);
0853     QCOMPARE(window->layer(), NormalLayer);
0854 
0855     // activating the fullscreen window again, should move it to active layer
0856     workspace()->activateWindow(window);
0857     QTRY_COMPARE(window->layer(), ActiveLayer);
0858 }
0859 
0860 void X11WindowTest::testActivateFocusedWindow()
0861 {
0862     // The window manager may call XSetInputFocus() on a window that already has focus, in which
0863     // case no FocusIn event will be generated and the window won't be marked as active. This test
0864     // verifies that we handle that subtle case properly.
0865 
0866     QFETCH_GLOBAL(qreal, scale);
0867     kwinApp()->setXwaylandScale(scale);
0868 
0869     Test::XcbConnectionPtr connection = Test::createX11Connection();
0870     QVERIFY(!xcb_connection_has_error(connection.get()));
0871 
0872     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0873 
0874     const QRect windowGeometry(0, 0, 100, 200);
0875     xcb_size_hints_t hints;
0876     memset(&hints, 0, sizeof(hints));
0877     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0878     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0879 
0880     // Create the first test window.
0881     const xcb_window_t windowId1 = xcb_generate_id(connection.get());
0882     xcb_create_window(connection.get(), XCB_COPY_FROM_PARENT, windowId1, rootWindow(),
0883                       windowGeometry.x(), windowGeometry.y(),
0884                       windowGeometry.width(), windowGeometry.height(),
0885                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0886     xcb_icccm_set_wm_normal_hints(connection.get(), windowId1, &hints);
0887     xcb_change_property(connection.get(), XCB_PROP_MODE_REPLACE, windowId1,
0888                         atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId1);
0889     xcb_map_window(connection.get(), windowId1);
0890     xcb_flush(connection.get());
0891     QVERIFY(windowCreatedSpy.wait());
0892     X11Window *window1 = windowCreatedSpy.first().first().value<X11Window *>();
0893     QVERIFY(window1);
0894     QCOMPARE(window1->window(), windowId1);
0895     QCOMPARE(window1->isActive(), true);
0896 
0897     // Create the second test window.
0898     const xcb_window_t windowId2 = xcb_generate_id(connection.get());
0899     xcb_create_window(connection.get(), XCB_COPY_FROM_PARENT, windowId2, rootWindow(),
0900                       windowGeometry.x(), windowGeometry.y(),
0901                       windowGeometry.width(), windowGeometry.height(),
0902                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0903     xcb_icccm_set_wm_normal_hints(connection.get(), windowId2, &hints);
0904     xcb_change_property(connection.get(), XCB_PROP_MODE_REPLACE, windowId2,
0905                         atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId2);
0906     xcb_map_window(connection.get(), windowId2);
0907     xcb_flush(connection.get());
0908     QVERIFY(windowCreatedSpy.wait());
0909     X11Window *window2 = windowCreatedSpy.last().first().value<X11Window *>();
0910     QVERIFY(window2);
0911     QCOMPARE(window2->window(), windowId2);
0912     QCOMPARE(window2->isActive(), true);
0913 
0914     // When the second test window is destroyed, the window manager will attempt to activate the
0915     // next window in the focus chain, which is the first window.
0916     xcb_set_input_focus(connection.get(), XCB_INPUT_FOCUS_POINTER_ROOT, windowId1, XCB_CURRENT_TIME);
0917     xcb_destroy_window(connection.get(), windowId2);
0918     xcb_flush(connection.get());
0919     QVERIFY(Test::waitForWindowClosed(window2));
0920     QVERIFY(window1->isActive());
0921 
0922     // Destroy the first test window.
0923     xcb_destroy_window(connection.get(), windowId1);
0924     xcb_flush(connection.get());
0925     QVERIFY(Test::waitForWindowClosed(window1));
0926 }
0927 
0928 void X11WindowTest::testReentrantMoveResize()
0929 {
0930     // This test verifies that calling moveResize() from a slot connected directly
0931     // to the frameGeometryChanged() signal won't cause an infinite recursion.
0932 
0933     QFETCH_GLOBAL(qreal, scale);
0934     kwinApp()->setXwaylandScale(scale);
0935 
0936     // Create a test window.
0937     Test::XcbConnectionPtr c = Test::createX11Connection();
0938     QVERIFY(!xcb_connection_has_error(c.get()));
0939     const QRect windowGeometry(0, 0, 100, 200);
0940     xcb_window_t windowId = xcb_generate_id(c.get());
0941     xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
0942                       windowGeometry.x(),
0943                       windowGeometry.y(),
0944                       windowGeometry.width(),
0945                       windowGeometry.height(),
0946                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
0947     xcb_size_hints_t hints;
0948     memset(&hints, 0, sizeof(hints));
0949     xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
0950     xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
0951     xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
0952     xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
0953     xcb_map_window(c.get(), windowId);
0954     xcb_flush(c.get());
0955 
0956     QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
0957     QVERIFY(windowCreatedSpy.wait());
0958     X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
0959     QVERIFY(window);
0960     QCOMPARE(window->pos(), QPoint(0, 0));
0961 
0962     // Let's pretend that there is a script that really wants the window to be at (100, 100).
0963     connect(window, &Window::frameGeometryChanged, this, [window]() {
0964         window->moveResize(QRectF(QPointF(100, 100), window->size()));
0965     });
0966 
0967     // Trigger the lambda above.
0968     window->move(QPoint(40, 50));
0969 
0970     // Eventually, the window will end up at (100, 100).
0971     QCOMPARE(window->pos(), QPoint(100, 100));
0972 
0973     // Destroy the test window.
0974     xcb_destroy_window(c.get(), windowId);
0975     xcb_flush(c.get());
0976     QVERIFY(Test::waitForWindowClosed(window));
0977 }
0978 
0979 WAYLANDTEST_MAIN(X11WindowTest)
0980 #include "x11_window_test.moc"