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 QStringList addresses = url.path().split(u',') + urlQuery.allQueryItemValues("to"_L1, QUrl::FullyDecoded);
264 if (!addresses.isEmpty())
265 options.insert("addresses"_L1, addresses);
266 if (const auto subject = urlQuery.queryItemValue("subject"_L1); !subject.isEmpty())
267 options.insert("subject"_L1, subject);
268 if (const auto body = urlQuery.queryItemValue("body"_L1); !body.isEmpty())
269 options.insert("body"_L1, body);
270 if (const auto cc = urlQuery.allQueryItemValues("cc"_L1, QUrl::FullyDecoded); !cc.isEmpty())
271 options.insert("cc"_L1, cc);
272 if (const auto bcc = urlQuery.allQueryItemValues("bcc"_L1, QUrl::FullyDecoded); !bcc.isEmpty())
273 options.insert("bcc"_L1, bcc);
274
275 // O_PATH seems to be present since Linux 2.6.39, which is not case of RHEL 6
276#ifdef O_PATH
277 QList<QDBusUnixFileDescriptor> attachments;
278 const QStringList attachmentUris = urlQuery.allQueryItemValues("attachment"_L1);
279
280 for (const QString &attachmentUri : attachmentUris) {
281 const int fd = qt_safe_open(QFile::encodeName(attachmentUri), O_PATH);
282 if (fd != -1) {
283 QDBusUnixFileDescriptor descriptor(fd);
284 attachments << descriptor;
285 qt_safe_close(fd);
286 }
287 }
288
289 if (attachments.size() > 0)
290 options.insert("attachment_fds"_L1, QVariant::fromValue(attachments));
291#endif
292
293 if (!xdgActivationToken.isEmpty()) {
294 options.insert("activation_token"_L1, xdgActivationToken);
295 }
296
297 QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
298 "/org/freedesktop/portal/desktop"_L1,
299 "org.freedesktop.portal.Email"_L1,
300 "ComposeEmail"_L1);
301
302 message << parentWindow << options;
303
304 return QDBusConnection::sessionBus().call(message);
305}
306
307namespace {
308struct XDGDesktopColor
309{
310 double r = 0;
311 double g = 0;
312 double b = 0;
313
314 QColor toQColor() const
315 {
316 constexpr auto rgbMax = 255;
317 return { static_cast<int>(r * rgbMax), static_cast<int>(g * rgbMax),
318 static_cast<int>(b * rgbMax) };
319 }
320};
321
322const QDBusArgument &operator>>(const QDBusArgument &argument, XDGDesktopColor &myStruct)
323{
324 argument.beginStructure();
325 argument >> myStruct.r >> myStruct.g >> myStruct.b;
326 argument.endStructure();
327 return argument;
328}
329
330class XdgDesktopPortalColorPicker : public QPlatformServiceColorPicker
331{
332 Q_OBJECT
333public:
334 XdgDesktopPortalColorPicker(const QString &parentWindowId, QWindow *parent)
335 : QPlatformServiceColorPicker(parent), m_parentWindowId(parentWindowId)
336 {
337 }
338
339 void pickColor() override
340 {
341 // DBus signature:
342 // PickColor (IN s parent_window,
343 // IN a{sv} options
344 // OUT o handle)
345 // Options:
346 // handle_token (s) - A string that will be used as the last element of the @handle.
347
348 QDBusMessage message = QDBusMessage::createMethodCall(
349 "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
350 "org.freedesktop.portal.Screenshot"_L1, "PickColor"_L1);
351 message << m_parentWindowId << QVariantMap();
352
353 QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
354 auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
355 connect(watcher, &QDBusPendingCallWatcher::finished, this,
356 [this](QDBusPendingCallWatcher *watcher) {
357 watcher->deleteLater();
358 QDBusPendingReply<QDBusObjectPath> reply = *watcher;
359 if (reply.isError()) {
360 qWarning("DBus call to pick color failed: %s",
361 qPrintable(reply.error().message()));
362 Q_EMIT colorPicked({});
363 } else {
364 QDBusConnection::sessionBus().connect(
365 "org.freedesktop.portal.Desktop"_L1, reply.value().path(),
366 "org.freedesktop.portal.Request"_L1, "Response"_L1, this,
367 // clang-format off
368 SLOT(gotColorResponse(uint,QVariantMap))
369 // clang-format on
370 );
371 }
372 });
373 }
374
375private Q_SLOTS:
376 void gotColorResponse(uint result, const QVariantMap &map)
377 {
378 if (result != 0)
379 return;
380 if (map.contains(u"color"_s)) {
381 XDGDesktopColor color{};
382 map.value(u"color"_s).value<QDBusArgument>() >> color;
383 Q_EMIT colorPicked(color.toQColor());
384 } else {
385 Q_EMIT colorPicked({});
386 }
387 deleteLater();
388 }
389
390private:
391 const QString m_parentWindowId;
392};
393
394void registerWithHostPortal()
395{
396 static bool registered = false;
397 if (registered) {
398 return;
399 }
400
401 auto message = QDBusMessage::createMethodCall(
402 "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
403 "org.freedesktop.host.portal.Registry"_L1, "Register"_L1);
404 message.setArguments({ QGuiApplication::desktopFileName(), QVariantMap() });
405 auto watcher =
406 new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message), qGuiApp);
407 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [watcher] {
408 watcher->deleteLater();
409 if (watcher->isError()) {
410 // Expected error when running against an older portal
411 if (watcher->error().type() == QDBusError::UnknownInterface || watcher->error().type() == QDBusError::UnknownMethod)
412 qCInfo(lcQpaServices) << "Failed to register with host portal" << watcher->error();
413 else
414 qCWarning(lcQpaServices) << "Failed to register with host portal" << watcher->error();
415 } else {
416 qCDebug(lcQpaServices) << "Successfully registered with host portal as" << QGuiApplication::desktopFileName();
417 registered = true;
418 }
419 });
420}
421} // namespace
422
423#endif // QT_CONFIG(dbus)
424
425QDesktopUnixServices::QDesktopUnixServices()
426{
427 if (detectDesktopEnvironment() == QByteArrayLiteral("UNKNOWN"))
428 return;
429
430#if QT_CONFIG(dbus)
431 if (qEnvironmentVariableIntValue("QT_NO_XDG_DESKTOP_PORTAL") > 0) {
432 return;
433 }
434 QDBusMessage message = QDBusMessage::createMethodCall(
435 "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
436 "org.freedesktop.DBus.Properties"_L1, "Get"_L1);
437 message << "org.freedesktop.portal.Screenshot"_L1
438 << "version"_L1;
439
440 QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
441 auto watcher = new QDBusPendingCallWatcher(pendingCall);
442 m_watcher = watcher;
443 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
444 [this](QDBusPendingCallWatcher *watcher) {
445 watcher->deleteLater();
446 QDBusPendingReply<QVariant> reply = *watcher;
447 if (reply.isError()) {
448 m_hasNoPortal = (reply.error().type() == QDBusError::ServiceUnknown);
449 } else {
450 if (reply.value().toUInt() >= 2)
451 m_hasScreenshotPortalWithColorPicking = true;
452 }
453 });
454
455 if (checkNeedPortalSupport()) {
456 return;
457 }
458
459 // The program might only set the desktopfilename after creating the app
460 // try again when it's running
461 if (!QGuiApplication::desktopFileName().isEmpty()) {
462 registerWithHostPortal();
463 } else {
464 QMetaObject::invokeMethod(
465 qGuiApp,
466 [] {
467 if (QGuiApplication::desktopFileName().isEmpty()) {
468 qCInfo(lcQpaServices) << "QGuiApplication::desktopFileName not set. Unable to register application with portal registry";
469 return;
470 }
471 registerWithHostPortal();
472 },
473 Qt::QueuedConnection);
474 }
475 m_portalWatcher = std::make_unique<QDBusServiceWatcher>(
476 "org.freedesktop.portal.Desktop"_L1, QDBusConnection::sessionBus(),
477 QDBusServiceWatcher::WatchForRegistration);
478 QObject::connect(m_portalWatcher.get(), &QDBusServiceWatcher::serviceRegistered,
479 m_portalWatcher.get(), &registerWithHostPortal);
480#endif
481}
482
483QDesktopUnixServices::~QDesktopUnixServices()
484{
485#if QT_CONFIG(dbus)
486 delete m_watcher;
487#endif
488}
489
490QPlatformServiceColorPicker *QDesktopUnixServices::colorPicker(QWindow *parent)
491{
492#if QT_CONFIG(dbus)
493 // Make double sure that we are in a wayland environment. In particular check
494 // WAYLAND_DISPLAY so also XWayland apps benefit from portal-based color picking.
495 // Outside wayland we'll rather rely on other means than the XDG desktop portal.
496 if (!qEnvironmentVariableIsEmpty("WAYLAND_DISPLAY")
497 || QGuiApplication::platformName().startsWith("wayland"_L1)) {
498 return new XdgDesktopPortalColorPicker(portalWindowIdentifier(parent), parent);
499 }
500 return nullptr;
501#else
502 Q_UNUSED(parent);
503 return nullptr;
504#endif
505}
506
507QByteArray QDesktopUnixServices::desktopEnvironment() const
508{
509 static const QByteArray result = detectDesktopEnvironment();
510 return result;
511}
512
513QString QDesktopUnixServices::portalFocusWindowIdentifier()
514{
515 if (QWindow *focusWindow = QGuiApplication::focusWindow())
516 return portalWindowIdentifier(focusWindow);
517 return QString();
518}
519
520template <typename F>
521bool QDesktopUnixServices::runWithXdgActivationToken(F callback, const QUrl &url)
522{
523#if QT_CONFIG(wayland)
524 QWindow *window = qGuiApp->focusWindow();
525
526 if (!window)
527 return (this->*callback)(url, {});
528
529 auto waylandApp = dynamic_cast<QNativeInterface::QWaylandApplication *>(
530 qGuiApp->platformNativeInterface());
531 auto waylandWindow =
532 dynamic_cast<QNativeInterface::Private::QWaylandWindow *>(window->handle());
533
534 if (!waylandWindow || !waylandApp)
535 return (this->*callback)(url, {});
536
537 // we must capture url by value, because the callback will be run after
538 // the caller has returned to the event loop
539 auto functionToCall = [this, callback, url](const QString &xdgActivationToken) {
540 return (this->*callback)(url, xdgActivationToken);
541 };
542 QObject::connect(waylandWindow,
543 &QNativeInterface::Private::QWaylandWindow::xdgActivationTokenCreated,
544 waylandWindow, functionToCall, Qt::SingleShotConnection);
545 waylandWindow->requestXdgActivationToken(waylandApp->lastInputSerial());
546 return true;
547#else
548 return (this->*callback)(url, {});
549#endif
550}
551
552bool QDesktopUnixServices::openUrlWithoutPortal(const QUrl &url, const QString &xdgActivationToken)
553{
554 LaunchType launchType = url.scheme() == "mailto"_L1 ? LaunchType::Document : LaunchType::Browser;
555 return launchProcess(launchType, url, xdgActivationToken);
556};
557
558bool QDesktopUnixServices::openUrlWithPortal(const QUrl &url, const QString &xdgActivationToken)
559{
560# if QT_CONFIG(dbus)
561 const QString parentWindow = portalFocusWindowIdentifier();
562 QDBusError error = url.scheme() == "mailto"_L1
563 ? xdgDesktopPortalSendEmail(url, parentWindow, xdgActivationToken)
564 : xdgDesktopPortalOpenUrl(url, parentWindow, xdgActivationToken);
565 if (!error.isValid())
566 return true;
567 if (error.type() == QDBusError::ServiceUnknown)
568 m_hasNoPortal = true;
569 // portal activation failed, fall back to executing
570 return openUrlWithoutPortal(url, xdgActivationToken);
571# else
572 Q_UNUSED(url);
573 Q_UNUSED(xdgActivationToken);
574 Q_UNREACHABLE_RETURN(false);
575# endif
576}
577
578bool QDesktopUnixServices::openUrl(const QUrl &url)
579{
580 if (!m_hasNoPortal)
581 return runWithXdgActivationToken(&QDesktopUnixServices::openUrlWithPortal, url);
582
583 return runWithXdgActivationToken(&QDesktopUnixServices::openUrlWithoutPortal, url);
584}
585
586bool QDesktopUnixServices::openDocumentWithoutPortal(const QUrl &url, const QString &xdgActivationToken)
587{
588 return launchProcess(LaunchType::Document, url, xdgActivationToken);
589}
590
591bool QDesktopUnixServices::openDocumentWithPortal(const QUrl &url, const QString &xdgActivationToken)
592{
593# if QT_CONFIG(dbus)
594 const QString parentWindow = portalFocusWindowIdentifier();
595 QDBusError error = xdgDesktopPortalOpenFile(url, parentWindow, xdgActivationToken);
596 if (!error.isValid())
597 return true;
598 if (error.type() == QDBusError::ServiceUnknown)
599 m_hasNoPortal = true;
600 // portal activation failed, fall back to executing
601 return openDocumentWithoutPortal(url, xdgActivationToken);
602# else
603 Q_UNUSED(url);
604 Q_UNUSED(xdgActivationToken);
605 Q_UNREACHABLE_RETURN(false);
606# endif
607}
608
609bool QDesktopUnixServices::openDocument(const QUrl &url)
610{
611 if (!m_hasNoPortal)
612 return runWithXdgActivationToken(&QDesktopUnixServices::openDocumentWithPortal, url);
613
614 return runWithXdgActivationToken(&QDesktopUnixServices::openDocumentWithoutPortal, url);
615}
616
617#else
618QDesktopUnixServices::QDesktopUnixServices() = default;
619QDesktopUnixServices::~QDesktopUnixServices() = default;
620
621QByteArray QDesktopUnixServices::desktopEnvironment() const
622{
623 return QByteArrayLiteral("UNKNOWN");
624}
625
626bool QDesktopUnixServices::openUrl(const QUrl &url)
627{
628 Q_UNUSED(url);
629 qWarning("openUrl() not supported on this platform");
630 return false;
631}
632
633bool QDesktopUnixServices::openDocument(const QUrl &url)
634{
635 Q_UNUSED(url);
636 qWarning("openDocument() not supported on this platform");
637 return false;
638}
639
640QPlatformServiceColorPicker *QDesktopUnixServices::colorPicker(QWindow *parent)
641{
642 Q_UNUSED(parent);
643 return nullptr;
644}
645
646#endif // QT_CONFIG(multiprocess)
647
648QString QDesktopUnixServices::portalWindowIdentifier(QWindow *window)
649{
650 Q_UNUSED(window);
651 return QString();
652}
653
654
655void QDesktopUnixServices::registerDBusMenuForWindow(QWindow *window, const QString &service, const QString &path)
656{
657 Q_UNUSED(window);
658 Q_UNUSED(service);
659 Q_UNUSED(path);
660}
661
662void QDesktopUnixServices::unregisterDBusMenuForWindow(QWindow *window)
663{
664 Q_UNUSED(window);
665}
666
667
668bool QDesktopUnixServices::hasCapability(Capability capability) const
669{
670 switch (capability) {
671 case Capability::ColorPicking:
672 return m_hasScreenshotPortalWithColorPicking;
673 }
674 return false;
675}
676
677void QDesktopUnixServices::setApplicationBadge(qint64 number)
678{
679#if QT_CONFIG(dbus)
680 if (qGuiApp->desktopFileName().isEmpty()) {
681 qCWarning(lcQpaServices, "Cannot set badge number - QGuiApplication::desktopFileName() is empty");
682 return;
683 }
684
685
686 const QString launcherUrl = QStringLiteral("application://") + qGuiApp->desktopFileName() + QStringLiteral(".desktop");
687 const qint64 count = qBound(0, number, 9999);
688 QVariantMap dbusUnityProperties;
689
690 if (count > 0) {
691 dbusUnityProperties[QStringLiteral("count")] = count;
692 dbusUnityProperties[QStringLiteral("count-visible")] = true;
693 } else {
694 dbusUnityProperties[QStringLiteral("count-visible")] = false;
695 }
696
697 auto signal = QDBusMessage::createSignal(QStringLiteral("/com/canonical/unity/launcherentry/")
698 + qGuiApp->applicationName(), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update"));
699
700 signal.setArguments({launcherUrl, dbusUnityProperties});
701
702 QDBusConnection::sessionBus().send(signal);
703#else
704 Q_UNUSED(number)
705#endif
706}
707
708QT_END_NAMESPACE
709
710#include "qdesktopunixservices.moc"