File indexing completed on 2024-04-28 15:29:51

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999-2001 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 1999-2005 David Faure <faure@kde.org>
0005     SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 
0010 #include "kservice.h"
0011 #include "kmimetypefactory_p.h"
0012 #include "kservice_p.h"
0013 #include "ksycoca.h"
0014 #include "ksycoca_p.h"
0015 
0016 #include <qplatformdefs.h>
0017 
0018 #include <QDir>
0019 #include <QMap>
0020 #include <QMimeDatabase>
0021 
0022 #include <KAuthorized>
0023 #include <KConfigGroup>
0024 #include <KDesktopFile>
0025 #include <KLocalizedString>
0026 #include <KShell>
0027 
0028 #include <QDebug>
0029 #include <QStandardPaths>
0030 
0031 #include "kservicefactory_p.h"
0032 #include "kservicetypefactory_p.h"
0033 #include "kserviceutil_p.h"
0034 #include "servicesdebug.h"
0035 
0036 QDataStream &operator<<(QDataStream &s, const KService::ServiceTypeAndPreference &st)
0037 {
0038     s << st.preference << st.serviceType;
0039     return s;
0040 }
0041 QDataStream &operator>>(QDataStream &s, KService::ServiceTypeAndPreference &st)
0042 {
0043     s >> st.preference >> st.serviceType;
0044     return s;
0045 }
0046 
0047 void KServicePrivate::init(const KDesktopFile *config, KService *q)
0048 {
0049     const QString entryPath = q->entryPath();
0050     if (entryPath.isEmpty()) {
0051         // We are opening a "" service, this means whatever warning we might get is going to be misleading
0052         m_bValid = false;
0053         return;
0054     }
0055 
0056     bool absPath = !QDir::isRelativePath(entryPath);
0057 
0058     // TODO: it makes sense to have a KConstConfigGroup I guess
0059     const KConfigGroup desktopGroup = const_cast<KDesktopFile *>(config)->desktopGroup();
0060     QMap<QString, QString> entryMap = desktopGroup.entryMap();
0061 
0062     entryMap.remove(QStringLiteral("Encoding")); // reserved as part of Desktop Entry Standard
0063     entryMap.remove(QStringLiteral("Version")); // reserved as part of Desktop Entry Standard
0064 
0065     q->setDeleted(desktopGroup.readEntry("Hidden", false));
0066     entryMap.remove(QStringLiteral("Hidden"));
0067     if (q->isDeleted()) {
0068         m_bValid = false;
0069         return;
0070     }
0071 
0072     m_strName = config->readName();
0073     entryMap.remove(QStringLiteral("Name"));
0074     if (m_strName.isEmpty()) {
0075         // Try to make up a name.
0076         m_strName = entryPath;
0077         int i = m_strName.lastIndexOf(QLatin1Char('/'));
0078         m_strName = m_strName.mid(i + 1);
0079         i = m_strName.lastIndexOf(QLatin1Char('.'));
0080         if (i != -1) {
0081             m_strName.truncate(i);
0082         }
0083     }
0084 
0085     m_strType = config->readType();
0086     entryMap.remove(QStringLiteral("Type"));
0087     if (m_strType.isEmpty()) {
0088         /*kWarning(servicesDebugArea()) << "The desktop entry file " << entryPath
0089           << " has no Type=... entry."
0090           << " It should be \"Application\" or \"Service\"";
0091           m_bValid = false;
0092           return;*/
0093         m_strType = QStringLiteral("Application");
0094     } else if (m_strType != QLatin1String("Application") && m_strType != QLatin1String("Service")) {
0095         qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "instead of \"Application\" or \"Service\"";
0096         m_bValid = false;
0097         return;
0098     }
0099 
0100     // NOT readPathEntry, it is not XDG-compliant: it performs
0101     // various expansions, like $HOME.  Note that the expansion
0102     // behaviour still happens if the "e" flag is set, maintaining
0103     // backwards compatibility.
0104     m_strExec = desktopGroup.readEntry("Exec", QString());
0105     entryMap.remove(QStringLiteral("Exec"));
0106 
0107     // In case Try Exec is set, check if the application is available
0108     if (!config->tryExec()) {
0109         q->setDeleted(true);
0110         m_bValid = false;
0111         return;
0112     }
0113 
0114     const QStandardPaths::StandardLocation locationType = config->locationType();
0115 
0116     if ((m_strType == QLatin1String("Application")) && (locationType != QStandardPaths::ApplicationsLocation) && !absPath) {
0117         qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \""
0118                             << QStandardPaths::displayName(locationType) << "\" instead of \"Applications\"";
0119         m_bValid = false;
0120         return;
0121     }
0122 
0123     if ((m_strType == QLatin1String("Service")) && (locationType != QStandardPaths::GenericDataLocation) && !absPath) {
0124         qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \""
0125                             << QStandardPaths::displayName(locationType) << "\" instead of \"Shared Data\"/kservices5";
0126         m_bValid = false;
0127         return;
0128     }
0129 
0130     // entryPath To desktopEntryName
0131     // (e.g. "/home/x/.qttest/share/kservices5/fakepart2.desktop" --> "fakepart2")
0132     QString _name = KServiceUtilPrivate::completeBaseName(entryPath);
0133 
0134     m_strIcon = config->readIcon();
0135     entryMap.remove(QStringLiteral("Icon"));
0136     m_bTerminal = desktopGroup.readEntry("Terminal", false); // should be a property IMHO
0137     entryMap.remove(QStringLiteral("Terminal"));
0138     m_strTerminalOptions = desktopGroup.readEntry("TerminalOptions"); // should be a property IMHO
0139     entryMap.remove(QStringLiteral("TerminalOptions"));
0140     m_strWorkingDirectory = KShell::tildeExpand(config->readPath());
0141     entryMap.remove(QStringLiteral("Path"));
0142     m_strComment = config->readComment();
0143     entryMap.remove(QStringLiteral("Comment"));
0144     m_strGenName = config->readGenericName();
0145     entryMap.remove(QStringLiteral("GenericName"));
0146     QString _untranslatedGenericName = desktopGroup.readEntryUntranslated("GenericName");
0147     if (!_untranslatedGenericName.isEmpty()) {
0148         entryMap.insert(QStringLiteral("UntranslatedGenericName"), _untranslatedGenericName);
0149     }
0150 
0151     m_lstFormFactors = desktopGroup.readEntry("X-KDE-FormFactors", QStringList());
0152     entryMap.remove(QStringLiteral("X-KDE-FormFactors"));
0153 
0154     m_lstKeywords = desktopGroup.readXdgListEntry("Keywords", QStringList());
0155     entryMap.remove(QStringLiteral("Keywords"));
0156     m_lstKeywords += desktopGroup.readEntry("X-KDE-Keywords", QStringList());
0157     entryMap.remove(QStringLiteral("X-KDE-Keywords"));
0158     categories = desktopGroup.readXdgListEntry("Categories");
0159     entryMap.remove(QStringLiteral("Categories"));
0160     // TODO KDE5: only care for X-KDE-Library in Type=Service desktop files
0161     // This will prevent people defining a part and an app in the same desktop file
0162     // which makes user-preference handling difficult.
0163     m_strLibrary = desktopGroup.readEntry("X-KDE-Library");
0164     entryMap.remove(QStringLiteral("X-KDE-Library"));
0165     if (!m_strLibrary.isEmpty() && m_strType == QLatin1String("Application")) {
0166         qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType
0167                             << "but also has a X-KDE-Library key. This works for now,"
0168                                " but makes user-preference handling difficult, so support for this might"
0169                                " be removed at some point. Consider splitting it into two desktop files.";
0170     }
0171 
0172     QStringList lstServiceTypes = desktopGroup.readEntry("ServiceTypes", QStringList());
0173     entryMap.remove(QStringLiteral("ServiceTypes"));
0174     lstServiceTypes += desktopGroup.readEntry("X-KDE-ServiceTypes", QStringList());
0175     entryMap.remove(QStringLiteral("X-KDE-ServiceTypes"));
0176     lstServiceTypes += desktopGroup.readXdgListEntry("MimeType");
0177     entryMap.remove(QStringLiteral("MimeType"));
0178 
0179     if (m_strType == QLatin1String("Application") && !lstServiceTypes.contains(QLatin1String("Application")))
0180     // Applications implement the service type "Application" ;-)
0181     {
0182         lstServiceTypes += QStringLiteral("Application");
0183     }
0184 
0185     m_initialPreference = desktopGroup.readEntry("InitialPreference", 1);
0186     entryMap.remove(QStringLiteral("InitialPreference"));
0187 
0188     // Assign the "initial preference" to each mimetype/servicetype
0189     // (and to set such preferences in memory from kbuildsycoca)
0190     m_serviceTypes.reserve(lstServiceTypes.size());
0191     QListIterator<QString> st_it(lstServiceTypes);
0192     while (st_it.hasNext()) {
0193         const QString st = st_it.next();
0194         if (st.isEmpty()) {
0195             qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has an empty MimeType!";
0196             continue;
0197         }
0198         int initialPreference = m_initialPreference;
0199         if (st_it.hasNext()) {
0200             // TODO better syntax - separate group with mimetype=number entries?
0201             bool isNumber;
0202             const int val = st_it.peekNext().toInt(&isNumber);
0203             if (isNumber) {
0204                 initialPreference = val;
0205                 st_it.next();
0206             }
0207         }
0208         m_serviceTypes.push_back(KService::ServiceTypeAndPreference(initialPreference, st));
0209     }
0210 
0211 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 102)
0212     QString dbusStartupType = desktopGroup.readEntry("X-DBUS-StartupType").toLower();
0213     entryMap.remove(QStringLiteral("X-DBUS-StartupType"));
0214     if (dbusStartupType == QLatin1String("unique")) {
0215         m_DBUSStartusType = KService::DBusUnique;
0216     } else if (dbusStartupType == QLatin1String("multi")) {
0217         m_DBUSStartusType = KService::DBusMulti;
0218     } else {
0219         m_DBUSStartusType = KService::DBusNone;
0220     }
0221 #endif
0222 
0223     m_strDesktopEntryName = _name;
0224 
0225     // Exec lines from the KCMs always have the pattern "<program> m_strDesktopEntryName", see https://phabricator.kde.org/T13729
0226     const static bool hasSystemsettings = !QStandardPaths::findExecutable(QStringLiteral("systemsettings5")).isEmpty();
0227     if (m_strExec.isEmpty() && serviceTypes().contains(QStringLiteral("KCModule"))) {
0228         if (desktopGroup.readEntry("X-KDE-ParentApp") == QLatin1String("kinfocenter")) {
0229             m_strExec = QStringLiteral("kinfocenter ") + m_strDesktopEntryName;
0230         } else if (desktopGroup.readEntry("X-KDE-ParentApp") == QLatin1String("kcontrol")) {
0231             if (!desktopGroup.readEntry("X-KDE-System-Settings-Parent-Category").isEmpty() && hasSystemsettings) {
0232                 m_strExec = QStringLiteral("systemsettings5 ") + m_strDesktopEntryName;
0233             } else {
0234                 m_strExec = QStringLiteral("kcmshell5 ") + m_strDesktopEntryName;
0235             }
0236         }
0237     }
0238     m_bAllowAsDefault = desktopGroup.readEntry("AllowDefault", true);
0239     entryMap.remove(QStringLiteral("AllowDefault"));
0240 
0241     // allow plugin users to translate categories without needing a separate key
0242     auto entryIt = entryMap.find(QStringLiteral("X-KDE-PluginInfo-Category"));
0243     if (entryIt != entryMap.end()) {
0244         const QString &key = entryIt.key();
0245         m_mapProps.insert(key, QVariant(desktopGroup.readEntryUntranslated(key)));
0246         m_mapProps.insert(key + QLatin1String("-Translated"), QVariant(entryIt.value()));
0247         entryMap.erase(entryIt);
0248     }
0249 
0250     // Store all additional entries in the property map.
0251     // A QMap<QString,QString> would be easier for this but we can't
0252     // break BC, so we have to store it in m_mapProps.
0253     //  qDebug("Path = %s", entryPath.toLatin1().constData());
0254     auto it = entryMap.constBegin();
0255     for (; it != entryMap.constEnd(); ++it) {
0256         const QString key = it.key();
0257 
0258         // Ignore Actions, we parse that below
0259         if (key == QLatin1String("Actions")) {
0260             continue;
0261         }
0262 
0263         // do not store other translations like Name[fr]; kbuildsycoca will rerun if we change languages anyway
0264         if (!key.contains(QLatin1Char('['))) {
0265             // qCDebug(SERVICES) << "  Key =" << key << " Data =" << it.value();
0266             if (key == QLatin1String("X-Flatpak-RenamedFrom")) {
0267                 m_mapProps.insert(key, desktopGroup.readXdgListEntry(key));
0268             } else {
0269                 m_mapProps.insert(key, QVariant(it.value()));
0270             }
0271         }
0272     }
0273 
0274     // parse actions last since that may clone the service
0275     // we want all other information parsed by then
0276     if (entryMap.contains(QLatin1String("Actions"))) {
0277         parseActions(config, q);
0278     }
0279 }
0280 
0281 void KServicePrivate::parseActions(const KDesktopFile *config, KService *q)
0282 {
0283     const QStringList keys = config->readActions();
0284     if (keys.isEmpty()) {
0285         return;
0286     }
0287 
0288     KService::Ptr serviceClone(new KService(*q));
0289 
0290     for (const QString &group : keys) {
0291         if (group == QLatin1String("_SEPARATOR_")) {
0292             m_actions.append(KServiceAction(group, QString(), QString(), QString(), false, serviceClone));
0293             continue;
0294         }
0295 
0296         if (config->hasActionGroup(group)) {
0297             const KConfigGroup cg = config->actionGroup(group);
0298             if (!cg.hasKey("Name") || !cg.hasKey("Exec")) {
0299                 qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Name or no Exec key";
0300             } else {
0301                 const QMap<QString, QString> entries = cg.entryMap();
0302 
0303                 QVariantMap entriesVariants;
0304 
0305                 for (auto it = entries.constKeyValueBegin(); it != entries.constKeyValueEnd(); ++it) {
0306                     // Those are stored separately
0307                     if (it->first == QLatin1String("Name") || it->first == QLatin1String("Icon") || it->first == QLatin1String("Exec")
0308                         || it->first == QLatin1String("NoDisplay")) {
0309                         continue;
0310                     }
0311 
0312                     entriesVariants.insert(it->first, it->second);
0313                 }
0314 
0315                 KServiceAction action(group, cg.readEntry("Name"), cg.readEntry("Icon"), cg.readEntry("Exec"), cg.readEntry("NoDisplay", false), serviceClone);
0316                 action.setData(QVariant::fromValue(entriesVariants));
0317                 m_actions.append(action);
0318             }
0319         } else {
0320             qCWarning(SERVICES) << "The desktop file" << q->entryPath() << "references the action" << group << "but doesn't define it";
0321         }
0322     }
0323 
0324     serviceClone->setActions(m_actions);
0325 }
0326 
0327 void KServicePrivate::load(QDataStream &s)
0328 {
0329     qint8 def;
0330     qint8 term;
0331     qint8 dst;
0332     qint8 initpref;
0333 
0334     // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS!
0335     // !! This data structure should remain binary compatible at all times !!
0336     // You may add new fields at the end. Make sure to update KSYCOCA_VERSION
0337     // number in ksycoca.cpp
0338     // clang-format off
0339     s >> m_strType >> m_strName >> m_strExec >> m_strIcon
0340       >> term >> m_strTerminalOptions
0341       >> m_strWorkingDirectory >> m_strComment >> def >> m_mapProps
0342       >> m_strLibrary
0343       >> dst
0344       >> m_strDesktopEntryName
0345       >> initpref
0346       >> m_lstKeywords >> m_strGenName
0347       >> categories >> menuId >> m_actions >> m_serviceTypes
0348       >> m_lstFormFactors;
0349     // clang-format on
0350 
0351     m_bAllowAsDefault = bool(def);
0352     m_bTerminal = bool(term);
0353 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 102)
0354     m_DBUSStartusType = static_cast<KService::DBusStartupType>(dst);
0355 #endif
0356     m_initialPreference = initpref;
0357 
0358     m_bValid = true;
0359 }
0360 
0361 void KServicePrivate::save(QDataStream &s)
0362 {
0363     KSycocaEntryPrivate::save(s);
0364     qint8 def = m_bAllowAsDefault;
0365     qint8 initpref = m_initialPreference;
0366     qint8 term = m_bTerminal;
0367 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 102)
0368     qint8 dst = qint8(m_DBUSStartusType);
0369 #else
0370     qint8 dst = 0;
0371 #endif
0372 
0373     // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS!
0374     // !! This data structure should remain binary compatible at all times !!
0375     // You may add new fields at the end. Make sure to update KSYCOCA_VERSION
0376     // number in ksycoca.cpp
0377     s << m_strType << m_strName << m_strExec << m_strIcon << term << m_strTerminalOptions << m_strWorkingDirectory << m_strComment << def << m_mapProps
0378       << m_strLibrary << dst << m_strDesktopEntryName << initpref << m_lstKeywords << m_strGenName << categories << menuId << m_actions << m_serviceTypes
0379       << m_lstFormFactors;
0380 }
0381 
0382 ////
0383 
0384 KService::KService(const QString &_name, const QString &_exec, const QString &_icon)
0385     : KSycocaEntry(*new KServicePrivate(QString()))
0386 {
0387     Q_D(KService);
0388     d->m_strType = QStringLiteral("Application");
0389     d->m_strName = _name;
0390     d->m_strExec = _exec;
0391     d->m_strIcon = _icon;
0392     d->m_bTerminal = false;
0393     d->m_bAllowAsDefault = true;
0394     d->m_initialPreference = 10;
0395 }
0396 
0397 KService::KService(const QString &_fullpath)
0398     : KSycocaEntry(*new KServicePrivate(_fullpath))
0399 {
0400     Q_D(KService);
0401 
0402     KDesktopFile config(_fullpath);
0403     d->init(&config, this);
0404 }
0405 
0406 KService::KService(const KDesktopFile *config, const QString &entryPath)
0407     : KSycocaEntry(*new KServicePrivate(entryPath.isEmpty() ? config->fileName() : entryPath))
0408 {
0409     Q_D(KService);
0410 
0411     d->init(config, this);
0412 }
0413 
0414 KService::KService(QDataStream &_str, int _offset)
0415     : KSycocaEntry(*new KServicePrivate(_str, _offset))
0416 {
0417     Q_D(KService);
0418     KService::Ptr serviceClone(new KService(*this));
0419     for (KServiceAction &action : d->m_actions) {
0420         action.setService(serviceClone);
0421     }
0422 }
0423 
0424 KService::KService(const KService &other)
0425     : KSycocaEntry(*new KServicePrivate(*other.d_func()))
0426 {
0427 }
0428 
0429 KService::~KService()
0430 {
0431 }
0432 
0433 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 104)
0434 bool KService::hasServiceType(const QString &serviceType) const
0435 {
0436     Q_D(const KService);
0437 
0438     if (!d->m_bValid) {
0439         return false; // (useless) safety test
0440     }
0441     const KServiceType::Ptr ptr = KServiceType::serviceType(serviceType);
0442     if (!ptr) {
0443         return false;
0444     }
0445     const int serviceOffset = offset();
0446     // doesn't seem to work:
0447     // if ( serviceOffset == 0 )
0448     //    serviceOffset = serviceByStorageId( storageId() );
0449     if (serviceOffset) {
0450         KSycoca::self()->ensureCacheValid();
0451         return KSycocaPrivate::self()->serviceFactory()->hasOffer(ptr->offset(), ptr->serviceOffersOffset(), serviceOffset);
0452     }
0453 
0454     // fall-back code for services that are NOT from ksycoca
0455     // For each service type we are associated with, if it doesn't
0456     // match then we try its parent service types.
0457     const QString serviceTypeName = ptr->name();
0458     auto matchFunc = [&serviceTypeName](const ServiceTypeAndPreference &typePref) {
0459         const QString &st = typePref.serviceType;
0460         // qCDebug(SERVICES) << "    has " << typePref;
0461         if (st == serviceTypeName) {
0462             return true;
0463         }
0464 
0465         // also the case of parent servicetypes
0466         KServiceType::Ptr p = KServiceType::serviceType(st);
0467         if (p && p->inherits(serviceTypeName)) {
0468             return true;
0469         }
0470 
0471         return false;
0472     };
0473 
0474     return std::any_of(d->m_serviceTypes.cbegin(), d->m_serviceTypes.cend(), matchFunc);
0475 }
0476 #endif
0477 
0478 bool KService::hasMimeType(const QString &mimeType) const
0479 {
0480     Q_D(const KService);
0481     QMimeDatabase db;
0482     const QString mime = db.mimeTypeForName(mimeType).name();
0483     if (mime.isEmpty()) {
0484         return false;
0485     }
0486     int serviceOffset = offset();
0487     if (serviceOffset) {
0488         KSycoca::self()->ensureCacheValid();
0489         KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory();
0490         const int mimeOffset = factory->entryOffset(mime);
0491         const int serviceOffersOffset = factory->serviceOffersOffset(mime);
0492         if (serviceOffersOffset == -1) {
0493             return false;
0494         }
0495         return KSycocaPrivate::self()->serviceFactory()->hasOffer(mimeOffset, serviceOffersOffset, serviceOffset);
0496     }
0497 
0498     auto matchFunc = [&mime](const ServiceTypeAndPreference &typePref) {
0499         // qCDebug(SERVICES) << "    has " << typePref;
0500         if (typePref.serviceType == mime) {
0501             return true;
0502         }
0503         // TODO: should we handle inherited MIME types here?
0504         // KMimeType was in kio when this code was written, this is the only reason it's not done.
0505         // But this should matter only in a very rare case, since most code gets KServices from ksycoca.
0506         // Warning, change hasServiceType if you implement this here (and check kbuildservicefactory).
0507         return false;
0508     };
0509 
0510     // fall-back code for services that are NOT from ksycoca
0511     return std::any_of(d->m_serviceTypes.cbegin(), d->m_serviceTypes.cend(), matchFunc);
0512 }
0513 
0514 QVariant KServicePrivate::property(const QString &_name) const
0515 {
0516     return property(_name, QMetaType::UnknownType);
0517 }
0518 
0519 // Return a string QVariant if string isn't null, and invalid variant otherwise
0520 // (the variant must be invalid if the field isn't in the .desktop file)
0521 // This allows trader queries like "exist Library" to work.
0522 static QVariant makeStringVariant(const QString &string)
0523 {
0524     // Using isEmpty here would be wrong.
0525     // Empty is "specified but empty", null is "not specified" (in the .desktop file)
0526     return string.isNull() ? QVariant() : QVariant(string);
0527 }
0528 
0529 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 102)
0530 QVariant KService::property(const QString &_name, QVariant::Type t) const
0531 {
0532     Q_D(const KService);
0533     return d->property(_name, (QMetaType::Type)t);
0534 }
0535 #endif
0536 
0537 QVariant KService::property(const QString &_name, QMetaType::Type t) const
0538 {
0539     Q_D(const KService);
0540     return d->property(_name, t);
0541 }
0542 
0543 QVariant KServicePrivate::property(const QString &_name, QMetaType::Type t) const
0544 {
0545     if (_name == QLatin1String("Type")) {
0546         return QVariant(m_strType); // can't be null
0547     } else if (_name == QLatin1String("Name")) {
0548         return QVariant(m_strName); // can't be null
0549     } else if (_name == QLatin1String("Exec")) {
0550         return makeStringVariant(m_strExec);
0551     } else if (_name == QLatin1String("Icon")) {
0552         return makeStringVariant(m_strIcon);
0553     } else if (_name == QLatin1String("Terminal")) {
0554         return QVariant(m_bTerminal);
0555     } else if (_name == QLatin1String("TerminalOptions")) {
0556         return makeStringVariant(m_strTerminalOptions);
0557     } else if (_name == QLatin1String("Path")) {
0558         return makeStringVariant(m_strWorkingDirectory);
0559     } else if (_name == QLatin1String("Comment")) {
0560         return makeStringVariant(m_strComment);
0561     } else if (_name == QLatin1String("GenericName")) {
0562         return makeStringVariant(m_strGenName);
0563     } else if (_name == QLatin1String("ServiceTypes")) {
0564         return QVariant(serviceTypes());
0565     } else if (_name == QLatin1String("AllowAsDefault")) {
0566         return QVariant(m_bAllowAsDefault);
0567     } else if (_name == QLatin1String("InitialPreference")) {
0568         return QVariant(m_initialPreference);
0569     } else if (_name == QLatin1String("Library")) {
0570         return makeStringVariant(m_strLibrary);
0571     } else if (_name == QLatin1String("DesktopEntryPath")) { // can't be null
0572         return QVariant(path);
0573     } else if (_name == QLatin1String("DesktopEntryName")) {
0574         return QVariant(m_strDesktopEntryName); // can't be null
0575     } else if (_name == QLatin1String("Categories")) {
0576         return QVariant(categories);
0577     } else if (_name == QLatin1String("Keywords")) {
0578         return QVariant(m_lstKeywords);
0579     } else if (_name == QLatin1String("FormFactors")) {
0580         return QVariant(m_lstFormFactors);
0581     }
0582 
0583     // Ok we need to convert the property from a QString to its real type.
0584     // Maybe the caller helped us.
0585     if (t == QMetaType::UnknownType) {
0586         // No luck, let's ask KServiceTypeFactory what the type of this property
0587         // is supposed to be.
0588         // ######### this looks in all servicetypes, not just the ones this service supports!
0589         KSycoca::self()->ensureCacheValid();
0590         t = KSycocaPrivate::self()->serviceTypeFactory()->findPropertyTypeByName(_name);
0591         if (t == QMetaType::UnknownType) {
0592             qCDebug(SERVICES) << "Request for unknown property" << _name;
0593             return QVariant(); // Unknown property: Invalid variant.
0594         }
0595     }
0596 
0597     auto it = m_mapProps.constFind(_name);
0598     if (it == m_mapProps.cend() || !it.value().isValid()) {
0599         // qCDebug(SERVICES) << "Property not found " << _name;
0600         return QVariant(); // No property set.
0601     }
0602 
0603     if (t == QMetaType::QString) {
0604         return it.value(); // no conversion necessary
0605     } else {
0606         // All others
0607         // For instance properties defined as StringList, like MimeTypes.
0608         // XXX This API is accessible only through a friend declaration.
0609 
0610 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0611         return KConfigGroup::convertToQVariant(_name.toUtf8().constData(), it.value().toString().toUtf8(), QVariant(static_cast<QVariant::Type>(t)));
0612 #else
0613         return KConfigGroup::convertToQVariant(_name.toUtf8().constData(), it.value().toString().toUtf8(), QVariant(QMetaType(t)));
0614 #endif
0615     }
0616 }
0617 
0618 QStringList KServicePrivate::propertyNames() const
0619 {
0620     static const QStringList defaultKeys = {
0621         QStringLiteral("Type"),
0622         QStringLiteral("Name"),
0623         QStringLiteral("Comment"),
0624         QStringLiteral("GenericName"),
0625         QStringLiteral("Icon"),
0626         QStringLiteral("Exec"),
0627         QStringLiteral("Terminal"),
0628         QStringLiteral("TerminalOptions"),
0629         QStringLiteral("Path"),
0630         QStringLiteral("ServiceTypes"),
0631         QStringLiteral("AllowAsDefault"),
0632         QStringLiteral("InitialPreference"),
0633         QStringLiteral("Library"),
0634         QStringLiteral("DesktopEntryPath"),
0635         QStringLiteral("DesktopEntryName"),
0636         QStringLiteral("Keywords"),
0637         QStringLiteral("FormFactors"),
0638         QStringLiteral("Categories"),
0639     };
0640 
0641     return m_mapProps.keys() + defaultKeys;
0642 }
0643 
0644 KService::List KService::allServices()
0645 {
0646     KSycoca::self()->ensureCacheValid();
0647     return KSycocaPrivate::self()->serviceFactory()->allServices();
0648 }
0649 
0650 KService::Ptr KService::serviceByDesktopPath(const QString &_name)
0651 {
0652     KSycoca::self()->ensureCacheValid();
0653     return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name);
0654 }
0655 
0656 KService::Ptr KService::serviceByDesktopName(const QString &_name)
0657 {
0658     KSycoca::self()->ensureCacheValid();
0659     return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name);
0660 }
0661 
0662 KService::Ptr KService::serviceByMenuId(const QString &_name)
0663 {
0664     KSycoca::self()->ensureCacheValid();
0665     return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(_name);
0666 }
0667 
0668 KService::Ptr KService::serviceByStorageId(const QString &_storageId)
0669 {
0670     KSycoca::self()->ensureCacheValid();
0671     return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId);
0672 }
0673 
0674 bool KService::substituteUid() const
0675 {
0676     QVariant v = property(QStringLiteral("X-KDE-SubstituteUID"), QMetaType::Bool);
0677     return v.isValid() && v.toBool();
0678 }
0679 
0680 QString KService::username() const
0681 {
0682     // See also KDesktopFile::tryExec()
0683     QString user;
0684     QVariant v = property(QStringLiteral("X-KDE-Username"), QMetaType::QString);
0685     user = v.isValid() ? v.toString() : QString();
0686     if (user.isEmpty()) {
0687         user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT"));
0688     }
0689     if (user.isEmpty()) {
0690         user = QStringLiteral("root");
0691     }
0692     return user;
0693 }
0694 
0695 bool KService::showInCurrentDesktop() const
0696 {
0697     Q_D(const KService);
0698 
0699     const QString envVar = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
0700 
0701 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0702     QVector<QStringView> currentDesktops = QStringView(envVar).split(QLatin1Char(':'), Qt::SkipEmptyParts);
0703 #else
0704     QVector<QStringRef> currentDesktops = envVar.splitRef(QLatin1Char(':'), Qt::SkipEmptyParts);
0705 #endif
0706 
0707     const QString kde = QStringLiteral("KDE");
0708     if (currentDesktops.isEmpty()) {
0709         // This could be an old display manager, or e.g. a failsafe session with no desktop name
0710         // In doubt, let's say we show KDE stuff.
0711 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0712         currentDesktops.append(kde);
0713 #else
0714         currentDesktops.append(&kde);
0715 #endif
0716     }
0717 
0718     // This algorithm is described in the desktop entry spec
0719 
0720     auto it = d->m_mapProps.constFind(QStringLiteral("OnlyShowIn"));
0721     if (it != d->m_mapProps.cend()) {
0722         const QVariant &val = it.value();
0723         if (val.isValid()) {
0724             const QStringList aList = val.toString().split(QLatin1Char(';'));
0725             return std::any_of(currentDesktops.cbegin(), currentDesktops.cend(), [&aList](const auto desktop) {
0726                 return aList.contains(desktop);
0727             });
0728         }
0729     }
0730 
0731     it = d->m_mapProps.constFind(QStringLiteral("NotShowIn"));
0732     if (it != d->m_mapProps.cend()) {
0733         const QVariant &val = it.value();
0734         if (val.isValid()) {
0735             const QStringList aList = val.toString().split(QLatin1Char(';'));
0736             return std::none_of(currentDesktops.cbegin(), currentDesktops.cend(), [&aList](const auto desktop) {
0737                 return aList.contains(desktop);
0738             });
0739         }
0740     }
0741 
0742     return true;
0743 }
0744 
0745 bool KService::showOnCurrentPlatform() const
0746 {
0747     Q_D(const KService);
0748     const QString platform = QCoreApplication::instance()->property("platformName").toString();
0749     if (platform.isEmpty()) {
0750         return true;
0751     }
0752 
0753     auto it = d->m_mapProps.find(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"));
0754     if ((it != d->m_mapProps.end()) && (it->isValid())) {
0755         const QStringList aList = it->toString().split(QLatin1Char(';'));
0756         if (!aList.contains(platform)) {
0757             return false;
0758         }
0759     }
0760 
0761     it = d->m_mapProps.find(QStringLiteral("X-KDE-NotShowOnQtPlatforms"));
0762     if ((it != d->m_mapProps.end()) && (it->isValid())) {
0763         const QStringList aList = it->toString().split(QLatin1Char(';'));
0764         if (aList.contains(platform)) {
0765             return false;
0766         }
0767     }
0768     return true;
0769 }
0770 
0771 bool KService::noDisplay() const
0772 {
0773     if (qvariant_cast<bool>(property(QStringLiteral("NoDisplay"), QMetaType::Bool))) {
0774         return true;
0775     }
0776 
0777     if (!showInCurrentDesktop()) {
0778         return true;
0779     }
0780 
0781     if (!showOnCurrentPlatform()) {
0782         return true;
0783     }
0784 
0785     if (!KAuthorized::authorizeControlModule(storageId())) {
0786         return true;
0787     }
0788 
0789     return false;
0790 }
0791 
0792 QString KService::untranslatedGenericName() const
0793 {
0794     QVariant v = property(QStringLiteral("UntranslatedGenericName"), QMetaType::QString);
0795     return v.isValid() ? v.toString() : QString();
0796 }
0797 
0798 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 87)
0799 QString KService::parentApp() const
0800 {
0801     Q_D(const KService);
0802     auto it = d->m_mapProps.constFind(QStringLiteral("X-KDE-ParentApp"));
0803     if (it != d->m_mapProps.cend()) {
0804         const QVariant &val = it.value();
0805         if (val.isValid()) {
0806             return val.toString();
0807         }
0808     }
0809 
0810     return {};
0811 }
0812 #endif
0813 
0814 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 87)
0815 QString KService::pluginKeyword() const
0816 {
0817     Q_D(const KService);
0818     QMap<QString, QVariant>::ConstIterator it = d->m_mapProps.find(QStringLiteral("X-KDE-PluginKeyword"));
0819     if ((it == d->m_mapProps.end()) || (!it->isValid())) {
0820         return QString();
0821     }
0822 
0823     return it->toString();
0824 }
0825 #endif
0826 
0827 QString KService::docPath() const
0828 {
0829     Q_D(const KService);
0830 
0831     for (const QString &str : {QStringLiteral("X-DocPath"), QStringLiteral("DocPath")}) {
0832         auto it = d->m_mapProps.constFind(str);
0833         if (it != d->m_mapProps.cend()) {
0834             const QVariant variant = it.value();
0835             Q_ASSERT(variant.isValid());
0836             const QString path = variant.toString();
0837             if (!path.isEmpty()) {
0838                 return path;
0839             }
0840         }
0841     }
0842 
0843     return {};
0844 }
0845 
0846 bool KService::allowMultipleFiles() const
0847 {
0848     Q_D(const KService);
0849     // Can we pass multiple files on the command line or do we have to start the application for every single file ?
0850     return (d->m_strExec.contains(QLatin1String("%F")) //
0851             || d->m_strExec.contains(QLatin1String("%U")) //
0852             || d->m_strExec.contains(QLatin1String("%N")) //
0853             || d->m_strExec.contains(QLatin1String("%D")));
0854 }
0855 
0856 QStringList KService::categories() const
0857 {
0858     Q_D(const KService);
0859     return d->categories;
0860 }
0861 
0862 QString KService::menuId() const
0863 {
0864     Q_D(const KService);
0865     return d->menuId;
0866 }
0867 
0868 void KService::setMenuId(const QString &_menuId)
0869 {
0870     Q_D(KService);
0871     d->menuId = _menuId;
0872 }
0873 
0874 QString KService::storageId() const
0875 {
0876     Q_D(const KService);
0877     return d->storageId();
0878 }
0879 
0880 // not sure this is still used anywhere...
0881 QString KService::locateLocal() const
0882 {
0883     Q_D(const KService);
0884     if (d->menuId.isEmpty() //
0885         || entryPath().startsWith(QLatin1String(".hidden")) //
0886         || (QDir::isRelativePath(entryPath()) && d->categories.isEmpty())) {
0887         return KDesktopFile::locateLocal(entryPath());
0888     }
0889 
0890     return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + d->menuId;
0891 }
0892 
0893 QString KService::newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId, const QStringList *reservedMenuIds)
0894 {
0895     Q_UNUSED(showInMenu); // TODO KDE5: remove argument
0896 
0897     QString base = suggestedName;
0898     QString result;
0899     for (int i = 1; true; i++) {
0900         if (i == 1) {
0901             result = base + QStringLiteral(".desktop");
0902         } else {
0903             result = base + QStringLiteral("-%1.desktop").arg(i);
0904         }
0905 
0906         if (reservedMenuIds && reservedMenuIds->contains(result)) {
0907             continue;
0908         }
0909 
0910         // Lookup service by menu-id
0911         KService::Ptr s = serviceByMenuId(result);
0912         if (s) {
0913             continue;
0914         }
0915 
0916         if (!QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("applications/") + result).isEmpty()) {
0917             continue;
0918         }
0919 
0920         break;
0921     }
0922     if (menuId) {
0923         *menuId = result;
0924     }
0925 
0926     return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + result;
0927 }
0928 
0929 bool KService::isApplication() const
0930 {
0931     Q_D(const KService);
0932     return d->m_strType == QLatin1String("Application");
0933 }
0934 
0935 QString KService::exec() const
0936 {
0937     Q_D(const KService);
0938     return d->m_strExec;
0939 }
0940 
0941 QString KService::library() const
0942 {
0943     Q_D(const KService);
0944     return d->m_strLibrary;
0945 }
0946 
0947 QString KService::icon() const
0948 {
0949     Q_D(const KService);
0950     return d->m_strIcon;
0951 }
0952 
0953 QString KService::terminalOptions() const
0954 {
0955     Q_D(const KService);
0956     return d->m_strTerminalOptions;
0957 }
0958 
0959 bool KService::terminal() const
0960 {
0961     Q_D(const KService);
0962     return d->m_bTerminal;
0963 }
0964 
0965 bool KService::runOnDiscreteGpu() const
0966 {
0967     QVariant prop = property(QStringLiteral("PrefersNonDefaultGPU"), QMetaType::Bool);
0968     if (!prop.isValid()) {
0969         // For backwards compatibility
0970         prop = property(QStringLiteral("X-KDE-RunOnDiscreteGpu"), QMetaType::Bool);
0971     }
0972 
0973     return prop.isValid() && prop.toBool();
0974 }
0975 
0976 QString KService::desktopEntryName() const
0977 {
0978     Q_D(const KService);
0979     return d->m_strDesktopEntryName;
0980 }
0981 
0982 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 102)
0983 KService::DBusStartupType KService::dbusStartupType() const
0984 {
0985     Q_D(const KService);
0986     return d->m_DBUSStartusType;
0987 }
0988 #endif
0989 
0990 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 63)
0991 QString KService::path() const
0992 {
0993     Q_D(const KService);
0994     return d->m_strWorkingDirectory;
0995 }
0996 #endif
0997 
0998 QString KService::workingDirectory() const
0999 {
1000     Q_D(const KService);
1001     return d->m_strWorkingDirectory;
1002 }
1003 
1004 QString KService::comment() const
1005 {
1006     Q_D(const KService);
1007     return d->m_strComment;
1008 }
1009 
1010 QString KService::genericName() const
1011 {
1012     Q_D(const KService);
1013     return d->m_strGenName;
1014 }
1015 
1016 QStringList KService::keywords() const
1017 {
1018     Q_D(const KService);
1019     return d->m_lstKeywords;
1020 }
1021 
1022 QStringList KServicePrivate::serviceTypes() const
1023 {
1024     QStringList ret;
1025     ret.reserve(m_serviceTypes.size());
1026 
1027     std::transform(m_serviceTypes.cbegin(), m_serviceTypes.cend(), std::back_inserter(ret), [](const KService::ServiceTypeAndPreference &typePref) {
1028         Q_ASSERT(!typePref.serviceType.isEmpty());
1029         return typePref.serviceType;
1030     });
1031 
1032     return ret;
1033 }
1034 
1035 #if KSERVICE_ENABLE_DEPRECATED_SINCE(5, 104)
1036 QStringList KService::serviceTypes() const
1037 {
1038     Q_D(const KService);
1039     return d->serviceTypes();
1040 }
1041 #endif
1042 
1043 QStringList KService::mimeTypes() const
1044 {
1045     Q_D(const KService);
1046 
1047     QMimeDatabase db;
1048     QStringList ret;
1049 
1050     for (const KService::ServiceTypeAndPreference &s : d->m_serviceTypes) {
1051         const QString servType = s.serviceType;
1052         if (db.mimeTypeForName(servType).isValid()) { // keep only mimetypes, filter out servicetypes
1053             ret.append(servType);
1054         }
1055     }
1056 
1057     return ret;
1058 }
1059 
1060 QStringList KService::supportedProtocols() const
1061 {
1062     Q_D(const KService);
1063 
1064     QStringList ret;
1065     for (const KService::ServiceTypeAndPreference &s : d->m_serviceTypes) {
1066         const QString servType = s.serviceType;
1067         if (servType.startsWith(QLatin1String("x-scheme-handler/"))) {
1068             ret.append(servType.mid(17)); // remove x-scheme-handler/ prefix
1069         }
1070     }
1071 
1072     const QStringList protocols = property(QStringLiteral("X-KDE-Protocols"), QMetaType::QStringList).toStringList();
1073     for (const QString &protocol : protocols) {
1074         if (!ret.contains(protocol)) {
1075             ret.append(protocol);
1076         }
1077     }
1078 
1079     return ret;
1080 }
1081 
1082 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 67)
1083 bool KService::allowAsDefault() const
1084 {
1085     Q_D(const KService);
1086     return d->m_bAllowAsDefault;
1087 }
1088 #endif
1089 
1090 int KService::initialPreference() const
1091 {
1092     Q_D(const KService);
1093     return d->m_initialPreference;
1094 }
1095 
1096 void KService::setTerminal(bool b)
1097 {
1098     Q_D(KService);
1099     d->m_bTerminal = b;
1100 }
1101 
1102 void KService::setTerminalOptions(const QString &options)
1103 {
1104     Q_D(KService);
1105     d->m_strTerminalOptions = options;
1106 }
1107 
1108 void KService::setExec(const QString &exec)
1109 {
1110     Q_D(KService);
1111 
1112     if (!exec.isEmpty()) {
1113         d->m_strExec = exec;
1114         d->path.clear();
1115     }
1116 }
1117 
1118 void KService::setWorkingDirectory(const QString &workingDir)
1119 {
1120     Q_D(KService);
1121 
1122     if (!workingDir.isEmpty()) {
1123         d->m_strWorkingDirectory = workingDir;
1124         d->path.clear();
1125     }
1126 }
1127 
1128 QVector<KService::ServiceTypeAndPreference> &KService::_k_accessServiceTypes()
1129 {
1130     Q_D(KService);
1131     return d->m_serviceTypes;
1132 }
1133 
1134 QList<KServiceAction> KService::actions() const
1135 {
1136     Q_D(const KService);
1137     return d->m_actions;
1138 }
1139 
1140 #if KCOREADDONS_BUILD_DEPRECATED_SINCE(5, 86)
1141 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 86)
1142 KService::operator KPluginName() const
1143 {
1144     if (!isValid()) {
1145         return KPluginName::fromErrorString(i18n("The provided service is not valid"));
1146     }
1147 
1148     if (library().isEmpty()) {
1149         return KPluginName::fromErrorString(i18n("The service '%1' provides no library or the Library key is missing", entryPath()));
1150     }
1151 
1152     return KPluginName(library());
1153 }
1154 #endif
1155 #endif
1156 
1157 QString KService::aliasFor() const
1158 {
1159     return KServiceUtilPrivate::completeBaseName(property(QStringLiteral("X-KDE-AliasFor"), QMetaType::QString).toString());
1160 }
1161 
1162 void KService::setActions(const QList<KServiceAction> &actions)
1163 {
1164     Q_D(KService);
1165     d->m_actions = actions;
1166 }