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(c: '/'); |
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 | XDGDesktopColor color{}; |
358 | map.value(key: u"color"_s ).value<QDBusArgument>() >> color; |
359 | Q_EMIT colorPicked(color: color.toQColor()); |
360 | deleteLater(); |
361 | } |
362 | |
363 | private: |
364 | const QString m_parentWindowId; |
365 | }; |
366 | } // namespace |
367 | |
368 | #endif // QT_CONFIG(dbus) |
369 | |
370 | QGenericUnixServices::QGenericUnixServices() |
371 | { |
372 | #if QT_CONFIG(dbus) |
373 | if (qEnvironmentVariableIntValue(varName: "QT_NO_XDG_DESKTOP_PORTAL" ) > 0) { |
374 | return; |
375 | } |
376 | QDBusMessage message = QDBusMessage::createMethodCall( |
377 | destination: "org.freedesktop.portal.Desktop"_L1 , path: "/org/freedesktop/portal/desktop"_L1 , |
378 | interface: "org.freedesktop.DBus.Properties"_L1 , method: "Get"_L1 ); |
379 | message << "org.freedesktop.portal.Screenshot"_L1 |
380 | << "version"_L1 ; |
381 | |
382 | QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); |
383 | auto watcher = new QDBusPendingCallWatcher(pendingCall); |
384 | QObject::connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: watcher, |
385 | slot: [this](QDBusPendingCallWatcher *watcher) { |
386 | watcher->deleteLater(); |
387 | QDBusPendingReply<QVariant> reply = *watcher; |
388 | if (!reply.isError() && reply.value().toUInt() >= 2) |
389 | m_hasScreenshotPortalWithColorPicking = true; |
390 | }); |
391 | |
392 | #endif |
393 | } |
394 | |
395 | QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent) |
396 | { |
397 | #if QT_CONFIG(dbus) |
398 | // Make double sure that we are in a wayland environment. In particular check |
399 | // WAYLAND_DISPLAY so also XWayland apps benefit from portal-based color picking. |
400 | // Outside wayland we'll rather rely on other means than the XDG desktop portal. |
401 | if (!qEnvironmentVariableIsEmpty(varName: "WAYLAND_DISPLAY" ) |
402 | || QGuiApplication::platformName().startsWith(s: "wayland"_L1 )) { |
403 | return new XdgDesktopPortalColorPicker(portalWindowIdentifier(window: parent), parent); |
404 | } |
405 | return nullptr; |
406 | #else |
407 | Q_UNUSED(parent); |
408 | return nullptr; |
409 | #endif |
410 | } |
411 | |
412 | QByteArray QGenericUnixServices::desktopEnvironment() const |
413 | { |
414 | static const QByteArray result = detectDesktopEnvironment(); |
415 | return result; |
416 | } |
417 | |
418 | template<typename F> |
419 | void runWithXdgActivationToken(F &&functionToCall) |
420 | { |
421 | QWindow *window = qGuiApp->focusWindow(); |
422 | |
423 | if (!window) { |
424 | functionToCall({}); |
425 | return; |
426 | } |
427 | |
428 | auto waylandApp = dynamic_cast<QNativeInterface::QWaylandApplication *>( |
429 | qGuiApp->platformNativeInterface()); |
430 | auto waylandWindow = |
431 | dynamic_cast<QNativeInterface::Private::QWaylandWindow *>(window->handle()); |
432 | |
433 | if (!waylandWindow || !waylandApp) { |
434 | functionToCall({}); |
435 | return; |
436 | } |
437 | |
438 | QObject::connect(waylandWindow, |
439 | &QNativeInterface::Private::QWaylandWindow::xdgActivationTokenCreated, |
440 | waylandWindow, functionToCall, Qt::SingleShotConnection); |
441 | waylandWindow->requestXdgActivationToken(serial: waylandApp->lastInputSerial()); |
442 | } |
443 | |
444 | bool QGenericUnixServices::openUrl(const QUrl &url) |
445 | { |
446 | auto openUrlInternal = [this](const QUrl &url, const QString &xdgActivationToken) { |
447 | if (url.scheme() == "mailto"_L1 ) { |
448 | # if QT_CONFIG(dbus) |
449 | if (checkNeedPortalSupport()) { |
450 | const QString parentWindow = QGuiApplication::focusWindow() |
451 | ? portalWindowIdentifier(window: QGuiApplication::focusWindow()) |
452 | : QString(); |
453 | QDBusError error = xdgDesktopPortalSendEmail(url, parentWindow, xdgActivationToken); |
454 | if (!error.isValid()) |
455 | return true; |
456 | |
457 | // service not running, fall back |
458 | } |
459 | # endif |
460 | return openDocument(url); |
461 | } |
462 | |
463 | # if QT_CONFIG(dbus) |
464 | if (checkNeedPortalSupport()) { |
465 | const QString parentWindow = QGuiApplication::focusWindow() |
466 | ? portalWindowIdentifier(window: QGuiApplication::focusWindow()) |
467 | : QString(); |
468 | QDBusError error = xdgDesktopPortalOpenUrl(url, parentWindow, xdgActivationToken); |
469 | if (!error.isValid()) |
470 | return true; |
471 | } |
472 | # endif |
473 | |
474 | if (m_webBrowser.isEmpty() |
475 | && !detectWebBrowser(desktop: desktopEnvironment(), checkBrowserVariable: true, browser: &m_webBrowser)) { |
476 | qWarning(msg: "Unable to detect a web browser to launch '%s'" , qPrintable(url.toString())); |
477 | return false; |
478 | } |
479 | return launch(launcher: m_webBrowser, url, xdgActivationToken); |
480 | }; |
481 | |
482 | if (QGuiApplication::platformName().startsWith(s: "wayland"_L1 )) { |
483 | runWithXdgActivationToken( |
484 | functionToCall: [openUrlInternal, url](const QString &token) { openUrlInternal(url, token); }); |
485 | |
486 | return true; |
487 | |
488 | } else { |
489 | return openUrlInternal(url, QString()); |
490 | } |
491 | } |
492 | |
493 | bool QGenericUnixServices::openDocument(const QUrl &url) |
494 | { |
495 | auto openDocumentInternal = [this](const QUrl &url, const QString &xdgActivationToken) { |
496 | |
497 | # if QT_CONFIG(dbus) |
498 | if (checkNeedPortalSupport()) { |
499 | const QString parentWindow = QGuiApplication::focusWindow() |
500 | ? portalWindowIdentifier(window: QGuiApplication::focusWindow()) |
501 | : QString(); |
502 | QDBusError error = xdgDesktopPortalOpenFile(url, parentWindow, xdgActivationToken); |
503 | if (!error.isValid()) |
504 | return true; |
505 | } |
506 | # endif |
507 | |
508 | if (m_documentLauncher.isEmpty() |
509 | && !detectWebBrowser(desktop: desktopEnvironment(), checkBrowserVariable: false, browser: &m_documentLauncher)) { |
510 | qWarning(msg: "Unable to detect a launcher for '%s'" , qPrintable(url.toString())); |
511 | return false; |
512 | } |
513 | return launch(launcher: m_documentLauncher, url, xdgActivationToken); |
514 | }; |
515 | |
516 | if (QGuiApplication::platformName().startsWith(s: "wayland"_L1 )) { |
517 | runWithXdgActivationToken(functionToCall: [openDocumentInternal, url](const QString &token) { |
518 | openDocumentInternal(url, token); |
519 | }); |
520 | |
521 | return true; |
522 | } else { |
523 | return openDocumentInternal(url, QString()); |
524 | } |
525 | } |
526 | |
527 | #else |
528 | QGenericUnixServices::QGenericUnixServices() = default; |
529 | |
530 | QByteArray QGenericUnixServices::desktopEnvironment() const |
531 | { |
532 | return QByteArrayLiteral("UNKNOWN" ); |
533 | } |
534 | |
535 | bool QGenericUnixServices::openUrl(const QUrl &url) |
536 | { |
537 | Q_UNUSED(url); |
538 | qWarning("openUrl() not supported on this platform" ); |
539 | return false; |
540 | } |
541 | |
542 | bool QGenericUnixServices::openDocument(const QUrl &url) |
543 | { |
544 | Q_UNUSED(url); |
545 | qWarning("openDocument() not supported on this platform" ); |
546 | return false; |
547 | } |
548 | |
549 | QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent) |
550 | { |
551 | Q_UNUSED(parent); |
552 | return nullptr; |
553 | } |
554 | |
555 | #endif // QT_NO_MULTIPROCESS |
556 | |
557 | QString QGenericUnixServices::portalWindowIdentifier(QWindow *window) |
558 | { |
559 | if (QGuiApplication::platformName() == QLatin1String("xcb" )) |
560 | return "x11:"_L1 + QString::number(window->winId(), base: 16); |
561 | |
562 | return QString(); |
563 | } |
564 | |
565 | bool QGenericUnixServices::hasCapability(Capability capability) const |
566 | { |
567 | switch (capability) { |
568 | case Capability::ColorPicking: |
569 | return m_hasScreenshotPortalWithColorPicking; |
570 | } |
571 | return false; |
572 | } |
573 | |
574 | void QGenericUnixServices::setApplicationBadge(qint64 number) |
575 | { |
576 | #if QT_CONFIG(dbus) |
577 | if (qGuiApp->desktopFileName().isEmpty()) { |
578 | qWarning(msg: "QGuiApplication::desktopFileName() is empty" ); |
579 | return; |
580 | } |
581 | |
582 | |
583 | const QString launcherUrl = QStringLiteral("application://" ) + qGuiApp->desktopFileName() + QStringLiteral(".desktop" ); |
584 | const qint64 count = qBound(min: 0, val: number, max: 9999); |
585 | QVariantMap dbusUnityProperties; |
586 | |
587 | if (count > 0) { |
588 | dbusUnityProperties[QStringLiteral("count" )] = count; |
589 | dbusUnityProperties[QStringLiteral("count-visible" )] = true; |
590 | } else { |
591 | dbusUnityProperties[QStringLiteral("count-visible" )] = false; |
592 | } |
593 | |
594 | auto signal = QDBusMessage::createSignal(QStringLiteral("/com/canonical/unity/launcherentry/" ) |
595 | + qGuiApp->applicationName(), QStringLiteral("com.canonical.Unity.LauncherEntry" ), QStringLiteral("Update" )); |
596 | |
597 | signal.setArguments({launcherUrl, dbusUnityProperties}); |
598 | |
599 | QDBusConnection::sessionBus().send(message: signal); |
600 | #else |
601 | Q_UNUSED(number) |
602 | #endif |
603 | } |
604 | |
605 | QT_END_NAMESPACE |
606 | |
607 | #include "qgenericunixservices.moc" |
608 | |