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
33void 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
169void 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
215void 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
244void 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
262KService::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
274KService::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
283KService::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
291KService::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
301KService::KService(const KService &other)
302 : KSycocaEntry(*new KServicePrivate(*other.d_func()))
303{
304}
305
306KService::~KService()
307{
308}
309
310bool 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
333QVariant KService::property(const QString &_name, QMetaType::Type t) const
334{
335 Q_D(const KService);
336 return d->property(_name, t);
337}
338
339template<>
340QString 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
379QVariant 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
409KService::List KService::allServices()
410{
411 KSycoca::self()->ensureCacheValid();
412 return KSycocaPrivate::self()->serviceFactory()->allServices();
413}
414
415KService::Ptr KService::serviceByDesktopPath(const QString &_name)
416{
417 KSycoca::self()->ensureCacheValid();
418 return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name);
419}
420
421KService::Ptr KService::serviceByDesktopName(const QString &_name)
422{
423 KSycoca::self()->ensureCacheValid();
424 return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name);
425}
426
427KService::Ptr KService::serviceByMenuId(const QString &_name)
428{
429 KSycoca::self()->ensureCacheValid();
430 return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(menuId: _name);
431}
432
433KService::Ptr KService::serviceByStorageId(const QString &_storageId)
434{
435 KSycoca::self()->ensureCacheValid();
436 return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId);
437}
438
439bool KService::substituteUid() const
440{
441 return property<bool>(QStringLiteral("X-KDE-SubstituteUID"));
442}
443
444QString 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
457bool 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
499bool 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
525bool 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
541QString KService::untranslatedGenericName() const
542{
543 Q_D(const KService);
544 return d->m_untranslatedGenericName;
545}
546
547QString KService::untranslatedName() const
548{
549 Q_D(const KService);
550 return d->m_untranslatedName;
551}
552
553QString 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
572bool 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
582QStringList KService::categories() const
583{
584 Q_D(const KService);
585 return d->categories;
586}
587
588QString KService::menuId() const
589{
590 Q_D(const KService);
591 return d->menuId;
592}
593
594void KService::setMenuId(const QString &_menuId)
595{
596 Q_D(KService);
597 d->menuId = _menuId;
598}
599
600QString KService::storageId() const
601{
602 Q_D(const KService);
603 return d->storageId();
604}
605
606// not sure this is still used anywhere...
607QString 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
619QString KService::newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId, const QStringList *reservedMenuIds)
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
655bool KService::isApplication() const
656{
657 Q_D(const KService);
658 return d->m_strType == QLatin1String("Application");
659}
660
661QString KService::exec() const
662{
663 Q_D(const KService);
664 return d->m_strExec;
665}
666
667QString KService::icon() const
668{
669 Q_D(const KService);
670 return d->m_strIcon;
671}
672
673QString KService::terminalOptions() const
674{
675 Q_D(const KService);
676 return d->m_strTerminalOptions;
677}
678
679bool KService::terminal() const
680{
681 Q_D(const KService);
682 return d->m_bTerminal;
683}
684
685bool 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
696QString KService::desktopEntryName() const
697{
698 Q_D(const KService);
699 return d->m_strDesktopEntryName;
700}
701
702QString KService::workingDirectory() const
703{
704 Q_D(const KService);
705 return d->m_strWorkingDirectory;
706}
707
708QString KService::comment() const
709{
710 Q_D(const KService);
711 return d->m_strComment;
712}
713
714QString KService::genericName() const
715{
716 Q_D(const KService);
717 return d->m_strGenName;
718}
719
720QStringList KService::keywords() const
721{
722 Q_D(const KService);
723 return d->m_lstKeywords;
724}
725
726QStringList 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
740QStringList 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
756QStringList 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
774void KService::setTerminal(bool b)
775{
776 Q_D(KService);
777 d->m_bTerminal = b;
778}
779
780void KService::setTerminalOptions(const QString &options)
781{
782 Q_D(KService);
783 d->m_strTerminalOptions = options;
784}
785
786void 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
796void 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
806QList<KServiceAction> KService::actions() const
807{
808 Q_D(const KService);
809 return d->m_actions;
810}
811
812QString KService::aliasFor() const
813{
814 return KServiceUtilPrivate::completeBaseName(filepath: property<QString>(QStringLiteral("X-KDE-AliasFor")));
815}
816
817void KService::setActions(const QList<KServiceAction> &actions)
818{
819 Q_D(KService);
820 d->m_actions = actions;
821}
822
823std::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

source code of kservice/src/services/kservice.cpp