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 | 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 | |
219 | void 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 | |
248 | void 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 | |
266 | KService::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 | |
278 | KService::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 | |
287 | KService::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 | |
295 | KService::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 | |
305 | KService::KService(const KService &other) |
306 | : KSycocaEntry(*new KServicePrivate(*other.d_func())) |
307 | { |
308 | } |
309 | |
310 | KService::~KService() |
311 | { |
312 | } |
313 | |
314 | bool 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 | |
337 | QVariant KService::property(const QString &_name, QMetaType::Type t) const |
338 | { |
339 | Q_D(const KService); |
340 | return d->property(_name, t); |
341 | } |
342 | |
343 | template<> |
344 | QString 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 | |
383 | QVariant 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 | |
413 | KService::List KService::allServices() |
414 | { |
415 | KSycoca::self()->ensureCacheValid(); |
416 | return KSycocaPrivate::self()->serviceFactory()->allServices(); |
417 | } |
418 | |
419 | KService::Ptr KService::serviceByDesktopPath(const QString &_name) |
420 | { |
421 | KSycoca::self()->ensureCacheValid(); |
422 | return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name); |
423 | } |
424 | |
425 | KService::Ptr KService::serviceByDesktopName(const QString &_name) |
426 | { |
427 | KSycoca::self()->ensureCacheValid(); |
428 | return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name); |
429 | } |
430 | |
431 | KService::Ptr KService::(const QString &_name) |
432 | { |
433 | KSycoca::self()->ensureCacheValid(); |
434 | return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(menuId: _name); |
435 | } |
436 | |
437 | KService::Ptr KService::serviceByStorageId(const QString &_storageId) |
438 | { |
439 | KSycoca::self()->ensureCacheValid(); |
440 | return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId); |
441 | } |
442 | |
443 | bool KService::substituteUid() const |
444 | { |
445 | return property<bool>(QStringLiteral("X-KDE-SubstituteUID" )); |
446 | } |
447 | |
448 | QString 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 | |
461 | bool 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 | |
503 | bool 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 | |
529 | bool 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 | |
545 | QString KService::untranslatedGenericName() const |
546 | { |
547 | Q_D(const KService); |
548 | return d->m_untranslatedGenericName; |
549 | } |
550 | |
551 | QString KService::untranslatedName() const |
552 | { |
553 | Q_D(const KService); |
554 | return d->m_untranslatedName; |
555 | } |
556 | |
557 | QString 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 | |
576 | bool 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 | |
586 | QStringList KService::categories() const |
587 | { |
588 | Q_D(const KService); |
589 | return d->categories; |
590 | } |
591 | |
592 | QString KService::() const |
593 | { |
594 | Q_D(const KService); |
595 | return d->menuId; |
596 | } |
597 | |
598 | void KService::(const QString &) |
599 | { |
600 | Q_D(KService); |
601 | d->menuId = _menuId; |
602 | } |
603 | |
604 | QString KService::storageId() const |
605 | { |
606 | Q_D(const KService); |
607 | return d->storageId(); |
608 | } |
609 | |
610 | // not sure this is still used anywhere... |
611 | QString 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 | |
623 | QString KService::newServicePath(bool , const QString &suggestedName, QString *, const QStringList *) |
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 | |
659 | bool KService::isApplication() const |
660 | { |
661 | Q_D(const KService); |
662 | return d->m_strType == QLatin1String("Application" ); |
663 | } |
664 | |
665 | QString KService::exec() const |
666 | { |
667 | Q_D(const KService); |
668 | return d->m_strExec; |
669 | } |
670 | |
671 | QString KService::icon() const |
672 | { |
673 | Q_D(const KService); |
674 | return d->m_strIcon; |
675 | } |
676 | |
677 | QString KService::terminalOptions() const |
678 | { |
679 | Q_D(const KService); |
680 | return d->m_strTerminalOptions; |
681 | } |
682 | |
683 | bool KService::terminal() const |
684 | { |
685 | Q_D(const KService); |
686 | return d->m_bTerminal; |
687 | } |
688 | |
689 | bool 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 | |
700 | QString KService::desktopEntryName() const |
701 | { |
702 | Q_D(const KService); |
703 | return d->m_strDesktopEntryName; |
704 | } |
705 | |
706 | QString KService::workingDirectory() const |
707 | { |
708 | Q_D(const KService); |
709 | return d->m_strWorkingDirectory; |
710 | } |
711 | |
712 | QString KService::() const |
713 | { |
714 | Q_D(const KService); |
715 | return d->m_strComment; |
716 | } |
717 | |
718 | QString KService::genericName() const |
719 | { |
720 | Q_D(const KService); |
721 | return d->m_strGenName; |
722 | } |
723 | |
724 | QStringList KService::keywords() const |
725 | { |
726 | Q_D(const KService); |
727 | return d->m_lstKeywords; |
728 | } |
729 | |
730 | QStringList 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 | |
744 | QStringList 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 | |
760 | QStringList 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 | |
778 | void KService::setTerminal(bool b) |
779 | { |
780 | Q_D(KService); |
781 | d->m_bTerminal = b; |
782 | } |
783 | |
784 | void KService::setTerminalOptions(const QString &options) |
785 | { |
786 | Q_D(KService); |
787 | d->m_strTerminalOptions = options; |
788 | } |
789 | |
790 | void 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 | |
800 | void 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 | |
810 | QList<KServiceAction> KService::actions() const |
811 | { |
812 | Q_D(const KService); |
813 | return d->m_actions; |
814 | } |
815 | |
816 | QString KService::aliasFor() const |
817 | { |
818 | return KServiceUtilPrivate::completeBaseName(filepath: property<QString>(QStringLiteral("X-KDE-AliasFor" ))); |
819 | } |
820 | |
821 | void KService::setActions(const QList<KServiceAction> &actions) |
822 | { |
823 | Q_D(KService); |
824 | d->m_actions = actions; |
825 | } |
826 | |
827 | std::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 | |