File indexing completed on 2024-05-05 17:35:53

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2019 David Edmundson <davidedmundson@kde.org>
0006     SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 #include "kwin_wayland_test.h"
0011 
0012 #include "core/output.h"
0013 #include "core/outputbackend.h"
0014 #include "cursor.h"
0015 #include "placement.h"
0016 #include "wayland_server.h"
0017 #include "window.h"
0018 #include "workspace.h"
0019 
0020 #include <KWayland/Client/compositor.h>
0021 #include <KWayland/Client/plasmashell.h>
0022 #include <KWayland/Client/shm_pool.h>
0023 #include <KWayland/Client/surface.h>
0024 
0025 using namespace KWin;
0026 
0027 static const QString s_socketName = QStringLiteral("wayland_test_kwin_placement-0");
0028 
0029 struct PlaceWindowResult
0030 {
0031     QSizeF initiallyConfiguredSize;
0032     Test::XdgToplevel::States initiallyConfiguredStates;
0033     QRectF finalGeometry;
0034 };
0035 
0036 class TestPlacement : public QObject
0037 {
0038     Q_OBJECT
0039 
0040 private Q_SLOTS:
0041     void init();
0042     void cleanup();
0043     void initTestCase();
0044 
0045     void testPlaceSmart();
0046     void testPlaceMaximized();
0047     void testPlaceMaximizedLeavesFullscreen();
0048     void testPlaceCentered();
0049     void testPlaceUnderMouse();
0050     void testPlaceZeroCornered();
0051     void testPlaceRandom();
0052     void testFullscreen();
0053 
0054 private:
0055     void setPlacementPolicy(PlacementPolicy policy);
0056     /*
0057      * Create a window and return relevant results for testing
0058      * defaultSize is the buffer size to use if the compositor returns an empty size in the first configure
0059      * event.
0060      */
0061     std::pair<PlaceWindowResult, std::unique_ptr<KWayland::Client::Surface>> createAndPlaceWindow(const QSize &defaultSize);
0062 };
0063 
0064 void TestPlacement::init()
0065 {
0066     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell));
0067 
0068     workspace()->setActiveOutput(QPoint(640, 512));
0069     KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512));
0070 }
0071 
0072 void TestPlacement::cleanup()
0073 {
0074     Test::destroyWaylandConnection();
0075 }
0076 
0077 void TestPlacement::initTestCase()
0078 {
0079     qRegisterMetaType<KWin::Window *>();
0080     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0081     QVERIFY(waylandServer()->init(s_socketName));
0082     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024)));
0083 
0084     kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
0085 
0086     kwinApp()->start();
0087     QVERIFY(applicationStartedSpy.wait());
0088     const auto outputs = workspace()->outputs();
0089     QCOMPARE(outputs.count(), 2);
0090     QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024));
0091     QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024));
0092 }
0093 
0094 void TestPlacement::setPlacementPolicy(PlacementPolicy policy)
0095 {
0096     auto group = kwinApp()->config()->group("Windows");
0097     group.writeEntry("Placement", Placement::policyToString(policy));
0098     group.sync();
0099     Workspace::self()->slotReconfigure();
0100 }
0101 
0102 std::pair<PlaceWindowResult, std::unique_ptr<KWayland::Client::Surface>> TestPlacement::createAndPlaceWindow(const QSize &defaultSize)
0103 {
0104     PlaceWindowResult rc;
0105 
0106     // create a new window
0107     std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0108     auto shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get());
0109 
0110     QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested);
0111     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0112     surface->commit(KWayland::Client::Surface::CommitFlag::None);
0113     surfaceConfigureRequestedSpy.wait();
0114 
0115     rc.initiallyConfiguredSize = toplevelConfigureRequestedSpy[0][0].toSize();
0116     rc.initiallyConfiguredStates = toplevelConfigureRequestedSpy[0][1].value<Test::XdgToplevel::States>();
0117     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy[0][0].toUInt());
0118 
0119     QSizeF size = rc.initiallyConfiguredSize;
0120 
0121     if (size.isEmpty()) {
0122         size = defaultSize;
0123     }
0124 
0125     auto window = Test::renderAndWaitForShown(surface.get(), size.toSize(), Qt::red);
0126 
0127     rc.finalGeometry = window->frameGeometry();
0128     return {rc, std::move(surface)};
0129 }
0130 
0131 void TestPlacement::testPlaceSmart()
0132 {
0133     setPlacementPolicy(PlacementSmart);
0134 
0135     std::vector<std::unique_ptr<KWayland::Client::Surface>> surfaces;
0136     QRegion usedArea;
0137 
0138     for (int i = 0; i < 4; i++) {
0139         auto [windowPlacement, surface] = createAndPlaceWindow(QSize(600, 500));
0140         // smart placement shouldn't define a size on windows
0141         QCOMPARE(windowPlacement.initiallyConfiguredSize, QSize(0, 0));
0142         QCOMPARE(windowPlacement.finalGeometry.size(), QSize(600, 500));
0143 
0144         // exact placement isn't a defined concept that should be tested
0145         // but the goal of smart placement is to make sure windows don't overlap until they need to
0146         // 4 windows of 600, 500 should fit without overlap
0147         QVERIFY(!usedArea.intersects(windowPlacement.finalGeometry.toRect()));
0148         usedArea += windowPlacement.finalGeometry.toRect();
0149         surfaces.push_back(std::move(surface));
0150     }
0151 }
0152 
0153 void TestPlacement::testPlaceMaximized()
0154 {
0155     setPlacementPolicy(PlacementMaximizing);
0156 
0157     // add a top panel
0158     std::unique_ptr<KWayland::Client::Surface> panelSurface(Test::createSurface());
0159     std::unique_ptr<QObject> panelShellSurface(Test::createXdgToplevelSurface(panelSurface.get()));
0160     std::unique_ptr<KWayland::Client::PlasmaShellSurface> plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.get()));
0161     plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel);
0162     plasmaSurface->setPosition(QPoint(0, 0));
0163     Test::renderAndWaitForShown(panelSurface.get(), QSize(1280, 20), Qt::blue);
0164 
0165     std::vector<std::unique_ptr<KWayland::Client::Surface>> surfaces;
0166 
0167     // all windows should be initially maximized with an initial configure size sent
0168     for (int i = 0; i < 4; i++) {
0169         auto [windowPlacement, surface] = createAndPlaceWindow(QSize(600, 500));
0170         QVERIFY(windowPlacement.initiallyConfiguredStates & Test::XdgToplevel::State::Maximized);
0171         QCOMPARE(windowPlacement.initiallyConfiguredSize, QSize(1280, 1024 - 20));
0172         QCOMPARE(windowPlacement.finalGeometry, QRect(0, 20, 1280, 1024 - 20)); // under the panel
0173         surfaces.push_back(std::move(surface));
0174     }
0175 }
0176 
0177 void TestPlacement::testPlaceMaximizedLeavesFullscreen()
0178 {
0179     setPlacementPolicy(PlacementMaximizing);
0180 
0181     // add a top panel
0182     std::unique_ptr<KWayland::Client::Surface> panelSurface(Test::createSurface());
0183     std::unique_ptr<QObject> panelShellSurface(Test::createXdgToplevelSurface(panelSurface.get()));
0184     std::unique_ptr<KWayland::Client::PlasmaShellSurface> plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.get()));
0185     plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel);
0186     plasmaSurface->setPosition(QPoint(0, 0));
0187     Test::renderAndWaitForShown(panelSurface.get(), QSize(1280, 20), Qt::blue);
0188 
0189     std::vector<std::unique_ptr<KWayland::Client::Surface>> surfaces;
0190 
0191     // all windows should be initially fullscreen with an initial configure size sent, despite the policy
0192     for (int i = 0; i < 4; i++) {
0193         std::unique_ptr<KWayland::Client::Surface> surface = Test::createSurface();
0194         auto shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get());
0195         shellSurface->set_fullscreen(nullptr);
0196         QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested);
0197         QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0198         surface->commit(KWayland::Client::Surface::CommitFlag::None);
0199         QVERIFY(surfaceConfigureRequestedSpy.wait());
0200 
0201         auto initiallyConfiguredSize = toplevelConfigureRequestedSpy[0][0].toSize();
0202         auto initiallyConfiguredStates = toplevelConfigureRequestedSpy[0][1].value<Test::XdgToplevel::States>();
0203         shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy[0][0].toUInt());
0204 
0205         auto window = Test::renderAndWaitForShown(surface.get(), initiallyConfiguredSize, Qt::red);
0206 
0207         QVERIFY(initiallyConfiguredStates & Test::XdgToplevel::State::Fullscreen);
0208         QCOMPARE(initiallyConfiguredSize, QSize(1280, 1024));
0209         QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024));
0210 
0211         surfaces.push_back(std::move(surface));
0212     }
0213 }
0214 
0215 void TestPlacement::testPlaceCentered()
0216 {
0217     // This test verifies that Centered placement policy works.
0218 
0219     KConfigGroup group = kwinApp()->config()->group("Windows");
0220     group.writeEntry("Placement", Placement::policyToString(PlacementCentered));
0221     group.sync();
0222     workspace()->slotReconfigure();
0223 
0224     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0225     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0226     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red);
0227     QVERIFY(window);
0228     QCOMPARE(window->frameGeometry(), QRect(590, 487, 100, 50));
0229 
0230     shellSurface.reset();
0231     QVERIFY(Test::waitForWindowDestroyed(window));
0232 }
0233 
0234 void TestPlacement::testPlaceUnderMouse()
0235 {
0236     // This test verifies that Under Mouse placement policy works.
0237 
0238     KConfigGroup group = kwinApp()->config()->group("Windows");
0239     group.writeEntry("Placement", Placement::policyToString(PlacementUnderMouse));
0240     group.sync();
0241     workspace()->slotReconfigure();
0242 
0243     KWin::Cursors::self()->mouse()->setPos(QPoint(200, 300));
0244     QCOMPARE(KWin::Cursors::self()->mouse()->pos(), QPoint(200, 300));
0245 
0246     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0247     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0248     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red);
0249     QVERIFY(window);
0250     QCOMPARE(window->frameGeometry(), QRect(150, 275, 100, 50));
0251 
0252     shellSurface.reset();
0253     QVERIFY(Test::waitForWindowDestroyed(window));
0254 }
0255 
0256 void TestPlacement::testPlaceZeroCornered()
0257 {
0258     // This test verifies that the Zero-Cornered placement policy works.
0259 
0260     KConfigGroup group = kwinApp()->config()->group("Windows");
0261     group.writeEntry("Placement", Placement::policyToString(PlacementZeroCornered));
0262     group.sync();
0263     workspace()->slotReconfigure();
0264 
0265     std::unique_ptr<KWayland::Client::Surface> surface1(Test::createSurface());
0266     std::unique_ptr<Test::XdgToplevel> shellSurface1(Test::createXdgToplevelSurface(surface1.get()));
0267     Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::red);
0268     QVERIFY(window1);
0269     QCOMPARE(window1->pos(), QPoint(0, 0));
0270     QCOMPARE(window1->size(), QSize(100, 50));
0271 
0272     std::unique_ptr<KWayland::Client::Surface> surface2(Test::createSurface());
0273     std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get()));
0274     Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue);
0275     QVERIFY(window2);
0276     QCOMPARE(window2->pos(), window1->pos() + workspace()->cascadeOffset(window2));
0277     QCOMPARE(window2->size(), QSize(100, 50));
0278 
0279     std::unique_ptr<KWayland::Client::Surface> surface3(Test::createSurface());
0280     std::unique_ptr<Test::XdgToplevel> shellSurface3(Test::createXdgToplevelSurface(surface3.get()));
0281     Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::green);
0282     QVERIFY(window3);
0283     QCOMPARE(window3->pos(), window2->pos() + workspace()->cascadeOffset(window3));
0284     QCOMPARE(window3->size(), QSize(100, 50));
0285 
0286     shellSurface3.reset();
0287     QVERIFY(Test::waitForWindowDestroyed(window3));
0288     shellSurface2.reset();
0289     QVERIFY(Test::waitForWindowDestroyed(window2));
0290     shellSurface1.reset();
0291     QVERIFY(Test::waitForWindowDestroyed(window1));
0292 }
0293 
0294 void TestPlacement::testPlaceRandom()
0295 {
0296     // This test verifies that Random placement policy works.
0297 
0298     KConfigGroup group = kwinApp()->config()->group("Windows");
0299     group.writeEntry("Placement", Placement::policyToString(PlacementRandom));
0300     group.sync();
0301     workspace()->slotReconfigure();
0302 
0303     std::unique_ptr<KWayland::Client::Surface> surface1(Test::createSurface());
0304     std::unique_ptr<Test::XdgToplevel> shellSurface1(Test::createXdgToplevelSurface(surface1.get()));
0305     Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::red);
0306     QVERIFY(window1);
0307     QCOMPARE(window1->size(), QSize(100, 50));
0308 
0309     std::unique_ptr<KWayland::Client::Surface> surface2(Test::createSurface());
0310     std::unique_ptr<Test::XdgToplevel> shellSurface2(Test::createXdgToplevelSurface(surface2.get()));
0311     Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue);
0312     QVERIFY(window2);
0313     QVERIFY(window2->pos() != window1->pos());
0314     QCOMPARE(window2->size(), QSize(100, 50));
0315 
0316     std::unique_ptr<KWayland::Client::Surface> surface3(Test::createSurface());
0317     std::unique_ptr<Test::XdgToplevel> shellSurface3(Test::createXdgToplevelSurface(surface3.get()));
0318     Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::green);
0319     QVERIFY(window3);
0320     QVERIFY(window3->pos() != window1->pos());
0321     QVERIFY(window3->pos() != window2->pos());
0322     QCOMPARE(window3->size(), QSize(100, 50));
0323 
0324     shellSurface3.reset();
0325     QVERIFY(Test::waitForWindowDestroyed(window3));
0326     shellSurface2.reset();
0327     QVERIFY(Test::waitForWindowDestroyed(window2));
0328     shellSurface1.reset();
0329     QVERIFY(Test::waitForWindowDestroyed(window1));
0330 }
0331 
0332 void TestPlacement::testFullscreen()
0333 {
0334     const QList<Output *> outputs = workspace()->outputs();
0335 
0336     setPlacementPolicy(PlacementSmart);
0337     std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
0338     std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
0339 
0340     Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red);
0341     QVERIFY(window);
0342     window->sendToOutput(outputs[0]);
0343 
0344     // Wait for the configure event with the activated state.
0345     QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested);
0346     QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
0347     QVERIFY(surfaceConfigureRequestedSpy.wait());
0348 
0349     window->setFullScreen(true);
0350 
0351     QSignalSpy geometryChangedSpy(window, &Window::frameGeometryChanged);
0352     QVERIFY(surfaceConfigureRequestedSpy.wait());
0353     shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
0354     Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).toSize(), Qt::red);
0355     QVERIFY(geometryChangedSpy.wait());
0356     QCOMPARE(window->frameGeometry(), outputs[0]->geometry());
0357 
0358     // this doesn't require a round trip, so should be immediate
0359     window->sendToOutput(outputs[1]);
0360     QCOMPARE(window->frameGeometry(), outputs[1]->geometry());
0361     QCOMPARE(geometryChangedSpy.count(), 2);
0362 }
0363 
0364 WAYLANDTEST_MAIN(TestPlacement)
0365 #include "placement_test.moc"