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

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 "core/output.h"
0012 #include "core/outputbackend.h"
0013 #include "cursor.h"
0014 #include "wayland_server.h"
0015 #include "workspace.h"
0016 
0017 #include <KWayland/Client/output.h>
0018 #include <KWayland/Client/registry.h>
0019 #include <KWayland/Client/xdgoutput.h>
0020 
0021 using namespace KWin;
0022 
0023 static const QString s_socketName = QStringLiteral("wayland_test_kwin_screen_changes-0");
0024 
0025 class ScreenChangesTest : public QObject
0026 {
0027     Q_OBJECT
0028 private Q_SLOTS:
0029     void initTestCase();
0030     void init();
0031     void cleanup();
0032 
0033     void testScreenAddRemove();
0034 };
0035 
0036 void ScreenChangesTest::initTestCase()
0037 {
0038     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0039     QVERIFY(waylandServer()->init(s_socketName));
0040     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024)));
0041 
0042     kwinApp()->start();
0043     QVERIFY(applicationStartedSpy.wait());
0044     setenv("QT_QPA_PLATFORM", "wayland", true);
0045 }
0046 
0047 void ScreenChangesTest::init()
0048 {
0049     QVERIFY(Test::setupWaylandConnection());
0050 
0051     workspace()->setActiveOutput(QPoint(640, 512));
0052     KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512));
0053 }
0054 
0055 void ScreenChangesTest::cleanup()
0056 {
0057     Test::destroyWaylandConnection();
0058 }
0059 
0060 void ScreenChangesTest::testScreenAddRemove()
0061 {
0062     // this test verifies that when a new screen is added it gets synced to Wayland
0063 
0064     // first create a registry to get signals about Outputs announced/removed
0065     KWayland::Client::Registry registry;
0066     QSignalSpy allAnnounced(&registry, &KWayland::Client::Registry::interfacesAnnounced);
0067     QSignalSpy outputAnnouncedSpy(&registry, &KWayland::Client::Registry::outputAnnounced);
0068     QSignalSpy outputRemovedSpy(&registry, &KWayland::Client::Registry::outputRemoved);
0069     registry.create(Test::waylandConnection());
0070     QVERIFY(registry.isValid());
0071     registry.setup();
0072     QVERIFY(allAnnounced.wait());
0073     const auto xdgOMData = registry.interface(KWayland::Client::Registry::Interface::XdgOutputUnstableV1);
0074     auto xdgOutputManager = registry.createXdgOutputManager(xdgOMData.name, xdgOMData.version);
0075 
0076     // should be one output
0077     QCOMPARE(workspace()->outputs().count(), 1);
0078     QCOMPARE(outputAnnouncedSpy.count(), 1);
0079     const quint32 firstOutputId = outputAnnouncedSpy.first().first().value<quint32>();
0080     QVERIFY(firstOutputId != 0u);
0081     outputAnnouncedSpy.clear();
0082 
0083     // let's announce a new output
0084     const QVector<QRect> geometries{QRect(0, 0, 1280, 1024), QRect(1280, 0, 1280, 1024)};
0085     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs",
0086                               Qt::DirectConnection,
0087                               Q_ARG(QVector<QRect>, geometries));
0088     auto outputs = workspace()->outputs();
0089     QCOMPARE(outputs.count(), 2);
0090     QCOMPARE(outputs[0]->geometry(), geometries[0]);
0091     QCOMPARE(outputs[1]->geometry(), geometries[1]);
0092 
0093     // this should result in it getting announced, two new outputs are added...
0094     QVERIFY(outputAnnouncedSpy.wait());
0095     if (outputAnnouncedSpy.count() < 2) {
0096         QVERIFY(outputAnnouncedSpy.wait());
0097     }
0098     QCOMPARE(outputAnnouncedSpy.count(), 2);
0099     // ... and afterward the previous output gets removed
0100     if (outputRemovedSpy.isEmpty()) {
0101         QVERIFY(outputRemovedSpy.wait());
0102     }
0103     QCOMPARE(outputRemovedSpy.count(), 1);
0104     QCOMPARE(outputRemovedSpy.first().first().value<quint32>(), firstOutputId);
0105 
0106     // let's wait a little bit to ensure we don't get more events
0107     QTest::qWait(100);
0108     QCOMPARE(outputAnnouncedSpy.count(), 2);
0109     QCOMPARE(outputRemovedSpy.count(), 1);
0110 
0111     // let's create the output objects to ensure they are correct
0112     std::unique_ptr<KWayland::Client::Output> o1(registry.createOutput(outputAnnouncedSpy.first().first().value<quint32>(), outputAnnouncedSpy.first().last().value<quint32>()));
0113     QVERIFY(o1->isValid());
0114     QSignalSpy o1ChangedSpy(o1.get(), &KWayland::Client::Output::changed);
0115     QVERIFY(o1ChangedSpy.wait());
0116     KWin::Output *serverOutput1 = kwinApp()->outputBackend()->findOutput(o1->name()); // use wl_output.name to find the compositor side output
0117     QCOMPARE(o1->globalPosition(), serverOutput1->geometry().topLeft());
0118     QCOMPARE(o1->pixelSize(), serverOutput1->modeSize());
0119     std::unique_ptr<KWayland::Client::Output> o2(registry.createOutput(outputAnnouncedSpy.last().first().value<quint32>(), outputAnnouncedSpy.last().last().value<quint32>()));
0120     QVERIFY(o2->isValid());
0121     QSignalSpy o2ChangedSpy(o2.get(), &KWayland::Client::Output::changed);
0122     QVERIFY(o2ChangedSpy.wait());
0123     KWin::Output *serverOutput2 = kwinApp()->outputBackend()->findOutput(o2->name()); // use wl_output.name to find the compositor side output
0124     QCOMPARE(o2->globalPosition(), serverOutput2->geometry().topLeft());
0125     QCOMPARE(o2->pixelSize(), serverOutput2->modeSize());
0126 
0127     // and check XDGOutput is synced
0128     std::unique_ptr<KWayland::Client::XdgOutput> xdgO1(xdgOutputManager->getXdgOutput(o1.get()));
0129     QSignalSpy xdgO1ChangedSpy(xdgO1.get(), &KWayland::Client::XdgOutput::changed);
0130     QVERIFY(xdgO1ChangedSpy.wait());
0131     QCOMPARE(xdgO1->logicalPosition(), serverOutput1->geometry().topLeft());
0132     QCOMPARE(xdgO1->logicalSize(), serverOutput1->geometry().size());
0133     std::unique_ptr<KWayland::Client::XdgOutput> xdgO2(xdgOutputManager->getXdgOutput(o2.get()));
0134     QSignalSpy xdgO2ChangedSpy(xdgO2.get(), &KWayland::Client::XdgOutput::changed);
0135     QVERIFY(xdgO2ChangedSpy.wait());
0136     QCOMPARE(xdgO2->logicalPosition(), serverOutput2->geometry().topLeft());
0137     QCOMPARE(xdgO2->logicalSize(), serverOutput2->geometry().size());
0138 
0139     QVERIFY(xdgO1->name().startsWith("Virtual-"));
0140     QVERIFY(xdgO1->name() != xdgO2->name());
0141     QVERIFY(!xdgO1->description().isEmpty());
0142 
0143     // now let's try to remove one output again
0144     outputAnnouncedSpy.clear();
0145     outputRemovedSpy.clear();
0146 
0147     QSignalSpy o1RemovedSpy(o1.get(), &KWayland::Client::Output::removed);
0148     QSignalSpy o2RemovedSpy(o2.get(), &KWayland::Client::Output::removed);
0149 
0150     const QVector<QRect> geometries2{QRect(0, 0, 1280, 1024)};
0151     QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs",
0152                               Qt::DirectConnection,
0153                               Q_ARG(QVector<QRect>, geometries2));
0154     outputs = workspace()->outputs();
0155     QCOMPARE(outputs.count(), 1);
0156     QCOMPARE(outputs[0]->geometry(), geometries2.at(0));
0157 
0158     QVERIFY(outputAnnouncedSpy.wait());
0159     QCOMPARE(outputAnnouncedSpy.count(), 1);
0160     if (o1RemovedSpy.isEmpty()) {
0161         QVERIFY(o1RemovedSpy.wait());
0162     }
0163     if (o2RemovedSpy.isEmpty()) {
0164         QVERIFY(o2RemovedSpy.wait());
0165     }
0166     // now wait a bit to ensure we don't get more events
0167     QTest::qWait(100);
0168     QCOMPARE(outputAnnouncedSpy.count(), 1);
0169     QCOMPARE(o1RemovedSpy.count(), 1);
0170     QCOMPARE(o2RemovedSpy.count(), 1);
0171     QCOMPARE(outputRemovedSpy.count(), 2);
0172 }
0173 
0174 WAYLANDTEST_MAIN(ScreenChangesTest)
0175 #include "screen_changes_test.moc"