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