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 }