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 qCWarning(SERVICES) << "The desktop file" << q->entryPath() << "references the action" << group << "but doesn't define it";
186 continue;
187 }
188
189 const KConfigGroup cg = config->actionGroup(group);
190 if (!cg.hasKey(key: "Name")) {
191 qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Name key";
192 continue;
193 }
194
195 if (!cg.hasKey(key: "Exec") && !config->desktopGroup().readEntry(key: "DBusActivatable", defaultValue: false)) {
196 qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Exec key and not D-Bus activatable";
197 continue;
198 }
199
200 const QMap<QString, QString> entries = cg.entryMap();
201 QVariantMap entriesVariants;
202 for (const auto &[key, value] : entries.asKeyValueRange()) {
203 // Those are stored separately
204 if (key == QLatin1String("Name") || key == QLatin1String("Icon") || key == QLatin1String("Exec") || key == QLatin1String("NoDisplay")) {
205 continue;
206 }
207
208 entriesVariants.insert(key, value);
209 }
210
211 KServiceAction action(group, cg.readEntry(key: "Name"), cg.readEntry(key: "Icon"), cg.readEntry(key: "Exec"), cg.readEntry(key: "NoDisplay", defaultValue: false), serviceClone);
212 action.setData(QVariant::fromValue(value: entriesVariants));
213 m_actions.append(t: action);
214 }
215
216 serviceClone->setActions(m_actions);
217}
218
219void KServicePrivate::load(QDataStream &s)
220{
221 qint8 def;
222 qint8 term;
223 qint8 dst;
224
225 // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 6.x VERSIONS!
226 // !! This data structure should remain binary compatible at all times !!
227 // You may add new fields at the end. Make sure to update KSYCOCA_VERSION
228 // number in ksycoca.cpp
229 // clang-format off
230 s >> m_strType >> m_strName >> m_strExec >> m_strIcon
231 >> term >> m_strTerminalOptions
232 >> m_strWorkingDirectory >> m_strComment >> def >> m_mapProps
233 >> m_strLibrary
234 >> dst
235 >> m_strDesktopEntryName
236 >> m_lstKeywords >> m_strGenName
237 >> categories >> menuId >> m_actions
238 >> m_lstFormFactors
239 >> m_untranslatedName >> m_untranslatedGenericName >> m_mimeTypes;
240 // clang-format on
241
242 m_bAllowAsDefault = bool(def);
243 m_bTerminal = bool(term);
244
245 m_bValid = true;
246}
247
248void KServicePrivate::save(QDataStream &s)
249{
250 KSycocaEntryPrivate::save(s);
251 qint8 def = m_bAllowAsDefault;
252 qint8 term = m_bTerminal;
253 qint8 dst = 0;
254
255 // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 6.x VERSIONS!
256 // !! This data structure should remain binary compatible at all times !!
257 // You may add new fields at the end. Make sure to update KSYCOCA_VERSION
258 // number in ksycoca.cpp
259 s << m_strType << m_strName << m_strExec << m_strIcon << term << m_strTerminalOptions << m_strWorkingDirectory << m_strComment << def << m_mapProps
260 << m_strLibrary << dst << m_strDesktopEntryName << m_lstKeywords << m_strGenName << categories << menuId << m_actions << m_lstFormFactors
261 << m_untranslatedName << m_untranslatedGenericName << m_mimeTypes;
262}
263
264////
265
266KService::KService(const QString &_name, const QString &_exec, const QString &_icon)
267 : KSycocaEntry(*new KServicePrivate(QString()))
268{
269 Q_D(KService);
270 d->m_strType = QStringLiteral("Application");
271 d->m_strName = _name;
272 d->m_strExec = _exec;
273 d->m_strIcon = _icon;
274 d->m_bTerminal = false;
275 d->m_bAllowAsDefault = true;
276}
277
278KService::KService(const QString &_fullpath)
279 : KSycocaEntry(*new KServicePrivate(_fullpath))
280{
281 Q_D(KService);
282
283 KDesktopFile config(_fullpath);
284 d->init(config: &config, q: this);
285}
286
287KService::KService(const KDesktopFile *config, const QString &entryPath)
288 : KSycocaEntry(*new KServicePrivate(entryPath.isEmpty() ? config->fileName() : entryPath))
289{
290 Q_D(KService);
291
292 d->init(config, q: this);
293}
294
295KService::KService(QDataStream &_str, int _offset)
296 : KSycocaEntry(*new KServicePrivate(_str, _offset))
297{
298 Q_D(KService);
299 KService::Ptr serviceClone(new KService(*this));
300 for (KServiceAction &action : d->m_actions) {
301 action.setService(serviceClone);
302 }
303}
304
305KService::KService(const KService &other)
306 : KSycocaEntry(*new KServicePrivate(*other.d_func()))
307{
308}
309
310KService::~KService()
311{
312}
313
314bool KService::hasMimeType(const QString &mimeType) const
315{
316 Q_D(const KService);
317 QMimeDatabase db;
318 const QString mime = db.mimeTypeForName(nameOrAlias: mimeType).name();
319 if (mime.isEmpty()) {
320 return false;
321 }
322 int serviceOffset = offset();
323 if (serviceOffset) {
324 KSycoca::self()->ensureCacheValid();
325 KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory();
326 const int mimeOffset = factory->entryOffset(mimeTypeName: mime);
327 const int serviceOffersOffset = factory->serviceOffersOffset(mimeTypeName: mime);
328 if (serviceOffersOffset == -1) {
329 return false;
330 }
331 return KSycocaPrivate::self()->serviceFactory()->hasOffer(serviceTypeOffset: mimeOffset, serviceOffersOffset, testedServiceOffset: serviceOffset);
332 }
333
334 return d->m_mimeTypes.contains(str: mime);
335}
336
337QVariant KService::property(const QString &_name, QMetaType::Type t) const
338{
339 Q_D(const KService);
340 return d->property(_name, t);
341}
342
343template<>
344QString KService::property<QString>(const QString &_name) const
345{
346 Q_D(const KService);
347
348 if (_name == QLatin1String("Type")) {
349 return d->m_strType;
350 } else if (_name == QLatin1String("Name")) {
351 return d->m_strName;
352 } else if (_name == QLatin1String("Exec")) {
353 return d->m_strExec;
354 } else if (_name == QLatin1String("Icon")) {
355 return d->m_strIcon;
356 } else if (_name == QLatin1String("TerminalOptions")) {
357 return d->m_strTerminalOptions;
358 } else if (_name == QLatin1String("Path")) {
359 return d->m_strWorkingDirectory;
360 } else if (_name == QLatin1String("Comment")) {
361 return d->m_strComment;
362 } else if (_name == QLatin1String("GenericName")) {
363 return d->m_strGenName;
364 } else if (_name == QLatin1String("DesktopEntryPath")) {
365 return d->path;
366 } else if (_name == QLatin1String("DesktopEntryName")) {
367 return d->m_strDesktopEntryName;
368 } else if (_name == QLatin1String("UntranslatedName")) {
369 return d->m_untranslatedName;
370 } else if (_name == QLatin1String("UntranslatedGenericName")) {
371 return d->m_untranslatedGenericName;
372 }
373
374 auto it = d->m_mapProps.constFind(key: _name);
375
376 if (it != d->m_mapProps.cend()) {
377 return it.value().toString();
378 }
379
380 return QString();
381}
382
383QVariant KServicePrivate::property(const QString &_name, QMetaType::Type t) const
384{
385 if (_name == QLatin1String("Terminal")) {
386 return QVariant(m_bTerminal);
387 } else if (_name == QLatin1String("AllowAsDefault")) {
388 return QVariant(m_bAllowAsDefault);
389 } else if (_name == QLatin1String("Categories")) {
390 return QVariant(categories);
391 } else if (_name == QLatin1String("Keywords")) {
392 return QVariant(m_lstKeywords);
393 } else if (_name == QLatin1String("FormFactors")) {
394 return QVariant(m_lstFormFactors);
395 }
396
397 auto it = m_mapProps.constFind(key: _name);
398 if (it == m_mapProps.cend() || !it.value().isValid()) {
399 // qCDebug(SERVICES) << "Property not found " << _name;
400 return QVariant(); // No property set.
401 }
402
403 if (it->typeId() == t) {
404 return it.value(); // no conversion necessary
405 } else {
406 // All others
407 // For instance properties defined as StringList, like MimeTypes.
408 // XXX This API is accessible only through a friend declaration.
409 return KConfigGroup::convertToQVariant(pKey: _name.toUtf8().constData(), value: it.value().toString().toUtf8(), aDefault: QVariant(QMetaType(t)));
410 }
411}
412
413KService::List KService::allServices()
414{
415 KSycoca::self()->ensureCacheValid();
416 return KSycocaPrivate::self()->serviceFactory()->allServices();
417}
418
419KService::Ptr KService::serviceByDesktopPath(const QString &_name)
420{
421 KSycoca::self()->ensureCacheValid();
422 return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name);
423}
424
425KService::Ptr KService::serviceByDesktopName(const QString &_name)
426{
427 KSycoca::self()->ensureCacheValid();
428 return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name);
429}
430
431KService::Ptr KService::serviceByMenuId(const QString &_name)
432{
433 KSycoca::self()->ensureCacheValid();
434 return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(menuId: _name);
435}
436
437KService::Ptr KService::serviceByStorageId(const QString &_storageId)
438{
439 KSycoca::self()->ensureCacheValid();
440 return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId);
441}
442
443bool KService::substituteUid() const
444{
445 return property<bool>(QStringLiteral("X-KDE-SubstituteUID"));
446}
447
448QString KService::username() const
449{
450 // See also KDesktopFile::tryExec()
451 QString user = property<QString>(QStringLiteral("X-KDE-Username"));
452 if (user.isEmpty()) {
453 user = QString::fromLocal8Bit(ba: qgetenv(varName: "ADMIN_ACCOUNT"));
454 }
455 if (user.isEmpty()) {
456 user = QStringLiteral("root");
457 }
458 return user;
459}
460
461bool KService::showInCurrentDesktop() const
462{
463 Q_D(const KService);
464
465 const QString envVar = QString::fromLatin1(ba: qgetenv(varName: "XDG_CURRENT_DESKTOP"));
466
467 QList<QStringView> currentDesktops = QStringView(envVar).split(sep: QLatin1Char(':'), behavior: Qt::SkipEmptyParts);
468
469 const QString kde = QStringLiteral("KDE");
470 if (currentDesktops.isEmpty()) {
471 // This could be an old display manager, or e.g. a failsafe session with no desktop name
472 // In doubt, let's say we show KDE stuff.
473 currentDesktops.append(t: kde);
474 }
475
476 // This algorithm is described in the desktop entry spec
477
478 auto it = d->m_mapProps.constFind(QStringLiteral("OnlyShowIn"));
479 if (it != d->m_mapProps.cend()) {
480 const QVariant &val = it.value();
481 if (val.isValid()) {
482 const QStringList aList = val.toString().split(sep: QLatin1Char(';'));
483 return std::any_of(first: currentDesktops.cbegin(), last: currentDesktops.cend(), pred: [&aList](const auto desktop) {
484 return aList.contains(desktop);
485 });
486 }
487 }
488
489 it = d->m_mapProps.constFind(QStringLiteral("NotShowIn"));
490 if (it != d->m_mapProps.cend()) {
491 const QVariant &val = it.value();
492 if (val.isValid()) {
493 const QStringList aList = val.toString().split(sep: QLatin1Char(';'));
494 return std::none_of(first: currentDesktops.cbegin(), last: currentDesktops.cend(), pred: [&aList](const auto desktop) {
495 return aList.contains(desktop);
496 });
497 }
498 }
499
500 return true;
501}
502
503bool KService::showOnCurrentPlatform() const
504{
505 Q_D(const KService);
506 const QString platform = QCoreApplication::instance()->property(name: "platformName").toString();
507 if (platform.isEmpty()) {
508 return true;
509 }
510
511 auto it = d->m_mapProps.find(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"));
512 if ((it != d->m_mapProps.end()) && (it->isValid())) {
513 const QStringList aList = it->toString().split(sep: QLatin1Char(';'));
514 if (!aList.contains(str: platform)) {
515 return false;
516 }
517 }
518
519 it = d->m_mapProps.find(QStringLiteral("X-KDE-NotShowOnQtPlatforms"));
520 if ((it != d->m_mapProps.end()) && (it->isValid())) {
521 const QStringList aList = it->toString().split(sep: QLatin1Char(';'));
522 if (aList.contains(str: platform)) {
523 return false;
524 }
525 }
526 return true;
527}
528
529bool KService::noDisplay() const
530{
531 if (property<bool>(QStringLiteral("NoDisplay"))) {
532 return true;
533 }
534
535 if (!showInCurrentDesktop()) {
536 return true;
537 }
538
539 if (!showOnCurrentPlatform()) {
540 return true;
541 }
542 return false;
543}
544
545QString KService::untranslatedGenericName() const
546{
547 Q_D(const KService);
548 return d->m_untranslatedGenericName;
549}
550
551QString KService::untranslatedName() const
552{
553 Q_D(const KService);
554 return d->m_untranslatedName;
555}
556
557QString KService::docPath() const
558{
559 Q_D(const KService);
560
561 for (const QString &str : {QStringLiteral("X-DocPath"), QStringLiteral("DocPath")}) {
562 auto it = d->m_mapProps.constFind(key: str);
563 if (it != d->m_mapProps.cend()) {
564 const QVariant variant = it.value();
565 Q_ASSERT(variant.isValid());
566 const QString path = variant.toString();
567 if (!path.isEmpty()) {
568 return path;
569 }
570 }
571 }
572
573 return {};
574}
575
576bool KService::allowMultipleFiles() const
577{
578 Q_D(const KService);
579 // Can we pass multiple files on the command line or do we have to start the application for every single file ?
580 return (d->m_strExec.contains(s: QLatin1String("%F")) //
581 || d->m_strExec.contains(s: QLatin1String("%U")) //
582 || d->m_strExec.contains(s: QLatin1String("%N")) //
583 || d->m_strExec.contains(s: QLatin1String("%D")));
584}
585
586QStringList KService::categories() const
587{
588 Q_D(const KService);
589 return d->categories;
590}
591
592QString KService::menuId() const
593{
594 Q_D(const KService);
595 return d->menuId;
596}
597
598void KService::setMenuId(const QString &_menuId)
599{
600 Q_D(KService);
601 d->menuId = _menuId;
602}
603
604QString KService::storageId() const
605{
606 Q_D(const KService);
607 return d->storageId();
608}
609
610// not sure this is still used anywhere...
611QString KService::locateLocal() const
612{
613 Q_D(const KService);
614 if (d->menuId.isEmpty() //
615 || entryPath().startsWith(s: QLatin1String(".hidden")) //
616 || (QDir::isRelativePath(path: entryPath()) && d->categories.isEmpty())) {
617 return KDesktopFile::locateLocal(path: entryPath());
618 }
619
620 return QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + d->menuId;
621}
622
623QString KService::newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId, const QStringList *reservedMenuIds)
624{
625 Q_UNUSED(showInMenu); // TODO KDE5: remove argument
626
627 QString base = suggestedName;
628 QString result;
629 for (int i = 1; true; i++) {
630 if (i == 1) {
631 result = base + QStringLiteral(".desktop");
632 } else {
633 result = base + QStringLiteral("-%1.desktop").arg(a: i);
634 }
635
636 if (reservedMenuIds && reservedMenuIds->contains(str: result)) {
637 continue;
638 }
639
640 // Lookup service by menu-id
641 KService::Ptr s = serviceByMenuId(name: result);
642 if (s) {
643 continue;
644 }
645
646 if (!QStandardPaths::locate(type: QStandardPaths::GenericDataLocation, fileName: QLatin1String("applications/") + result).isEmpty()) {
647 continue;
648 }
649
650 break;
651 }
652 if (menuId) {
653 *menuId = result;
654 }
655
656 return QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + result;
657}
658
659bool KService::isApplication() const
660{
661 Q_D(const KService);
662 return d->m_strType == QLatin1String("Application");
663}
664
665QString KService::exec() const
666{
667 Q_D(const KService);
668 return d->m_strExec;
669}
670
671QString KService::icon() const
672{
673 Q_D(const KService);
674 return d->m_strIcon;
675}
676
677QString KService::terminalOptions() const
678{
679 Q_D(const KService);
680 return d->m_strTerminalOptions;
681}
682
683bool KService::terminal() const
684{
685 Q_D(const KService);
686 return d->m_bTerminal;
687}
688
689bool KService::runOnDiscreteGpu() const
690{
691 QVariant prop = property<bool>(QStringLiteral("PrefersNonDefaultGPU"));
692 if (!prop.isValid()) {
693 // For backwards compatibility
694 prop = property<bool>(QStringLiteral("X-KDE-RunOnDiscreteGpu"));
695 }
696
697 return prop.isValid() && prop.toBool();
698}
699
700QString KService::desktopEntryName() const
701{
702 Q_D(const KService);
703 return d->m_strDesktopEntryName;
704}
705
706QString KService::workingDirectory() const
707{
708 Q_D(const KService);
709 return d->m_strWorkingDirectory;
710}
711
712QString KService::comment() const
713{
714 Q_D(const KService);
715 return d->m_strComment;
716}
717
718QString KService::genericName() const
719{
720 Q_D(const KService);
721 return d->m_strGenName;
722}
723
724QStringList KService::keywords() const
725{
726 Q_D(const KService);
727 return d->m_lstKeywords;
728}
729
730QStringList KService::mimeTypes() const
731{
732 Q_D(const KService);
733 QMimeDatabase db;
734 QStringList ret;
735
736 for (const auto &mimeName : d->m_mimeTypes) {
737 if (db.mimeTypeForName(nameOrAlias: mimeName).isValid()) { // keep only mimetypes, filter out servicetypes
738 ret.append(t: mimeName);
739 }
740 }
741 return ret;
742}
743
744QStringList KService::schemeHandlers() const
745{
746 Q_D(const KService);
747
748 QStringList ret;
749
750 const QLatin1String schemeHandlerPrefix("x-scheme-handler/");
751 for (const auto &mimeName : d->m_mimeTypes) {
752 if (mimeName.startsWith(s: schemeHandlerPrefix)) {
753 ret.append(t: mimeName.mid(position: schemeHandlerPrefix.size()));
754 }
755 }
756
757 return ret;
758}
759
760QStringList KService::supportedProtocols() const
761{
762 Q_D(const KService);
763
764 QStringList ret;
765
766 ret << schemeHandlers();
767
768 const QStringList protocols = property<QStringList>(QStringLiteral("X-KDE-Protocols"));
769 for (const QString &protocol : protocols) {
770 if (!ret.contains(str: protocol)) {
771 ret.append(t: protocol);
772 }
773 }
774
775 return ret;
776}
777
778void KService::setTerminal(bool b)
779{
780 Q_D(KService);
781 d->m_bTerminal = b;
782}
783
784void KService::setTerminalOptions(const QString &options)
785{
786 Q_D(KService);
787 d->m_strTerminalOptions = options;
788}
789
790void KService::setExec(const QString &exec)
791{
792 Q_D(KService);
793
794 if (!exec.isEmpty()) {
795 d->m_strExec = exec;
796 d->path.clear();
797 }
798}
799
800void KService::setWorkingDirectory(const QString &workingDir)
801{
802 Q_D(KService);
803
804 if (!workingDir.isEmpty()) {
805 d->m_strWorkingDirectory = workingDir;
806 d->path.clear();
807 }
808}
809
810QList<KServiceAction> KService::actions() const
811{
812 Q_D(const KService);
813 return d->m_actions;
814}
815
816QString KService::aliasFor() const
817{
818 return KServiceUtilPrivate::completeBaseName(filepath: property<QString>(QStringLiteral("X-KDE-AliasFor")));
819}
820
821void KService::setActions(const QList<KServiceAction> &actions)
822{
823 Q_D(KService);
824 d->m_actions = actions;
825}
826
827std::optional<bool> KService::startupNotify() const
828{
829 Q_D(const KService);
830
831 if (QVariant value = d->m_mapProps.value(QStringLiteral("StartupNotify")); value.isValid()) {
832 return value.toBool();
833 }
834
835 if (QVariant value = d->m_mapProps.value(QStringLiteral("X-KDE-StartupNotify")); value.isValid()) {
836 return value.toBool();
837 }
838
839 return {};
840}
841

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