Warning, file /plasma/kwin/autotests/integration/x11_window_test.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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