File indexing completed on 2024-05-19 05:38:44
0001 /* 0002 SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #pragma once 0008 0009 #include <QDateTime> 0010 #include <QProcess> 0011 #include <QRasterWindow> 0012 #include <QSignalSpy> 0013 #include <QStandardPaths> 0014 #include <QTest> 0015 #include <QtGui/private/qtx11extras_p.h> 0016 0017 #include <KIconLoader> 0018 #include <KSycoca> 0019 #include <KWindowSystem> 0020 0021 #include "abstractwindowtasksmodel.h" 0022 #include "samplewidgetwindow.h" 0023 0024 extern KSERVICE_EXPORT int ksycoca_ms_between_checks; 0025 0026 namespace TestUtils 0027 { 0028 using namespace TaskManager; 0029 constexpr const char *dummyDesktopFileName = "org.kde.plasma.test.dummy.desktop"; 0030 0031 void initTestCase() 0032 { 0033 ksycoca_ms_between_checks = 0; 0034 } 0035 0036 void cleanupTestCase() 0037 { 0038 QFile dummyFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("applications") 0039 + QDir::separator() + QString::fromLatin1(dummyDesktopFileName)); 0040 dummyFile.remove(); 0041 0042 QProcess killAllTestWindowProcess; 0043 killAllTestWindowProcess.setProgram(QStringLiteral("killall")); 0044 killAllTestWindowProcess.setArguments({QStringLiteral("-9"), QStringLiteral("samplewidgetwindow")}); 0045 killAllTestWindowProcess.start(); 0046 killAllTestWindowProcess.waitForFinished(); 0047 } 0048 0049 std::unique_ptr<QRasterWindow> createSingleWindow(const AbstractWindowTasksModel &model, const QString &title, QModelIndex &index) 0050 { 0051 auto window = std::make_unique<QRasterWindow>(); 0052 window->setTitle(title); 0053 window->setBaseSize(QSize(320, 240)); 0054 0055 QSignalSpy rowInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted); 0056 window->show(); 0057 if (rowInsertedSpy.empty()) { 0058 Q_ASSERT(rowInsertedSpy.wait()); 0059 } 0060 0061 // Find the window index 0062 const auto results = model.match(model.index(0, 0), Qt::DisplayRole, title); 0063 Q_ASSERT(results.size() == 1); 0064 index = results.at(0); 0065 Q_ASSERT(index.isValid()); 0066 qDebug() << "Window title:" << index.data(Qt::DisplayRole).toString(); 0067 0068 return window; 0069 } 0070 0071 void testOpenCloseWindow(const AbstractWindowTasksModel &model) 0072 { 0073 auto findWindow = [&model](const QString &windowTitle) { 0074 for (int i = 0; i < model.rowCount(); ++i) { 0075 const QString title = model.index(i, 0).data(Qt::DisplayRole).toString(); 0076 if (title == windowTitle) { 0077 return true; 0078 } 0079 } 0080 return false; 0081 }; 0082 0083 // Create a window to test if the model can receive it 0084 QSignalSpy rowInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted); 0085 0086 const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString()); 0087 QModelIndex index; 0088 auto window = createSingleWindow(model, title, index); 0089 0090 // A new window appears 0091 // Find the window in the model 0092 QVERIFY(findWindow(title)); 0093 0094 // Change the title of the window 0095 { 0096 QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged); 0097 const QString newTitle = title + QStringLiteral("__newtitle__"); 0098 window->setTitle(newTitle); 0099 QVERIFY(dataChangedSpy.wait()); 0100 QTRY_VERIFY(dataChangedSpy.constLast().at(2).value<QList<int>>().contains(Qt::DisplayRole)); 0101 // Make sure the title is updated 0102 QTRY_VERIFY(!findWindow(title)); 0103 QTRY_VERIFY(findWindow(newTitle)); 0104 } 0105 0106 // Now close the window 0107 { 0108 int modelCount = model.rowCount(); 0109 QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved); 0110 window->close(); 0111 QVERIFY(rowsRemovedSpy.wait()); 0112 QCOMPARE(modelCount - 1, model.rowCount()); 0113 } 0114 } 0115 0116 void testFullscreen(const AbstractWindowTasksModel &model) 0117 { 0118 const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString()); 0119 QModelIndex index; 0120 auto window = createSingleWindow(model, title, index); 0121 0122 QVERIFY(!index.data(AbstractTasksModel::IsFullScreen).toBool()); 0123 0124 QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged); 0125 window->showFullScreen(); 0126 dataChangedSpy.wait(); 0127 0128 // There can be more than one dataChanged signal being emitted due to caching 0129 QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) { 0130 return list.at(2).value<QList<int>>().contains(AbstractTasksModel::IsFullScreen); 0131 })); 0132 QTRY_VERIFY(index.data(AbstractTasksModel::IsFullScreen).toBool()); 0133 dataChangedSpy.clear(); 0134 window->showNormal(); 0135 QVERIFY(dataChangedSpy.wait()); 0136 // There can be more than one dataChanged signal being emitted due to caching 0137 QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) { 0138 return list.at(2).value<QList<int>>().contains(AbstractTasksModel::IsFullScreen); 0139 })); 0140 QTRY_VERIFY(!index.data(AbstractTasksModel::IsFullScreen).toBool()); 0141 } 0142 0143 void testGeometry(const AbstractWindowTasksModel &model) 0144 { 0145 const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString()); 0146 QModelIndex index; 0147 auto window = createSingleWindow(model, title, index); 0148 0149 const QSize oldSize = index.data(AbstractTasksModel::Geometry).toRect().size(); 0150 QCoreApplication::processEvents(); 0151 QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged); 0152 window->resize(QSize(240, 320)); 0153 QVERIFY(dataChangedSpy.wait()); 0154 0155 // There can be more than one dataChanged signal being emitted due to caching 0156 // When using openbox the test is flaky 0157 QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) { 0158 return list.at(2).value<QList<int>>().contains(AbstractTasksModel::Geometry); 0159 })); 0160 QTRY_VERIFY(index.data(AbstractTasksModel::Geometry).toRect().size() != oldSize); 0161 } 0162 0163 void testStackingOrder(const AbstractWindowTasksModel &model) 0164 { 0165 const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString()); 0166 QModelIndex index; 0167 auto firstWindow = createSingleWindow(model, title, index); 0168 QTRY_VERIFY(index.data(AbstractTasksModel::StackingOrder).toInt() >= 0); 0169 const int stackingOrder1 = index.data(AbstractTasksModel::StackingOrder).toInt(); 0170 0171 // Create another window to make stacking order change 0172 QModelIndex index2; 0173 auto secondWindow = createSingleWindow(model, QStringLiteral("second test window"), index2); 0174 QTRY_VERIFY(index2.data(AbstractTasksModel::StackingOrder).toInt() >= 0); 0175 const int stackingOrder2 = index2.data(AbstractTasksModel::StackingOrder).toInt(); 0176 QVERIFY2( 0177 stackingOrder1 < stackingOrder2, 0178 QLatin1String("stackingOrder1: %1 stackingOrder2: %2").arg(QString::number(stackingOrder1), QString::number(stackingOrder2)).toLatin1().constData()); 0179 0180 firstWindow->close(); 0181 QCoreApplication::processEvents(); 0182 QTRY_VERIFY(index2.data(AbstractTasksModel::StackingOrder).toInt() < stackingOrder2); 0183 } 0184 0185 void createDesktopFile(const char *fileName, const std::vector<std::string> &lines, QString &path) 0186 { 0187 path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("applications") + QDir::separator() 0188 + QString::fromUtf8(fileName); 0189 0190 QFile out(path); 0191 if (out.exists()) { 0192 qDebug() << "Removing the old desktop file in" << path; 0193 out.remove(); 0194 } 0195 0196 qDebug() << "Creating a desktop file in" << path; 0197 QVERIFY(out.open(QIODevice::WriteOnly)); 0198 out.write("[Desktop Entry]\n"); 0199 for (const std::string &l : lines) { 0200 out.write((l + "\n").c_str()); 0201 } 0202 out.close(); 0203 0204 KSycoca::self()->ensureCacheValid(); 0205 } 0206 0207 void testModelDataFromDesktopFile(const AbstractWindowTasksModel &model) 0208 { 0209 // Case 1: A normal window 0210 std::vector<std::string> lines; 0211 lines.emplace_back("Name=DummyWindow"); 0212 lines.emplace_back("GenericName=DummyGenericName"); 0213 lines.emplace_back(std::string("Exec=") + TaskManagerTest::samplewidgetwindowExecutablePath); 0214 lines.emplace_back("Terminal=false"); 0215 lines.emplace_back("Type=Application"); 0216 lines.emplace_back(std::string("Icon=") + QFINDTESTDATA("data/windows/none.png").toStdString()); 0217 0218 auto testCanLaunchNewInstance = [&](bool canLaunchNewInstance) { 0219 QString desktopFilePath; 0220 0221 QSignalSpy rowsInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted); 0222 QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved); 0223 0224 TestUtils::createDesktopFile(dummyDesktopFileName, lines, desktopFilePath); 0225 QProcess sampleWindowProcess; 0226 0227 sampleWindowProcess.setProgram(QString::fromUtf8(TaskManagerTest::samplewidgetwindowExecutablePath)); 0228 sampleWindowProcess.setArguments(QStringList{ 0229 QStringLiteral("__testwindow__%1").arg(QString::number(QDateTime::currentDateTime().offsetFromUtc())), 0230 QFINDTESTDATA("data/windows/samplewidgetwindow.png"), 0231 }); 0232 sampleWindowProcess.start(); 0233 rowsInsertedSpy.wait(); 0234 0235 // find newly created window 0236 const auto results = model.match(model.index(0, 0), Qt::DisplayRole, sampleWindowProcess.arguments().at(0)); 0237 QVERIFY(results.size() == 1); 0238 QModelIndex index = results.at(0); 0239 QVERIFY(index.isValid()); 0240 0241 QCOMPARE(index.data(AbstractTasksModel::AppName).toString(), QStringLiteral("DummyWindow")); 0242 QCOMPARE(index.data(AbstractTasksModel::GenericName).toString(), QStringLiteral("DummyGenericName")); 0243 QCOMPARE(index.data(AbstractTasksModel::LauncherUrl).toUrl(), QUrl(QStringLiteral("applications:%1").arg(QString::fromLatin1(dummyDesktopFileName)))); 0244 QCOMPARE(index.data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl(), 0245 QUrl(QStringLiteral("applications:%1").arg(QString::fromLatin1(dummyDesktopFileName)))); 0246 0247 // Test icon should use the icon from the desktop file (Not the png file filled with red color) 0248 const QIcon windowIcon = index.data(Qt::DecorationRole).value<QIcon>(); 0249 QVERIFY(!windowIcon.isNull()); 0250 QVERIFY(windowIcon.pixmap(KIconLoader::SizeLarge).toImage().pixelColor(KIconLoader::SizeLarge / 2, KIconLoader::SizeLarge / 2).red() < 200); 0251 0252 QCOMPARE(index.data(AbstractTasksModel::CanLaunchNewInstance).toBool(), canLaunchNewInstance); 0253 0254 sampleWindowProcess.terminate(); 0255 QVERIFY(rowsRemovedSpy.wait()); 0256 sampleWindowProcess.waitForFinished(); 0257 }; 0258 0259 // Case 1: Default case 0260 testCanLaunchNewInstance(true); 0261 0262 // Case 2: Set SingleMainWindow or X-GNOME-SingleWindow or both 0263 lines.emplace_back("SingleMainWindow=true"); 0264 testCanLaunchNewInstance(false); 0265 0266 lines.pop_back(); 0267 lines.emplace_back("X-GNOME-SingleWindow=true"); 0268 testCanLaunchNewInstance(false); 0269 0270 lines.pop_back(); 0271 lines.emplace_back("SingleMainWindow=false"); 0272 lines.emplace_back("X-GNOME-SingleWindow=true"); 0273 testCanLaunchNewInstance(false); 0274 0275 lines.pop_back(); 0276 lines.pop_back(); 0277 lines.emplace_back("SingleMainWindow=true"); 0278 lines.emplace_back("X-GNOME-SingleWindow=false"); 0279 testCanLaunchNewInstance(false); 0280 0281 lines.pop_back(); 0282 lines.pop_back(); 0283 lines.emplace_back("SingleMainWindow=false"); 0284 lines.emplace_back("X-GNOME-SingleWindow=false"); 0285 testCanLaunchNewInstance(true); 0286 } 0287 0288 void testRequest(AbstractWindowTasksModel &model) 0289 { 0290 constexpr int timeout = 30000; // openbox is slow to respond 0291 QSignalSpy rowsInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted); 0292 0293 QProcess sampleWindowProcess; 0294 sampleWindowProcess.setProgram(QString::fromUtf8(TaskManagerTest::samplewidgetwindowExecutablePath)); 0295 sampleWindowProcess.setArguments(QStringList{ 0296 QStringLiteral("__testwindow__%1").arg(QString::number(QDateTime::currentDateTime().offsetFromUtc())), 0297 }); 0298 sampleWindowProcess.start(); 0299 QVERIFY(rowsInsertedSpy.wait(timeout)); 0300 0301 // Find the window index 0302 auto findWindowIndex = [&model](QPersistentModelIndex &index, const QString &title) { 0303 const auto results = model.match(model.index(0, 0), Qt::DisplayRole, title); 0304 qCritical() << "results for" << title << results << "total:" << model.rowCount(); 0305 QVERIFY(results.size() == 1); 0306 index = results.at(0); 0307 QVERIFY(index.isValid()); 0308 qDebug() << "Window title:" << index.data(Qt::DisplayRole).toString(); 0309 }; 0310 0311 QPersistentModelIndex index; 0312 findWindowIndex(index, sampleWindowProcess.arguments().at(0)); 0313 QVERIFY(index.isValid()); 0314 0315 { 0316 qDebug("requestNewInstance"); 0317 model.requestNewInstance(index); 0318 QVERIFY(rowsInsertedSpy.wait(timeout)); 0319 } 0320 0321 QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged); 0322 { 0323 qDebug("requestToggleMinimized"); 0324 QCoreApplication::processEvents(); 0325 dataChangedSpy.clear(); 0326 model.requestToggleMinimized(index); 0327 if (KWindowSystem::isPlatformX11()) { 0328 QX11Info::getTimestamp(); // roundtrip 0329 } 0330 if (dataChangedSpy.empty()) { 0331 QVERIFY(dataChangedSpy.wait(timeout)); 0332 } 0333 QTRY_VERIFY(index.data(AbstractTasksModel::IsMinimized).toBool()); 0334 } 0335 0336 { 0337 qDebug("requestActivate"); 0338 QCoreApplication::processEvents(); 0339 dataChangedSpy.clear(); 0340 model.requestActivate(index); 0341 if (KWindowSystem::isPlatformX11()) { 0342 QX11Info::getTimestamp(); // roundtrip 0343 } 0344 if (dataChangedSpy.empty()) { 0345 QVERIFY(dataChangedSpy.wait(timeout)); 0346 } 0347 QTRY_VERIFY(!index.data(AbstractTasksModel::IsMinimized).toBool()); 0348 } 0349 0350 { 0351 qDebug("requestToggleMaximized"); 0352 QCoreApplication::processEvents(); 0353 dataChangedSpy.clear(); 0354 model.requestToggleMaximized(index); 0355 if (KWindowSystem::isPlatformX11()) { 0356 QX11Info::getTimestamp(); // roundtrip 0357 } 0358 if (dataChangedSpy.empty()) { 0359 QVERIFY(dataChangedSpy.wait(timeout)); 0360 } 0361 QTRY_VERIFY(!index.data(AbstractTasksModel::IsMinimized).toBool()); 0362 QTRY_VERIFY(index.data(AbstractTasksModel::IsMaximized).toBool()); 0363 0364 QCoreApplication::processEvents(); 0365 dataChangedSpy.clear(); 0366 model.requestToggleMaximized(index); 0367 if (KWindowSystem::isPlatformX11()) { 0368 QX11Info::getTimestamp(); // roundtrip 0369 } 0370 if (dataChangedSpy.empty()) { 0371 QVERIFY(dataChangedSpy.wait(timeout)); 0372 } 0373 QTRY_VERIFY(!index.data(AbstractTasksModel::IsMinimized).toBool()); 0374 QTRY_VERIFY(!index.data(AbstractTasksModel::IsMaximized).toBool()); 0375 } 0376 0377 { 0378 qDebug("requestToggleKeepAbove"); 0379 QCoreApplication::processEvents(); 0380 dataChangedSpy.clear(); 0381 model.requestToggleKeepAbove(index); 0382 if (KWindowSystem::isPlatformX11()) { 0383 QX11Info::getTimestamp(); // roundtrip 0384 } 0385 if (dataChangedSpy.empty()) { 0386 QVERIFY(dataChangedSpy.wait(timeout)); 0387 } 0388 QTRY_VERIFY(index.data(AbstractTasksModel::IsKeepAbove).toBool()); 0389 QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepBelow).toBool()); 0390 } 0391 0392 { 0393 qDebug("requestToggleKeepBelow"); 0394 QCoreApplication::processEvents(); 0395 dataChangedSpy.clear(); 0396 model.requestToggleKeepBelow(index); 0397 if (KWindowSystem::isPlatformX11()) { 0398 QX11Info::getTimestamp(); // roundtrip 0399 } 0400 if (dataChangedSpy.empty()) { 0401 QVERIFY(dataChangedSpy.wait(timeout)); 0402 } 0403 QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepAbove).toBool()); 0404 QTRY_VERIFY(index.data(AbstractTasksModel::IsKeepBelow).toBool()); 0405 0406 QCoreApplication::processEvents(); 0407 dataChangedSpy.clear(); 0408 model.requestToggleKeepBelow(index); 0409 if (KWindowSystem::isPlatformX11()) { 0410 QX11Info::getTimestamp(); // roundtrip 0411 } 0412 if (dataChangedSpy.empty()) { 0413 QVERIFY(dataChangedSpy.wait(timeout)); 0414 } 0415 QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepAbove).toBool()); 0416 QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepBelow).toBool()); 0417 } 0418 0419 { 0420 qDebug("requestToggleFullScreen"); 0421 QCoreApplication::processEvents(); 0422 dataChangedSpy.clear(); 0423 model.requestToggleFullScreen(index); 0424 if (KWindowSystem::isPlatformX11()) { 0425 QX11Info::getTimestamp(); // roundtrip 0426 } 0427 if (dataChangedSpy.empty()) { 0428 QVERIFY(dataChangedSpy.wait(timeout)); 0429 } 0430 QTRY_VERIFY(index.data(AbstractTasksModel::IsFullScreen).toBool()); 0431 0432 QCoreApplication::processEvents(); 0433 dataChangedSpy.clear(); 0434 model.requestToggleFullScreen(index); 0435 if (KWindowSystem::isPlatformX11()) { 0436 QX11Info::getTimestamp(); // roundtrip 0437 } 0438 if (dataChangedSpy.empty()) { 0439 QVERIFY(dataChangedSpy.wait(timeout)); 0440 } 0441 QTRY_VERIFY(!index.data(AbstractTasksModel::IsFullScreen).toBool()); 0442 } 0443 0444 if (KWindowSystem::isPlatformX11()) { 0445 qDebug("requestToggleShaded"); 0446 QCoreApplication::processEvents(); 0447 dataChangedSpy.clear(); 0448 model.requestToggleShaded(index); 0449 if (KWindowSystem::isPlatformX11()) { 0450 QX11Info::getTimestamp(); // roundtrip 0451 } 0452 if (dataChangedSpy.empty()) { 0453 QVERIFY(dataChangedSpy.wait(timeout)); 0454 } 0455 QTRY_VERIFY(index.data(AbstractTasksModel::IsShaded).toBool()); 0456 0457 QCoreApplication::processEvents(); 0458 dataChangedSpy.clear(); 0459 model.requestToggleShaded(index); 0460 if (KWindowSystem::isPlatformX11()) { 0461 QX11Info::getTimestamp(); // roundtrip 0462 } 0463 if (dataChangedSpy.empty()) { 0464 QVERIFY(dataChangedSpy.wait(timeout)); 0465 } 0466 QTRY_VERIFY(!index.data(AbstractTasksModel::IsShaded).toBool()); 0467 } 0468 0469 QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved); 0470 { 0471 qDebug("requestClose"); 0472 model.requestClose(index); 0473 if (KWindowSystem::isPlatformX11()) { 0474 QX11Info::getTimestamp(); // roundtrip 0475 } 0476 if (rowsRemovedSpy.empty()) { 0477 QVERIFY(rowsRemovedSpy.wait(timeout)); 0478 } 0479 } 0480 0481 { 0482 qDebug("Close the new instance"); 0483 findWindowIndex(index, QStringLiteral("__test_window_no_title__")); 0484 rowsRemovedSpy.clear(); 0485 model.requestClose(index); 0486 if (KWindowSystem::isPlatformX11()) { 0487 QX11Info::getTimestamp(); // roundtrip 0488 } 0489 if (rowsRemovedSpy.empty()) { 0490 QVERIFY(rowsRemovedSpy.wait(timeout)); 0491 } 0492 } 0493 } 0494 }