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