Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdesktopunixservices.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:execute-external-code
4
6#include <QtGui/private/qtguiglobal_p.h>
8#include "qwindow.h"
9#include <QtGui/qpa/qplatformwindow_p.h>
10#include <QtGui/qpa/qplatformwindow.h>
11#include <QtGui/qpa/qplatformnativeinterface.h>
12
13#include <QtCore/QDebug>
14#include <QtCore/QFile>
15#include <QtCore/QFileInfo>
16#if QT_CONFIG(process)
17# include <QtCore/QProcess>
18#endif
19#if QT_CONFIG(settings)
20#include <QtCore/QSettings>
21#endif
22#include <QtCore/QStandardPaths>
23#include <QtCore/QUrl>
24
25#if QT_CONFIG(dbus)
26// These QtCore includes are needed for xdg-desktop-portal support
27#include <QtCore/private/qcore_unix_p.h>
28
29#include <QtCore/QFileInfo>
30#include <QtCore/QUrlQuery>
31
32#include <QtDBus/QDBusConnection>
33#include <QtDBus/QDBusServiceWatcher>
34#include <QtDBus/QDBusMessage>
35#include <QtDBus/QDBusPendingCall>
36#include <QtDBus/QDBusPendingCallWatcher>
37#include <QtDBus/QDBusPendingReply>
38#include <QtDBus/QDBusUnixFileDescriptor>
39
40#include <fcntl.h>
41
42#endif // QT_CONFIG(dbus)
43
44#include <stdlib.h>
45
46QT_BEGIN_NAMESPACE
47
48using namespace Qt::StringLiterals;
49
50#if QT_CONFIG(multiprocess)
51
52static inline QByteArray detectDesktopEnvironment()
53{
54 const QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP");
55 if (!xdgCurrentDesktop.isEmpty())
56 return xdgCurrentDesktop.toUpper(); // KDE, GNOME, UNITY, LXDE, MATE, XFCE...
57
58 // Classic fallbacks
59 if (!qEnvironmentVariableIsEmpty("KDE_FULL_SESSION"))
60 return QByteArrayLiteral("KDE");
61 if (!qEnvironmentVariableIsEmpty("GNOME_DESKTOP_SESSION_ID"))
62 return QByteArrayLiteral("GNOME");
63
64 // Fallback to checking $DESKTOP_SESSION (unreliable)
65 QByteArray desktopSession = qgetenv("DESKTOP_SESSION");
66
67 // This can be a path in /usr/share/xsessions
68 int slash = desktopSession.lastIndexOf('/');
69 if (slash != -1) {
70#if QT_CONFIG(settings)
71 QSettings desktopFile(QFile::decodeName(desktopSession + ".desktop"), QSettings::IniFormat);
72 desktopFile.beginGroup(QStringLiteral("Desktop Entry"));
73 QByteArray desktopName = desktopFile.value(QStringLiteral("DesktopNames")).toByteArray();
74 if (!desktopName.isEmpty())
75 return desktopName;
76#endif
77
78 // try decoding just the basename
79 desktopSession = desktopSession.mid(slash + 1);
80 }
81
82 if (desktopSession == "gnome")
83 return QByteArrayLiteral("GNOME");
84 else if (desktopSession == "xfce")
85 return QByteArrayLiteral("XFCE");
86 else if (desktopSession == "kde")
87 return QByteArrayLiteral("KDE");
88
89 return QByteArrayLiteral("UNKNOWN");
90}
91
92static inline bool checkExecutable(const QString &candidate, QString *result)
93{
94 *result = QStandardPaths::findExecutable(candidate);
95 return !result->isEmpty();
96}
97
98[[maybe_unused]]
99static inline bool detectWebBrowser(QDesktopUnixServices::LaunchType type,
100 const QByteArray &desktop, QString *browser)
101{
102 const char *browsers[] = {"google-chrome", "firefox", "mozilla", "opera"};
103
104 Q_PRE(browser->isEmpty());
105 if (checkExecutable(QStringLiteral("xdg-open"), browser))
106 return true;
107
108 if (type == QDesktopUnixServices::LaunchType::Browser) {
109 QString browserVariable = qEnvironmentVariable("DEFAULT_BROWSER");
110 if (browserVariable.isEmpty())
111 browserVariable = qEnvironmentVariable("BROWSER");
112 if (!browserVariable.isEmpty() && checkExecutable(browserVariable, browser))
113 return true;
114 }
115
116 if (desktop == QByteArrayView("KDE")) {
117 if (checkExecutable(QStringLiteral("kde-open"), browser))
118 return true;
119 if (checkExecutable(QStringLiteral("kde-open5"), browser))
120 return true;
121 } else if (desktop == QByteArrayView("GNOME")) {
122 if (checkExecutable(QStringLiteral("gnome-open"), browser))
123 return true;
124 }
125
126 for (size_t i = 0; i < sizeof(browsers)/sizeof(char *); ++i)
127 if (checkExecutable(QLatin1StringView(browsers[i]), browser))
128 return true;
129 return false;
130}
131
132bool QDesktopUnixServices::launchProcess(LaunchType type, const QUrl &url,
133 const QString &xdgActivationToken)
134{
135#if QT_CONFIG(process)
136 QString &program = type == LaunchType::Browser ? m_webBrowser : m_documentLauncher;
137 if (program.isEmpty())
138 detectWebBrowser(type, desktopEnvironment(), &program);
139
140 QString errorString;
141 QString urlString = url.toString(QUrl::FullyEncoded);
142 if (!program.isEmpty()) {
143 QProcess process;
144 Q_ASSERT(QFileInfo(program).isAbsolute());
145 // AXIVION Next Line Qt-Security-QProcessStart: executable is absolute from PATH
146 process.setProgram(program);
147 process.setArguments({ urlString });
148 qCDebug(lcQpaServices, "Launching %ls %ls", qUtf16Printable(program),
149 qUtf16Printable(urlString));
150
151 if (!xdgActivationToken.isEmpty()) {
152 auto env = QProcessEnvironment::systemEnvironment();
153 env.insert(u"XDG_ACTIVATION_TOKEN"_s, xdgActivationToken);
154 process.setProcessEnvironment(env);
155 }
156 // AXIVION Next Line Qt-Security-QProcessStart: executable is absolute from PATH
157 if (process.startDetached(nullptr))
158 return true;
159 errorString = process.errorString();
160 } else {
161 errorString = u"Unable to detect a launcher"_s;
162 }
163
164 qCWarning(lcQpaServices, "Launch of '%ls %ls' failed: %ls",
165 qUtf16Printable(program), qUtf16Printable(urlString),
166 qUtf16Printable(errorString));
167#else
168 Q_UNUSED(type);
169 Q_UNUSED(xdgActivationToken);
170 qCWarning(lcQpaServices, "Launch for '%ls' failed: QProcess not available",
171 qUtf16Printable(url.toEncoded()));
172#endif // QT_CONFIG(process)
173 return false;
174}
175
176#if QT_CONFIG(dbus)
177static inline bool checkNeedPortalSupport()
178{
179 return QFileInfo::exists("/.flatpak-info"_L1) || qEnvironmentVariableIsSet("SNAP");
180}
181
182static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url, const QString &parentWindow,
183 const QString &xdgActivationToken)
184{
185 // DBus signature:
186 // OpenFile (IN s parent_window,
187 // IN h fd,
188 // IN a{sv} options,
189 // OUT o handle)
190 // Options:
191 // handle_token (s) - A string that will be used as the last element of the @handle.
192 // writable (b) - Whether to allow the chosen application to write to the file.
193
194 const int fd = qt_safe_open(QFile::encodeName(url.toLocalFile()), O_RDONLY);
195 if (fd != -1) {
196 QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
197 "/org/freedesktop/portal/desktop"_L1,
198 "org.freedesktop.portal.OpenURI"_L1,
199 "OpenFile"_L1);
200
201 QDBusUnixFileDescriptor descriptor;
202 descriptor.giveFileDescriptor(fd);
203
204 QVariantMap options = {};
205
206 if (!xdgActivationToken.isEmpty()) {
207 options.insert("activation_token"_L1, xdgActivationToken);
208 }
209
210 message << parentWindow << QVariant::fromValue(descriptor) << options;
211
212 return QDBusConnection::sessionBus().call(message);
213 }
214
215 return QDBusMessage::createError(QDBusError::InternalError, qt_error_string());
216}
217
218static inline QDBusMessage xdgDesktopPortalOpenUrl(const QUrl &url, const QString &parentWindow,
219 const QString &xdgActivationToken)
220{
221 // DBus signature:
222 // OpenURI (IN s parent_window,
223 // IN s uri,
224 // IN a{sv} options,
225 // OUT o handle)
226 // Options:
227 // handle_token (s) - A string that will be used as the last element of the @handle.
228 // writable (b) - Whether to allow the chosen application to write to the file.
229 // This key only takes effect the uri points to a local file that is exported in the document portal,
230 // and the chosen application is sandboxed itself.
231
232 QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
233 "/org/freedesktop/portal/desktop"_L1,
234 "org.freedesktop.portal.OpenURI"_L1,
235 "OpenURI"_L1);
236 // FIXME parent_window_id and handle writable option
237 QVariantMap options;
238
239 if (!xdgActivationToken.isEmpty()) {
240 options.insert("activation_token"_L1, xdgActivationToken);
241 }
242
243 message << parentWindow << url.toString() << options;
244
245 return QDBusConnection::sessionBus().call(message);
246}
247
248static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url, const QString &parentWindow,
249 const QString &xdgActivationToken)
250{
251 // DBus signature:
252 // ComposeEmail (IN s parent_window,
253 // IN a{sv} options,
254 // OUT o handle)
255 // Options:
256 // address (s) - The email address to send to.
257 // subject (s) - The subject for the email.
258 // body (s) - The body for the email.
259 // attachment_fds (ah) - File descriptors for files to attach.
260
261 QUrlQuery urlQuery(url);
262 QVariantMap options;
263 options.insert("address"_L1, url.path());
264 options.insert("subject"_L1, urlQuery.queryItemValue("subject"_L1));
265 options.insert("body"_L1, urlQuery.queryItemValue("body"_L1));
266
267 // O_PATH seems to be present since Linux 2.6.39, which is not case of RHEL 6
268#ifdef O_PATH
269 QList<QDBusUnixFileDescriptor> attachments;
270 const QStringList attachmentUris = urlQuery.allQueryItemValues("attachment"_L1);
271
272 for (const QString &attachmentUri : attachmentUris) {
273 const int fd = qt_safe_open(QFile::encodeName(attachmentUri), O_PATH);
274 if (fd != -1) {
275 QDBusUnixFileDescriptor descriptor(fd);
276 attachments << descriptor;
277 qt_safe_close(fd);
278 }
279 }
280
281 options.insert("attachment_fds"_L1, QVariant::fromValue(attachments));
282#endif
283
284 if (!xdgActivationToken.isEmpty()) {
285 options.insert("activation_token"_L1, xdgActivationToken);
286 }
287
288 QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
289 "/org/freedesktop/portal/desktop"_L1,
290 "org.freedesktop.portal.Email"_L1,
291 "ComposeEmail"_L1);
292
293 message << parentWindow << options;
294
295 return QDBusConnection::sessionBus().call(message);
296}
297
298namespace {
299struct XDGDesktopColor
300{
301 double r = 0;
302 double g = 0;
303 double b = 0;
304
305 QColor toQColor() const
306 {
307 constexpr auto rgbMax = 255;
308 return { static_cast<int>(r * rgbMax), static_cast<int>(g * rgbMax),
309 static_cast<int>(b * rgbMax) };
310 }
311};
312
313const QDBusArgument &operator>>(const QDBusArgument &argument, XDGDesktopColor &myStruct)
314{
315 argument.beginStructure();
316 argument >> myStruct.r >> myStruct.g >> myStruct.b;
317 argument.endStructure();
318 return argument;
319}
320
321class XdgDesktopPortalColorPicker : public QPlatformServiceColorPicker
322{
323 Q_OBJECT
324public:
325 XdgDesktopPortalColorPicker(const QString &parentWindowId, QWindow *parent)
326 : QPlatformServiceColorPicker(parent), m_parentWindowId(parentWindowId)
327 {
328 }
329
330 void pickColor() override
331 {
332 // DBus signature:
333 // PickColor (IN s parent_window,
334 // IN a{sv} options
335 // OUT o handle)
336 // Options:
337 // handle_token (s) - A string that will be used as the last element of the @handle.
338
339 QDBusMessage message = QDBusMessage::createMethodCall(
340 "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
341 "org.freedesktop.portal.Screenshot"_L1, "PickColor"_L1);
342 message << m_parentWindowId << QVariantMap();
343
344 QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
345 auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
346 connect(watcher, &QDBusPendingCallWatcher::finished, this,
347 [this](QDBusPendingCallWatcher *watcher) {
348 watcher->deleteLater();
349 QDBusPendingReply<QDBusObjectPath> reply = *watcher;
350 if (reply.isError()) {
351 qWarning("DBus call to pick color failed: %s",
352 qPrintable(reply.error().message()));
353 Q_EMIT colorPicked({});
354 } else {
355 QDBusConnection::sessionBus().connect(
356 "org.freedesktop.portal.Desktop"_L1, reply.value().path(),
357 "org.freedesktop.portal.Request"_L1, "Response"_L1, this,
358 // clang-format off
359 SLOT(gotColorResponse(uint,QVariantMap))
360 // clang-format on
361 );
362 }
363 });
364 }
365
366private Q_SLOTS:
367 void gotColorResponse(uint result, const QVariantMap &map)
368 {
369 if (result != 0)
370 return;
371 if (map.contains(u"color"_s)) {
372 XDGDesktopColor color{};
373 map.value(u"color"_s).value<QDBusArgument>() >> color;
374 Q_EMIT colorPicked(color.toQColor());
375 } else {
376 Q_EMIT colorPicked({});
377 }
378 deleteLater();
379 }
380
381private:
382 const QString m_parentWindowId;
383};
384
385void registerWithHostPortal()
386{
387 static bool registered = false;
388 if (registered) {
389 return;
390 }
391
392 auto message = QDBusMessage::createMethodCall(
393 "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
394 "org.freedesktop.host.portal.Registry"_L1, "Register"_L1);
395 message.setArguments({ QGuiApplication::desktopFileName(), QVariantMap() });
396 auto watcher =
397 new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message), qGuiApp);
398 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [watcher] {
399 watcher->deleteLater();
400 if (watcher->isError()) {
401 // Expected error when running against an older portal
402 if (watcher->error().type() == QDBusError::UnknownInterface || watcher->error().type() == QDBusError::UnknownMethod)
403 qCInfo(lcQpaServices) << "Failed to register with host portal" << watcher->error();
404 else
405 qCWarning(lcQpaServices) << "Failed to register with host portal" << watcher->error();
406 } else {
407 qCDebug(lcQpaServices) << "Successfully registered with host portal as" << QGuiApplication::desktopFileName();
408 registered = true;
409 }
410 });
411}
412} // namespace
413
414#endif // QT_CONFIG(dbus)
415
416QDesktopUnixServices::QDesktopUnixServices()
417{
418 if (detectDesktopEnvironment() == QByteArrayLiteral("UNKNOWN"))
419 return;
420
421#if QT_CONFIG(dbus)
422 if (qEnvironmentVariableIntValue("QT_NO_XDG_DESKTOP_PORTAL") > 0) {
423 return;
424 }
425 QDBusMessage message = QDBusMessage::createMethodCall(
426 "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
427 "org.freedesktop.DBus.Properties"_L1, "Get"_L1);
428 message << "org.freedesktop.portal.Screenshot"_L1
429 << "version"_L1;
430
431 QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
432 auto watcher = new QDBusPendingCallWatcher(pendingCall);
433 m_watcher = watcher;
434 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
435 [this](QDBusPendingCallWatcher *watcher) {
436 watcher->deleteLater();
437 QDBusPendingReply<QVariant> reply = *watcher;
438 if (reply.isError()) {
439 m_hasNoPortal = (reply.error().type() == QDBusError::ServiceUnknown);
440 } else {
441 if (reply.value().toUInt() >= 2)
442 m_hasScreenshotPortalWithColorPicking = true;
443 }
444 });
445
446 if (checkNeedPortalSupport()) {
447 return;
448 }
449
450 // The program might only set the desktopfilename after creating the app
451 // try again when it's running
452 if (!QGuiApplication::desktopFileName().isEmpty()) {
453 registerWithHostPortal();
454 } else {
455 QMetaObject::invokeMethod(
456 qGuiApp,
457 [] {
458 if (QGuiApplication::desktopFileName().isEmpty()) {
459 qCInfo(lcQpaServices) << "QGuiApplication::desktopFileName not set. Unable to register application with portal registry";
460 return;
461 }
462 registerWithHostPortal();
463 },
464 Qt::QueuedConnection);
465 }
466 m_portalWatcher = std::make_unique<QDBusServiceWatcher>(
467 "org.freedesktop.portal.Desktop"_L1, QDBusConnection::sessionBus(),
468 QDBusServiceWatcher::WatchForRegistration);
469 QObject::connect(m_portalWatcher.get(), &QDBusServiceWatcher::serviceRegistered,
470 m_portalWatcher.get(), &registerWithHostPortal);
471#endif
472}
473
474QDesktopUnixServices::~QDesktopUnixServices()
475{
476#if QT_CONFIG(dbus)
477 delete m_watcher;
478#endif
479}
480
481QPlatformServiceColorPicker *QDesktopUnixServices::colorPicker(QWindow *parent)
482{
483#if QT_CONFIG(dbus)
484 // Make double sure that we are in a wayland environment. In particular check
485 // WAYLAND_DISPLAY so also XWayland apps benefit from portal-based color picking.
486 // Outside wayland we'll rather rely on other means than the XDG desktop portal.
487 if (!qEnvironmentVariableIsEmpty("WAYLAND_DISPLAY")
488 || QGuiApplication::platformName().startsWith("wayland"_L1)) {
489 return new XdgDesktopPortalColorPicker(portalWindowIdentifier(parent), parent);
490 }
491 return nullptr;
492#else
493 Q_UNUSED(parent);
494 return nullptr;
495#endif
496}
497
498QByteArray QDesktopUnixServices::desktopEnvironment() const
499{
500 static const QByteArray result = detectDesktopEnvironment();
501 return result;
502}
503
504QString QDesktopUnixServices::portalFocusWindowIdentifier()
505{
506 if (QWindow *focusWindow = QGuiApplication::focusWindow())
507 return portalWindowIdentifier(focusWindow);
508 return QString();
509}
510
511template<typename F>
512bool runWithXdgActivationToken(F &&functionToCall)
513{
514#if QT_CONFIG(wayland)
515 QWindow *window = qGuiApp->focusWindow();
516
517 if (!window)
518 return functionToCall({});
519
520 auto waylandApp = dynamic_cast<QNativeInterface::QWaylandApplication *>(
521 qGuiApp->platformNativeInterface());
522 auto waylandWindow =
523 dynamic_cast<QNativeInterface::Private::QWaylandWindow *>(window->handle());
524
525 if (!waylandWindow || !waylandApp)
526 return functionToCall({});
527
528 QObject::connect(waylandWindow,
529 &QNativeInterface::Private::QWaylandWindow::xdgActivationTokenCreated,
530 waylandWindow, functionToCall, Qt::SingleShotConnection);
531 waylandWindow->requestXdgActivationToken(waylandApp->lastInputSerial());
532 return true;
533#else
534 return functionToCall({});
535#endif
536}
537
538bool QDesktopUnixServices::openUrl(const QUrl &url)
539{
540 auto openUrlWithoutPortal = [&](const QString &xdgActivationToken) {
541 if (url.scheme() == "mailto"_L1)
542 return openDocument(url);
543
544 return launchProcess(LaunchType::Browser, url, xdgActivationToken);
545 };
546
547# if QT_CONFIG(dbus)
548 if (!m_hasNoPortal) {
549 auto openUrlWithPortal = [&](const QString &xdgActivationToken) {
550 const QString parentWindow = portalFocusWindowIdentifier();
551 QDBusError error = url.scheme() == "mailto"_L1
552 ? xdgDesktopPortalSendEmail(url, parentWindow, xdgActivationToken)
553 : xdgDesktopPortalOpenUrl(url, parentWindow, xdgActivationToken);
554 if (!error.isValid())
555 return true;
556 if (error.type() == QDBusError::ServiceUnknown)
557 m_hasNoPortal = true;
558 // portal activation failed, fall back to executing
559 return openUrlWithoutPortal(xdgActivationToken);
560 };
561 return runWithXdgActivationToken(openUrlWithPortal);
562 }
563# endif
564
565 return runWithXdgActivationToken(openUrlWithoutPortal);
566}
567
568bool QDesktopUnixServices::openDocument(const QUrl &url)
569{
570 auto openDocumentWithoutPortal = [&](const QString &xdgActivationToken) {
571 return launchProcess(LaunchType::Document, url, xdgActivationToken);
572 };
573
574# if QT_CONFIG(dbus)
575 if (!m_hasNoPortal) {
576 auto openDocumentWithPortal = [&](const QString &xdgActivationToken) {
577 const QString parentWindow = portalFocusWindowIdentifier();
578 QDBusError error = xdgDesktopPortalOpenFile(url, parentWindow, xdgActivationToken);
579 if (!error.isValid())
580 return true;
581 if (error.type() == QDBusError::ServiceUnknown)
582 m_hasNoPortal = true;
583 // portal activation failed, fall back to executing
584 return openDocumentWithoutPortal(xdgActivationToken);
585 };
586 return runWithXdgActivationToken(openDocumentWithPortal);
587 }
588# endif
589
590 return runWithXdgActivationToken(openDocumentWithoutPortal);
591}
592
593#else
594QDesktopUnixServices::QDesktopUnixServices() = default;
595QDesktopUnixServices::~QDesktopUnixServices() = default;
596
597QByteArray QDesktopUnixServices::desktopEnvironment() const
598{
599 return QByteArrayLiteral("UNKNOWN");
600}
601
602bool QDesktopUnixServices::openUrl(const QUrl &url)
603{
604 Q_UNUSED(url);
605 qWarning("openUrl() not supported on this platform");
606 return false;
607}
608
609bool QDesktopUnixServices::openDocument(const QUrl &url)
610{
611 Q_UNUSED(url);
612 qWarning("openDocument() not supported on this platform");
613 return false;
614}
615
616QPlatformServiceColorPicker *QDesktopUnixServices::colorPicker(QWindow *parent)
617{
618 Q_UNUSED(parent);
619 return nullptr;
620}
621
622#endif // QT_CONFIG(multiprocess)
623
624QString QDesktopUnixServices::portalWindowIdentifier(QWindow *window)
625{
626 Q_UNUSED(window);
627 return QString();
628}
629
630
631void QDesktopUnixServices::registerDBusMenuForWindow(QWindow *window, const QString &service, const QString &path)
632{
633 Q_UNUSED(window);
634 Q_UNUSED(service);
635 Q_UNUSED(path);
636}
637
638void QDesktopUnixServices::unregisterDBusMenuForWindow(QWindow *window)
639{
640 Q_UNUSED(window);
641}
642
643
644bool QDesktopUnixServices::hasCapability(Capability capability) const
645{
646 switch (capability) {
647 case Capability::ColorPicking:
648 return m_hasScreenshotPortalWithColorPicking;
649 }
650 return false;
651}
652
653void QDesktopUnixServices::setApplicationBadge(qint64 number)
654{
655#if QT_CONFIG(dbus)
656 if (qGuiApp->desktopFileName().isEmpty()) {
657 qCWarning(lcQpaServices, "Cannot set badge number - QGuiApplication::desktopFileName() is empty");
658 return;
659 }
660
661
662 const QString launcherUrl = QStringLiteral("application://") + qGuiApp->desktopFileName() + QStringLiteral(".desktop");
663 const qint64 count = qBound(0, number, 9999);
664 QVariantMap dbusUnityProperties;
665
666 if (count > 0) {
667 dbusUnityProperties[QStringLiteral("count")] = count;
668 dbusUnityProperties[QStringLiteral("count-visible")] = true;
669 } else {
670 dbusUnityProperties[QStringLiteral("count-visible")] = false;
671 }
672
673 auto signal = QDBusMessage::createSignal(QStringLiteral("/com/canonical/unity/launcherentry/")
674 + qGuiApp->applicationName(), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update"));
675
676 signal.setArguments({launcherUrl, dbusUnityProperties});
677
678 QDBusConnection::sessionBus().send(signal);
679#else
680 Q_UNUSED(number)
681#endif
682}
683
684QT_END_NAMESPACE
685
686#include "qdesktopunixservices.moc"