diff --git a/autotests/kmimeassociationstest.cpp b/autotests/kmimeassociationstest.cpp index 0991d73..b079cf1 100644 --- a/autotests/kmimeassociationstest.cpp +++ b/autotests/kmimeassociationstest.cpp @@ -1,560 +1,561 @@ /* This file is part of the KDE libraries Copyright (c) 2008 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "setupxdgdirs.h" #include "kmimeassociations_p.h" #include #include #include "ksycoca_p.h" #include #include #include // We need a factory that returns the same KService::Ptr every time it's asked for a given service. // Otherwise the changes to the service's serviceTypes by KMimeAssociationsTest have no effect class FakeServiceFactory : public KServiceFactory { public: FakeServiceFactory(KSycoca *db) : KServiceFactory(db) {} ~FakeServiceFactory(); KService::Ptr findServiceByMenuId(const QString &name) override { //qDebug() << name; KService::Ptr result = m_cache.value(name); if (!result) { result = KServiceFactory::findServiceByMenuId(name); m_cache.insert(name, result); } //qDebug() << name << result.data(); return result; } KService::Ptr findServiceByDesktopPath(const QString &name) override { KService::Ptr result = m_cache.value(name); // yeah, same cache, I don't care :) if (!result) { result = KServiceFactory::findServiceByDesktopPath(name); m_cache.insert(name, result); } return result; } private: QMap m_cache; }; // Helper method for all the trader tests, comes from kmimetypetest.cpp static bool offerListHasService(const KService::List &offers, const QString &entryPath, bool expected /* if set, show error if not found */) { // ksycoca resolves to canonical paths, so do it here as well const QString realPath = QFileInfo(entryPath).canonicalFilePath(); Q_ASSERT(!realPath.isEmpty()); bool found = false; for (const KService::Ptr &serv : offers) { if (serv->entryPath() == realPath) { if (found) { // should be there only once qWarning("ERROR: %s was found twice in the list", qPrintable(realPath)); return false; // make test fail } found = true; } } if (!found && expected) { qWarning() << "ERROR:" << realPath << "not found in offer list. Here's the full list:"; for (const KService::Ptr &serv : offers) { qDebug() << serv->entryPath(); } } return found; } static void writeAppDesktopFile(const QString &path, const QStringList &mimeTypes, int initialPreference = 1) { KDesktopFile file(path); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakeApplication"); group.writeEntry("Type", "Application"); group.writeEntry("Exec", "ls"); group.writeEntry("OnlyShowIn", "KDE;UDE"); group.writeEntry("NotShowIn", "GNOME"); group.writeEntry("InitialPreference", initialPreference); group.writeXdgListEntry("MimeType", mimeTypes); } /** * This unit test verifies the parsing of mimeapps.list files, both directly * and via kbuildsycoca (and making trader queries). */ class KMimeAssociationsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { setupXdgDirs(); QStandardPaths::setTestModeEnabled(true); - qputenv("XDG_CURRENT_DESKTOP", "KDE"); + // The Plasma bit makes no sense, but this is just to test that this is treated as a colon-separated list + qputenv("XDG_CURRENT_DESKTOP", "KDE:Plasma"); m_localConfig = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/'); QDir(m_localConfig).removeRecursively(); QVERIFY(QDir().mkpath(m_localConfig)); m_localApps = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/'); QDir(m_localApps).removeRecursively(); QVERIFY(QDir().mkpath(m_localApps)); QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/'); QDir(cacheDir).removeRecursively(); // Create fake application (associated with text/plain in mimeapps.list) fakeTextApplication = m_localApps + "faketextapplication.desktop"; writeAppDesktopFile(fakeTextApplication, QStringList() << QStringLiteral("text/plain")); // Create fake application (associated with text/plain in mimeapps.list) fakeTextApplicationPrefixed = m_localApps + "fakepfx/faketextapplicationpfx.desktop"; writeAppDesktopFile(fakeTextApplicationPrefixed, QStringList() << QStringLiteral("text/plain")); // A fake "default" application for text/plain (high initial preference, but not in mimeapps.list) fakeDefaultTextApplication = m_localApps + "fakedefaulttextapplication.desktop"; writeAppDesktopFile(fakeDefaultTextApplication, QStringList() << QStringLiteral("text/plain"), 9); // An app (like emacs) listing explicitly the derived mimetype (c-src); not in mimeapps.list // This interacted badly with mimeapps.list listing another app for text/plain, but the // lookup found this app first, due to c-src. The fix: ignoring derived mimetypes when // the base mimetype is already listed. // // Also include aliases (msword), to check they don't cancel each other out. fakeCSrcApplication = m_localApps + "fakecsrcmswordapplication.desktop"; writeAppDesktopFile(fakeCSrcApplication, QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/c-src") << QStringLiteral("application/vnd.ms-word") << QStringLiteral("application/msword"), 8); fakeJpegApplication = m_localApps + "fakejpegapplication.desktop"; writeAppDesktopFile(fakeJpegApplication, QStringList() << QStringLiteral("image/jpeg")); fakeArkApplication = m_localApps + "fakearkapplication.desktop"; writeAppDesktopFile(fakeArkApplication, QStringList() << QStringLiteral("application/zip")); fakeHtmlApplication = m_localApps + "fakehtmlapplication.desktop"; writeAppDesktopFile(fakeHtmlApplication, QStringList() << QStringLiteral("text/html")); fakeHtmlApplicationPrefixed = m_localApps + "fakepfx/fakehtmlapplicationpfx.desktop"; writeAppDesktopFile(fakeHtmlApplicationPrefixed, QStringList() << QStringLiteral("text/html")); // Update ksycoca in ~/.qttest after creating the above runKBuildSycoca(); // Create factory on the heap and don't delete it. This must happen after // Sycoca is built, in case it did not exist before. // It registers to KSycoca, which deletes it at end of program execution. KServiceFactory *factory = new FakeServiceFactory(KSycoca::self()); KSycocaPrivate::self()->m_serviceFactory = factory; QCOMPARE(KSycocaPrivate::self()->serviceFactory(), factory); // For debugging: print all services and their storageId #if 0 const KService::List lst = KService::allServices(); QVERIFY(!lst.isEmpty()); for (const KService::Ptr &serv : lst) { qDebug() << serv->entryPath() << serv->storageId() /*<< serv->desktopEntryName()*/; } #endif KService::Ptr fakeApplicationService = KService::serviceByStorageId(QStringLiteral("faketextapplication.desktop")); QVERIFY(fakeApplicationService); m_mimeAppsFileContents = "[Added Associations]\n" "image/jpeg=fakejpegapplication.desktop;\n" "text/html=fakehtmlapplication.desktop;fakehtmlapplicationpfx.desktop;\n" "text/plain=fakepfx-faketextapplicationpfx.desktop;gvim.desktop;wine.desktop;idontexist.desktop;\n" // test alias resolution "application/x-pdf=fakejpegapplication.desktop;\n" // test x-scheme-handler (#358159) (missing trailing ';' as per xdg-mime bug...) "x-scheme-handler/mailto=faketextapplication.desktop\n" "[Added KParts/ReadOnlyPart Associations]\n" "text/plain=katepart.desktop;\n" "[Removed Associations]\n" "image/jpeg=firefox.desktop;\n" "text/html=gvim.desktop;abiword.desktop;\n" "[Default Applications]\n" "text/plain=faketextapplication.desktop;second-faketextapplicationpfx.desktop\n"; // Expected results preferredApps[QStringLiteral("image/jpeg")] << QStringLiteral("fakejpegapplication.desktop"); preferredApps[QStringLiteral("application/pdf")] << QStringLiteral("fakejpegapplication.desktop"); preferredApps[QStringLiteral("text/plain")] << QStringLiteral("faketextapplication.desktop") << QStringLiteral("second-faketextapplicationpfx.desktop") << QStringLiteral("fakepfx-faketextapplicationpfx.desktop") << QStringLiteral("gvim.desktop"); preferredApps[QStringLiteral("text/x-csrc")] << QStringLiteral("faketextapplication.desktop") << QStringLiteral("fakepfx-faketextapplicationpfx.desktop") << QStringLiteral("gvim.desktop"); preferredApps[QStringLiteral("text/html")] << QStringLiteral("fakehtmlapplication.desktop") << QStringLiteral("fakepfx-fakehtmlapplicationpfx.desktop"); preferredApps[QStringLiteral("application/msword")] << QStringLiteral("fakecsrcmswordapplication.desktop"); preferredApps[QStringLiteral("x-scheme-handler/mailto")] << QStringLiteral("faketextapplication.desktop"); removedApps[QStringLiteral("image/jpeg")] << QStringLiteral("firefox.desktop"); removedApps[QStringLiteral("text/html")] << QStringLiteral("gvim.desktop") << QStringLiteral("abiword.desktop"); // Clean-up non-existing apps removeNonExisting(preferredApps); removeNonExisting(removedApps); } void cleanupTestCase() { QFile::remove(m_localConfig + "/mimeapps.list"); runKBuildSycoca(); } void testParseSingleFile() { KOfferHash offerHash; KMimeAssociations parser(offerHash, KSycocaPrivate::self()->serviceFactory()); QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QFile tempFile(tempDir.path() + "/mimeapps.list"); QVERIFY(tempFile.open(QIODevice::WriteOnly)); tempFile.write(m_mimeAppsFileContents); const QString fileName = tempFile.fileName(); tempFile.close(); //QTest::ignoreMessage(QtDebugMsg, "findServiceByDesktopPath: idontexist.desktop not found"); parser.parseMimeAppsList(fileName, 100); for (ExpectedResultsMap::const_iterator it = preferredApps.constBegin(), end = preferredApps.constEnd(); it != end; ++it) { const QString mime = it.key(); // The data for derived types and aliases isn't for this test (which only looks at mimeapps.list) if (mime == QLatin1String("text/x-csrc") || mime == QLatin1String("application/msword")) { continue; } const QList offers = offerHash.offersFor(mime); for (const QString &service : it.value()) { KService::Ptr serv = KService::serviceByStorageId(service); if (serv && !offersContains(offers, serv)) { qDebug() << "expected offer" << serv->entryPath() << "not in offers for" << mime << ":"; for (const KServiceOffer &offer : offers) { qDebug() << offer.service()->storageId(); } QFAIL("offer does not have servicetype"); } } } for (ExpectedResultsMap::const_iterator it = removedApps.constBegin(), end = removedApps.constEnd(); it != end; ++it) { const QString mime = it.key(); const QList offers = offerHash.offersFor(mime); for (const QString &service : it.value()) { KService::Ptr serv = KService::serviceByStorageId(service); if (serv && offersContains(offers, serv)) { //qDebug() << serv.data() << serv->entryPath() << "does not have" << mime; QFAIL("offer should not have servicetype"); } } } } void testGlobalAndLocalFiles() { KOfferHash offerHash; KMimeAssociations parser(offerHash, KSycocaPrivate::self()->serviceFactory()); // Write global file QTemporaryDir tempDirGlobal; QVERIFY(tempDirGlobal.isValid()); QFile tempFileGlobal(tempDirGlobal.path() + "/mimeapps.list"); QVERIFY(tempFileGlobal.open(QIODevice::WriteOnly)); QByteArray globalAppsFileContents = "[Added Associations]\n" "image/jpeg=firefox.desktop;\n" // removed by local config "text/html=firefox.desktop;\n" // mdv "image/png=fakejpegapplication.desktop;\n"; tempFileGlobal.write(globalAppsFileContents); const QString globalFileName = tempFileGlobal.fileName(); tempFileGlobal.close(); // We didn't keep it, so we need to write the local file again QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QFile tempFile(tempDir.path() + "/mimeapps.list"); QVERIFY(tempFile.open(QIODevice::WriteOnly)); tempFile.write(m_mimeAppsFileContents); const QString fileName = tempFile.fileName(); tempFile.close(); parser.parseMimeAppsList(globalFileName, 1000); parser.parseMimeAppsList(fileName, 1050); // += 50 is correct. QList offers = offerHash.offersFor(QStringLiteral("image/jpeg")); std::stable_sort(offers.begin(), offers.end()); // like kbuildservicefactory.cpp does const QStringList expectedJpegApps = preferredApps[QStringLiteral("image/jpeg")]; QCOMPARE(assembleOffers(offers), expectedJpegApps); offers = offerHash.offersFor(QStringLiteral("text/html")); std::stable_sort(offers.begin(), offers.end()); QStringList textHtmlApps = preferredApps[QStringLiteral("text/html")]; if (KService::serviceByStorageId(QStringLiteral("firefox.desktop"))) { textHtmlApps.append(QStringLiteral("firefox.desktop")); } qDebug() << assembleOffers(offers); QCOMPARE(assembleOffers(offers), textHtmlApps); offers = offerHash.offersFor(QStringLiteral("image/png")); std::stable_sort(offers.begin(), offers.end()); QCOMPARE(assembleOffers(offers), QStringList() << QStringLiteral("fakejpegapplication.desktop")); } void testSetupRealFile() { writeToMimeApps(m_mimeAppsFileContents); // Test a trader query KService::List offers = KMimeTypeTrader::self()->query(QStringLiteral("image/jpeg")); QVERIFY(!offers.isEmpty()); //qDebug() << m_mimeAppsFileContents; //qDebug() << "preferred apps for jpeg: " << preferredApps.value("image/jpeg"); //for (int i = 0; i < offers.count(); ++i) { // qDebug() << "offers for" << "image/jpeg" << ":" << i << offers[i]->storageId(); //} QCOMPARE(offers.first()->storageId(), QStringLiteral("fakejpegapplication.desktop")); // Now the generic variant of the above test: // for each mimetype, check that the preferred apps are as specified for (ExpectedResultsMap::const_iterator it = preferredApps.constBegin(), end = preferredApps.constEnd(); it != end; ++it) { const QString mime = it.key(); const KService::List offers = KMimeTypeTrader::self()->query(mime); const QStringList offerIds = assembleServices(offers, it.value().count()); if (offerIds != it.value()) { qDebug() << "offers for" << mime << ":"; for (int i = 0; i < offers.count(); ++i) { qDebug() << " " << i << ":" << offers[i]->storageId(); } qDebug() << " Expected:" << it.value(); const QStringList expectedPreferredServices = it.value(); for (int i = 0; i < expectedPreferredServices.count(); ++i) { qDebug() << mime << i << expectedPreferredServices[i]; //QCOMPARE(expectedPreferredServices[i], offers[i]->storageId()); } } QCOMPARE(offerIds, it.value()); } } void testMultipleInheritance() { // application/x-shellscript inherits from both text/plain and application/x-executable KService::List offers = KMimeTypeTrader::self()->query(QStringLiteral("application/x-shellscript")); QVERIFY(offerListHasService(offers, fakeTextApplication, true)); } void testRemoveAssociationFromParent() { // I removed kate from text/plain, and it would still appear in text/x-java. // First, let's check our fake app is associated with text/plain KService::List offers = KMimeTypeTrader::self()->query(QStringLiteral("text/plain")); QVERIFY(offerListHasService(offers, fakeTextApplication, true)); writeToMimeApps(QByteArray("[Removed Associations]\n" "text/plain=faketextapplication.desktop;\n")); offers = KMimeTypeTrader::self()->query(QStringLiteral("text/plain")); QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); offers = KMimeTypeTrader::self()->query(QStringLiteral("text/x-java")); QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); } void testRemovedImplicitAssociation() // remove (implicit) assoc from derived mimetype { // #164584: Removing ark from opendocument.text didn't work const QString opendocument = QStringLiteral("application/vnd.oasis.opendocument.text"); // [sanity checking of s-m-i installation] QMimeType mime = QMimeDatabase().mimeTypeForName(opendocument); QVERIFY(mime.isValid()); if (!mime.inherits(QStringLiteral("application/zip"))) { // CentOS patches out the application/zip inheritance from application/vnd.oasis.opendocument.text!! Grmbl. QSKIP("Broken distro where application/vnd.oasis.opendocument.text doesn't inherit from application/zip"); } KService::List offers = KMimeTypeTrader::self()->query(opendocument); QVERIFY(offerListHasService(offers, fakeArkApplication, true)); writeToMimeApps(QByteArray("[Removed Associations]\n" "application/vnd.oasis.opendocument.text=fakearkapplication.desktop;\n")); offers = KMimeTypeTrader::self()->query(opendocument); QVERIFY(!offerListHasService(offers, fakeArkApplication, false)); offers = KMimeTypeTrader::self()->query(QStringLiteral("application/zip")); QVERIFY(offerListHasService(offers, fakeArkApplication, true)); } void testRemovedImplicitAssociation178560() { // #178560: Removing ark from interface/x-winamp-skin didn't work // Using application/x-kns (another zip-derived mimetype) nowadays. const QString mime = QStringLiteral("application/x-kns"); // That mimetype comes from kcoreaddons, let's make sure it's properly installed { QMimeDatabase db; QMimeType mime = db.mimeTypeForName(QStringLiteral("application/x-kns")); QVERIFY(mime.isValid()); QCOMPARE(mime.name(), QStringLiteral("application/x-kns")); QVERIFY(mime.inherits(QStringLiteral("application/zip"))); } KService::List offers = KMimeTypeTrader::self()->query(mime); QVERIFY(offerListHasService(offers, fakeArkApplication, true)); writeToMimeApps(QByteArray("[Removed Associations]\n" "application/x-kns=fakearkapplication.desktop;\n")); offers = KMimeTypeTrader::self()->query(mime); QVERIFY(!offerListHasService(offers, fakeArkApplication, false)); offers = KMimeTypeTrader::self()->query(QStringLiteral("application/zip")); QVERIFY(offerListHasService(offers, fakeArkApplication, true)); } // remove assoc from a mime which is both a parent and a derived mimetype void testRemovedMiddleAssociation() { // More tricky: x-theme inherits x-desktop inherits text/plain, // if we remove an association for x-desktop then x-theme shouldn't // get it from text/plain... KService::List offers; writeToMimeApps(QByteArray("[Removed Associations]\n" "application/x-desktop=faketextapplication.desktop;\n")); offers = KMimeTypeTrader::self()->query(QStringLiteral("text/plain")); QVERIFY(offerListHasService(offers, fakeTextApplication, true)); offers = KMimeTypeTrader::self()->query(QStringLiteral("application/x-desktop")); QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); offers = KMimeTypeTrader::self()->query(QStringLiteral("application/x-theme")); QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); } private: typedef QMap ExpectedResultsMap; void runKBuildSycoca() { // Wait for notifyDatabaseChanged DBus signal // (The real KCM code simply does the refresh in a slot, asynchronously) QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); KBuildSycoca builder; QVERIFY(builder.recreate()); if (spy.isEmpty()) { spy.wait(); } } void writeToMimeApps(const QByteArray &contents) { QString mimeAppsPath = m_localConfig + "/mimeapps.list"; QFile mimeAppsFile(mimeAppsPath); QVERIFY(mimeAppsFile.open(QIODevice::WriteOnly)); mimeAppsFile.write(contents); mimeAppsFile.close(); runKBuildSycoca(); } static bool offersContains(const QList &offers, KService::Ptr serv) { for (const KServiceOffer &offer : offers) { if (offer.service()->storageId() == serv->storageId()) { return true; } } return false; } static QStringList assembleOffers(const QList &offers) { QStringList lst; for (const KServiceOffer &offer : offers) { lst.append(offer.service()->storageId()); } return lst; } static QStringList assembleServices(const QList &services, int maxCount = -1) { QStringList lst; for (const KService::Ptr &service : services) { lst.append(service->storageId()); if (maxCount > -1 && lst.count() == maxCount) { break; } } return lst; } void removeNonExisting(ExpectedResultsMap &erm) { for (ExpectedResultsMap::iterator it = erm.begin(), end = erm.end(); it != end; ++it) { QMutableStringListIterator serv_it(it.value()); while (serv_it.hasNext()) { if (!KService::serviceByStorageId(serv_it.next())) { //qDebug() << "removing non-existing entry" << serv_it.value(); serv_it.remove(); } } } } QString m_localApps; QString m_localConfig; QByteArray m_mimeAppsFileContents; QString fakeTextApplication; QString fakeTextApplicationPrefixed; QString fakeDefaultTextApplication; QString fakeCSrcApplication; QString fakeJpegApplication; QString fakeHtmlApplication; QString fakeHtmlApplicationPrefixed; QString fakeArkApplication; ExpectedResultsMap preferredApps; ExpectedResultsMap removedApps; }; FakeServiceFactory::~FakeServiceFactory() { } QTEST_GUILESS_MAIN(KMimeAssociationsTest) #include "kmimeassociationstest.moc" diff --git a/src/services/kservice.cpp b/src/services/kservice.cpp index 1696ebd..eae6a00 100644 --- a/src/services/kservice.cpp +++ b/src/services/kservice.cpp @@ -1,1041 +1,1043 @@ /* This file is part of the KDE libraries * Copyright (C) 1999 - 2001 Waldo Bastian * Copyright (C) 1999 - 2005 David Faure * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation; * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kservice.h" #include "kservice_p.h" #include "kmimetypefactory_p.h" #include "ksycoca.h" #include "ksycoca_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kservicefactory_p.h" #include "kservicetypefactory_p.h" #include "kserviceutil_p.h" #include "servicesdebug.h" QDataStream &operator<<(QDataStream &s, const KService::ServiceTypeAndPreference &st) { s << st.preference << st.serviceType; return s; } QDataStream &operator>>(QDataStream &s, KService::ServiceTypeAndPreference &st) { s >> st.preference >> st.serviceType; return s; } void KServicePrivate::init(const KDesktopFile *config, KService *q) { const QString entryPath = q->entryPath(); bool absPath = !QDir::isRelativePath(entryPath); // TODO: it makes sense to have a KConstConfigGroup I guess const KConfigGroup desktopGroup = const_cast(config)->desktopGroup(); QMap entryMap = desktopGroup.entryMap(); entryMap.remove(QStringLiteral("Encoding")); // reserved as part of Desktop Entry Standard entryMap.remove(QStringLiteral("Version")); // reserved as part of Desktop Entry Standard q->setDeleted(desktopGroup.readEntry("Hidden", false)); entryMap.remove(QStringLiteral("Hidden")); if (q->isDeleted()) { m_bValid = false; return; } m_strName = config->readName(); entryMap.remove(QStringLiteral("Name")); if (m_strName.isEmpty()) { // Try to make up a name. m_strName = entryPath; int i = m_strName.lastIndexOf(QLatin1Char('/')); m_strName = m_strName.mid(i + 1); i = m_strName.lastIndexOf(QLatin1Char('.')); if (i != -1) { m_strName.truncate(i); } } m_strType = config->readType(); entryMap.remove(QStringLiteral("Type")); if (m_strType.isEmpty()) { /*kWarning(servicesDebugArea()) << "The desktop entry file " << entryPath << " has no Type=... entry." << " It should be \"Application\" or \"Service\""; m_bValid = false; return;*/ m_strType = QStringLiteral("Application"); } else if (m_strType != QLatin1String("Application") && m_strType != QLatin1String("Service")) { qCWarning(SERVICES) << "The desktop entry file " << entryPath << " has Type=" << m_strType << " instead of \"Application\" or \"Service\""; m_bValid = false; return; } // NOT readPathEntry, it is not XDG-compliant: it performs // various expansions, like $HOME. Note that the expansion // behaviour still happens if the "e" flag is set, maintaining // backwards compatibility. m_strExec = desktopGroup.readEntry("Exec", QString()); entryMap.remove(QStringLiteral("Exec")); if (m_strType == QLatin1String("Application")) { // It's an application? Should have an Exec line then, otherwise we can't run it if (m_strExec.isEmpty()) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but no Exec line"; m_bValid = false; return; } } // In case Try Exec is set, check if the application is available if (!config->tryExec()) { q->setDeleted(true); m_bValid = false; return; } const QStandardPaths::StandardLocation resource = config->resource(); if ((m_strType == QLatin1String("Application")) && (resource != QStandardPaths::ApplicationsLocation) && !absPath) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \"" << QStandardPaths::displayName(resource) << "\" instead of \"Applications\""; m_bValid = false; return; } if ((m_strType == QLatin1String("Service")) && (resource != QStandardPaths::GenericDataLocation) && !absPath) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \"" << QStandardPaths::displayName(resource) << "\" instead of \"Shared Data\"/kservices5"; m_bValid = false; return; } // entryPath To desktopEntryName // (e.g. "/home/x/.qttest/share/kservices5/fakepart2.desktop" --> "fakepart2") QString _name = KServiceUtilPrivate::completeBaseName(entryPath); m_strIcon = config->readIcon(); entryMap.remove(QStringLiteral("Icon")); m_bTerminal = desktopGroup.readEntry("Terminal", false); // should be a property IMHO entryMap.remove(QStringLiteral("Terminal")); m_strTerminalOptions = desktopGroup.readEntry("TerminalOptions"); // should be a property IMHO entryMap.remove(QStringLiteral("TerminalOptions")); m_strWorkingDirectory = config->readPath(); entryMap.remove(QStringLiteral("Path")); m_strComment = config->readComment(); entryMap.remove(QStringLiteral("Comment")); m_strGenName = config->readGenericName(); entryMap.remove(QStringLiteral("GenericName")); QString _untranslatedGenericName = desktopGroup.readEntryUntranslated("GenericName"); if (!_untranslatedGenericName.isEmpty()) { entryMap.insert(QStringLiteral("UntranslatedGenericName"), _untranslatedGenericName); } m_lstFormFactors = desktopGroup.readEntry("X-KDE-FormFactors", QStringList()); entryMap.remove(QStringLiteral("X-KDE-FormFactors")); m_lstKeywords = desktopGroup.readXdgListEntry("Keywords", QStringList()); entryMap.remove(QStringLiteral("Keywords")); m_lstKeywords += desktopGroup.readEntry("X-KDE-Keywords", QStringList()); entryMap.remove(QStringLiteral("X-KDE-Keywords")); categories = desktopGroup.readXdgListEntry("Categories"); entryMap.remove(QStringLiteral("Categories")); // TODO KDE5: only care for X-KDE-Library in Type=Service desktop files // This will prevent people defining a part and an app in the same desktop file // which makes user-preference handling difficult. m_strLibrary = desktopGroup.readEntry("X-KDE-Library"); entryMap.remove(QStringLiteral("X-KDE-Library")); if (!m_strLibrary.isEmpty() && m_strType == QLatin1String("Application")) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but also has a X-KDE-Library key. This works for now," " but makes user-preference handling difficult, so support for this might" " be removed at some point. Consider splitting it into two desktop files."; } QStringList lstServiceTypes = desktopGroup.readEntry("ServiceTypes", QStringList()); entryMap.remove(QStringLiteral("ServiceTypes")); lstServiceTypes += desktopGroup.readEntry("X-KDE-ServiceTypes", QStringList()); entryMap.remove(QStringLiteral("X-KDE-ServiceTypes")); lstServiceTypes += desktopGroup.readXdgListEntry("MimeType"); entryMap.remove(QStringLiteral("MimeType")); if (m_strType == QLatin1String("Application") && !lstServiceTypes.contains(QLatin1String("Application"))) // Applications implement the service type "Application" ;-) { lstServiceTypes += QStringLiteral("Application"); } m_initialPreference = desktopGroup.readEntry("InitialPreference", 1); entryMap.remove(QStringLiteral("InitialPreference")); // Assign the "initial preference" to each mimetype/servicetype // (and to set such preferences in memory from kbuildsycoca) m_serviceTypes.reserve(lstServiceTypes.size()); QListIterator st_it(lstServiceTypes); while (st_it.hasNext()) { const QString st = st_it.next(); if (st.isEmpty()) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has an empty mimetype!"; continue; } int initialPreference = m_initialPreference; if (st_it.hasNext()) { // TODO better syntax - separate group with mimetype=number entries? bool isNumber; const int val = st_it.peekNext().toInt(&isNumber); if (isNumber) { initialPreference = val; st_it.next(); } } m_serviceTypes.push_back(KService::ServiceTypeAndPreference(initialPreference, st)); } if (entryMap.contains(QLatin1String("Actions"))) { parseActions(config, q); } QString dbusStartupType = desktopGroup.readEntry("X-DBUS-StartupType").toLower(); entryMap.remove(QStringLiteral("X-DBUS-StartupType")); if (dbusStartupType == QLatin1String("unique")) { m_DBUSStartusType = KService::DBusUnique; } else if (dbusStartupType == QLatin1String("multi")) { m_DBUSStartusType = KService::DBusMulti; } else { m_DBUSStartusType = KService::DBusNone; } m_strDesktopEntryName = _name.toLower(); m_bAllowAsDefault = desktopGroup.readEntry("AllowDefault", true); entryMap.remove(QStringLiteral("AllowDefault")); // allow plugin users to translate categories without needing a separate key QMap::Iterator entry = entryMap.find(QStringLiteral("X-KDE-PluginInfo-Category")); if (entry != entryMap.end()) { const QString &key = entry.key(); m_mapProps.insert(key, QVariant(desktopGroup.readEntryUntranslated(key))); m_mapProps.insert(key + QLatin1String("-Translated"), QVariant(*entry)); entryMap.erase(entry); } // Store all additional entries in the property map. // A QMap would be easier for this but we can't // break BC, so we have to store it in m_mapProps. // qDebug("Path = %s", entryPath.toLatin1().constData()); QMap::ConstIterator it = entryMap.constBegin(); for (; it != entryMap.constEnd(); ++it) { const QString key = it.key(); // do not store other translations like Name[fr]; kbuildsycoca will rerun if we change languages anyway if (!key.contains(QLatin1Char('['))) { //qCDebug(SERVICES) << " Key =" << key << " Data =" << *it; m_mapProps.insert(key, QVariant(*it)); } } } void KServicePrivate::parseActions(const KDesktopFile *config, KService *q) { const QStringList keys = config->readActions(); if (keys.isEmpty()) { return; } QStringList::ConstIterator it = keys.begin(); const QStringList::ConstIterator end = keys.end(); for (; it != end; ++it) { const QString group = *it; if (group == QLatin1String("_SEPARATOR_")) { m_actions.append(KServiceAction(group, QString(), QString(), QString(), false)); continue; } if (config->hasActionGroup(group)) { const KConfigGroup cg = config->actionGroup(group); if (!cg.hasKey("Name") || !cg.hasKey("Exec")) { qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Name or no Exec key"; } else { m_actions.append(KServiceAction(group, cg.readEntry("Name"), cg.readEntry("Icon"), cg.readEntry("Exec"), cg.readEntry("NoDisplay", false))); } } else { qCWarning(SERVICES) << "The desktop file" << q->entryPath() << "references the action" << group << "but doesn't define it"; } } } void KServicePrivate::load(QDataStream &s) { qint8 def, term; qint8 dst, initpref; // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS! // !! This data structure should remain binary compatible at all times !! // You may add new fields at the end. Make sure to update KSYCOCA_VERSION // number in ksycoca.cpp s >> m_strType >> m_strName >> m_strExec >> m_strIcon >> term >> m_strTerminalOptions >> m_strWorkingDirectory >> m_strComment >> def >> m_mapProps >> m_strLibrary >> dst >> m_strDesktopEntryName >> initpref >> m_lstKeywords >> m_strGenName >> categories >> menuId >> m_actions >> m_serviceTypes >> m_lstFormFactors; m_bAllowAsDefault = bool(def); m_bTerminal = bool(term); m_DBUSStartusType = static_cast(dst); m_initialPreference = initpref; m_bValid = true; } void KServicePrivate::save(QDataStream &s) { KSycocaEntryPrivate::save(s); qint8 def = m_bAllowAsDefault, initpref = m_initialPreference; qint8 term = m_bTerminal; qint8 dst = qint8(m_DBUSStartusType); // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS! // !! This data structure should remain binary compatible at all times !! // You may add new fields at the end. Make sure to update KSYCOCA_VERSION // number in ksycoca.cpp s << m_strType << m_strName << m_strExec << m_strIcon << term << m_strTerminalOptions << m_strWorkingDirectory << m_strComment << def << m_mapProps << m_strLibrary << dst << m_strDesktopEntryName << initpref << m_lstKeywords << m_strGenName << categories << menuId << m_actions << m_serviceTypes << m_lstFormFactors; } //// KService::KService(const QString &_name, const QString &_exec, const QString &_icon) : KSycocaEntry(*new KServicePrivate(QString())) { Q_D(KService); d->m_strType = QStringLiteral("Application"); d->m_strName = _name; d->m_strExec = _exec; d->m_strIcon = _icon; d->m_bTerminal = false; d->m_bAllowAsDefault = true; d->m_initialPreference = 10; } KService::KService(const QString &_fullpath) : KSycocaEntry(*new KServicePrivate(_fullpath)) { Q_D(KService); KDesktopFile config(_fullpath); d->init(&config, this); } KService::KService(const KDesktopFile *config, const QString &entryPath) : KSycocaEntry(*new KServicePrivate(entryPath.isEmpty() ? config->fileName() : entryPath)) { Q_D(KService); d->init(config, this); } KService::KService(QDataStream &_str, int _offset) : KSycocaEntry(*new KServicePrivate(_str, _offset)) { } KService::KService(const KService &other) : KSycocaEntry(*new KServicePrivate(*static_cast(other.d_ptr))) { } KService::~KService() { } bool KService::hasServiceType(const QString &serviceType) const { Q_D(const KService); if (!d->m_bValid) { return false; // (useless) safety test } const KServiceType::Ptr ptr = KServiceType::serviceType(serviceType); if (!ptr) { return false; } const int serviceOffset = offset(); // doesn't seem to work: //if ( serviceOffset == 0 ) // serviceOffset = serviceByStorageId( storageId() ); if (serviceOffset) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->hasOffer(ptr->offset(), ptr->serviceOffersOffset(), serviceOffset); } // fall-back code for services that are NOT from ksycoca // For each service type we are associated with, if it doesn't // match then we try its parent service types. QVector::ConstIterator it = d->m_serviceTypes.begin(); for (; it != d->m_serviceTypes.end(); ++it) { const QString &st = (*it).serviceType; //qCDebug(SERVICES) << " has " << (*it); if (st == ptr->name()) { return true; } // also the case of parent servicetypes KServiceType::Ptr p = KServiceType::serviceType(st); if (p && p->inherits(ptr->name())) { return true; } } return false; } bool KService::hasMimeType(const QString &mimeType) const { Q_D(const KService); QMimeDatabase db; const QString mime = db.mimeTypeForName(mimeType).name(); if (mime.isEmpty()) { return false; } int serviceOffset = offset(); if (serviceOffset) { KSycoca::self()->ensureCacheValid(); KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory(); const int mimeOffset = factory->entryOffset(mime); const int serviceOffersOffset = factory->serviceOffersOffset(mime); if (serviceOffersOffset == -1) { return false; } return KSycocaPrivate::self()->serviceFactory()->hasOffer(mimeOffset, serviceOffersOffset, serviceOffset); } // fall-back code for services that are NOT from ksycoca QVector::ConstIterator it = d->m_serviceTypes.begin(); for (; it != d->m_serviceTypes.end(); ++it) { const QString &st = (*it).serviceType; //qCDebug(SERVICES) << " has " << (*it); if (st == mime) { return true; } // TODO: should we handle inherited mimetypes here? // KMimeType was in kio when this code was written, this is the only reason it's not done. // But this should matter only in a very rare case, since most code gets KServices from ksycoca. // Warning, change hasServiceType if you implement this here (and check kbuildservicefactory). } return false; } QVariant KServicePrivate::property(const QString &_name) const { return property(_name, QVariant::Invalid); } // Return a string QVariant if string isn't null, and invalid variant otherwise // (the variant must be invalid if the field isn't in the .desktop file) // This allows trader queries like "exist Library" to work. static QVariant makeStringVariant(const QString &string) { // Using isEmpty here would be wrong. // Empty is "specified but empty", null is "not specified" (in the .desktop file) return string.isNull() ? QVariant() : QVariant(string); } QVariant KService::property(const QString &_name, QVariant::Type t) const { Q_D(const KService); return d->property(_name, t); } QVariant KServicePrivate::property(const QString &_name, QVariant::Type t) const { if (_name == QLatin1String("Type")) { return QVariant(m_strType); // can't be null } else if (_name == QLatin1String("Name")) { return QVariant(m_strName); // can't be null } else if (_name == QLatin1String("Exec")) { return makeStringVariant(m_strExec); } else if (_name == QLatin1String("Icon")) { return makeStringVariant(m_strIcon); } else if (_name == QLatin1String("Terminal")) { return QVariant(m_bTerminal); } else if (_name == QLatin1String("TerminalOptions")) { return makeStringVariant(m_strTerminalOptions); } else if (_name == QLatin1String("Path")) { return makeStringVariant(m_strWorkingDirectory); } else if (_name == QLatin1String("Comment")) { return makeStringVariant(m_strComment); } else if (_name == QLatin1String("GenericName")) { return makeStringVariant(m_strGenName); } else if (_name == QLatin1String("ServiceTypes")) { return QVariant(serviceTypes()); } else if (_name == QLatin1String("AllowAsDefault")) { return QVariant(m_bAllowAsDefault); } else if (_name == QLatin1String("InitialPreference")) { return QVariant(m_initialPreference); } else if (_name == QLatin1String("Library")) { return makeStringVariant(m_strLibrary); } else if (_name == QLatin1String("DesktopEntryPath")) { // can't be null return QVariant(path); } else if (_name == QLatin1String("DesktopEntryName")) { return QVariant(m_strDesktopEntryName); // can't be null } else if (_name == QLatin1String("Categories")) { return QVariant(categories); } else if (_name == QLatin1String("Keywords")) { return QVariant(m_lstKeywords); } else if (_name == QLatin1String("FormFactors")) { return QVariant(m_lstFormFactors); } // Ok we need to convert the property from a QString to its real type. // Maybe the caller helped us. if (t == QVariant::Invalid) { // No luck, let's ask KServiceTypeFactory what the type of this property // is supposed to be. // ######### this looks in all servicetypes, not just the ones this service supports! KSycoca::self()->ensureCacheValid(); t = KSycocaPrivate::self()->serviceTypeFactory()->findPropertyTypeByName(_name); if (t == QVariant::Invalid) { qCDebug(SERVICES) << "Request for unknown property" << _name; return QVariant(); // Unknown property: Invalid variant. } } QMap::ConstIterator it = m_mapProps.find(_name); if ((it == m_mapProps.end()) || (!it->isValid())) { //qCDebug(SERVICES) << "Property not found " << _name; return QVariant(); // No property set. } if (t == QVariant::String) { return *it; // no conversion necessary } else { // All others // For instance properties defined as StringList, like MimeTypes. // XXX This API is accessible only through a friend declaration. return KConfigGroup::convertToQVariant(_name.toUtf8().constData(), it->toString().toUtf8(), t); } } QStringList KServicePrivate::propertyNames() const { QStringList res; QMap::ConstIterator it = m_mapProps.begin(); for (; it != m_mapProps.end(); ++it) { res.append(it.key()); } res.append(QStringLiteral("Type")); res.append(QStringLiteral("Name")); res.append(QStringLiteral("Comment")); res.append(QStringLiteral("GenericName")); res.append(QStringLiteral("Icon")); res.append(QStringLiteral("Exec")); res.append(QStringLiteral("Terminal")); res.append(QStringLiteral("TerminalOptions")); res.append(QStringLiteral("Path")); res.append(QStringLiteral("ServiceTypes")); res.append(QStringLiteral("AllowAsDefault")); res.append(QStringLiteral("InitialPreference")); res.append(QStringLiteral("Library")); res.append(QStringLiteral("DesktopEntryPath")); res.append(QStringLiteral("DesktopEntryName")); res.append(QStringLiteral("Keywords")); res.append(QStringLiteral("FormFactors")); res.append(QStringLiteral("Categories")); return res; } KService::List KService::allServices() { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->allServices(); } KService::Ptr KService::serviceByDesktopPath(const QString &_name) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name); } KService::Ptr KService::serviceByDesktopName(const QString &_name) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name); } KService::Ptr KService::serviceByMenuId(const QString &_name) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(_name); } KService::Ptr KService::serviceByStorageId(const QString &_storageId) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId); } bool KService::substituteUid() const { QVariant v = property(QStringLiteral("X-KDE-SubstituteUID"), QVariant::Bool); return v.isValid() && v.toBool(); } QString KService::username() const { // See also KDesktopFile::tryExec() QString user; QVariant v = property(QStringLiteral("X-KDE-Username"), QVariant::String); user = v.isValid() ? v.toString() : QString(); if (user.isEmpty()) { user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT")); } if (user.isEmpty()) { user = QStringLiteral("root"); } return user; } bool KService::showInCurrentDesktop() const { Q_D(const KService); - QStringList currentDesktops(QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"))); + const QString envVar = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP")); + QVector currentDesktops(envVar.splitRef(QLatin1Char(':'), Qt::SkipEmptyParts)); + const QString kde = QStringLiteral("KDE"); if (currentDesktops.isEmpty()) { // This could be an old display manager, or e.g. a failsafe session with no desktop name // In doubt, let's say we show KDE stuff. - currentDesktops << QStringLiteral("KDE"); + currentDesktops.append(&kde); } // This algorithm is described in the desktop entry spec QMap::ConstIterator it = d->m_mapProps.find(QStringLiteral("OnlyShowIn")); if ((it != d->m_mapProps.end()) && (it->isValid())) { const QStringList aList = it->toString().split(QLatin1Char(';')); - for (const QString &desktop : qAsConst(currentDesktops)) { + for (const QStringRef &desktop : qAsConst(currentDesktops)) { if (aList.contains(desktop)) { return true; } } return false; } it = d->m_mapProps.find(QStringLiteral("NotShowIn")); if ((it != d->m_mapProps.end()) && (it->isValid())) { const QStringList aList = it->toString().split(QLatin1Char(';')); - for (const QString &desktop : qAsConst(currentDesktops)) { + for (const QStringRef &desktop : qAsConst(currentDesktops)) { if (aList.contains(desktop)) { return false; } } } return true; } bool KService::showOnCurrentPlatform() const { Q_D(const KService); const QString platform = QCoreApplication::instance()->property("platformName").toString(); if (platform.isEmpty()) { return true; } auto it = d->m_mapProps.find(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms")); if ((it != d->m_mapProps.end()) && (it->isValid())) { const QStringList aList = it->toString().split(QLatin1Char(';')); if (!aList.contains(platform)) { return false; } } it = d->m_mapProps.find(QStringLiteral("X-KDE-NotShowOnQtPlatforms")); if ((it != d->m_mapProps.end()) && (it->isValid())) { const QStringList aList = it->toString().split(QLatin1Char(';')); if (aList.contains(platform)) { return false; } } return true; } bool KService::noDisplay() const { if (qvariant_cast(property(QStringLiteral("NoDisplay"), QVariant::Bool))) { return true; } if (!showInCurrentDesktop()) { return true; } if (!showOnCurrentPlatform()) { return true; } if (!KAuthorized::authorizeControlModule(storageId())) { return true; } return false; } QString KService::untranslatedGenericName() const { QVariant v = property(QStringLiteral("UntranslatedGenericName"), QVariant::String); return v.isValid() ? v.toString() : QString(); } QString KService::parentApp() const { Q_D(const KService); QMap::ConstIterator it = d->m_mapProps.find(QStringLiteral("X-KDE-ParentApp")); if ((it == d->m_mapProps.end()) || (!it->isValid())) { return QString(); } return it->toString(); } QString KService::pluginKeyword() const { Q_D(const KService); QMap::ConstIterator it = d->m_mapProps.find(QStringLiteral("X-KDE-PluginKeyword")); if ((it == d->m_mapProps.end()) || (!it->isValid())) { return QString(); } return it->toString(); } QString KService::docPath() const { Q_D(const KService); QMap::ConstIterator it = d->m_mapProps.find(QStringLiteral("X-DocPath")); if ((it == d->m_mapProps.end()) || (!it->isValid())) { it = d->m_mapProps.find(QStringLiteral("DocPath")); if ((it == d->m_mapProps.end()) || (!it->isValid())) { return QString(); } } return it->toString(); } bool KService::allowMultipleFiles() const { Q_D(const KService); // Can we pass multiple files on the command line or do we have to start the application for every single file ? return (d->m_strExec.contains(QLatin1String("%F")) || d->m_strExec.contains(QLatin1String("%U")) || d->m_strExec.contains(QLatin1String("%N")) || d->m_strExec.contains(QLatin1String("%D"))); } QStringList KService::categories() const { Q_D(const KService); return d->categories; } QString KService::menuId() const { Q_D(const KService); return d->menuId; } void KService::setMenuId(const QString &_menuId) { Q_D(KService); d->menuId = _menuId; } QString KService::storageId() const { Q_D(const KService); return d->storageId(); } // not sure this is still used anywhere... QString KService::locateLocal() const { Q_D(const KService); if (d->menuId.isEmpty() || entryPath().startsWith(QLatin1String(".hidden")) || (QDir::isRelativePath(entryPath()) && d->categories.isEmpty())) { return KDesktopFile::locateLocal(entryPath()); } return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + d->menuId; } QString KService::newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId, const QStringList *reservedMenuIds) { Q_UNUSED(showInMenu); // TODO KDE5: remove argument QString base = suggestedName; QString result; for (int i = 1; true; i++) { if (i == 1) { result = base + QStringLiteral(".desktop"); } else { result = base + QStringLiteral("-%1.desktop").arg(i); } if (reservedMenuIds && reservedMenuIds->contains(result)) { continue; } // Lookup service by menu-id KService::Ptr s = serviceByMenuId(result); if (s) { continue; } if (!QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("applications/") + result).isEmpty()) { continue; } break; } if (menuId) { *menuId = result; } return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + result; } bool KService::isApplication() const { Q_D(const KService); return d->m_strType == QLatin1String("Application"); } QString KService::exec() const { Q_D(const KService); if (d->m_strType == QLatin1String("Application") && d->m_strExec.isEmpty()) { qCWarning(SERVICES) << "The desktop entry file " << entryPath() << " has Type=" << d->m_strType << " but has no Exec field."; } return d->m_strExec; } QString KService::library() const { Q_D(const KService); return d->m_strLibrary; } QString KService::icon() const { Q_D(const KService); return d->m_strIcon; } QString KService::terminalOptions() const { Q_D(const KService); return d->m_strTerminalOptions; } bool KService::terminal() const { Q_D(const KService); return d->m_bTerminal; } bool KService::runOnDiscreteGpu() const { QVariant v = property(QStringLiteral("X-KDE-RunOnDiscreteGpu"), QVariant::Bool); return v.isValid() && v.toBool(); } QString KService::desktopEntryName() const { Q_D(const KService); return d->m_strDesktopEntryName; } KService::DBusStartupType KService::dbusStartupType() const { Q_D(const KService); return d->m_DBUSStartusType; } #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 63) QString KService::path() const { Q_D(const KService); return d->m_strWorkingDirectory; } #endif QString KService::workingDirectory() const { Q_D(const KService); return d->m_strWorkingDirectory; } QString KService::comment() const { Q_D(const KService); return d->m_strComment; } QString KService::genericName() const { Q_D(const KService); return d->m_strGenName; } QStringList KService::keywords() const { Q_D(const KService); return d->m_lstKeywords; } QStringList KServicePrivate::serviceTypes() const { QStringList ret; QVector::const_iterator it = m_serviceTypes.begin(); for (; it < m_serviceTypes.end(); ++it) { Q_ASSERT(!(*it).serviceType.isEmpty()); ret.append((*it).serviceType); } return ret; } QStringList KService::serviceTypes() const { Q_D(const KService); return d->serviceTypes(); } QStringList KService::mimeTypes() const { Q_D(const KService); QStringList ret; QMimeDatabase db; QVector::const_iterator it = d->m_serviceTypes.begin(); for (; it < d->m_serviceTypes.end(); ++it) { const QString sv = (*it).serviceType; if (db.mimeTypeForName(sv).isValid()) { // keep only mimetypes, filter out servicetypes ret.append(sv); } } return ret; } #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 67) bool KService::allowAsDefault() const { Q_D(const KService); return d->m_bAllowAsDefault; } #endif int KService::initialPreference() const { Q_D(const KService); return d->m_initialPreference; } void KService::setTerminal(bool b) { Q_D(KService); d->m_bTerminal = b; } void KService::setTerminalOptions(const QString &options) { Q_D(KService); d->m_strTerminalOptions = options; } void KService::setExec(const QString &exec) { Q_D(KService); if (!exec.isEmpty()) { d->m_strExec = exec; d->path.clear(); } } QVector &KService::_k_accessServiceTypes() { Q_D(KService); return d->m_serviceTypes; } QList KService::actions() const { Q_D(const KService); return d->m_actions; } KService::operator KPluginName() const { if (!isValid()) { return KPluginName::fromErrorString(i18n("The provided service is not valid")); } if (library().isEmpty()) { return KPluginName::fromErrorString(i18n("The service '%1' provides no library or the Library key is missing", entryPath())); } return KPluginName(library()); }