File indexing completed on 2024-05-12 05:30:05

0001 /*
0002     SPDX-FileCopyrightText: 2015 Daniel Vrátil <dvratil@redhat.com>
0003     SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 #include "../../kded/config.h"
0008 #include "../../common/globals.h"
0009 
0010 #include <QObject>
0011 #include <QtTest>
0012 
0013 #include <KScreen/Config>
0014 #include <KScreen/EDID>
0015 #include <KScreen/Mode>
0016 #include <KScreen/Output>
0017 #include <KScreen/Screen>
0018 
0019 #include <memory>
0020 
0021 class TestConfig : public QObject
0022 {
0023     Q_OBJECT
0024 
0025 private Q_SLOTS:
0026     void initTestCase();
0027 
0028     void testSimpleConfig();
0029     void testTwoScreenConfig();
0030     void testRotatedScreenConfig();
0031     void testDisabledScreenConfig();
0032     void testConfig404();
0033     void testCorruptConfig();
0034     void testCorruptEmptyConfig();
0035     void testCorruptUselessConfig();
0036     void testNullConfig();
0037     void testIdenticalOutputs();
0038     void testMoveConfig();
0039     void testFixedConfig();
0040 
0041 private:
0042     QTemporaryDir m_temporaryDir;
0043     std::unique_ptr<Config> createConfig(bool output1Connected, bool output2Conected);
0044 };
0045 
0046 std::unique_ptr<Config> TestConfig::createConfig(bool output1Connected, bool output2Connected)
0047 {
0048     KScreen::ScreenPtr screen = KScreen::ScreenPtr::create();
0049     screen->setCurrentSize(QSize(1920, 1080));
0050     screen->setMaxSize(QSize(32768, 32768));
0051     screen->setMinSize(QSize(8, 8));
0052 
0053     QList<QSize> sizes({QSize(320, 240), QSize(640, 480), QSize(1024, 768), QSize(1280, 1024), QSize(1920, 1280)});
0054     KScreen::ModeList modes;
0055     for (int i = 0; i < sizes.count(); ++i) {
0056         const QSize &size = sizes[i];
0057         KScreen::ModePtr mode = KScreen::ModePtr::create();
0058         mode->setId(QStringLiteral("MODE-%1").arg(i));
0059         mode->setName(QStringLiteral("%1x%2").arg(size.width()).arg(size.height()));
0060         mode->setSize(size);
0061         mode->setRefreshRate(60.0);
0062         modes.insert(mode->id(), mode);
0063     }
0064 
0065     KScreen::OutputPtr output1 = KScreen::OutputPtr::create();
0066     output1->setId(1);
0067     output1->setName(QStringLiteral("OUTPUT-1"));
0068     output1->setPos(QPoint(0, 0));
0069     output1->setConnected(output1Connected);
0070     output1->setEnabled(output1Connected);
0071     if (output1Connected) {
0072         output1->setModes(modes);
0073     }
0074 
0075     KScreen::OutputPtr output2 = KScreen::OutputPtr::create();
0076     output2->setId(2);
0077     output2->setName(QStringLiteral("OUTPUT-2"));
0078     output2->setPos(QPoint(0, 0));
0079     output2->setConnected(output2Connected);
0080     if (output2Connected) {
0081         output2->setModes(modes);
0082     }
0083 
0084     KScreen::ConfigPtr config = KScreen::ConfigPtr::create();
0085     config->setScreen(screen);
0086     config->addOutput(output1);
0087     config->addOutput(output2);
0088 
0089     auto configWrapper = std::unique_ptr<Config>(new Config(config));
0090 
0091     return configWrapper;
0092 }
0093 
0094 void TestConfig::initTestCase()
0095 {
0096     qputenv("XDG_DATA_HOME", m_temporaryDir.path().toUtf8());
0097     QFile::link(QStringLiteral(TEST_DATA "serializerdata"), Config::configsDirPath().chopped(1));
0098     qputenv("KSCREEN_LOGGING", "false");
0099 }
0100 
0101 void TestConfig::testSimpleConfig()
0102 {
0103     auto configWrapper = createConfig(true, false);
0104     configWrapper = configWrapper->readFile(QStringLiteral("simpleConfig.json"));
0105 
0106     auto config = configWrapper->data();
0107     QVERIFY(config);
0108     QCOMPARE(config->connectedOutputs().count(), 1);
0109 
0110     auto output = config->connectedOutputs().first();
0111     QCOMPARE(output->name(), QLatin1String("OUTPUT-1"));
0112     QCOMPARE(output->currentModeId(), QLatin1String("MODE-4"));
0113     QCOMPARE(output->currentMode()->size(), QSize(1920, 1280));
0114     QCOMPARE(output->isEnabled(), true);
0115     QCOMPARE(output->rotation(), KScreen::Output::None);
0116     QCOMPARE(output->pos(), QPoint(0, 0));
0117     QCOMPARE(output->isPrimary(), true);
0118 
0119     auto screen = config->screen();
0120     QCOMPARE(screen->currentSize(), QSize(1920, 1280));
0121 }
0122 
0123 void TestConfig::testTwoScreenConfig()
0124 {
0125     auto configWrapper = createConfig(true, true);
0126     configWrapper = configWrapper->readFile(QStringLiteral("twoScreenConfig.json"));
0127 
0128     auto config = configWrapper->data();
0129     QVERIFY(config);
0130 
0131     QCOMPARE(config->connectedOutputs().count(), 2);
0132 
0133     auto output = config->connectedOutputs().first();
0134     QCOMPARE(output->name(), QLatin1String("OUTPUT-1"));
0135     QCOMPARE(output->currentModeId(), QLatin1String("MODE-4"));
0136     QCOMPARE(output->currentMode()->size(), QSize(1920, 1280));
0137     QCOMPARE(output->isEnabled(), true);
0138     QCOMPARE(output->rotation(), KScreen::Output::None);
0139     QCOMPARE(output->pos(), QPoint(0, 0));
0140     QCOMPARE(output->isPrimary(), true);
0141 
0142     output = config->connectedOutputs().last();
0143     QCOMPARE(output->name(), QLatin1String("OUTPUT-2"));
0144     QCOMPARE(output->currentModeId(), QLatin1String("MODE-3"));
0145     QCOMPARE(output->currentMode()->size(), QSize(1280, 1024));
0146     QCOMPARE(output->isEnabled(), true);
0147     QCOMPARE(output->rotation(), KScreen::Output::None);
0148     QCOMPARE(output->pos(), QPoint(1920, 0));
0149     QCOMPARE(output->isPrimary(), false);
0150 
0151     auto screen = config->screen();
0152     QCOMPARE(screen->currentSize(), QSize(3200, 1280));
0153 }
0154 
0155 void TestConfig::testRotatedScreenConfig()
0156 {
0157     auto configWrapper = createConfig(true, true);
0158     configWrapper = configWrapper->readFile(QStringLiteral("rotatedScreenConfig.json"));
0159 
0160     auto config = configWrapper->data();
0161     QVERIFY(config);
0162 
0163     QCOMPARE(config->connectedOutputs().count(), 2);
0164 
0165     auto output = config->connectedOutputs().first();
0166     QCOMPARE(output->name(), QLatin1String("OUTPUT-1"));
0167     QCOMPARE(output->currentModeId(), QLatin1String("MODE-4"));
0168     QCOMPARE(output->currentMode()->size(), QSize(1920, 1280));
0169     QCOMPARE(output->isEnabled(), true);
0170     QCOMPARE(output->rotation(), KScreen::Output::None);
0171     QCOMPARE(output->pos(), QPoint(0, 0));
0172     QCOMPARE(output->isPrimary(), true);
0173 
0174     output = config->connectedOutputs().last();
0175     QCOMPARE(output->name(), QLatin1String("OUTPUT-2"));
0176     QCOMPARE(output->currentModeId(), QLatin1String("MODE-3"));
0177     QCOMPARE(output->currentMode()->size(), QSize(1280, 1024));
0178     QCOMPARE(output->isEnabled(), true);
0179     QCOMPARE(output->rotation(), KScreen::Output::Left);
0180     QCOMPARE(output->pos(), QPoint(1920, 0));
0181     QCOMPARE(output->isPrimary(), false);
0182 
0183     auto screen = config->screen();
0184     QCOMPARE(screen->currentSize(), QSize(2944, 1280));
0185 }
0186 
0187 void TestConfig::testDisabledScreenConfig()
0188 {
0189     auto configWrapper = createConfig(true, true);
0190     configWrapper = configWrapper->readFile(QStringLiteral("disabledScreenConfig.json"));
0191 
0192     auto config = configWrapper->data();
0193     QVERIFY(config);
0194 
0195     QCOMPARE(config->connectedOutputs().count(), 2);
0196 
0197     auto output = config->connectedOutputs().first();
0198     QCOMPARE(output->name(), QLatin1String("OUTPUT-1"));
0199     QCOMPARE(output->currentModeId(), QLatin1String("MODE-4"));
0200     QCOMPARE(output->currentMode()->size(), QSize(1920, 1280));
0201     QCOMPARE(output->isEnabled(), true);
0202     QCOMPARE(output->rotation(), KScreen::Output::None);
0203     QCOMPARE(output->pos(), QPoint(0, 0));
0204     QCOMPARE(output->isPrimary(), true);
0205 
0206     output = config->connectedOutputs().last();
0207     QCOMPARE(output->name(), QLatin1String("OUTPUT-2"));
0208     QCOMPARE(output->isEnabled(), false);
0209 
0210     auto screen = config->screen();
0211     QCOMPARE(screen->currentSize(), QSize(1920, 1280));
0212 }
0213 
0214 void TestConfig::testConfig404()
0215 {
0216     auto configWrapper = createConfig(true, true);
0217     configWrapper = configWrapper->readFile(QStringLiteral("filenotfoundConfig.json"));
0218 
0219     QVERIFY(!configWrapper);
0220 }
0221 
0222 void TestConfig::testCorruptConfig()
0223 {
0224     auto configWrapper = createConfig(true, true);
0225     configWrapper = configWrapper->readFile(QStringLiteral("corruptConfig.json"));
0226     auto config = configWrapper->data();
0227 
0228     QVERIFY(config);
0229     QCOMPARE(config->outputs().count(), 2);
0230     QVERIFY(config->isValid());
0231 }
0232 
0233 void TestConfig::testCorruptEmptyConfig()
0234 {
0235     auto configWrapper = createConfig(true, true);
0236     configWrapper = configWrapper->readFile(QStringLiteral("corruptEmptyConfig.json"));
0237     auto config = configWrapper->data();
0238 
0239     QVERIFY(config);
0240     QCOMPARE(config->outputs().count(), 2);
0241     QVERIFY(config->isValid());
0242 }
0243 
0244 void TestConfig::testCorruptUselessConfig()
0245 {
0246     auto configWrapper = createConfig(true, true);
0247     configWrapper = configWrapper->readFile(QStringLiteral("corruptUselessConfig.json"));
0248     auto config = configWrapper->data();
0249 
0250     QVERIFY(config);
0251     QCOMPARE(config->outputs().count(), 2);
0252     QVERIFY(config->isValid());
0253 }
0254 
0255 void TestConfig::testNullConfig()
0256 {
0257     QSKIP("crashes");
0258     Config nullConfig(nullptr);
0259     QVERIFY(!nullConfig.data());
0260 
0261     // Null configs have empty configIds
0262     QVERIFY(nullConfig.id().isEmpty());
0263 
0264     // Load config from a file not found results in a nullptr
0265     auto config = createConfig(true, true);
0266     QVERIFY(!config->readFile(QString()));
0267 
0268     // Wrong config file name should fail to save
0269     QVERIFY(!config->writeFile(QString()));
0270 }
0271 
0272 void TestConfig::testIdenticalOutputs()
0273 {
0274     // Test configuration of a video wall with 6 identical outputs connected
0275     // this is the autotest for https://bugs.kde.org/show_bug.cgi?id=325277
0276     KScreen::ScreenPtr screen = KScreen::ScreenPtr::create();
0277     screen->setCurrentSize(QSize(1920, 1080));
0278     screen->setMaxSize(QSize(32768, 32768));
0279     screen->setMinSize(QSize(8, 8));
0280 
0281     QList<QSize> sizes({QSize(640, 480), QSize(1024, 768), QSize(1920, 1080), QSize(1280, 1024), QSize(1920, 1280)});
0282     KScreen::ModeList modes;
0283     for (int i = 0; i < sizes.count(); ++i) {
0284         const QSize &size = sizes[i];
0285         KScreen::ModePtr mode = KScreen::ModePtr::create();
0286         mode->setId(QStringLiteral("MODE-%1").arg(i));
0287         mode->setName(QStringLiteral("%1x%2").arg(size.width()).arg(size.height()));
0288         mode->setSize(size);
0289         mode->setRefreshRate(60.0);
0290         modes.insert(mode->id(), mode);
0291     }
0292     // This one is important, the output id in the config file is a hash of it
0293     QByteArray data = QByteArray::fromBase64(
0294         "AP///////wAQrBbwTExLQQ4WAQOANCB46h7Frk80sSYOUFSlSwCBgKlA0QBxTwEBAQEBAQEBKDyAoHCwI0AwIDYABkQhAAAaAAAA/wBGNTI1TTI0NUFLTEwKAAAA/ABERUxMIFUyNDEwCiAgAAAA/"
0295         "QA4TB5REQAKICAgICAgAToCAynxUJAFBAMCBxYBHxITFCAVEQYjCQcHZwMMABAAOC2DAQAA4wUDAQI6gBhxOC1AWCxFAAZEIQAAHgEdgBhxHBYgWCwlAAZEIQAAngEdAHJR0B4gbihVAAZEIQAAHow"
0296         "K0Iog4C0QED6WAAZEIQAAGAAAAAAAAAAAAAAAAAAAPg==");
0297 
0298     // When setting up the outputs, make sure they're not added in alphabetical order
0299     // or in the same order of the config file, as that makes the tests accidentally pass
0300 
0301     KScreen::OutputPtr output1 = KScreen::OutputPtr::create();
0302     output1->setId(1);
0303     output1->setEdid(data);
0304     output1->setName(QStringLiteral("DisplayPort-0"));
0305     output1->setPos(QPoint(0, 0));
0306     output1->setConnected(true);
0307     output1->setEnabled(false);
0308     output1->setModes(modes);
0309 
0310     KScreen::OutputPtr output2 = KScreen::OutputPtr::create();
0311     output2->setId(2);
0312     output2->setEdid(data);
0313     output2->setName(QStringLiteral("DisplayPort-1"));
0314     output2->setPos(QPoint(0, 0));
0315     output2->setConnected(true);
0316     output2->setEnabled(false);
0317     output2->setModes(modes);
0318 
0319     KScreen::OutputPtr output3 = KScreen::OutputPtr::create();
0320     output3->setId(3);
0321     output3->setEdid(data);
0322     output3->setName(QStringLiteral("DisplayPort-2"));
0323     output3->setPos(QPoint(0, 0));
0324     output3->setConnected(true);
0325     output3->setEnabled(false);
0326     output3->setModes(modes);
0327 
0328     KScreen::OutputPtr output6 = KScreen::OutputPtr::create();
0329     output6->setId(6);
0330     output6->setEdid(data);
0331     output6->setName(QStringLiteral("DVI-0"));
0332     output6->setPos(QPoint(0, 0));
0333     output6->setConnected(true);
0334     output6->setEnabled(false);
0335     output6->setModes(modes);
0336 
0337     KScreen::OutputPtr output4 = KScreen::OutputPtr::create();
0338     output4->setId(4);
0339     output4->setEdid(data);
0340     output4->setName(QStringLiteral("DisplayPort-3"));
0341     output4->setPos(QPoint(0, 0));
0342     output4->setConnected(true);
0343     output4->setEnabled(false);
0344     output4->setModes(modes);
0345 
0346     KScreen::OutputPtr output5 = KScreen::OutputPtr::create();
0347     output5->setId(5);
0348     output5->setEdid(data);
0349     output5->setName(QStringLiteral("DVI-1"));
0350     output5->setPos(QPoint(0, 0));
0351     output5->setConnected(true);
0352     output5->setEnabled(false);
0353     output5->setModes(modes);
0354 
0355     KScreen::ConfigPtr config = KScreen::ConfigPtr::create();
0356     config->setScreen(screen);
0357     config->addOutput(output6);
0358     config->addOutput(output2);
0359     config->addOutput(output5);
0360     config->addOutput(output4);
0361     config->addOutput(output3);
0362     config->addOutput(output1);
0363 
0364     Config configWrapper(config);
0365 
0366     QHash<QString, QPoint> positions;
0367     positions[QStringLiteral("DisplayPort-0")] = QPoint(0, 1080);
0368     positions[QStringLiteral("DisplayPort-1")] = QPoint(2100, 30);
0369     positions[QStringLiteral("DisplayPort-2")] = QPoint(2100, 1080);
0370     positions[QStringLiteral("DisplayPort-3")] = QPoint(4020, 0);
0371     positions[QStringLiteral("DVI-0")] = QPoint(4020, 1080);
0372     positions[QStringLiteral("DVI-1")] = QPoint(0, 0);
0373 
0374     auto configWrapper2 = configWrapper.readFile(QStringLiteral("outputgrid_2x3.json"));
0375     KScreen::ConfigPtr config2 = configWrapper2->data();
0376     QVERIFY(config2);
0377     QVERIFY(config != config2);
0378 
0379     QCOMPARE(config2->connectedOutputs().count(), 6);
0380     Q_FOREACH (auto output, config2->connectedOutputs()) {
0381         QVERIFY(positions.keys().contains(output->name()));
0382         QVERIFY(output->name() != output->hash());
0383         QCOMPARE(positions[output->name()], output->pos());
0384         QCOMPARE(output->currentMode()->size(), QSize(1920, 1080));
0385         QCOMPARE(output->currentMode()->refreshRate(), 60.0);
0386         QVERIFY(output->isEnabled());
0387     }
0388     QCOMPARE(config2->screen()->currentSize(), QSize(5940, 2160));
0389 }
0390 
0391 void TestConfig::testMoveConfig()
0392 {
0393     QSKIP("crashes");
0394     // Test if restoring a config using Serializer::moveConfig(src, dest) works
0395     // https://bugs.kde.org/show_bug.cgi?id=353029
0396 
0397     // Load a dualhead config
0398     auto configWrapper = createConfig(true, true);
0399     configWrapper = configWrapper->readFile(QStringLiteral("twoScreenConfig.json"));
0400 
0401     auto config = configWrapper->data();
0402     QVERIFY(config);
0403 
0404     // Make sure we don't write into TEST_DATA
0405     QStandardPaths::setTestModeEnabled(true);
0406     // TODO: this needs setup of the control directory
0407 
0408     // Basic assumptions for the remainder of our tests, this is the situation where the lid is opened
0409     QCOMPARE(config->connectedOutputs().count(), 2);
0410 
0411     auto output = config->connectedOutputs().first();
0412     QCOMPARE(output->name(), QLatin1String("OUTPUT-1"));
0413     QCOMPARE(output->isEnabled(), true);
0414     QCOMPARE(output->isPrimary(), true);
0415 
0416     auto output2 = config->connectedOutputs().last();
0417     QCOMPARE(output2->name(), QLatin1String("OUTPUT-2"));
0418     QCOMPARE(output2->isEnabled(), true);
0419     QCOMPARE(output2->isPrimary(), false);
0420 
0421     // we fake the lid being closed, first save our current config to _lidOpened
0422     configWrapper->writeOpenLidFile();
0423 
0424     // ... then switch off the panel, set primary to the other output
0425     output->setEnabled(false);
0426     config->setPrimaryOutput(output2);
0427 
0428     // save config as the current one, this is the config we don't want restored, and which we'll overwrite
0429     configWrapper->writeFile();
0430 
0431     QCOMPARE(output->isEnabled(), false);
0432     QCOMPARE(output->isPrimary(), false);
0433     QCOMPARE(output2->isPrimary(), true);
0434 
0435     // Check if both files exist
0436     const QString closedPath = Config::configsDirPath() % configWrapper->id();
0437     const QString openedPath = closedPath % QStringLiteral("_lidOpened");
0438 
0439     QFile openCfg(openedPath);
0440     QFile closedCfg(closedPath);
0441     QVERIFY(openCfg.exists());
0442     QVERIFY(closedCfg.exists());
0443 
0444     // Switcheroolooloo...
0445     configWrapper = configWrapper->readOpenLidFile();
0446     QVERIFY(configWrapper);
0447 
0448     // Check actual files, src should be gone, dest must exist
0449     QVERIFY(!openCfg.exists());
0450     QVERIFY(closedCfg.exists());
0451 
0452     // Make sure the laptop panel is enabled and primary again
0453     config = configWrapper->data();
0454 
0455     output = config->connectedOutputs().first();
0456     QCOMPARE(output->name(), QLatin1String("OUTPUT-1"));
0457     QCOMPARE(output->isEnabled(), true);
0458     QCOMPARE(output->isPrimary(), true);
0459 
0460     output2 = config->connectedOutputs().last();
0461     QCOMPARE(output2->name(), QLatin1String("OUTPUT-2"));
0462     QCOMPARE(output2->isEnabled(), true);
0463     QCOMPARE(output2->isPrimary(), false);
0464 
0465     // Make sure we don't screw up when there's no _lidOpened config
0466     configWrapper = configWrapper->readOpenLidFile();
0467     config = configWrapper->data();
0468 
0469     output = config->connectedOutputs().first();
0470     QCOMPARE(output->name(), QLatin1String("OUTPUT-1"));
0471     QCOMPARE(output->isEnabled(), true);
0472     QCOMPARE(output->isPrimary(), true);
0473 
0474     output2 = config->connectedOutputs().last();
0475     QCOMPARE(output2->name(), QLatin1String("OUTPUT-2"));
0476     QCOMPARE(output2->isEnabled(), true);
0477     QCOMPARE(output2->isPrimary(), false);
0478 }
0479 
0480 void TestConfig::testFixedConfig()
0481 {
0482     // Load a dualhead config
0483     auto configWrapper = createConfig(true, true);
0484     configWrapper = configWrapper->readFile(QStringLiteral("twoScreenConfig.json"));
0485     auto config = configWrapper->data();
0486     QVERIFY(config);
0487 
0488     // TODO: this needs setup of the control directory
0489 
0490     const QString fixedCfgPath = Config::configsDirPath() % Config::s_fixedConfigFileName;
0491     // save config as the current one, this is the config we don't want restored, and which we'll overwrite
0492     configWrapper->writeFile(fixedCfgPath);
0493 
0494     // Check if both files exist
0495     QFile fixedCfg(fixedCfgPath);
0496     QVERIFY(fixedCfg.exists());
0497     // Cleanup
0498     fixedCfg.remove();
0499 }
0500 
0501 QTEST_MAIN(TestConfig)
0502 
0503 #include "configtest.moc"