File indexing completed on 2024-06-16 06:57:50

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 = entryMap.take(QStringLiteral("X-KDE-FormFactors")).split(QLatin1Char(' '), Qt::SkipEmptyParts);
0130 
0131     if (entryMap.remove(QStringLiteral("Keywords"))) {
0132         m_lstKeywords = desktopGroup.readXdgListEntry("Keywords");
0133     }
0134     m_lstKeywords += entryMap.take(QStringLiteral("X-KDE-Keywords")).split(QLatin1Char(' '), Qt::SkipEmptyParts);
0135     if (entryMap.remove(QStringLiteral("Categories"))) {
0136         categories = desktopGroup.readXdgListEntry("Categories");
0137     }
0138 
0139     if (entryMap.remove(QStringLiteral("InitialPreference"))) {
0140         m_initialPreference = desktopGroup.readEntry("InitialPreference", 1);
0141     }
0142 
0143     if (entryMap.remove(QStringLiteral("MimeType"))) {
0144         const QStringList lstServiceTypes = desktopGroup.readXdgListEntry("MimeType");
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 
0169     m_strDesktopEntryName = _name;
0170 
0171     if (entryMap.remove(QStringLiteral("AllowDefault"))) {
0172         m_bAllowAsDefault = desktopGroup.readEntry("AllowDefault", true);
0173     }
0174 
0175     // Store all additional entries in the property map.
0176     // A QMap<QString,QString> would be easier for this but we can't
0177     // break BC, so we have to store it in m_mapProps.
0178     //  qDebug("Path = %s", entryPath.toLatin1().constData());
0179     auto it = entryMap.constBegin();
0180     for (; it != entryMap.constEnd(); ++it) {
0181         const QString key = it.key();
0182 
0183         // Ignore Actions, we parse that below
0184         if (key == QLatin1String("Actions")) {
0185             continue;
0186         }
0187 
0188         // do not store other translations like Name[fr]; kbuildsycoca will rerun if we change languages anyway
0189         if (!key.contains(QLatin1Char('['))) {
0190             // qCDebug(SERVICES) << "  Key =" << key << " Data =" << it.value();
0191             if (key == QLatin1String("X-Flatpak-RenamedFrom")) {
0192                 m_mapProps.insert(key, desktopGroup.readXdgListEntry(key));
0193             } else {
0194                 m_mapProps.insert(key, QVariant(it.value()));
0195             }
0196         }
0197     }
0198 
0199     // parse actions last since that may clone the service
0200     // we want all other information parsed by then
0201     if (entryMap.contains(QLatin1String("Actions"))) {
0202         parseActions(config, q);
0203     }
0204 }
0205 
0206 void KServicePrivate::parseActions(const KDesktopFile *config, KService *q)
0207 {
0208     const QStringList keys = config->readActions();
0209     if (keys.isEmpty()) {
0210         return;
0211     }
0212 
0213     KService::Ptr serviceClone(new KService(*q));
0214 
0215     for (const QString &group : keys) {
0216         if (group == QLatin1String("_SEPARATOR_")) {
0217             m_actions.append(KServiceAction(group, QString(), QString(), QString(), false, serviceClone));
0218             continue;
0219         }
0220 
0221         if (config->hasActionGroup(group)) {
0222             const KConfigGroup cg = config->actionGroup(group);
0223             if (!cg.hasKey("Name") || !cg.hasKey("Exec")) {
0224                 qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Name or no Exec key";
0225             } else {
0226                 const QMap<QString, QString> entries = cg.entryMap();
0227 
0228                 QVariantMap entriesVariants;
0229 
0230                 for (auto it = entries.constKeyValueBegin(); it != entries.constKeyValueEnd(); ++it) {
0231                     // Those are stored separately
0232                     if (it->first == QLatin1String("Name") || it->first == QLatin1String("Icon") || it->first == QLatin1String("Exec")
0233                         || it->first == QLatin1String("NoDisplay")) {
0234                         continue;
0235                     }
0236 
0237                     entriesVariants.insert(it->first, it->second);
0238                 }
0239 
0240                 KServiceAction action(group, cg.readEntry("Name"), cg.readEntry("Icon"), cg.readEntry("Exec"), cg.readEntry("NoDisplay", false), serviceClone);
0241                 action.setData(QVariant::fromValue(entriesVariants));
0242                 m_actions.append(action);
0243             }
0244         } else {
0245             qCWarning(SERVICES) << "The desktop file" << q->entryPath() << "references the action" << group << "but doesn't define it";
0246         }
0247     }
0248 
0249     serviceClone->setActions(m_actions);
0250 }
0251 
0252 void KServicePrivate::load(QDataStream &s)
0253 {
0254     qint8 def;
0255     qint8 term;
0256     qint8 dst;
0257     qint8 initpref;
0258 
0259     // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS!
0260     // !! This data structure should remain binary compatible at all times !!
0261     // You may add new fields at the end. Make sure to update KSYCOCA_VERSION
0262     // number in ksycoca.cpp
0263     // clang-format off
0264     s >> m_strType >> m_strName >> m_strExec >> m_strIcon
0265       >> term >> m_strTerminalOptions
0266       >> m_strWorkingDirectory >> m_strComment >> def >> m_mapProps
0267       >> m_strLibrary
0268       >> dst
0269       >> m_strDesktopEntryName
0270       >> initpref
0271       >> m_lstKeywords >> m_strGenName
0272       >> categories >> menuId >> m_actions >> m_serviceTypes
0273       >> m_lstFormFactors
0274       >> m_untranslatedName >> m_untranslatedGenericName;
0275     // clang-format on
0276 
0277     m_bAllowAsDefault = bool(def);
0278     m_bTerminal = bool(term);
0279     m_initialPreference = initpref;
0280 
0281     m_bValid = true;
0282 }
0283 
0284 void KServicePrivate::save(QDataStream &s)
0285 {
0286     KSycocaEntryPrivate::save(s);
0287     qint8 def = m_bAllowAsDefault;
0288     qint8 initpref = m_initialPreference;
0289     qint8 term = m_bTerminal;
0290     qint8 dst = 0;
0291 
0292     // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS!
0293     // !! This data structure should remain binary compatible at all times !!
0294     // You may add new fields at the end. Make sure to update KSYCOCA_VERSION
0295     // number in ksycoca.cpp
0296     s << m_strType << m_strName << m_strExec << m_strIcon << term << m_strTerminalOptions << m_strWorkingDirectory << m_strComment << def << m_mapProps
0297       << m_strLibrary << dst << m_strDesktopEntryName << initpref << m_lstKeywords << m_strGenName << categories << menuId << m_actions << m_serviceTypes
0298       << m_lstFormFactors << m_untranslatedName << m_untranslatedGenericName;
0299 }
0300 
0301 ////
0302 
0303 KService::KService(const QString &_name, const QString &_exec, const QString &_icon)
0304     : KSycocaEntry(*new KServicePrivate(QString()))
0305 {
0306     Q_D(KService);
0307     d->m_strType = QStringLiteral("Application");
0308     d->m_strName = _name;
0309     d->m_strExec = _exec;
0310     d->m_strIcon = _icon;
0311     d->m_bTerminal = false;
0312     d->m_bAllowAsDefault = true;
0313     d->m_initialPreference = 10;
0314 }
0315 
0316 KService::KService(const QString &_fullpath)
0317     : KSycocaEntry(*new KServicePrivate(_fullpath))
0318 {
0319     Q_D(KService);
0320 
0321     KDesktopFile config(_fullpath);
0322     d->init(&config, this);
0323 }
0324 
0325 KService::KService(const KDesktopFile *config, const QString &entryPath)
0326     : KSycocaEntry(*new KServicePrivate(entryPath.isEmpty() ? config->fileName() : entryPath))
0327 {
0328     Q_D(KService);
0329 
0330     d->init(config, this);
0331 }
0332 
0333 KService::KService(QDataStream &_str, int _offset)
0334     : KSycocaEntry(*new KServicePrivate(_str, _offset))
0335 {
0336     Q_D(KService);
0337     KService::Ptr serviceClone(new KService(*this));
0338     for (KServiceAction &action : d->m_actions) {
0339         action.setService(serviceClone);
0340     }
0341 }
0342 
0343 KService::KService(const KService &other)
0344     : KSycocaEntry(*new KServicePrivate(*other.d_func()))
0345 {
0346 }
0347 
0348 KService::~KService()
0349 {
0350 }
0351 
0352 bool KService::hasMimeType(const QString &mimeType) const
0353 {
0354     Q_D(const KService);
0355     QMimeDatabase db;
0356     const QString mime = db.mimeTypeForName(mimeType).name();
0357     if (mime.isEmpty()) {
0358         return false;
0359     }
0360     int serviceOffset = offset();
0361     if (serviceOffset) {
0362         KSycoca::self()->ensureCacheValid();
0363         KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory();
0364         const int mimeOffset = factory->entryOffset(mime);
0365         const int serviceOffersOffset = factory->serviceOffersOffset(mime);
0366         if (serviceOffersOffset == -1) {
0367             return false;
0368         }
0369         return KSycocaPrivate::self()->serviceFactory()->hasOffer(mimeOffset, serviceOffersOffset, serviceOffset);
0370     }
0371 
0372     auto matchFunc = [&mime](const ServiceTypeAndPreference &typePref) {
0373         // qCDebug(SERVICES) << "    has " << typePref;
0374         if (typePref.serviceType == mime) {
0375             return true;
0376         }
0377         // TODO: should we handle inherited MIME types here?
0378         // KMimeType was in kio when this code was written, this is the only reason it's not done.
0379         // But this should matter only in a very rare case, since most code gets KServices from ksycoca.
0380         // Warning, change hasServiceType if you implement this here (and check kbuildservicefactory).
0381         return false;
0382     };
0383 
0384     // fall-back code for services that are NOT from ksycoca
0385     return std::any_of(d->m_serviceTypes.cbegin(), d->m_serviceTypes.cend(), matchFunc);
0386 }
0387 
0388 QVariant KService::property(const QString &_name, QMetaType::Type t) const
0389 {
0390     Q_D(const KService);
0391     return d->property(_name, t);
0392 }
0393 
0394 template<>
0395 QString KService::property<QString>(const QString &_name) const
0396 {
0397     Q_D(const KService);
0398 
0399     if (_name == QLatin1String("Type")) {
0400         return d->m_strType;
0401     } else if (_name == QLatin1String("Name")) {
0402         return d->m_strName;
0403     } else if (_name == QLatin1String("Exec")) {
0404         return d->m_strExec;
0405     } else if (_name == QLatin1String("Icon")) {
0406         return d->m_strIcon;
0407     } else if (_name == QLatin1String("TerminalOptions")) {
0408         return d->m_strTerminalOptions;
0409     } else if (_name == QLatin1String("Path")) {
0410         return d->m_strWorkingDirectory;
0411     } else if (_name == QLatin1String("Comment")) {
0412         return d->m_strComment;
0413     } else if (_name == QLatin1String("GenericName")) {
0414         return d->m_strGenName;
0415     } else if (_name == QLatin1String("DesktopEntryPath")) {
0416         return d->path;
0417     } else if (_name == QLatin1String("DesktopEntryName")) {
0418         return d->m_strDesktopEntryName;
0419     } else if (_name == QLatin1String("UntranslatedName")) {
0420         return d->m_untranslatedName;
0421     } else if (_name == QLatin1String("UntranslatedGenericName")) {
0422         return d->m_untranslatedGenericName;
0423     }
0424 
0425     auto it = d->m_mapProps.constFind(_name);
0426 
0427     if (it != d->m_mapProps.cend()) {
0428         return it.value().toString();
0429     }
0430 
0431     return QString();
0432 }
0433 
0434 QVariant KServicePrivate::property(const QString &_name, QMetaType::Type t) const
0435 {
0436     if (_name == QLatin1String("Terminal")) {
0437         return QVariant(m_bTerminal);
0438     } else if (_name == QLatin1String("AllowAsDefault")) {
0439         return QVariant(m_bAllowAsDefault);
0440     } else if (_name == QLatin1String("InitialPreference")) {
0441         return QVariant(m_initialPreference);
0442     } else if (_name == QLatin1String("Categories")) {
0443         return QVariant(categories);
0444     } else if (_name == QLatin1String("Keywords")) {
0445         return QVariant(m_lstKeywords);
0446     } else if (_name == QLatin1String("FormFactors")) {
0447         return QVariant(m_lstFormFactors);
0448     }
0449 
0450     auto it = m_mapProps.constFind(_name);
0451     if (it == m_mapProps.cend() || !it.value().isValid()) {
0452         // qCDebug(SERVICES) << "Property not found " << _name;
0453         return QVariant(); // No property set.
0454     }
0455 
0456     if (it->typeId() == t) {
0457         return it.value(); // no conversion necessary
0458     } else {
0459         // All others
0460         // For instance properties defined as StringList, like MimeTypes.
0461         // XXX This API is accessible only through a friend declaration.
0462         return KConfigGroup::convertToQVariant(_name.toUtf8().constData(), it.value().toString().toUtf8(), QVariant(QMetaType(t)));
0463     }
0464 }
0465 
0466 KService::List KService::allServices()
0467 {
0468     KSycoca::self()->ensureCacheValid();
0469     return KSycocaPrivate::self()->serviceFactory()->allServices();
0470 }
0471 
0472 KService::Ptr KService::serviceByDesktopPath(const QString &_name)
0473 {
0474     KSycoca::self()->ensureCacheValid();
0475     return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name);
0476 }
0477 
0478 KService::Ptr KService::serviceByDesktopName(const QString &_name)
0479 {
0480     KSycoca::self()->ensureCacheValid();
0481     return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name);
0482 }
0483 
0484 KService::Ptr KService::serviceByMenuId(const QString &_name)
0485 {
0486     KSycoca::self()->ensureCacheValid();
0487     return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(_name);
0488 }
0489 
0490 KService::Ptr KService::serviceByStorageId(const QString &_storageId)
0491 {
0492     KSycoca::self()->ensureCacheValid();
0493     return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId);
0494 }
0495 
0496 bool KService::substituteUid() const
0497 {
0498     return property<bool>(QStringLiteral("X-KDE-SubstituteUID"));
0499 }
0500 
0501 QString KService::username() const
0502 {
0503     // See also KDesktopFile::tryExec()
0504     QString user = property<QString>(QStringLiteral("X-KDE-Username"));
0505     if (user.isEmpty()) {
0506         user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT"));
0507     }
0508     if (user.isEmpty()) {
0509         user = QStringLiteral("root");
0510     }
0511     return user;
0512 }
0513 
0514 bool KService::showInCurrentDesktop() const
0515 {
0516     Q_D(const KService);
0517 
0518     const QString envVar = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
0519 
0520     QList<QStringView> currentDesktops = QStringView(envVar).split(QLatin1Char(':'), Qt::SkipEmptyParts);
0521 
0522     const QString kde = QStringLiteral("KDE");
0523     if (currentDesktops.isEmpty()) {
0524         // This could be an old display manager, or e.g. a failsafe session with no desktop name
0525         // In doubt, let's say we show KDE stuff.
0526         currentDesktops.append(kde);
0527     }
0528 
0529     // This algorithm is described in the desktop entry spec
0530 
0531     auto it = d->m_mapProps.constFind(QStringLiteral("OnlyShowIn"));
0532     if (it != d->m_mapProps.cend()) {
0533         const QVariant &val = it.value();
0534         if (val.isValid()) {
0535             const QStringList aList = val.toString().split(QLatin1Char(';'));
0536             return std::any_of(currentDesktops.cbegin(), currentDesktops.cend(), [&aList](const auto desktop) {
0537                 return aList.contains(desktop);
0538             });
0539         }
0540     }
0541 
0542     it = d->m_mapProps.constFind(QStringLiteral("NotShowIn"));
0543     if (it != d->m_mapProps.cend()) {
0544         const QVariant &val = it.value();
0545         if (val.isValid()) {
0546             const QStringList aList = val.toString().split(QLatin1Char(';'));
0547             return std::none_of(currentDesktops.cbegin(), currentDesktops.cend(), [&aList](const auto desktop) {
0548                 return aList.contains(desktop);
0549             });
0550         }
0551     }
0552 
0553     return true;
0554 }
0555 
0556 bool KService::showOnCurrentPlatform() const
0557 {
0558     Q_D(const KService);
0559     const QString platform = QCoreApplication::instance()->property("platformName").toString();
0560     if (platform.isEmpty()) {
0561         return true;
0562     }
0563 
0564     auto it = d->m_mapProps.find(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"));
0565     if ((it != d->m_mapProps.end()) && (it->isValid())) {
0566         const QStringList aList = it->toString().split(QLatin1Char(';'));
0567         if (!aList.contains(platform)) {
0568             return false;
0569         }
0570     }
0571 
0572     it = d->m_mapProps.find(QStringLiteral("X-KDE-NotShowOnQtPlatforms"));
0573     if ((it != d->m_mapProps.end()) && (it->isValid())) {
0574         const QStringList aList = it->toString().split(QLatin1Char(';'));
0575         if (aList.contains(platform)) {
0576             return false;
0577         }
0578     }
0579     return true;
0580 }
0581 
0582 bool KService::noDisplay() const
0583 {
0584     if (property<bool>(QStringLiteral("NoDisplay"))) {
0585         return true;
0586     }
0587 
0588     if (!showInCurrentDesktop()) {
0589         return true;
0590     }
0591 
0592     if (!showOnCurrentPlatform()) {
0593         return true;
0594     }
0595     return false;
0596 }
0597 
0598 QString KService::untranslatedGenericName() const
0599 {
0600     Q_D(const KService);
0601     return d->m_untranslatedGenericName;
0602 }
0603 
0604 QString KService::untranslatedName() const
0605 {
0606     Q_D(const KService);
0607     return d->m_untranslatedName;
0608 }
0609 
0610 QString KService::docPath() const
0611 {
0612     Q_D(const KService);
0613 
0614     for (const QString &str : {QStringLiteral("X-DocPath"), QStringLiteral("DocPath")}) {
0615         auto it = d->m_mapProps.constFind(str);
0616         if (it != d->m_mapProps.cend()) {
0617             const QVariant variant = it.value();
0618             Q_ASSERT(variant.isValid());
0619             const QString path = variant.toString();
0620             if (!path.isEmpty()) {
0621                 return path;
0622             }
0623         }
0624     }
0625 
0626     return {};
0627 }
0628 
0629 bool KService::allowMultipleFiles() const
0630 {
0631     Q_D(const KService);
0632     // Can we pass multiple files on the command line or do we have to start the application for every single file ?
0633     return (d->m_strExec.contains(QLatin1String("%F")) //
0634             || d->m_strExec.contains(QLatin1String("%U")) //
0635             || d->m_strExec.contains(QLatin1String("%N")) //
0636             || d->m_strExec.contains(QLatin1String("%D")));
0637 }
0638 
0639 QStringList KService::categories() const
0640 {
0641     Q_D(const KService);
0642     return d->categories;
0643 }
0644 
0645 QString KService::menuId() const
0646 {
0647     Q_D(const KService);
0648     return d->menuId;
0649 }
0650 
0651 void KService::setMenuId(const QString &_menuId)
0652 {
0653     Q_D(KService);
0654     d->menuId = _menuId;
0655 }
0656 
0657 QString KService::storageId() const
0658 {
0659     Q_D(const KService);
0660     return d->storageId();
0661 }
0662 
0663 // not sure this is still used anywhere...
0664 QString KService::locateLocal() const
0665 {
0666     Q_D(const KService);
0667     if (d->menuId.isEmpty() //
0668         || entryPath().startsWith(QLatin1String(".hidden")) //
0669         || (QDir::isRelativePath(entryPath()) && d->categories.isEmpty())) {
0670         return KDesktopFile::locateLocal(entryPath());
0671     }
0672 
0673     return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + d->menuId;
0674 }
0675 
0676 QString KService::newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId, const QStringList *reservedMenuIds)
0677 {
0678     Q_UNUSED(showInMenu); // TODO KDE5: remove argument
0679 
0680     QString base = suggestedName;
0681     QString result;
0682     for (int i = 1; true; i++) {
0683         if (i == 1) {
0684             result = base + QStringLiteral(".desktop");
0685         } else {
0686             result = base + QStringLiteral("-%1.desktop").arg(i);
0687         }
0688 
0689         if (reservedMenuIds && reservedMenuIds->contains(result)) {
0690             continue;
0691         }
0692 
0693         // Lookup service by menu-id
0694         KService::Ptr s = serviceByMenuId(result);
0695         if (s) {
0696             continue;
0697         }
0698 
0699         if (!QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("applications/") + result).isEmpty()) {
0700             continue;
0701         }
0702 
0703         break;
0704     }
0705     if (menuId) {
0706         *menuId = result;
0707     }
0708 
0709     return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + result;
0710 }
0711 
0712 bool KService::isApplication() const
0713 {
0714     Q_D(const KService);
0715     return d->m_strType == QLatin1String("Application");
0716 }
0717 
0718 QString KService::exec() const
0719 {
0720     Q_D(const KService);
0721     return d->m_strExec;
0722 }
0723 
0724 QString KService::icon() const
0725 {
0726     Q_D(const KService);
0727     return d->m_strIcon;
0728 }
0729 
0730 QString KService::terminalOptions() const
0731 {
0732     Q_D(const KService);
0733     return d->m_strTerminalOptions;
0734 }
0735 
0736 bool KService::terminal() const
0737 {
0738     Q_D(const KService);
0739     return d->m_bTerminal;
0740 }
0741 
0742 bool KService::runOnDiscreteGpu() const
0743 {
0744     QVariant prop = property<bool>(QStringLiteral("PrefersNonDefaultGPU"));
0745     if (!prop.isValid()) {
0746         // For backwards compatibility
0747         prop = property<bool>(QStringLiteral("X-KDE-RunOnDiscreteGpu"));
0748     }
0749 
0750     return prop.isValid() && prop.toBool();
0751 }
0752 
0753 QString KService::desktopEntryName() const
0754 {
0755     Q_D(const KService);
0756     return d->m_strDesktopEntryName;
0757 }
0758 
0759 QString KService::workingDirectory() const
0760 {
0761     Q_D(const KService);
0762     return d->m_strWorkingDirectory;
0763 }
0764 
0765 QString KService::comment() const
0766 {
0767     Q_D(const KService);
0768     return d->m_strComment;
0769 }
0770 
0771 QString KService::genericName() const
0772 {
0773     Q_D(const KService);
0774     return d->m_strGenName;
0775 }
0776 
0777 QStringList KService::keywords() const
0778 {
0779     Q_D(const KService);
0780     return d->m_lstKeywords;
0781 }
0782 
0783 QStringList KService::mimeTypes() const
0784 {
0785     Q_D(const KService);
0786 
0787     QMimeDatabase db;
0788     QStringList ret;
0789 
0790     for (const KService::ServiceTypeAndPreference &s : d->m_serviceTypes) {
0791         const QString servType = s.serviceType;
0792         if (db.mimeTypeForName(servType).isValid()) { // keep only mimetypes, filter out servicetypes
0793             ret.append(servType);
0794         }
0795     }
0796     return ret;
0797 }
0798 
0799 QStringList KService::schemeHandlers() const
0800 {
0801     Q_D(const KService);
0802 
0803     QStringList ret;
0804 
0805     const QLatin1String schemeHandlerPrefix("x-scheme-handler/");
0806     for (const KService::ServiceTypeAndPreference &s : d->m_serviceTypes) {
0807         const QString servType = s.serviceType;
0808         if (servType.startsWith(schemeHandlerPrefix)) {
0809             ret.append(servType.mid(schemeHandlerPrefix.size()));
0810         }
0811     }
0812 
0813     return ret;
0814 }
0815 
0816 QStringList KService::supportedProtocols() const
0817 {
0818     Q_D(const KService);
0819 
0820     QStringList ret;
0821 
0822     ret << schemeHandlers();
0823 
0824     const QStringList protocols = property<QStringList>(QStringLiteral("X-KDE-Protocols"));
0825     for (const QString &protocol : protocols) {
0826         if (!ret.contains(protocol)) {
0827             ret.append(protocol);
0828         }
0829     }
0830 
0831     return ret;
0832 }
0833 
0834 int KService::initialPreference() const
0835 {
0836     Q_D(const KService);
0837     return d->m_initialPreference;
0838 }
0839 
0840 void KService::setTerminal(bool b)
0841 {
0842     Q_D(KService);
0843     d->m_bTerminal = b;
0844 }
0845 
0846 void KService::setTerminalOptions(const QString &options)
0847 {
0848     Q_D(KService);
0849     d->m_strTerminalOptions = options;
0850 }
0851 
0852 void KService::setExec(const QString &exec)
0853 {
0854     Q_D(KService);
0855 
0856     if (!exec.isEmpty()) {
0857         d->m_strExec = exec;
0858         d->path.clear();
0859     }
0860 }
0861 
0862 void KService::setWorkingDirectory(const QString &workingDir)
0863 {
0864     Q_D(KService);
0865 
0866     if (!workingDir.isEmpty()) {
0867         d->m_strWorkingDirectory = workingDir;
0868         d->path.clear();
0869     }
0870 }
0871 
0872 QList<KService::ServiceTypeAndPreference> KService::_k_accessServiceTypes()
0873 {
0874     Q_D(KService);
0875 
0876     return d->m_serviceTypes;
0877 }
0878 
0879 QList<KServiceAction> KService::actions() const
0880 {
0881     Q_D(const KService);
0882     return d->m_actions;
0883 }
0884 
0885 QString KService::aliasFor() const
0886 {
0887     return KServiceUtilPrivate::completeBaseName(property<QString>(QStringLiteral("X-KDE-AliasFor")));
0888 }
0889 
0890 void KService::setActions(const QList<KServiceAction> &actions)
0891 {
0892     Q_D(KService);
0893     d->m_actions = actions;
0894 }
0895 
0896 std::optional<bool> KService::startupNotify() const
0897 {
0898     Q_D(const KService);
0899 
0900     if (QVariant value = d->m_mapProps.value(QStringLiteral("StartupNotify")); value.isValid()) {
0901         return value.toBool();
0902     }
0903 
0904     if (QVariant value = d->m_mapProps.value(QStringLiteral("X-KDE-StartupNotify")); value.isValid()) {
0905         return value.toBool();
0906     }
0907 
0908     return {};
0909 }