File indexing completed on 2024-12-01 12:38:24

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org>
0004     SPDX-FileCopyrightText: 2006-2020 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "kapplicationtrader.h"
0010 
0011 #include "kmimetypefactory_p.h"
0012 #include "kservicefactory_p.h"
0013 #include "kservicetypefactory_p.h"
0014 #include "ksycoca.h"
0015 #include "ksycoca_p.h"
0016 #include "servicesdebug.h"
0017 
0018 #include <QMimeDatabase>
0019 
0020 #include <KConfigGroup>
0021 #include <KSharedConfig>
0022 
0023 static KService::List mimeTypeSycocaServiceOffers(const QString &mimeType)
0024 {
0025     KService::List lst;
0026     QMimeDatabase db;
0027     QString mime = db.mimeTypeForName(mimeType).name();
0028     if (mime.isEmpty()) {
0029         if (!mimeType.startsWith(QLatin1String("x-scheme-handler/"))) { // don't warn for unknown scheme handler mimetypes
0030             qCWarning(SERVICES) << "KApplicationTrader: mimeType" << mimeType << "not found";
0031             return lst; // empty
0032         }
0033         mime = mimeType;
0034     }
0035     KSycoca::self()->ensureCacheValid();
0036     KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory();
0037     const int offset = factory->entryOffset(mime);
0038     if (!offset) {
0039         qCWarning(SERVICES) << "KApplicationTrader: mimeType" << mimeType << "not found";
0040         return lst; // empty
0041     }
0042     const int serviceOffersOffset = factory->serviceOffersOffset(mime);
0043     if (serviceOffersOffset > -1) {
0044         lst = KSycocaPrivate::self()->serviceFactory()->serviceOffers(offset, serviceOffersOffset);
0045     }
0046     return lst;
0047 }
0048 
0049 // Filter the offers for the requested MIME type in order to keep only applications.
0050 static void filterMimeTypeOffers(KService::List &list) // static, internal
0051 {
0052     KServiceType::Ptr genericServiceTypePtr = KServiceType::serviceType(QStringLiteral("Application"));
0053     Q_ASSERT(genericServiceTypePtr);
0054 
0055     KSycoca::self()->ensureCacheValid();
0056     KServiceFactory *serviceFactory = KSycocaPrivate::self()->serviceFactory();
0057 
0058     // Remove non-Applications (TODO KF6: kill plugin desktop files, then kill this code)
0059     auto removeFunc = [&](const KService::Ptr &serv) {
0060         return !serviceFactory->hasOffer(genericServiceTypePtr, serv);
0061     };
0062     list.erase(std::remove_if(list.begin(), list.end(), removeFunc), list.end());
0063 }
0064 
0065 static void applyFilter(KService::List &list, KApplicationTrader::FilterFunc filterFunc, bool mustShowInCurrentDesktop)
0066 {
0067     if (list.isEmpty()) {
0068         return;
0069     }
0070 
0071     // Find all services matching the constraint
0072     // and remove the other ones
0073     auto removeFunc = [&](const KService::Ptr &serv) {
0074         return (filterFunc && !filterFunc(serv)) || (mustShowInCurrentDesktop && !serv->showInCurrentDesktop());
0075     };
0076     list.erase(std::remove_if(list.begin(), list.end(), removeFunc), list.end());
0077 }
0078 
0079 KService::List KApplicationTrader::query(FilterFunc filterFunc)
0080 {
0081     // Get all applications
0082     KSycoca::self()->ensureCacheValid();
0083     KServiceType::Ptr servTypePtr = KSycocaPrivate::self()->serviceTypeFactory()->findServiceTypeByName(QStringLiteral("Application"));
0084     Q_ASSERT(servTypePtr);
0085     if (servTypePtr->serviceOffersOffset() == -1) {
0086         return KService::List();
0087     }
0088 
0089     KService::List lst = KSycocaPrivate::self()->serviceFactory()->serviceOffers(servTypePtr);
0090 
0091     applyFilter(lst, filterFunc, true); // true = filter out service with NotShowIn=KDE or equivalent
0092 
0093     qCDebug(SERVICES) << "query returning" << lst.count() << "offers";
0094     return lst;
0095 }
0096 
0097 KService::List KApplicationTrader::queryByMimeType(const QString &mimeType, FilterFunc filterFunc)
0098 {
0099     // Get all services of this MIME type.
0100     KService::List lst = mimeTypeSycocaServiceOffers(mimeType);
0101     filterMimeTypeOffers(lst);
0102 
0103     applyFilter(lst, filterFunc, false); // false = allow NotShowIn=KDE services listed in mimeapps.list
0104 
0105     qCDebug(SERVICES) << "query for mimeType" << mimeType << "returning" << lst.count() << "offers";
0106     return lst;
0107 }
0108 
0109 KService::Ptr KApplicationTrader::preferredService(const QString &mimeType)
0110 {
0111     const KService::List offers = queryByMimeType(mimeType);
0112     if (!offers.isEmpty()) {
0113         return offers.at(0);
0114     }
0115     return KService::Ptr();
0116 }
0117 
0118 void KApplicationTrader::setPreferredService(const QString &mimeType, const KService::Ptr service)
0119 {
0120     if (mimeType.isEmpty() || !(service && service->isValid())) {
0121         return;
0122     }
0123     KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation);
0124 
0125     // Save the default application according to mime-apps-spec 1.0
0126     KConfigGroup defaultApp(profile, "Default Applications");
0127     defaultApp.writeXdgListEntry(mimeType, QStringList(service->storageId()));
0128 
0129     KConfigGroup addedApps(profile, "Added Associations");
0130     QStringList apps = addedApps.readXdgListEntry(mimeType);
0131     apps.removeAll(service->storageId());
0132     apps.prepend(service->storageId()); // make it the preferred app
0133     addedApps.writeXdgListEntry(mimeType, apps);
0134 
0135     profile->sync();
0136 
0137     // Also make sure the "auto embed" setting for this MIME type is off
0138     KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals);
0139     fileTypesConfig->group("EmbedSettings").writeEntry(QStringLiteral("embed-") + mimeType, false);
0140     fileTypesConfig->sync();
0141 }
0142 
0143 bool KApplicationTrader::isSubsequence(const QString &pattern, const QString &text, Qt::CaseSensitivity cs)
0144 {
0145     if (pattern.isEmpty()) {
0146         return false;
0147     }
0148     const bool chk_case = cs == Qt::CaseSensitive;
0149 
0150     auto textIt = text.cbegin();
0151     auto patternIt = pattern.cbegin();
0152     for (; textIt != text.cend() && patternIt != pattern.cend(); ++textIt) {
0153         if ((chk_case && *textIt == *patternIt) || (!chk_case && textIt->toLower() == patternIt->toLower())) {
0154             ++patternIt;
0155         }
0156     }
0157     return patternIt == pattern.cend();
0158 }