File indexing completed on 2024-05-12 17:10:21

0001 /*
0002     SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include <QObject>
0008 
0009 #include <QApplication>
0010 #include <QDir>
0011 #include <QScreen>
0012 #include <QSignalSpy>
0013 #include <QStandardPaths>
0014 #include <QTemporaryDir>
0015 #include <QTest>
0016 
0017 #include "../screenpool.h"
0018 #include "mockcompositor.h"
0019 #include "xdgoutputv1.h"
0020 
0021 #include "config-KDECI_BUILD.h"
0022 
0023 using namespace MockCompositor;
0024 
0025 class ScreenPoolTest : public QObject, DefaultCompositor
0026 {
0027     Q_OBJECT
0028 
0029 private Q_SLOTS:
0030     void initTestCase();
0031     void cleanupTestCase();
0032 
0033     void testScreenInsertion();
0034     void testRedundantScreenInsertion();
0035     void testMoveOutOfRedundant();
0036     void testMoveInRedundant();
0037     void testOrderSwap();
0038     void testPrimarySwapToRedundant();
0039     void testMoveRedundantToMakePrimary();
0040     void testMoveInRedundantToLosePrimary();
0041     void testSecondScreenRemoval();
0042     void testThirdScreenRemoval();
0043     void testLastScreenRemoval();
0044     void testFakeToRealScreen();
0045 
0046 private:
0047     ScreenPool *m_screenPool;
0048 };
0049 
0050 void ScreenPoolTest::initTestCase()
0051 {
0052     QStandardPaths::setTestModeEnabled(true);
0053     qRegisterMetaType<QScreen *>();
0054 
0055     m_screenPool = new ScreenPool(this);
0056 
0057     QTRY_COMPARE(QGuiApplication::screens().size(), 1);
0058     QTRY_COMPARE(m_screenPool->screenOrder().size(), 1);
0059     QCOMPARE(QGuiApplication::screens().first()->name(), QStringLiteral("WL-1"));
0060     QCOMPARE(QGuiApplication::primaryScreen(), QGuiApplication::screens().first());
0061     QCOMPARE(QGuiApplication::primaryScreen(), m_screenPool->primaryScreen());
0062     QCOMPARE(m_screenPool->idForScreen(m_screenPool->primaryScreen()), 0);
0063     QCOMPARE(m_screenPool->screenForId(0)->name(), QStringLiteral("WL-1"));
0064 }
0065 
0066 void ScreenPoolTest::cleanupTestCase()
0067 {
0068 #if !KDECI_BUILD
0069     QCOMPOSITOR_COMPARE(getAll<Output>().size(), 1); // Only the default output should be left
0070     QTRY_COMPARE(QGuiApplication::screens().size(), 1);
0071     QTRY_VERIFY2(isClean(), qPrintable(dirtyMessage()));
0072 #endif
0073 
0074     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("ScreenConnectors"));
0075     cg.deleteGroup();
0076     cg.sync();
0077 }
0078 
0079 void ScreenPoolTest::testScreenInsertion()
0080 {
0081     QSignalSpy addedSpy(qGuiApp, SIGNAL(screenAdded(QScreen *)));
0082     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0083 
0084     // Add a new output
0085     exec([=] {
0086         OutputData data;
0087         data.mode.resolution = {1920, 1080};
0088         data.position = {1920, 0};
0089         data.physicalSize = data.mode.physicalSizeForDpi(96);
0090         // NOTE: assumes that when a screen is added it will already have the final geometry
0091         add<Output>(data);
0092         outputOrder()->setList({"WL-1", "WL-2"});
0093     });
0094 
0095     addedSpy.wait();
0096 
0097     orderChangeSpy.wait();
0098 
0099     QCOMPARE(orderChangeSpy.size(), 1);
0100     QCOMPARE(QGuiApplication::screens().size(), 2);
0101     QCOMPARE(m_screenPool->screenOrder().size(), 2);
0102     QCOMPARE(addedSpy.size(), 1);
0103 
0104     QScreen *newScreen = addedSpy.takeFirst().at(0).value<QScreen *>();
0105     QCOMPARE(newScreen->name(), QStringLiteral("WL-2"));
0106     QCOMPARE(newScreen->geometry(), QRect(1920, 0, 1920, 1080));
0107     // Check mapping
0108     QCOMPARE(m_screenPool->idForScreen(newScreen), 1);
0109     QCOMPARE(m_screenPool->screenForId(1)->name(), QStringLiteral("WL-2"));
0110 }
0111 
0112 void ScreenPoolTest::testRedundantScreenInsertion()
0113 {
0114     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0115     QSignalSpy addedFromAppSpy(qGuiApp, SIGNAL(screenAdded(QScreen *)));
0116 
0117     // Add a new output
0118     exec([=] {
0119         OutputData data;
0120         data.mode.resolution = {1280, 720};
0121         data.position = {1920, 0};
0122         data.physicalSize = data.mode.physicalSizeForDpi(96);
0123         // NOTE: assumes that when a screen is added it will already have the final geometry
0124         add<Output>(data);
0125         outputOrder()->setList({"WL-1", "WL-2", "WL-3"});
0126     });
0127 
0128     addedFromAppSpy.wait();
0129     orderChangeSpy.wait(250);
0130     // only addedFromAppSpy will have registered something, nothing in orderChangeSpy,
0131     // on ScreenPool API POV is like this new screen doesn't exist, because is redundant to WL-2
0132     QCOMPARE(QGuiApplication::screens().size(), 3);
0133     QCOMPARE(m_screenPool->screenOrder().size(), 2);
0134     QCOMPARE(addedFromAppSpy.size(), 1);
0135     QCOMPARE(orderChangeSpy.size(), 0);
0136 
0137     QScreen *newScreen = addedFromAppSpy.takeFirst().at(0).value<QScreen *>();
0138     QCOMPARE(newScreen->name(), QStringLiteral("WL-3"));
0139     QCOMPARE(newScreen->geometry(), QRect(1920, 0, 1280, 720));
0140     QVERIFY(!m_screenPool->screenOrder().contains(newScreen));
0141 
0142     QCOMPARE(m_screenPool->idForScreen(newScreen), -1);
0143 }
0144 
0145 void ScreenPoolTest::testMoveOutOfRedundant()
0146 {
0147     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0148 
0149     exec([=] {
0150         auto *out = output(2);
0151         auto *xdgOut = xdgOutput(out);
0152         out->m_data.mode.resolution = {1280, 2048};
0153         xdgOut->sendLogicalSize(QSize(1280, 2048));
0154         out->sendDone();
0155         outputOrder()->setList({"WL-1", "WL-2", "WL-3"});
0156     });
0157 
0158     orderChangeSpy.wait();
0159     QCOMPARE(orderChangeSpy.size(), 1);
0160 
0161     QList<QScreen *> newOrder = orderChangeSpy[0].at(0).value<QList<QScreen *>>();
0162     QCOMPARE(newOrder.count(), 3);
0163     QCOMPARE(newOrder[1]->name(), QStringLiteral("WL-2"));
0164     QCOMPARE(newOrder[1]->geometry(), QRect(1920, 0, 1920, 1080));
0165     QVERIFY(m_screenPool->screenOrder().contains(newOrder[0]));
0166 }
0167 
0168 void ScreenPoolTest::testMoveInRedundant()
0169 {
0170     QSignalSpy removedSpy(m_screenPool, SIGNAL(screenRemoved(QScreen *)));
0171 
0172     exec([=] {
0173         auto *out = output(2);
0174         auto *xdgOut = xdgOutput(out);
0175         out->m_data.mode.resolution = {1280, 720};
0176         xdgOut->sendLogicalSize(QSize(1280, 720));
0177         out->sendDone();
0178         outputOrder()->setList({"WL-1", "WL-2", "WL-3"});
0179     });
0180 
0181     removedSpy.wait();
0182     QCOMPARE(removedSpy.size(), 1);
0183     QScreen *oldScreen = removedSpy.takeFirst().at(0).value<QScreen *>();
0184     QCOMPARE(oldScreen->name(), QStringLiteral("WL-3"));
0185     QCOMPARE(oldScreen->geometry(), QRect(1920, 0, 1280, 720));
0186     QVERIFY(!m_screenPool->screenOrder().contains(oldScreen));
0187 }
0188 
0189 void ScreenPoolTest::testOrderSwap()
0190 {
0191     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0192 
0193     // Check ScreenPool mapping before switch
0194     QList<QScreen *> oldOrder = m_screenPool->screenOrder();
0195     QCOMPARE(oldOrder.count(), 2);
0196     QCOMPARE(oldOrder[0]->name(), QStringLiteral("WL-1"));
0197     QCOMPARE(oldOrder[1]->name(), QStringLiteral("WL-2"));
0198 
0199     QCOMPARE(m_screenPool->screenOrder()[0]->name(), QStringLiteral("WL-1"));
0200     QCOMPARE(m_screenPool->screenOrder()[1]->name(), QStringLiteral("WL-2"));
0201 
0202     // Set a primary screen
0203     // TODO: "WL-2", "WL-1", "WL-3" when tests are self contained
0204     exec([=] {
0205         outputOrder()->setList({"WL-2", "WL-1", "WL-3"});
0206     });
0207 
0208     orderChangeSpy.wait();
0209 
0210     QCOMPARE(orderChangeSpy.size(), 1);
0211     QList<QScreen *> newOrder = orderChangeSpy[0].at(0).value<QList<QScreen *>>();
0212     QCOMPARE(newOrder.count(), 2);
0213     QCOMPARE(newOrder[0], oldOrder[1]);
0214     QCOMPARE(newOrder[1], oldOrder[0]);
0215 
0216     QCOMPARE(newOrder[1]->name(), QStringLiteral("WL-1"));
0217     QCOMPARE(newOrder[1]->geometry(), QRect(0, 0, 1920, 1080));
0218     QCOMPARE(newOrder[0]->name(), QStringLiteral("WL-2"));
0219     QCOMPARE(newOrder[0]->geometry(), QRect(1920, 0, 1920, 1080));
0220 
0221     // Check ScreenPool mapping
0222     QCOMPARE(newOrder[0]->name(), QStringLiteral("WL-2"));
0223     QCOMPARE(m_screenPool->primaryScreen()->name(), newOrder[0]->name());
0224     QCOMPARE(m_screenPool->idForScreen(newOrder[0]), 0);
0225     QCOMPARE(m_screenPool->idForScreen(newOrder[1]), 1);
0226 }
0227 
0228 void ScreenPoolTest::testPrimarySwapToRedundant()
0229 {
0230     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0231 
0232     // Set a primary screen
0233     exec([=] {
0234         outputOrder()->setList({"WL-3", "WL-2", "WL-1"});
0235     });
0236 
0237     orderChangeSpy.wait(250);
0238     // Nothing will happen, is like WL3 doesn't exist
0239     QCOMPARE(orderChangeSpy.size(), 0);
0240 }
0241 
0242 void ScreenPoolTest::testMoveRedundantToMakePrimary()
0243 {
0244     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0245 
0246     exec([=] {
0247         auto *out = output(2);
0248         auto *xdgOut = xdgOutput(out);
0249         out->m_data.mode.resolution = {1280, 2048};
0250         xdgOut->sendLogicalSize(QSize(1280, 2048));
0251         out->sendDone();
0252         outputOrder()->setList({"WL-3", "WL-2", "WL-1"});
0253     });
0254 
0255     QTRY_COMPARE(orderChangeSpy.size(), 1);
0256     /*QScreen *newScreen = addedSpy.takeFirst().at(0).value<QScreen *>();
0257     QCOMPARE(newScreen->name(), QStringLiteral("WL-3"));
0258     QCOMPARE(newScreen->geometry(), QRect(1920, 0, 1280, 2048));
0259     QVERIFY(m_screenPool->screens().contains(newScreen));*/
0260 
0261     // Test the new primary
0262     QList<QScreen *> newOrder = orderChangeSpy[0].at(0).value<QList<QScreen *>>();
0263     QCOMPARE(newOrder.size(), 3);
0264     QCOMPARE(newOrder[0]->name(), QStringLiteral("WL-3"));
0265     QCOMPARE(newOrder[0]->geometry(), QRect(1920, 0, 1280, 2048));
0266     QCOMPARE(newOrder[1]->name(), QStringLiteral("WL-2"));
0267     QCOMPARE(newOrder[1]->geometry(), QRect(1920, 0, 1920, 1080));
0268     QCOMPARE(newOrder[2]->name(), QStringLiteral("WL-1"));
0269     QCOMPARE(newOrder[2]->geometry(), QRect(0, 0, 1920, 1080));
0270 
0271     // Check ScreenPool mapping
0272     QCOMPARE(m_screenPool->primaryScreen()->name(), QStringLiteral("WL-3"));
0273     QCOMPARE(m_screenPool->idForScreen(newOrder[0]), 0);
0274     QCOMPARE(m_screenPool->idForScreen(newOrder[1]), 1);
0275 }
0276 
0277 void ScreenPoolTest::testMoveInRedundantToLosePrimary()
0278 {
0279     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0280 
0281     QCOMPARE(m_screenPool->screenOrder().size(), 3);
0282     QScreen *oldScreen = m_screenPool->screenOrder()[0];
0283 
0284     exec([=] {
0285         auto *out = output(2);
0286         auto *xdgOut = xdgOutput(out);
0287         xdgOut->sendLogicalSize(QSize(1280, 720));
0288         out->m_data.mode.resolution = {1280, 720};
0289         out->sendDone();
0290         outputOrder()->setList({"WL-3", "WL-2", "WL-1"});
0291     });
0292 
0293     QTRY_COMPARE(orderChangeSpy.size(), 1);
0294     QList<QScreen *> newOrder = orderChangeSpy[0].at(0).value<QList<QScreen *>>();
0295     QCOMPARE(newOrder.size(), 2);
0296 
0297     QCOMPARE(newOrder[1]->name(), QStringLiteral("WL-1"));
0298     QCOMPARE(newOrder[1]->geometry(), QRect(0, 0, 1920, 1080));
0299     QCOMPARE(newOrder[0]->name(), QStringLiteral("WL-2"));
0300     QCOMPARE(newOrder[0]->geometry(), QRect(1920, 0, 1920, 1080));
0301 
0302     // Check ScreenPool mapping
0303     QCOMPARE(newOrder[0]->name(), QStringLiteral("WL-2"));
0304     QCOMPARE(m_screenPool->primaryScreen()->name(), newOrder[0]->name());
0305     QCOMPARE(m_screenPool->idForScreen(newOrder[0]), 0);
0306     QCOMPARE(m_screenPool->idForScreen(newOrder[1]), 1);
0307 
0308     QVERIFY(!newOrder.contains(oldScreen));
0309     QCOMPARE(oldScreen->name(), QStringLiteral("WL-3"));
0310     QCOMPARE(oldScreen->geometry(), QRect(1920, 0, 1280, 720));
0311     QVERIFY(!m_screenPool->screenOrder().contains(oldScreen));
0312 }
0313 
0314 void ScreenPoolTest::testSecondScreenRemoval()
0315 {
0316     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0317     QSignalSpy removedSpy(m_screenPool, SIGNAL(screenRemoved(QScreen *)));
0318 
0319     // Check ScreenPool mapping before switch
0320     QCOMPARE(m_screenPool->screenOrder()[0]->name(), QStringLiteral("WL-2"));
0321     QCOMPARE(m_screenPool->screenOrder()[1]->name(), QStringLiteral("WL-1"));
0322 
0323     // Remove an output
0324     exec([=] {
0325         remove(output(1));
0326         outputOrder()->setList({"WL-3", "WL-1"});
0327     });
0328 
0329     // Removing the primary screen, will change a primaryChange signal beforehand
0330     QTRY_COMPARE(orderChangeSpy.size(), 1);
0331     QCOMPARE(orderChangeSpy.size(), 1);
0332     QList<QScreen *> newOrder = orderChangeSpy[0].at(0).value<QList<QScreen *>>();
0333     QCOMPARE(newOrder.size(), 2);
0334     QCOMPARE(newOrder[0]->name(), QStringLiteral("WL-3"));
0335     QCOMPARE(newOrder[0]->geometry(), QRect(1920, 0, 1280, 720));
0336     QCOMPARE(newOrder[1]->name(), QStringLiteral("WL-1"));
0337     QCOMPARE(newOrder[1]->geometry(), QRect(0, 0, 1920, 1080));
0338 }
0339 
0340 void ScreenPoolTest::testThirdScreenRemoval()
0341 {
0342     QSignalSpy removedSpy(m_screenPool, SIGNAL(screenRemoved(QScreen *)));
0343     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0344 
0345     // Remove an output
0346     exec([=] {
0347         remove(output(1));
0348         outputOrder()->setList({"WL-1"});
0349     });
0350 
0351     // NOTE: we can neither access the data of removedSpy nor oldPrimary because at this point will be dangling
0352     removedSpy.wait();
0353     QTRY_COMPARE(orderChangeSpy.size(), 1);
0354 
0355     QCOMPARE(orderChangeSpy.size(), 1);
0356     QList<QScreen *> newOrder = orderChangeSpy[0].at(0).value<QList<QScreen *>>();
0357     QCOMPARE(newOrder.size(), 1);
0358     QCOMPARE(QGuiApplication::screens().size(), 1);
0359     QCOMPARE(m_screenPool->screenOrder().size(), 1);
0360     QScreen *lastScreen = m_screenPool->screenOrder().first();
0361     QCOMPARE(lastScreen->name(), QStringLiteral("WL-1"));
0362     QCOMPARE(lastScreen->geometry(), QRect(0, 0, 1920, 1080));
0363     QCOMPARE(m_screenPool->screenOrder().first(), lastScreen);
0364     // This shouldn't have changed after removing a non primary screen
0365     QCOMPARE(m_screenPool->screenOrder()[0]->name(), QStringLiteral("WL-1"));
0366 }
0367 
0368 void ScreenPoolTest::testLastScreenRemoval()
0369 {
0370     QSignalSpy removedSpy(m_screenPool, SIGNAL(screenRemoved(QScreen *)));
0371 
0372     // Remove an output
0373     exec([=] {
0374         remove(output(0));
0375         outputOrder()->setList({});
0376     });
0377 
0378     // NOTE: we can neither access the data of removedSpy nor oldPrimary because at this point will be dangling
0379     removedSpy.wait();
0380 
0381     QCOMPARE(QGuiApplication::screens().size(), 1);
0382     QCOMPARE(m_screenPool->screenOrder().size(), 0);
0383     QScreen *fakeScreen = QGuiApplication::screens().first();
0384     QCOMPARE(fakeScreen->name(), QString());
0385     QCOMPARE(fakeScreen->geometry(), QRect(0, 0, 0, 0));
0386 }
0387 
0388 void ScreenPoolTest::testFakeToRealScreen()
0389 {
0390 #if KDECI_BUILD
0391     QSKIP("testFakeToRealScreen causes assertion error in screenpool.cpp line 320");
0392 #endif
0393 
0394     QSignalSpy orderChangeSpy(m_screenPool, &ScreenPool::screenOrderChanged);
0395 
0396     // Add a new output
0397     exec([=] {
0398         OutputData data;
0399         data.mode.resolution = {1920, 1080};
0400         data.position = {0, 0};
0401         data.physicalSize = data.mode.physicalSizeForDpi(96);
0402         auto *out = add<Output>(data);
0403         auto *xdgOut = xdgOutput(out);
0404         xdgOut->m_name = QStringLiteral("WL-1");
0405         outputOrder()->setList({"WL-1"});
0406     });
0407 
0408     orderChangeSpy.wait();
0409     QList<QScreen *> newOrder = orderChangeSpy[0].at(0).value<QList<QScreen *>>();
0410     QCOMPARE(newOrder.size(), 1);
0411     QCOMPARE(QGuiApplication::screens().size(), 1);
0412     QCOMPARE(m_screenPool->screenOrder().size(), 1);
0413 
0414     QScreen *newScreen = newOrder[0];
0415     QCOMPARE(newScreen, QGuiApplication::screens()[0]);
0416     QCOMPARE(newScreen->name(), QStringLiteral("WL-1"));
0417     QCOMPARE(newScreen->geometry(), QRect(0, 0, 1920, 1080));
0418 
0419     QCOMPARE(m_screenPool->idForScreen(newScreen), 0);
0420 }
0421 
0422 QCOMPOSITOR_TEST_MAIN(ScreenPoolTest)
0423 
0424 #include "screenpooltest.moc"