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"