File indexing completed on 2023-12-10 11:12:22

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 <KConfigGroup>
0023 #include <KDesktopFile>
0024 #include <KShell>
0025 
0026 #include <QDebug>
0027 #include <QStandardPaths>
0028 
0029 #include "kservicefactory_p.h"
0030 #include "kserviceutil_p.h"
0031 #include "servicesdebug.h"
0032 
0033 QDataStream &operator<<(QDataStream &s, const KService::ServiceTypeAndPreference &st)
0034 {
0035     s << st.preference << st.serviceType;
0036     return s;
0037 }
0038 QDataStream &operator>>(QDataStream &s, KService::ServiceTypeAndPreference &st)
0039 {
0040     s >> st.preference >> st.serviceType;
0041     return s;
0042 }
0043 
0044 void KServicePrivate::init(const KDesktopFile *config, KService *q)
0045 {
0046     const QString entryPath = q->entryPath();
0047     if (entryPath.isEmpty()) {
0048         // We are opening a "" service, this means whatever warning we might get is going to be misleading
0049         m_bValid = false;
0050         return;
0051     }
0052 
0053     bool absPath = !QDir::isRelativePath(entryPath);
0054 
0055     const KConfigGroup desktopGroup = config->desktopGroup();
0056     QMap<QString, QString> entryMap = desktopGroup.entryMap();
0057 
0058     entryMap.remove(QStringLiteral("Encoding")); // reserved as part of Desktop Entry Standard
0059     entryMap.remove(QStringLiteral("Version")); // reserved as part of Desktop Entry Standard
0060 
0061     q->setDeleted(desktopGroup.readEntry("Hidden", false));
0062     entryMap.remove(QStringLiteral("Hidden"));
0063     if (q->isDeleted()) {
0064         m_bValid = false;
0065         return;
0066     }
0067 
0068     m_strName = config->readName();
0069     entryMap.remove(QStringLiteral("Name"));
0070     if (m_strName.isEmpty()) {
0071         // Try to make up a name.
0072         m_strName = entryPath;
0073         int i = m_strName.lastIndexOf(QLatin1Char('/'));
0074         m_strName = m_strName.mid(i + 1);
0075         i = m_strName.lastIndexOf(QLatin1Char('.'));
0076         if (i != -1) {
0077             m_strName.truncate(i);
0078         }
0079     }
0080 
0081     m_strType = entryMap.take(QStringLiteral("Type"));
0082     if (m_strType.isEmpty()) {
0083         qCWarning(SERVICES) << "The desktop entry file" << entryPath << "does not have a \"Type=Application\" set.";
0084         m_strType = QStringLiteral("Application");
0085     } else if (m_strType != QLatin1String("Application") && m_strType != QLatin1String("Service")) {
0086         qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "instead of \"Application\" or \"Service\"";
0087         m_bValid = false;
0088         return;
0089     }
0090 
0091     // NOT readPathEntry, it is not XDG-compliant: it performs
0092     // various expansions, like $HOME.  Note that the expansion
0093     // behaviour still happens if the "e" flag is set, maintaining
0094     // backwards compatibility.
0095     m_strExec = entryMap.take(QStringLiteral("Exec"));
0096 
0097     // In case Try Exec is set, check if the application is available
0098     if (!config->tryExec()) {
0099         q->setDeleted(true);
0100         m_bValid = false;
0101         return;
0102     }
0103 
0104     const QStandardPaths::StandardLocation locationType = config->locationType();
0105 
0106     if ((m_strType == QLatin1String("Application")) && (locationType != QStandardPaths::ApplicationsLocation) && !absPath) {
0107         qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \""
0108                             << QStandardPaths::displayName(locationType) << "\" instead of \"Applications\"";
0109         m_bValid = false;
0110         return;
0111     }
0112 
0113     // entryPath To desktopEntryName
0114     // (e.g. "/usr/share/applications/org.kde.kate" --> "org.kde.kate")
0115     QString _name = KServiceUtilPrivate::completeBaseName(entryPath);
0116 
0117     m_strIcon = entryMap.take(QStringLiteral("Icon"));
0118     m_bTerminal = desktopGroup.readEntry("Terminal", false);
0119     entryMap.remove(QStringLiteral("Terminal"));
0120     m_strTerminalOptions = entryMap.take(QStringLiteral("TerminalOptions"));
0121     m_strWorkingDirectory = KShell::tildeExpand(entryMap.take(QStringLiteral("Path")));
0122     m_strComment = entryMap.take(QStringLiteral("Comment"));
0123     m_strGenName = entryMap.take(QStringLiteral("GenericName"));
0124 
0125     // Store these as member variables too, because the lookup will be significanly faster
0126     m_untranslatedGenericName = desktopGroup.readEntryUntranslated("GenericName");
0127     m_untranslatedName = desktopGroup.readEntryUntranslated("Name");
0128 
0129     m_lstFormFactors = desktopGroup.readEntry("X-KDE-FormFactors", QStringList());
0130     entryMap.remove(QStringLiteral("X-KDE-FormFactors"));
0131 
0132     m_lstKeywords = desktopGroup.readXdgListEntry("Keywords", QStringList());
0133     entryMap.remove(QStringLiteral("Keywords"));
0134     m_lstKeywords += desktopGroup.readEntry("X-KDE-Keywords", QStringList());
0135     entryMap.remove(QStringLiteral("X-KDE-Keywords"));
0136     categories = desktopGroup.readXdgListEntry("Categories");
0137     entryMap.remove(QStringLiteral("Categories"));
0138 
0139     const QStringList lstServiceTypes = desktopGroup.readXdgListEntry("MimeType");
0140     entryMap.remove(QStringLiteral("MimeType"));
0141 
0142     m_initialPreference = desktopGroup.readEntry("InitialPreference", 1);
0143     entryMap.remove(QStringLiteral("InitialPreference"));
0144 
0145     // Assign the "initial preference" to each mimetype/servicetype
0146     // (and to set such preferences in memory from kbuildsycoca)
0147     m_serviceTypes.reserve(lstServiceTypes.size());
0148     QListIterator<QString> st_it(lstServiceTypes);
0149     while (st_it.hasNext()) {
0150         const QString st = st_it.next();
0151         if (st.isEmpty()) {
0152             qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has an empty MimeType!";
0153             continue;
0154         }
0155         int initialPreference = m_initialPreference;
0156         if (st_it.hasNext()) {
0157             // TODO better syntax - separate group with mimetype=number entries?
0158             bool isNumber;
0159             const int val = st_it.peekNext().toInt(&isNumber);
0160             if (isNumber) {
0161                 initialPreference = val;
0162                 st_it.next();
0163             }
0164         }
0165         m_serviceTypes.push_back(KService::ServiceTypeAndPreference(initialPreference, st));
0166     }
0167 
0168     m_strDesktopEntryName = _name;
0169 
0170     m_bAllowAsDefault = desktopGroup.readEntry("AllowDefault", true);
0171     entryMap.remove(QStringLiteral("AllowDefault"));
0172 
0173     // Store all additional entries in the property map.
0174     // A QMap<QString,QString> would be easier for this but we can't
0175     // break BC, so we have to store it in m_mapProps.
0176     //  qDebug("Path = %s", entryPath.toLatin1().constData());
0177     auto it = entryMap.constBegin();
0178     for (; it != entryMap.constEnd(); ++it) {
0179         const QString key = it.key();
0180 
0181         // Ignore Actions, we parse that below
0182         if (key == QLatin1String("Actions")) {
0183             continue;
0184         }
0185 
0186         // do not store other translations like Name[fr]; kbuildsycoca will rerun if we change languages anyway
0187         if (!key.contains(QLatin1Char('['))) {
0188             // qCDebug(SERVICES) << "  Key =" << key << " Data =" << it.value();
0189             if (key == QLatin1String("X-Flatpak-RenamedFrom")) {
0190                 m_mapProps.insert(key, desktopGroup.readXdgListEntry(key));
0191             } else {
0192                 m_mapProps.insert(key, QVariant(it.value()));
0193             }
0194         }
0195     }
0196 
0197     // parse actions last since that may clone the service
0198     // we want all other information parsed by then
0199     if (entryMap.contains(QLatin1String("Actions"))) {
0200         parseActions(config, q);
0201     }
0202 }
0203 
0204 void KServicePrivate::parseActions(const KDesktopFile *config, KService *q)
0205 {
0206     const QStringList keys = config->readActions();
0207     if (keys.isEmpty()) {
0208         return;
0209     }
0210 
0211     KService::Ptr serviceClone(new KService(*q));
0212 
0213     for (const QString &group : keys) {
0214         if (group == QLatin1String("_SEPARATOR_")) {
0215             m_actions.append(KServiceAction(group, QString(), QString(), QString(), false, serviceClone));
0216             continue;
0217         }
0218 
0219         if (config->hasActionGroup(group)) {
0220             const KConfigGroup cg = config->actionGroup(group);
0221             if (!cg.hasKey("Name") || !cg.hasKey("Exec")) {
0222                 qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Name or no Exec key";
0223             } else {
0224                 const QMap<QString, QString> entries = cg.entryMap();
0225 
0226                 QVariantMap entriesVariants;
0227 
0228                 for (auto it = entries.constKeyValueBegin(); it != entries.constKeyValueEnd(); ++it) {
0229                     // Those are stored separately
0230                     if (it->first == QLatin1String("Name") || it->first == QLatin1String("Icon") || it->first == QLatin1String("Exec")
0231                         || it->first == QLatin1String("NoDisplay")) {
0232                         continue;
0233                     }
0234 
0235                     entriesVariants.insert(it->first, it->second);
0236                 }
0237 
0238                 KServiceAction action(group, cg.readEntry("Name"), cg.readEntry("Icon"), cg.readEntry("Exec"), cg.readEntry("NoDisplay", false), serviceClone);
0239                 action.setData(QVariant::fromValue(entriesVariants));
0240                 m_actions.append(action);
0241             }
0242         } else {
0243             qCWarning(SERVICES) << "The desktop file" << q->entryPath() << "references the action" << group << "but doesn't define it";
0244         }
0245     }
0246 
0247     serviceClone->setActions(m_actions);
0248 }
0249 
0250 void KServicePrivate::load(QDataStream &s)
0251 {
0252     qint8 def;
0253     qint8 term;
0254     qint8 dst;
0255     qint8 initpref;
0256 
0257     // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS!
0258     // !! This data structure should remain binary compatible at all times !!
0259     // You may add new fields at the end. Make sure to update KSYCOCA_VERSION
0260     // number in ksycoca.cpp
0261     // clang-format off
0262     s >> m_strType >> m_strName >> m_strExec >> m_strIcon
0263       >> term >> m_strTerminalOptions
0264       >> m_strWorkingDirectory >> m_strComment >> def >> m_mapProps
0265       >> m_strLibrary
0266       >> dst
0267       >> m_strDesktopEntryName
0268       >> initpref
0269       >> m_lstKeywords >> m_strGenName
0270       >> categories >> menuId >> m_actions >> m_serviceTypes
0271       >> m_lstFormFactors
0272       >> m_untranslatedName >> m_untranslatedGenericName;
0273     // clang-format on
0274 
0275     m_bAllowAsDefault = bool(def);
0276     m_bTerminal = bool(term);
0277     m_initialPreference = initpref;
0278 
0279     m_bValid = true;
0280 }
0281 
0282 void KServicePrivate::save(QDataStream &s)
0283 {
0284     KSycocaEntryPrivate::save(s);
0285     qint8 def = m_bAllowAsDefault;
0286     qint8 initpref = m_initialPreference;
0287     qint8 term = m_bTerminal;
0288     qint8 dst = 0;
0289 
0290     // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS!
0291     // !! This data structure should remain binary compatible at all times !!
0292     // You may add new fields at the end. Make sure to update KSYCOCA_VERSION
0293     // number in ksycoca.cpp
0294     s << m_strType << m_strName << m_strExec << m_strIcon << term << m_strTerminalOptions << m_strWorkingDirectory << m_strComment << def << m_mapProps
0295       << m_strLibrary << dst << m_strDesktopEntryName << initpref << m_lstKeywords << m_strGenName << categories << menuId << m_actions << m_serviceTypes
0296       << m_lstFormFactors << m_untranslatedName << m_untranslatedGenericName;
0297 }
0298 
0299 ////
0300 
0301 KService::KService(const QString &_name, const QString &_exec, const QString &_icon)
0302     : KSycocaEntry(*new KServicePrivate(QString()))
0303 {
0304     Q_D(KService);
0305     d->m_strType = QStringLiteral("Application");
0306     d->m_strName = _name;
0307     d->m_strExec = _exec;
0308     d->m_strIcon = _icon;
0309     d->m_bTerminal = false;
0310     d->m_bAllowAsDefault = true;
0311     d->m_initialPreference = 10;
0312 }
0313 
0314 KService::KService(const QString &_fullpath)
0315     : KSycocaEntry(*new KServicePrivate(_fullpath))
0316 {
0317     Q_D(KService);
0318 
0319     KDesktopFile config(_fullpath);
0320     d->init(&config, this);
0321 }
0322 
0323 KService::KService(const KDesktopFile *config, const QString &entryPath)
0324     : KSycocaEntry(*new KServicePrivate(entryPath.isEmpty() ? config->fileName() : entryPath))
0325 {
0326     Q_D(KService);
0327 
0328     d->init(config, this);
0329 }
0330 
0331 KService::KService(QDataStream &_str, int _offset)
0332     : KSycocaEntry(*new KServicePrivate(_str, _offset))
0333 {
0334     Q_D(KService);
0335     KService::Ptr serviceClone(new KService(*this));
0336     for (KServiceAction &action : d->m_actions) {
0337         action.setService(serviceClone);
0338     }
0339 }
0340 
0341 KService::KService(const KService &other)
0342     : KSycocaEntry(*new KServicePrivate(*other.d_func()))
0343 {
0344 }
0345 
0346 KService::~KService()
0347 {
0348 }
0349 
0350 bool KService::hasMimeType(const QString &mimeType) const
0351 {
0352     Q_D(const KService);
0353     QMimeDatabase db;
0354     const QString mime = db.mimeTypeForName(mimeType).name();
0355     if (mime.isEmpty()) {
0356         return false;
0357     }
0358     int serviceOffset = offset();
0359     if (serviceOffset) {
0360         KSycoca::self()->ensureCacheValid();
0361         KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory();
0362         const int mimeOffset = factory->entryOffset(mime);
0363         const int serviceOffersOffset = factory->serviceOffersOffset(mime);
0364         if (serviceOffersOffset == -1) {
0365             return false;
0366         }
0367         return KSycocaPrivate::self()->serviceFactory()->hasOffer(mimeOffset, serviceOffersOffset, serviceOffset);
0368     }
0369 
0370     auto matchFunc = [&mime](const ServiceTypeAndPreference &typePref) {
0371         // qCDebug(SERVICES) << "    has " << typePref;
0372         if (typePref.serviceType == mime) {
0373             return true;
0374         }
0375         // TODO: should we handle inherited MIME types here?
0376         // KMimeType was in kio when this code was written, this is the only reason it's not done.
0377         // But this should matter only in a very rare case, since most code gets KServices from ksycoca.
0378         // Warning, change hasServiceType if you implement this here (and check kbuildservicefactory).
0379         return false;
0380     };
0381 
0382     // fall-back code for services that are NOT from ksycoca
0383     return std::any_of(d->m_serviceTypes.cbegin(), d->m_serviceTypes.cend(), matchFunc);
0384 }
0385 
0386 QVariant KServicePrivate::property(const QString &_name) const
0387 {
0388     return property(_name, QMetaType::UnknownType);
0389 }
0390 
0391 // Return a string QVariant if string isn't null, and invalid variant otherwise
0392 // (the variant must be invalid if the field isn't in the .desktop file)
0393 // This allows trader queries like "exist Library" to work.
0394 static QVariant makeStringVariant(const QString &string)
0395 {
0396     // Using isEmpty here would be wrong.
0397     // Empty is "specified but empty", null is "not specified" (in the .desktop file)
0398     return string.isNull() ? QVariant() : QVariant(string);
0399 }
0400 
0401 QVariant KService::property(const QString &_name, QMetaType::Type t) const
0402 {
0403     Q_D(const KService);
0404     return d->property(_name, t);
0405 }
0406 
0407 QMetaType::Type KServicePrivate::typeForProperty(const QString &name)
0408 {
0409     static const QMap<QString, QMetaType::Type> propertyTypeMap = {
0410         {QStringLiteral("NoDisplay"), QMetaType::Bool},
0411         {QStringLiteral("DocPath"), QMetaType::QString},
0412         {QStringLiteral("X-DocPath"), QMetaType::QString},
0413         {QStringLiteral("X-KDE-SubstituteUID"), QMetaType::Bool},
0414         {QStringLiteral("X-KDE-Username"), QMetaType::QString},
0415         {QStringLiteral("StartupWMClass"), QMetaType::QString},
0416         {QStringLiteral("StartupNotify"), QMetaType::Bool},
0417         {QStringLiteral("X-KDE-WMClass"), QMetaType::QString},
0418         {QStringLiteral("X-KDE-StartupNotify"), QMetaType::Bool},
0419         {QStringLiteral("X-DBUS-ServiceName"), QMetaType::QString},
0420         {QStringLiteral("X-DBUS-StartupType"), QMetaType::QString},
0421         {QStringLiteral("X-KDE-ParentApp"), QMetaType::QString},
0422         {QStringLiteral("X-KDE-HasTempFileOption"), QMetaType::Bool},
0423         {QStringLiteral("X-KDE-Protocols"), QMetaType::QStringList},
0424         {QStringLiteral("X-GNOME-UsesNotifications"), QMetaType::Bool},
0425         {QStringLiteral("X-Flatpak"), QMetaType::QString},
0426         {QStringLiteral("X-Flatpak-RenamedFrom"), QMetaType::QStringList},
0427         {QStringLiteral("X-KDE-Wayland-Interfaces"), QMetaType::QStringList},
0428         {QStringLiteral("X-KDE-Wayland-VirtualKeyboard"), QMetaType::Bool},
0429         {QStringLiteral("X-KDE-DBUS-Restricted-Interfaces"), QMetaType::QStringList},
0430         {QStringLiteral("X-KDE-AliasFor"), QMetaType::QString},
0431         {QStringLiteral("X-KDE-Shortcuts"), QMetaType::QStringList},
0432         {QStringLiteral("X-SnapInstanceName"), QMetaType::QString},
0433     };
0434 
0435     return propertyTypeMap[name];
0436 }
0437 
0438 QVariant KServicePrivate::property(const QString &_name, QMetaType::Type t) const
0439 {
0440     if (_name == QLatin1String("Type")) {
0441         return QVariant(m_strType); // can't be null
0442     } else if (_name == QLatin1String("Name")) {
0443         return QVariant(m_strName); // can't be null
0444     } else if (_name == QLatin1String("Exec")) {
0445         return makeStringVariant(m_strExec);
0446     } else if (_name == QLatin1String("Icon")) {
0447         return makeStringVariant(m_strIcon);
0448     } else if (_name == QLatin1String("Terminal")) {
0449         return QVariant(m_bTerminal);
0450     } else if (_name == QLatin1String("TerminalOptions")) {
0451         return makeStringVariant(m_strTerminalOptions);
0452     } else if (_name == QLatin1String("Path")) {
0453         return makeStringVariant(m_strWorkingDirectory);
0454     } else if (_name == QLatin1String("Comment")) {
0455         return makeStringVariant(m_strComment);
0456     } else if (_name == QLatin1String("GenericName")) {
0457         return makeStringVariant(m_strGenName);
0458     } else if (_name == QLatin1String("AllowAsDefault")) {
0459         return QVariant(m_bAllowAsDefault);
0460     } else if (_name == QLatin1String("InitialPreference")) {
0461         return QVariant(m_initialPreference);
0462     } else if (_name == QLatin1String("DesktopEntryPath")) { // can't be null
0463         return QVariant(path);
0464     } else if (_name == QLatin1String("DesktopEntryName")) {
0465         return QVariant(m_strDesktopEntryName); // can't be null
0466     } else if (_name == QLatin1String("Categories")) {
0467         return QVariant(categories);
0468     } else if (_name == QLatin1String("Keywords")) {
0469         return QVariant(m_lstKeywords);
0470     } else if (_name == QLatin1String("FormFactors")) {
0471         return QVariant(m_lstFormFactors);
0472     } else if (_name == QLatin1String("UntranslatedName")) {
0473         return QVariant(m_untranslatedName);
0474     } else if (_name == QLatin1String("UntranslatedGenericName")) {
0475         return QVariant(m_untranslatedGenericName);
0476     }
0477 
0478     // Ok we need to convert the property from a QString to its real type.
0479     // Maybe the caller helped us.
0480     if (t == QMetaType::UnknownType) {
0481         // No luck, let's ask KServiceTypeFactory what the type of this property
0482         // is supposed to be.
0483         // ######### this looks in all servicetypes, not just the ones this service supports!
0484         KSycoca::self()->ensureCacheValid();
0485         t = typeForProperty(_name);
0486         if (t == QMetaType::UnknownType) {
0487             qCDebug(SERVICES) << "Request for unknown property" << _name;
0488             return QVariant(); // Unknown property: Invalid variant.
0489         }
0490     }
0491 
0492     auto it = m_mapProps.constFind(_name);
0493     if (it == m_mapProps.cend() || !it.value().isValid()) {
0494         // qCDebug(SERVICES) << "Property not found " << _name;
0495         return QVariant(); // No property set.
0496     }
0497 
0498     if (t == QMetaType::QString) {
0499         return it.value(); // no conversion necessary
0500     } else if (it->typeId() == t) {
0501         return it.value(); // no conversion necessary
0502     } else {
0503         // All others
0504         // For instance properties defined as StringList, like MimeTypes.
0505         // XXX This API is accessible only through a friend declaration.
0506         return KConfigGroup::convertToQVariant(_name.toUtf8().constData(), it.value().toString().toUtf8(), QVariant(QMetaType(t)));
0507     }
0508 }
0509 
0510 QStringList KServicePrivate::propertyNames() const
0511 {
0512     static const QStringList defaultKeys = {
0513         QStringLiteral("Type"),
0514         QStringLiteral("Name"),
0515         QStringLiteral("Comment"),
0516         QStringLiteral("GenericName"),
0517         QStringLiteral("Icon"),
0518         QStringLiteral("Exec"),
0519         QStringLiteral("Terminal"),
0520         QStringLiteral("TerminalOptions"),
0521         QStringLiteral("Path"),
0522         QStringLiteral("AllowAsDefault"),
0523         QStringLiteral("InitialPreference"),
0524         QStringLiteral("DesktopEntryPath"),
0525         QStringLiteral("DesktopEntryName"),
0526         QStringLiteral("Keywords"),
0527         QStringLiteral("FormFactors"),
0528         QStringLiteral("Categories"),
0529     };
0530 
0531     return m_mapProps.keys() + defaultKeys;
0532 }
0533 
0534 KService::List KService::allServices()
0535 {
0536     KSycoca::self()->ensureCacheValid();
0537     return KSycocaPrivate::self()->serviceFactory()->allServices();
0538 }
0539 
0540 KService::Ptr KService::serviceByDesktopPath(const QString &_name)
0541 {
0542     KSycoca::self()->ensureCacheValid();
0543     return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name);
0544 }
0545 
0546 KService::Ptr KService::serviceByDesktopName(const QString &_name)
0547 {
0548     KSycoca::self()->ensureCacheValid();
0549     return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name);
0550 }
0551 
0552 KService::Ptr KService::serviceByMenuId(const QString &_name)
0553 {
0554     KSycoca::self()->ensureCacheValid();
0555     return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(_name);
0556 }
0557 
0558 KService::Ptr KService::serviceByStorageId(const QString &_storageId)
0559 {
0560     KSycoca::self()->ensureCacheValid();
0561     return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId);
0562 }
0563 
0564 bool KService::substituteUid() const
0565 {
0566     QVariant v = property(QStringLiteral("X-KDE-SubstituteUID"), QMetaType::Bool);
0567     return v.isValid() && v.toBool();
0568 }
0569 
0570 QString KService::username() const
0571 {
0572     // See also KDesktopFile::tryExec()
0573     QString user;
0574     QVariant v = property(QStringLiteral("X-KDE-Username"), QMetaType::QString);
0575     user = v.isValid() ? v.toString() : QString();
0576     if (user.isEmpty()) {
0577         user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT"));
0578     }
0579     if (user.isEmpty()) {
0580         user = QStringLiteral("root");
0581     }
0582     return user;
0583 }
0584 
0585 bool KService::showInCurrentDesktop() const
0586 {
0587     Q_D(const KService);
0588 
0589     const QString envVar = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
0590 
0591     QVector<QStringView> currentDesktops = QStringView(envVar).split(QLatin1Char(':'), Qt::SkipEmptyParts);
0592 
0593     const QString kde = QStringLiteral("KDE");
0594     if (currentDesktops.isEmpty()) {
0595         // This could be an old display manager, or e.g. a failsafe session with no desktop name
0596         // In doubt, let's say we show KDE stuff.
0597         currentDesktops.append(kde);
0598     }
0599 
0600     // This algorithm is described in the desktop entry spec
0601 
0602     auto it = d->m_mapProps.constFind(QStringLiteral("OnlyShowIn"));
0603     if (it != d->m_mapProps.cend()) {
0604         const QVariant &val = it.value();
0605         if (val.isValid()) {
0606             const QStringList aList = val.toString().split(QLatin1Char(';'));
0607             return std::any_of(currentDesktops.cbegin(), currentDesktops.cend(), [&aList](const auto desktop) {
0608                 return aList.contains(desktop);
0609             });
0610         }
0611     }
0612 
0613     it = d->m_mapProps.constFind(QStringLiteral("NotShowIn"));
0614     if (it != d->m_mapProps.cend()) {
0615         const QVariant &val = it.value();
0616         if (val.isValid()) {
0617             const QStringList aList = val.toString().split(QLatin1Char(';'));
0618             return std::none_of(currentDesktops.cbegin(), currentDesktops.cend(), [&aList](const auto desktop) {
0619                 return aList.contains(desktop);
0620             });
0621         }
0622     }
0623 
0624     return true;
0625 }
0626 
0627 bool KService::showOnCurrentPlatform() const
0628 {
0629     Q_D(const KService);
0630     const QString platform = QCoreApplication::instance()->property("platformName").toString();
0631     if (platform.isEmpty()) {
0632         return true;
0633     }
0634 
0635     auto it = d->m_mapProps.find(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"));
0636     if ((it != d->m_mapProps.end()) && (it->isValid())) {
0637         const QStringList aList = it->toString().split(QLatin1Char(';'));
0638         if (!aList.contains(platform)) {
0639             return false;
0640         }
0641     }
0642 
0643     it = d->m_mapProps.find(QStringLiteral("X-KDE-NotShowOnQtPlatforms"));
0644     if ((it != d->m_mapProps.end()) && (it->isValid())) {
0645         const QStringList aList = it->toString().split(QLatin1Char(';'));
0646         if (aList.contains(platform)) {
0647             return false;
0648         }
0649     }
0650     return true;
0651 }
0652 
0653 bool KService::noDisplay() const
0654 {
0655     if (qvariant_cast<bool>(property(QStringLiteral("NoDisplay"), QMetaType::Bool))) {
0656         return true;
0657     }
0658 
0659     if (!showInCurrentDesktop()) {
0660         return true;
0661     }
0662 
0663     if (!showOnCurrentPlatform()) {
0664         return true;
0665     }
0666     return false;
0667 }
0668 
0669 QString KService::untranslatedGenericName() const
0670 {
0671     Q_D(const KService);
0672     return d->m_untranslatedGenericName;
0673 }
0674 
0675 QString KService::untranslatedName() const
0676 {
0677     Q_D(const KService);
0678     return d->m_untranslatedName;
0679 }
0680 
0681 QString KService::docPath() const
0682 {
0683     Q_D(const KService);
0684 
0685     for (const QString &str : {QStringLiteral("X-DocPath"), QStringLiteral("DocPath")}) {
0686         auto it = d->m_mapProps.constFind(str);
0687         if (it != d->m_mapProps.cend()) {
0688             const QVariant variant = it.value();
0689             Q_ASSERT(variant.isValid());
0690             const QString path = variant.toString();
0691             if (!path.isEmpty()) {
0692                 return path;
0693             }
0694         }
0695     }
0696 
0697     return {};
0698 }
0699 
0700 bool KService::allowMultipleFiles() const
0701 {
0702     Q_D(const KService);
0703     // Can we pass multiple files on the command line or do we have to start the application for every single file ?
0704     return (d->m_strExec.contains(QLatin1String("%F")) //
0705             || d->m_strExec.contains(QLatin1String("%U")) //
0706             || d->m_strExec.contains(QLatin1String("%N")) //
0707             || d->m_strExec.contains(QLatin1String("%D")));
0708 }
0709 
0710 QStringList KService::categories() const
0711 {
0712     Q_D(const KService);
0713     return d->categories;
0714 }
0715 
0716 QString KService::menuId() const
0717 {
0718     Q_D(const KService);
0719     return d->menuId;
0720 }
0721 
0722 void KService::setMenuId(const QString &_menuId)
0723 {
0724     Q_D(KService);
0725     d->menuId = _menuId;
0726 }
0727 
0728 QString KService::storageId() const
0729 {
0730     Q_D(const KService);
0731     return d->storageId();
0732 }
0733 
0734 // not sure this is still used anywhere...
0735 QString KService::locateLocal() const
0736 {
0737     Q_D(const KService);
0738     if (d->menuId.isEmpty() //
0739         || entryPath().startsWith(QLatin1String(".hidden")) //
0740         || (QDir::isRelativePath(entryPath()) && d->categories.isEmpty())) {
0741         return KDesktopFile::locateLocal(entryPath());
0742     }
0743 
0744     return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + d->menuId;
0745 }
0746 
0747 QString KService::newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId, const QStringList *reservedMenuIds)
0748 {
0749     Q_UNUSED(showInMenu); // TODO KDE5: remove argument
0750 
0751     QString base = suggestedName;
0752     QString result;
0753     for (int i = 1; true; i++) {
0754         if (i == 1) {
0755             result = base + QStringLiteral(".desktop");
0756         } else {
0757             result = base + QStringLiteral("-%1.desktop").arg(i);
0758         }
0759 
0760         if (reservedMenuIds && reservedMenuIds->contains(result)) {
0761             continue;
0762         }
0763 
0764         // Lookup service by menu-id
0765         KService::Ptr s = serviceByMenuId(result);
0766         if (s) {
0767             continue;
0768         }
0769 
0770         if (!QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("applications/") + result).isEmpty()) {
0771             continue;
0772         }
0773 
0774         break;
0775     }
0776     if (menuId) {
0777         *menuId = result;
0778     }
0779 
0780     return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + result;
0781 }
0782 
0783 bool KService::isApplication() const
0784 {
0785     Q_D(const KService);
0786     return d->m_strType == QLatin1String("Application");
0787 }
0788 
0789 QString KService::exec() const
0790 {
0791     Q_D(const KService);
0792     return d->m_strExec;
0793 }
0794 
0795 QString KService::icon() const
0796 {
0797     Q_D(const KService);
0798     return d->m_strIcon;
0799 }
0800 
0801 QString KService::terminalOptions() const
0802 {
0803     Q_D(const KService);
0804     return d->m_strTerminalOptions;
0805 }
0806 
0807 bool KService::terminal() const
0808 {
0809     Q_D(const KService);
0810     return d->m_bTerminal;
0811 }
0812 
0813 bool KService::runOnDiscreteGpu() const
0814 {
0815     QVariant prop = property(QStringLiteral("PrefersNonDefaultGPU"), QMetaType::Bool);
0816     if (!prop.isValid()) {
0817         // For backwards compatibility
0818         prop = property(QStringLiteral("X-KDE-RunOnDiscreteGpu"), QMetaType::Bool);
0819     }
0820 
0821     return prop.isValid() && prop.toBool();
0822 }
0823 
0824 QString KService::desktopEntryName() const
0825 {
0826     Q_D(const KService);
0827     return d->m_strDesktopEntryName;
0828 }
0829 
0830 QString KService::workingDirectory() const
0831 {
0832     Q_D(const KService);
0833     return d->m_strWorkingDirectory;
0834 }
0835 
0836 QString KService::comment() const
0837 {
0838     Q_D(const KService);
0839     return d->m_strComment;
0840 }
0841 
0842 QString KService::genericName() const
0843 {
0844     Q_D(const KService);
0845     return d->m_strGenName;
0846 }
0847 
0848 QStringList KService::keywords() const
0849 {
0850     Q_D(const KService);
0851     return d->m_lstKeywords;
0852 }
0853 
0854 QStringList KService::mimeTypes() const
0855 {
0856     Q_D(const KService);
0857 
0858     QMimeDatabase db;
0859     QStringList ret;
0860 
0861     for (const KService::ServiceTypeAndPreference &s : d->m_serviceTypes) {
0862         const QString servType = s.serviceType;
0863         if (db.mimeTypeForName(servType).isValid()) { // keep only mimetypes, filter out servicetypes
0864             ret.append(servType);
0865         }
0866     }
0867     return ret;
0868 }
0869 
0870 QStringList KService::supportedProtocols() const
0871 {
0872     Q_D(const KService);
0873 
0874     QStringList ret;
0875 
0876     const QLatin1String schemeHandlerPrefix("x-scheme-handler/");
0877     for (const KService::ServiceTypeAndPreference &s : d->m_serviceTypes) {
0878         const QString servType = s.serviceType;
0879         if (servType.startsWith(schemeHandlerPrefix)) {
0880             ret.append(servType.mid(schemeHandlerPrefix.size()));
0881         }
0882     }
0883 
0884     const QStringList protocols = property(QStringLiteral("X-KDE-Protocols"), QMetaType::QStringList).toStringList();
0885     for (const QString &protocol : protocols) {
0886         if (!ret.contains(protocol)) {
0887             ret.append(protocol);
0888         }
0889     }
0890 
0891     return ret;
0892 }
0893 
0894 int KService::initialPreference() const
0895 {
0896     Q_D(const KService);
0897     return d->m_initialPreference;
0898 }
0899 
0900 void KService::setTerminal(bool b)
0901 {
0902     Q_D(KService);
0903     d->m_bTerminal = b;
0904 }
0905 
0906 void KService::setTerminalOptions(const QString &options)
0907 {
0908     Q_D(KService);
0909     d->m_strTerminalOptions = options;
0910 }
0911 
0912 void KService::setExec(const QString &exec)
0913 {
0914     Q_D(KService);
0915 
0916     if (!exec.isEmpty()) {
0917         d->m_strExec = exec;
0918         d->path.clear();
0919     }
0920 }
0921 
0922 void KService::setWorkingDirectory(const QString &workingDir)
0923 {
0924     Q_D(KService);
0925 
0926     if (!workingDir.isEmpty()) {
0927         d->m_strWorkingDirectory = workingDir;
0928         d->path.clear();
0929     }
0930 }
0931 
0932 QVector<KService::ServiceTypeAndPreference> KService::_k_accessServiceTypes()
0933 {
0934     Q_D(KService);
0935 
0936     return d->m_serviceTypes;
0937 }
0938 
0939 QList<KServiceAction> KService::actions() const
0940 {
0941     Q_D(const KService);
0942     return d->m_actions;
0943 }
0944 
0945 QString KService::aliasFor() const
0946 {
0947     return KServiceUtilPrivate::completeBaseName(property(QStringLiteral("X-KDE-AliasFor"), QMetaType::QString).toString());
0948 }
0949 
0950 void KService::setActions(const QList<KServiceAction> &actions)
0951 {
0952     Q_D(KService);
0953     d->m_actions = actions;
0954 }
0955 
0956 std::optional<bool> KService::startupNotify() const
0957 {
0958     Q_D(const KService);
0959 
0960     if (QVariant value = d->m_mapProps.value(QStringLiteral("StartupNotify")); value.isValid()) {
0961         return value.toBool();
0962     }
0963 
0964     if (QVariant value = d->m_mapProps.value(QStringLiteral("X-KDE-StartupNotify")); value.isValid()) {
0965         return value.toBool();
0966     }
0967 
0968     return {};
0969 }