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 }