1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 1999-2001 Waldo Bastian <bastian@kde.org> |
4 | SPDX-FileCopyrightText: 1999-2005 David Faure <faure@kde.org> |
5 | SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> |
6 | |
7 | SPDX-License-Identifier: LGPL-2.0-only |
8 | */ |
9 | |
10 | #include "kservice.h" |
11 | #include "kmimetypefactory_p.h" |
12 | #include "kservice_p.h" |
13 | #include "ksycoca.h" |
14 | #include "ksycoca_p.h" |
15 | |
16 | #include <qplatformdefs.h> |
17 | |
18 | #include <QDir> |
19 | #include <QMap> |
20 | #include <QMimeDatabase> |
21 | |
22 | #include <KConfigGroup> |
23 | #include <KDesktopFile> |
24 | #include <KShell> |
25 | |
26 | #include <QDebug> |
27 | #include <QStandardPaths> |
28 | |
29 | #include "kservicefactory_p.h" |
30 | #include "kserviceutil_p.h" |
31 | #include "servicesdebug.h" |
32 | |
33 | void KServicePrivate::init(const KDesktopFile *config, KService *q) |
34 | { |
35 | const QString entryPath = q->entryPath(); |
36 | if (entryPath.isEmpty()) { |
37 | // We are opening a "" service, this means whatever warning we might get is going to be misleading |
38 | m_bValid = false; |
39 | return; |
40 | } |
41 | |
42 | bool absPath = !QDir::isRelativePath(path: entryPath); |
43 | |
44 | const KConfigGroup desktopGroup = config->desktopGroup(); |
45 | QMap<QString, QString> entryMap = desktopGroup.entryMap(); |
46 | |
47 | entryMap.remove(QStringLiteral("Encoding" )); // reserved as part of Desktop Entry Standard |
48 | entryMap.remove(QStringLiteral("Version" )); // reserved as part of Desktop Entry Standard |
49 | |
50 | q->setDeleted(desktopGroup.readEntry(key: "Hidden" , defaultValue: false)); |
51 | entryMap.remove(QStringLiteral("Hidden" )); |
52 | if (q->isDeleted()) { |
53 | m_bValid = false; |
54 | return; |
55 | } |
56 | |
57 | m_strName = config->readName(); |
58 | entryMap.remove(QStringLiteral("Name" )); |
59 | if (m_strName.isEmpty()) { |
60 | // Try to make up a name. |
61 | m_strName = entryPath; |
62 | int i = m_strName.lastIndexOf(c: QLatin1Char('/')); |
63 | m_strName = m_strName.mid(position: i + 1); |
64 | i = m_strName.lastIndexOf(c: QLatin1Char('.')); |
65 | if (i != -1) { |
66 | m_strName.truncate(pos: i); |
67 | } |
68 | } |
69 | |
70 | m_strType = entryMap.take(QStringLiteral("Type" )); |
71 | if (m_strType.isEmpty()) { |
72 | qCWarning(SERVICES) << "The desktop entry file" << entryPath << "does not have a \"Type=Application\" set." ; |
73 | m_strType = QStringLiteral("Application" ); |
74 | } else if (m_strType != QLatin1String("Application" ) && m_strType != QLatin1String("Service" )) { |
75 | qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "instead of \"Application\" or \"Service\"" ; |
76 | m_bValid = false; |
77 | return; |
78 | } |
79 | |
80 | // NOT readPathEntry, it is not XDG-compliant: it performs |
81 | // various expansions, like $HOME. Note that the expansion |
82 | // behaviour still happens if the "e" flag is set, maintaining |
83 | // backwards compatibility. |
84 | m_strExec = entryMap.take(QStringLiteral("Exec" )); |
85 | |
86 | // In case Try Exec is set, check if the application is available |
87 | if (!config->tryExec()) { |
88 | q->setDeleted(true); |
89 | m_bValid = false; |
90 | return; |
91 | } |
92 | |
93 | const QStandardPaths::StandardLocation locationType = config->locationType(); |
94 | |
95 | if ((m_strType == QLatin1String("Application" )) && (locationType != QStandardPaths::ApplicationsLocation) && !absPath) { |
96 | qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \"" |
97 | << QStandardPaths::displayName(type: locationType) << "\" instead of \"Applications\"" ; |
98 | m_bValid = false; |
99 | return; |
100 | } |
101 | |
102 | // entryPath To desktopEntryName |
103 | // (e.g. "/usr/share/applications/org.kde.kate" --> "org.kde.kate") |
104 | QString _name = KServiceUtilPrivate::completeBaseName(filepath: entryPath); |
105 | |
106 | m_strIcon = entryMap.take(QStringLiteral("Icon" )); |
107 | m_bTerminal = desktopGroup.readEntry(key: "Terminal" , defaultValue: false); |
108 | entryMap.remove(QStringLiteral("Terminal" )); |
109 | m_strTerminalOptions = entryMap.take(QStringLiteral("TerminalOptions" )); |
110 | m_strWorkingDirectory = KShell::tildeExpand(path: entryMap.take(QStringLiteral("Path" ))); |
111 | m_strComment = entryMap.take(QStringLiteral("Comment" )); |
112 | m_strGenName = entryMap.take(QStringLiteral("GenericName" )); |
113 | |
114 | // Store these as member variables too, because the lookup will be significanly faster |
115 | m_untranslatedGenericName = desktopGroup.readEntryUntranslated(key: "GenericName" ); |
116 | m_untranslatedName = desktopGroup.readEntryUntranslated(key: "Name" ); |
117 | |
118 | m_lstFormFactors = entryMap.take(QStringLiteral("X-KDE-FormFactors" )).split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
119 | |
120 | if (entryMap.remove(QStringLiteral("Keywords" ))) { |
121 | m_lstKeywords = desktopGroup.readXdgListEntry(key: "Keywords" ); |
122 | } |
123 | m_lstKeywords += entryMap.take(QStringLiteral("X-KDE-Keywords" )).split(sep: QLatin1Char(' '), behavior: Qt::SkipEmptyParts); |
124 | if (entryMap.remove(QStringLiteral("Categories" ))) { |
125 | categories = desktopGroup.readXdgListEntry(key: "Categories" ); |
126 | } |
127 | |
128 | if (entryMap.remove(QStringLiteral("MimeType" ))) { |
129 | m_mimeTypes = desktopGroup.readXdgListEntry(key: "MimeType" ); |
130 | } |
131 | |
132 | m_strDesktopEntryName = _name; |
133 | |
134 | if (entryMap.remove(QStringLiteral("AllowDefault" ))) { |
135 | m_bAllowAsDefault = desktopGroup.readEntry(key: "AllowDefault" , defaultValue: true); |
136 | } |
137 | |
138 | // Store all additional entries in the property map. |
139 | // A QMap<QString,QString> would be easier for this but we can't |
140 | // break BC, so we have to store it in m_mapProps. |
141 | // qDebug("Path = %s", entryPath.toLatin1().constData()); |
142 | auto it = entryMap.constBegin(); |
143 | for (; it != entryMap.constEnd(); ++it) { |
144 | const QString key = it.key(); |
145 | |
146 | // Ignore Actions, we parse that below |
147 | if (key == QLatin1String("Actions" )) { |
148 | continue; |
149 | } |
150 | |
151 | // do not store other translations like Name[fr]; kbuildsycoca will rerun if we change languages anyway |
152 | if (!key.contains(c: QLatin1Char('['))) { |
153 | // qCDebug(SERVICES) << " Key =" << key << " Data =" << it.value(); |
154 | if (key == QLatin1String("X-Flatpak-RenamedFrom" )) { |
155 | m_mapProps.insert(key, value: desktopGroup.readXdgListEntry(pKey: key)); |
156 | } else { |
157 | m_mapProps.insert(key, value: QVariant(it.value())); |
158 | } |
159 | } |
160 | } |
161 | |
162 | // parse actions last since that may clone the service |
163 | // we want all other information parsed by then |
164 | if (entryMap.contains(key: QLatin1String("Actions" ))) { |
165 | parseActions(config, q); |
166 | } |
167 | } |
168 | |
169 | void KServicePrivate::parseActions(const KDesktopFile *config, KService *q) |
170 | { |
171 | const QStringList keys = config->readActions(); |
172 | if (keys.isEmpty()) { |
173 | return; |
174 | } |
175 | |
176 | KService::Ptr serviceClone(new KService(*q)); |
177 | |
178 | for (const QString &group : keys) { |
179 | if (group == QLatin1String("_SEPARATOR_" )) { |
180 | m_actions.append(t: KServiceAction(group, QString(), QString(), QString(), false, serviceClone)); |
181 | continue; |
182 | } |
183 | |
184 | if (config->hasActionGroup(group)) { |
185 | const KConfigGroup cg = config->actionGroup(group); |
186 | if (!cg.hasKey(key: "Name" ) || !cg.hasKey(key: "Exec" )) { |
187 | qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Name or no Exec key" ; |
188 | } else { |
189 | const QMap<QString, QString> entries = cg.entryMap(); |
190 | |
191 | QVariantMap entriesVariants; |
192 | |
193 | for (auto it = entries.constKeyValueBegin(); it != entries.constKeyValueEnd(); ++it) { |
194 | // Those are stored separately |
195 | if (it->first == QLatin1String("Name" ) || it->first == QLatin1String("Icon" ) || it->first == QLatin1String("Exec" ) |
196 | || it->first == QLatin1String("NoDisplay" )) { |
197 | continue; |
198 | } |
199 | |
200 | entriesVariants.insert(key: it->first, value: it->second); |
201 | } |
202 | |
203 | KServiceAction action(group, cg.readEntry(key: "Name" ), cg.readEntry(key: "Icon" ), cg.readEntry(key: "Exec" ), cg.readEntry(key: "NoDisplay" , defaultValue: false), serviceClone); |
204 | action.setData(QVariant::fromValue(value: entriesVariants)); |
205 | m_actions.append(t: action); |
206 | } |
207 | } else { |
208 | qCWarning(SERVICES) << "The desktop file" << q->entryPath() << "references the action" << group << "but doesn't define it" ; |
209 | } |
210 | } |
211 | |
212 | serviceClone->setActions(m_actions); |
213 | } |
214 | |
215 | void KServicePrivate::load(QDataStream &s) |
216 | { |
217 | qint8 def; |
218 | qint8 term; |
219 | qint8 dst; |
220 | |
221 | // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 6.x VERSIONS! |
222 | // !! This data structure should remain binary compatible at all times !! |
223 | // You may add new fields at the end. Make sure to update KSYCOCA_VERSION |
224 | // number in ksycoca.cpp |
225 | // clang-format off |
226 | s >> m_strType >> m_strName >> m_strExec >> m_strIcon |
227 | >> term >> m_strTerminalOptions |
228 | >> m_strWorkingDirectory >> m_strComment >> def >> m_mapProps |
229 | >> m_strLibrary |
230 | >> dst |
231 | >> m_strDesktopEntryName |
232 | >> m_lstKeywords >> m_strGenName |
233 | >> categories >> menuId >> m_actions |
234 | >> m_lstFormFactors |
235 | >> m_untranslatedName >> m_untranslatedGenericName >> m_mimeTypes; |
236 | // clang-format on |
237 | |
238 | m_bAllowAsDefault = bool(def); |
239 | m_bTerminal = bool(term); |
240 | |
241 | m_bValid = true; |
242 | } |
243 | |
244 | void KServicePrivate::save(QDataStream &s) |
245 | { |
246 | KSycocaEntryPrivate::save(s); |
247 | qint8 def = m_bAllowAsDefault; |
248 | qint8 term = m_bTerminal; |
249 | qint8 dst = 0; |
250 | |
251 | // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 6.x VERSIONS! |
252 | // !! This data structure should remain binary compatible at all times !! |
253 | // You may add new fields at the end. Make sure to update KSYCOCA_VERSION |
254 | // number in ksycoca.cpp |
255 | s << m_strType << m_strName << m_strExec << m_strIcon << term << m_strTerminalOptions << m_strWorkingDirectory << m_strComment << def << m_mapProps |
256 | << m_strLibrary << dst << m_strDesktopEntryName << m_lstKeywords << m_strGenName << categories << menuId << m_actions << m_lstFormFactors |
257 | << m_untranslatedName << m_untranslatedGenericName << m_mimeTypes; |
258 | } |
259 | |
260 | //// |
261 | |
262 | KService::KService(const QString &_name, const QString &_exec, const QString &_icon) |
263 | : KSycocaEntry(*new KServicePrivate(QString())) |
264 | { |
265 | Q_D(KService); |
266 | d->m_strType = QStringLiteral("Application" ); |
267 | d->m_strName = _name; |
268 | d->m_strExec = _exec; |
269 | d->m_strIcon = _icon; |
270 | d->m_bTerminal = false; |
271 | d->m_bAllowAsDefault = true; |
272 | } |
273 | |
274 | KService::KService(const QString &_fullpath) |
275 | : KSycocaEntry(*new KServicePrivate(_fullpath)) |
276 | { |
277 | Q_D(KService); |
278 | |
279 | KDesktopFile config(_fullpath); |
280 | d->init(config: &config, q: this); |
281 | } |
282 | |
283 | KService::KService(const KDesktopFile *config, const QString &entryPath) |
284 | : KSycocaEntry(*new KServicePrivate(entryPath.isEmpty() ? config->fileName() : entryPath)) |
285 | { |
286 | Q_D(KService); |
287 | |
288 | d->init(config, q: this); |
289 | } |
290 | |
291 | KService::KService(QDataStream &_str, int _offset) |
292 | : KSycocaEntry(*new KServicePrivate(_str, _offset)) |
293 | { |
294 | Q_D(KService); |
295 | KService::Ptr serviceClone(new KService(*this)); |
296 | for (KServiceAction &action : d->m_actions) { |
297 | action.setService(serviceClone); |
298 | } |
299 | } |
300 | |
301 | KService::KService(const KService &other) |
302 | : KSycocaEntry(*new KServicePrivate(*other.d_func())) |
303 | { |
304 | } |
305 | |
306 | KService::~KService() |
307 | { |
308 | } |
309 | |
310 | bool KService::hasMimeType(const QString &mimeType) const |
311 | { |
312 | Q_D(const KService); |
313 | QMimeDatabase db; |
314 | const QString mime = db.mimeTypeForName(nameOrAlias: mimeType).name(); |
315 | if (mime.isEmpty()) { |
316 | return false; |
317 | } |
318 | int serviceOffset = offset(); |
319 | if (serviceOffset) { |
320 | KSycoca::self()->ensureCacheValid(); |
321 | KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory(); |
322 | const int mimeOffset = factory->entryOffset(mimeTypeName: mime); |
323 | const int serviceOffersOffset = factory->serviceOffersOffset(mimeTypeName: mime); |
324 | if (serviceOffersOffset == -1) { |
325 | return false; |
326 | } |
327 | return KSycocaPrivate::self()->serviceFactory()->hasOffer(serviceTypeOffset: mimeOffset, serviceOffersOffset, testedServiceOffset: serviceOffset); |
328 | } |
329 | |
330 | return d->m_mimeTypes.contains(str: mime); |
331 | } |
332 | |
333 | QVariant KService::property(const QString &_name, QMetaType::Type t) const |
334 | { |
335 | Q_D(const KService); |
336 | return d->property(_name, t); |
337 | } |
338 | |
339 | template<> |
340 | QString KService::property<QString>(const QString &_name) const |
341 | { |
342 | Q_D(const KService); |
343 | |
344 | if (_name == QLatin1String("Type" )) { |
345 | return d->m_strType; |
346 | } else if (_name == QLatin1String("Name" )) { |
347 | return d->m_strName; |
348 | } else if (_name == QLatin1String("Exec" )) { |
349 | return d->m_strExec; |
350 | } else if (_name == QLatin1String("Icon" )) { |
351 | return d->m_strIcon; |
352 | } else if (_name == QLatin1String("TerminalOptions" )) { |
353 | return d->m_strTerminalOptions; |
354 | } else if (_name == QLatin1String("Path" )) { |
355 | return d->m_strWorkingDirectory; |
356 | } else if (_name == QLatin1String("Comment" )) { |
357 | return d->m_strComment; |
358 | } else if (_name == QLatin1String("GenericName" )) { |
359 | return d->m_strGenName; |
360 | } else if (_name == QLatin1String("DesktopEntryPath" )) { |
361 | return d->path; |
362 | } else if (_name == QLatin1String("DesktopEntryName" )) { |
363 | return d->m_strDesktopEntryName; |
364 | } else if (_name == QLatin1String("UntranslatedName" )) { |
365 | return d->m_untranslatedName; |
366 | } else if (_name == QLatin1String("UntranslatedGenericName" )) { |
367 | return d->m_untranslatedGenericName; |
368 | } |
369 | |
370 | auto it = d->m_mapProps.constFind(key: _name); |
371 | |
372 | if (it != d->m_mapProps.cend()) { |
373 | return it.value().toString(); |
374 | } |
375 | |
376 | return QString(); |
377 | } |
378 | |
379 | QVariant KServicePrivate::property(const QString &_name, QMetaType::Type t) const |
380 | { |
381 | if (_name == QLatin1String("Terminal" )) { |
382 | return QVariant(m_bTerminal); |
383 | } else if (_name == QLatin1String("AllowAsDefault" )) { |
384 | return QVariant(m_bAllowAsDefault); |
385 | } else if (_name == QLatin1String("Categories" )) { |
386 | return QVariant(categories); |
387 | } else if (_name == QLatin1String("Keywords" )) { |
388 | return QVariant(m_lstKeywords); |
389 | } else if (_name == QLatin1String("FormFactors" )) { |
390 | return QVariant(m_lstFormFactors); |
391 | } |
392 | |
393 | auto it = m_mapProps.constFind(key: _name); |
394 | if (it == m_mapProps.cend() || !it.value().isValid()) { |
395 | // qCDebug(SERVICES) << "Property not found " << _name; |
396 | return QVariant(); // No property set. |
397 | } |
398 | |
399 | if (it->typeId() == t) { |
400 | return it.value(); // no conversion necessary |
401 | } else { |
402 | // All others |
403 | // For instance properties defined as StringList, like MimeTypes. |
404 | // XXX This API is accessible only through a friend declaration. |
405 | return KConfigGroup::convertToQVariant(pKey: _name.toUtf8().constData(), value: it.value().toString().toUtf8(), aDefault: QVariant(QMetaType(t))); |
406 | } |
407 | } |
408 | |
409 | KService::List KService::allServices() |
410 | { |
411 | KSycoca::self()->ensureCacheValid(); |
412 | return KSycocaPrivate::self()->serviceFactory()->allServices(); |
413 | } |
414 | |
415 | KService::Ptr KService::serviceByDesktopPath(const QString &_name) |
416 | { |
417 | KSycoca::self()->ensureCacheValid(); |
418 | return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name); |
419 | } |
420 | |
421 | KService::Ptr KService::serviceByDesktopName(const QString &_name) |
422 | { |
423 | KSycoca::self()->ensureCacheValid(); |
424 | return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name); |
425 | } |
426 | |
427 | KService::Ptr KService::(const QString &_name) |
428 | { |
429 | KSycoca::self()->ensureCacheValid(); |
430 | return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(menuId: _name); |
431 | } |
432 | |
433 | KService::Ptr KService::serviceByStorageId(const QString &_storageId) |
434 | { |
435 | KSycoca::self()->ensureCacheValid(); |
436 | return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId); |
437 | } |
438 | |
439 | bool KService::substituteUid() const |
440 | { |
441 | return property<bool>(QStringLiteral("X-KDE-SubstituteUID" )); |
442 | } |
443 | |
444 | QString KService::username() const |
445 | { |
446 | // See also KDesktopFile::tryExec() |
447 | QString user = property<QString>(QStringLiteral("X-KDE-Username" )); |
448 | if (user.isEmpty()) { |
449 | user = QString::fromLocal8Bit(ba: qgetenv(varName: "ADMIN_ACCOUNT" )); |
450 | } |
451 | if (user.isEmpty()) { |
452 | user = QStringLiteral("root" ); |
453 | } |
454 | return user; |
455 | } |
456 | |
457 | bool KService::showInCurrentDesktop() const |
458 | { |
459 | Q_D(const KService); |
460 | |
461 | const QString envVar = QString::fromLatin1(ba: qgetenv(varName: "XDG_CURRENT_DESKTOP" )); |
462 | |
463 | QList<QStringView> currentDesktops = QStringView(envVar).split(sep: QLatin1Char(':'), behavior: Qt::SkipEmptyParts); |
464 | |
465 | const QString kde = QStringLiteral("KDE" ); |
466 | if (currentDesktops.isEmpty()) { |
467 | // This could be an old display manager, or e.g. a failsafe session with no desktop name |
468 | // In doubt, let's say we show KDE stuff. |
469 | currentDesktops.append(t: kde); |
470 | } |
471 | |
472 | // This algorithm is described in the desktop entry spec |
473 | |
474 | auto it = d->m_mapProps.constFind(QStringLiteral("OnlyShowIn" )); |
475 | if (it != d->m_mapProps.cend()) { |
476 | const QVariant &val = it.value(); |
477 | if (val.isValid()) { |
478 | const QStringList aList = val.toString().split(sep: QLatin1Char(';')); |
479 | return std::any_of(first: currentDesktops.cbegin(), last: currentDesktops.cend(), pred: [&aList](const auto desktop) { |
480 | return aList.contains(desktop); |
481 | }); |
482 | } |
483 | } |
484 | |
485 | it = d->m_mapProps.constFind(QStringLiteral("NotShowIn" )); |
486 | if (it != d->m_mapProps.cend()) { |
487 | const QVariant &val = it.value(); |
488 | if (val.isValid()) { |
489 | const QStringList aList = val.toString().split(sep: QLatin1Char(';')); |
490 | return std::none_of(first: currentDesktops.cbegin(), last: currentDesktops.cend(), pred: [&aList](const auto desktop) { |
491 | return aList.contains(desktop); |
492 | }); |
493 | } |
494 | } |
495 | |
496 | return true; |
497 | } |
498 | |
499 | bool KService::showOnCurrentPlatform() const |
500 | { |
501 | Q_D(const KService); |
502 | const QString platform = QCoreApplication::instance()->property(name: "platformName" ).toString(); |
503 | if (platform.isEmpty()) { |
504 | return true; |
505 | } |
506 | |
507 | auto it = d->m_mapProps.find(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms" )); |
508 | if ((it != d->m_mapProps.end()) && (it->isValid())) { |
509 | const QStringList aList = it->toString().split(sep: QLatin1Char(';')); |
510 | if (!aList.contains(str: platform)) { |
511 | return false; |
512 | } |
513 | } |
514 | |
515 | it = d->m_mapProps.find(QStringLiteral("X-KDE-NotShowOnQtPlatforms" )); |
516 | if ((it != d->m_mapProps.end()) && (it->isValid())) { |
517 | const QStringList aList = it->toString().split(sep: QLatin1Char(';')); |
518 | if (aList.contains(str: platform)) { |
519 | return false; |
520 | } |
521 | } |
522 | return true; |
523 | } |
524 | |
525 | bool KService::noDisplay() const |
526 | { |
527 | if (property<bool>(QStringLiteral("NoDisplay" ))) { |
528 | return true; |
529 | } |
530 | |
531 | if (!showInCurrentDesktop()) { |
532 | return true; |
533 | } |
534 | |
535 | if (!showOnCurrentPlatform()) { |
536 | return true; |
537 | } |
538 | return false; |
539 | } |
540 | |
541 | QString KService::untranslatedGenericName() const |
542 | { |
543 | Q_D(const KService); |
544 | return d->m_untranslatedGenericName; |
545 | } |
546 | |
547 | QString KService::untranslatedName() const |
548 | { |
549 | Q_D(const KService); |
550 | return d->m_untranslatedName; |
551 | } |
552 | |
553 | QString KService::docPath() const |
554 | { |
555 | Q_D(const KService); |
556 | |
557 | for (const QString &str : {QStringLiteral("X-DocPath" ), QStringLiteral("DocPath" )}) { |
558 | auto it = d->m_mapProps.constFind(key: str); |
559 | if (it != d->m_mapProps.cend()) { |
560 | const QVariant variant = it.value(); |
561 | Q_ASSERT(variant.isValid()); |
562 | const QString path = variant.toString(); |
563 | if (!path.isEmpty()) { |
564 | return path; |
565 | } |
566 | } |
567 | } |
568 | |
569 | return {}; |
570 | } |
571 | |
572 | bool KService::allowMultipleFiles() const |
573 | { |
574 | Q_D(const KService); |
575 | // Can we pass multiple files on the command line or do we have to start the application for every single file ? |
576 | return (d->m_strExec.contains(s: QLatin1String("%F" )) // |
577 | || d->m_strExec.contains(s: QLatin1String("%U" )) // |
578 | || d->m_strExec.contains(s: QLatin1String("%N" )) // |
579 | || d->m_strExec.contains(s: QLatin1String("%D" ))); |
580 | } |
581 | |
582 | QStringList KService::categories() const |
583 | { |
584 | Q_D(const KService); |
585 | return d->categories; |
586 | } |
587 | |
588 | QString KService::() const |
589 | { |
590 | Q_D(const KService); |
591 | return d->menuId; |
592 | } |
593 | |
594 | void KService::(const QString &) |
595 | { |
596 | Q_D(KService); |
597 | d->menuId = _menuId; |
598 | } |
599 | |
600 | QString KService::storageId() const |
601 | { |
602 | Q_D(const KService); |
603 | return d->storageId(); |
604 | } |
605 | |
606 | // not sure this is still used anywhere... |
607 | QString KService::locateLocal() const |
608 | { |
609 | Q_D(const KService); |
610 | if (d->menuId.isEmpty() // |
611 | || entryPath().startsWith(s: QLatin1String(".hidden" )) // |
612 | || (QDir::isRelativePath(path: entryPath()) && d->categories.isEmpty())) { |
613 | return KDesktopFile::locateLocal(path: entryPath()); |
614 | } |
615 | |
616 | return QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1String("/applications/" ) + d->menuId; |
617 | } |
618 | |
619 | QString KService::newServicePath(bool , const QString &suggestedName, QString *, const QStringList *) |
620 | { |
621 | Q_UNUSED(showInMenu); // TODO KDE5: remove argument |
622 | |
623 | QString base = suggestedName; |
624 | QString result; |
625 | for (int i = 1; true; i++) { |
626 | if (i == 1) { |
627 | result = base + QStringLiteral(".desktop" ); |
628 | } else { |
629 | result = base + QStringLiteral("-%1.desktop" ).arg(a: i); |
630 | } |
631 | |
632 | if (reservedMenuIds && reservedMenuIds->contains(str: result)) { |
633 | continue; |
634 | } |
635 | |
636 | // Lookup service by menu-id |
637 | KService::Ptr s = serviceByMenuId(name: result); |
638 | if (s) { |
639 | continue; |
640 | } |
641 | |
642 | if (!QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, fileName: QLatin1String("applications/" ) + result).isEmpty()) { |
643 | continue; |
644 | } |
645 | |
646 | break; |
647 | } |
648 | if (menuId) { |
649 | *menuId = result; |
650 | } |
651 | |
652 | return QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1String("/applications/" ) + result; |
653 | } |
654 | |
655 | bool KService::isApplication() const |
656 | { |
657 | Q_D(const KService); |
658 | return d->m_strType == QLatin1String("Application" ); |
659 | } |
660 | |
661 | QString KService::exec() const |
662 | { |
663 | Q_D(const KService); |
664 | return d->m_strExec; |
665 | } |
666 | |
667 | QString KService::icon() const |
668 | { |
669 | Q_D(const KService); |
670 | return d->m_strIcon; |
671 | } |
672 | |
673 | QString KService::terminalOptions() const |
674 | { |
675 | Q_D(const KService); |
676 | return d->m_strTerminalOptions; |
677 | } |
678 | |
679 | bool KService::terminal() const |
680 | { |
681 | Q_D(const KService); |
682 | return d->m_bTerminal; |
683 | } |
684 | |
685 | bool KService::runOnDiscreteGpu() const |
686 | { |
687 | QVariant prop = property<bool>(QStringLiteral("PrefersNonDefaultGPU" )); |
688 | if (!prop.isValid()) { |
689 | // For backwards compatibility |
690 | prop = property<bool>(QStringLiteral("X-KDE-RunOnDiscreteGpu" )); |
691 | } |
692 | |
693 | return prop.isValid() && prop.toBool(); |
694 | } |
695 | |
696 | QString KService::desktopEntryName() const |
697 | { |
698 | Q_D(const KService); |
699 | return d->m_strDesktopEntryName; |
700 | } |
701 | |
702 | QString KService::workingDirectory() const |
703 | { |
704 | Q_D(const KService); |
705 | return d->m_strWorkingDirectory; |
706 | } |
707 | |
708 | QString KService::() const |
709 | { |
710 | Q_D(const KService); |
711 | return d->m_strComment; |
712 | } |
713 | |
714 | QString KService::genericName() const |
715 | { |
716 | Q_D(const KService); |
717 | return d->m_strGenName; |
718 | } |
719 | |
720 | QStringList KService::keywords() const |
721 | { |
722 | Q_D(const KService); |
723 | return d->m_lstKeywords; |
724 | } |
725 | |
726 | QStringList KService::mimeTypes() const |
727 | { |
728 | Q_D(const KService); |
729 | QMimeDatabase db; |
730 | QStringList ret; |
731 | |
732 | for (const auto &mimeName : d->m_mimeTypes) { |
733 | if (db.mimeTypeForName(nameOrAlias: mimeName).isValid()) { // keep only mimetypes, filter out servicetypes |
734 | ret.append(t: mimeName); |
735 | } |
736 | } |
737 | return ret; |
738 | } |
739 | |
740 | QStringList KService::schemeHandlers() const |
741 | { |
742 | Q_D(const KService); |
743 | |
744 | QStringList ret; |
745 | |
746 | const QLatin1String schemeHandlerPrefix("x-scheme-handler/" ); |
747 | for (const auto &mimeName : d->m_mimeTypes) { |
748 | if (mimeName.startsWith(s: schemeHandlerPrefix)) { |
749 | ret.append(t: mimeName.mid(position: schemeHandlerPrefix.size())); |
750 | } |
751 | } |
752 | |
753 | return ret; |
754 | } |
755 | |
756 | QStringList KService::supportedProtocols() const |
757 | { |
758 | Q_D(const KService); |
759 | |
760 | QStringList ret; |
761 | |
762 | ret << schemeHandlers(); |
763 | |
764 | const QStringList protocols = property<QStringList>(QStringLiteral("X-KDE-Protocols" )); |
765 | for (const QString &protocol : protocols) { |
766 | if (!ret.contains(str: protocol)) { |
767 | ret.append(t: protocol); |
768 | } |
769 | } |
770 | |
771 | return ret; |
772 | } |
773 | |
774 | void KService::setTerminal(bool b) |
775 | { |
776 | Q_D(KService); |
777 | d->m_bTerminal = b; |
778 | } |
779 | |
780 | void KService::setTerminalOptions(const QString &options) |
781 | { |
782 | Q_D(KService); |
783 | d->m_strTerminalOptions = options; |
784 | } |
785 | |
786 | void KService::setExec(const QString &exec) |
787 | { |
788 | Q_D(KService); |
789 | |
790 | if (!exec.isEmpty()) { |
791 | d->m_strExec = exec; |
792 | d->path.clear(); |
793 | } |
794 | } |
795 | |
796 | void KService::setWorkingDirectory(const QString &workingDir) |
797 | { |
798 | Q_D(KService); |
799 | |
800 | if (!workingDir.isEmpty()) { |
801 | d->m_strWorkingDirectory = workingDir; |
802 | d->path.clear(); |
803 | } |
804 | } |
805 | |
806 | QList<KServiceAction> KService::actions() const |
807 | { |
808 | Q_D(const KService); |
809 | return d->m_actions; |
810 | } |
811 | |
812 | QString KService::aliasFor() const |
813 | { |
814 | return KServiceUtilPrivate::completeBaseName(filepath: property<QString>(QStringLiteral("X-KDE-AliasFor" ))); |
815 | } |
816 | |
817 | void KService::setActions(const QList<KServiceAction> &actions) |
818 | { |
819 | Q_D(KService); |
820 | d->m_actions = actions; |
821 | } |
822 | |
823 | std::optional<bool> KService::startupNotify() const |
824 | { |
825 | Q_D(const KService); |
826 | |
827 | if (QVariant value = d->m_mapProps.value(QStringLiteral("StartupNotify" )); value.isValid()) { |
828 | return value.toBool(); |
829 | } |
830 | |
831 | if (QVariant value = d->m_mapProps.value(QStringLiteral("X-KDE-StartupNotify" )); value.isValid()) { |
832 | return value.toBool(); |
833 | } |
834 | |
835 | return {}; |
836 | } |
837 | |