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