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

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