File indexing completed on 2024-05-19 09:23:20

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