File indexing completed on 2024-05-12 05:38:23

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