Sophie

Sophie

distrib > Mageia > cauldron > x86_64 > media > core-release-src > by-pkgid > 4c532bfb9916518564da5ba2805999f5 > files > 53

qtbase5-5.15.12-3.mga10.src.rpm

From 9d65da811207992a97c5391d1e2e1981f8bae114 Mon Sep 17 00:00:00 2001
From: Harald Sitter <sitter@kde.org>
Date: Mon, 11 Jul 2022 14:45:40 +0200
Subject: [PATCH 053/147] add color picking support on wayland using the XDG
 desktop portal

On wayland applications are not trusted to perform screen grabs by
default, it is however possible to let the user specifically pick the
color of a pixel using the XDG desktop portal (otherwise used for
sandboxing etc.). Try to use this portal on unix systems by default.

To support this use case some extra abstraction is necessary as this
constitutes a platformservice rather than a platform feature. To that
end the QPlatformService has gained a capability system and a pure
virtual helper class to facilitate asynchronous color picking. When
supported the color picking capability takes precedence over the custom
picking code in QColorDialog.

Fixes: QTBUG-81538
Change-Id: I4acb3af11d459e9d5ebefe5abbb41e50e3ccf7f0
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit b646c7b76c7787cff57bca0fde04d9f58abdfbb8)
---
 src/gui/kernel/qplatformservices.cpp          |  24 +++
 src/gui/kernel/qplatformservices.h            |  20 +++
 .../genericunix/qgenericunixservices.cpp      | 154 ++++++++++++++++++
 .../genericunix/qgenericunixservices_p.h      |   7 +-
 src/widgets/dialogs/qcolordialog.cpp          |  17 ++
 5 files changed, 221 insertions(+), 1 deletion(-)

diff --git a/src/gui/kernel/qplatformservices.cpp b/src/gui/kernel/qplatformservices.cpp
index fdc6a6c4aa..ac47f98c5d 100644
--- a/src/gui/kernel/qplatformservices.cpp
+++ b/src/gui/kernel/qplatformservices.cpp
@@ -55,6 +55,19 @@ QT_BEGIN_NAMESPACE
     \brief The QPlatformServices provides the backend for desktop-related functionality.
 */
 
+/*!
+    \enum QPlatformServices::Capability
+
+    Capabilities are used to determine a specific platform service's availability.
+
+    \value ColorPickingFromScreen The platform natively supports color picking from screen.
+    This capability indicates that the platform supports "opaque" color picking, where the
+    platform implements a complete user experience for color picking and outputs a color.
+    This is in contrast to the application implementing the color picking user experience
+    (taking care of showing a cross hair, instructing the platform integration to obtain
+    the color at a given pixel, etc.). The related service function is pickColor().
+ */
+
 QPlatformServices::QPlatformServices()
 { }
 
@@ -85,5 +98,16 @@ QByteArray QPlatformServices::desktopEnvironment() const
     return QByteArray("UNKNOWN");
 }
 
+QPlatformServiceColorPicker *QPlatformServices::colorPicker(QWindow *parent)
+{
+    Q_UNUSED(parent);
+    return nullptr;
+}
+
+bool QPlatformServices::hasCapability(Capability capability) const
+{
+    Q_UNUSED(capability)
+    return false;
+}
 
 QT_END_NAMESPACE
diff --git a/src/gui/kernel/qplatformservices.h b/src/gui/kernel/qplatformservices.h
index 5de96cfa7d..a8b2a4ce71 100644
--- a/src/gui/kernel/qplatformservices.h
+++ b/src/gui/kernel/qplatformservices.h
@@ -50,16 +50,32 @@
 //
 
 #include <QtGui/qtguiglobal.h>
+#include <QtCore/qobject.h>
 
 QT_BEGIN_NAMESPACE
 
 class QUrl;
+class QWindow;
+
+class Q_GUI_EXPORT QPlatformServiceColorPicker : public QObject
+{
+    Q_OBJECT
+public:
+    using QObject::QObject;
+    virtual void pickColor() = 0;
+Q_SIGNALS:
+    void colorPicked(const QColor &color);
+};
 
 class Q_GUI_EXPORT QPlatformServices
 {
 public:
     Q_DISABLE_COPY_MOVE(QPlatformServices)
 
+    enum Capability {
+        ColorPicking,
+    };
+
     QPlatformServices();
     virtual ~QPlatformServices() { }
 
@@ -67,6 +83,10 @@ public:
     virtual bool openDocument(const QUrl &url);
 
     virtual QByteArray desktopEnvironment() const;
+
+    virtual bool hasCapability(Capability capability) const;
+
+    virtual QPlatformServiceColorPicker *colorPicker(QWindow *parent = nullptr);
 };
 
 QT_END_NAMESPACE
diff --git a/src/platformsupport/services/genericunix/qgenericunixservices.cpp b/src/platformsupport/services/genericunix/qgenericunixservices.cpp
index f0d1722c95..fa92a0dfa6 100644
--- a/src/platformsupport/services/genericunix/qgenericunixservices.cpp
+++ b/src/platformsupport/services/genericunix/qgenericunixservices.cpp
@@ -58,6 +58,10 @@
 #include <QtCore/QFileInfo>
 #include <QtCore/QUrlQuery>
 
+#include <QtGui/QColor>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QWindow>
+
 #include <QtDBus/QDBusConnection>
 #include <QtDBus/QDBusMessage>
 #include <QtDBus/QDBusPendingCall>
@@ -298,8 +302,132 @@ static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url)
 
     return QDBusConnection::sessionBus().call(message);
 }
+
+namespace {
+struct XDGDesktopColor
+{
+    double r = 0;
+    double g = 0;
+    double b = 0;
+
+    QColor toQColor() const
+    {
+        constexpr auto rgbMax = 255;
+        return { static_cast<int>(r * rgbMax), static_cast<int>(g * rgbMax),
+                 static_cast<int>(b * rgbMax) };
+    }
+};
+
+const QDBusArgument &operator>>(const QDBusArgument &argument, XDGDesktopColor &myStruct)
+{
+    argument.beginStructure();
+    argument >> myStruct.r >> myStruct.g >> myStruct.b;
+    argument.endStructure();
+    return argument;
+}
+
+class XdgDesktopPortalColorPicker : public QPlatformServiceColorPicker
+{
+    Q_OBJECT
+public:
+    XdgDesktopPortalColorPicker(const QString &parentWindowId, QWindow *parent)
+        : QPlatformServiceColorPicker(parent), m_parentWindowId(parentWindowId)
+    {
+    }
+
+    void pickColor() override
+    {
+        // DBus signature:
+        // PickColor (IN   s      parent_window,
+        //            IN   a{sv}  options
+        //            OUT  o      handle)
+        // Options:
+        // handle_token (s) -  A string that will be used as the last element of the @handle.
+
+        QDBusMessage message = QDBusMessage::createMethodCall(
+                QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"),
+                QStringLiteral("org.freedesktop.portal.Screenshot"), QStringLiteral("PickColor"));
+        message << m_parentWindowId << QVariantMap();
+
+        QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
+        auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
+        connect(watcher, &QDBusPendingCallWatcher::finished, this,
+                [this](QDBusPendingCallWatcher *watcher) {
+                    watcher->deleteLater();
+                    QDBusPendingReply<QDBusObjectPath> reply = *watcher;
+                    if (reply.isError()) {
+                        qWarning("DBus call to pick color failed: %s",
+                                 qPrintable(reply.error().message()));
+                        Q_EMIT colorPicked({});
+                    } else {
+                        QDBusConnection::sessionBus().connect(
+                                QStringLiteral("org.freedesktop.portal.Desktop"), reply.value().path(),
+                                QStringLiteral("org.freedesktop.portal.Request"), QStringLiteral("Response"), this,
+                                // clang-format off
+                                SLOT(gotColorResponse(uint,QVariantMap))
+                                // clang-format on
+                        );
+                    }
+                });
+    }
+
+private Q_SLOTS:
+    void gotColorResponse(uint result, const QVariantMap &map)
+    {
+        if (result != 0)
+            return;
+        XDGDesktopColor color{};
+        map.value(QStringLiteral("color")).value<QDBusArgument>() >> color;
+        Q_EMIT colorPicked(color.toQColor());
+        deleteLater();
+    }
+
+private:
+    const QString m_parentWindowId;
+};
+} // namespace
+
 #endif // QT_CONFIG(dbus)
 
+QGenericUnixServices::QGenericUnixServices()
+{
+#if QT_CONFIG(dbus)
+    QDBusMessage message = QDBusMessage::createMethodCall(
+            QStringLiteral("org.freedesktop.portal.Desktop"), QStringLiteral("/org/freedesktop/portal/desktop"),
+            QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("Get"));
+    message << QStringLiteral("org.freedesktop.portal.Screenshot")
+            << QStringLiteral("version");
+
+    QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
+    auto watcher = new QDBusPendingCallWatcher(pendingCall);
+    QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
+                     [this](QDBusPendingCallWatcher *watcher) {
+                         watcher->deleteLater();
+                         QDBusPendingReply<QVariant> reply = *watcher;
+                         if (!reply.isError() && reply.value().toUInt() >= 2)
+                             m_hasScreenshotPortalWithColorPicking = true;
+                     });
+
+#endif
+}
+
+QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent)
+{
+#if QT_CONFIG(dbus)
+    // Make double sure that we are in a wayland environment. In particular check
+    // WAYLAND_DISPLAY so also XWayland apps benefit from portal-based color picking.
+    // Outside wayland we'll rather rely on other means than the XDG desktop portal.
+    if (!qEnvironmentVariableIsEmpty("WAYLAND_DISPLAY")
+        || QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
+        return new XdgDesktopPortalColorPicker(portalWindowIdentifier(parent), parent);
+    }
+    return nullptr;
+#else
+    Q_UNUSED(parent);
+    return nullptr;
+#endif
+}
+
 QByteArray QGenericUnixServices::desktopEnvironment() const
 {
     static const QByteArray result = detectDesktopEnvironment();
@@ -354,6 +482,8 @@ bool QGenericUnixServices::openDocument(const QUrl &url)
 }
 
 #else
+QGenericUnixServices::QGenericUnixServices() = default;
+
 QByteArray QGenericUnixServices::desktopEnvironment() const
 {
     return QByteArrayLiteral("UNKNOWN");
@@ -373,6 +503,30 @@ bool QGenericUnixServices::openDocument(const QUrl &url)
     return false;
 }
 
+QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent)
+{
+    Q_UNUSED(parent);
+    return nullptr;
+}
+
 #endif // QT_NO_MULTIPROCESS
 
+QString QGenericUnixServices::portalWindowIdentifier(QWindow *window)
+{
+    if (QGuiApplication::platformName() == QLatin1String("xcb"))
+        return QStringLiteral("x11:") + QString::number(window->winId(), 16);
+    return QString();
+}
+
+bool QGenericUnixServices::hasCapability(Capability capability) const
+{
+    switch (capability) {
+    case Capability::ColorPicking:
+        return m_hasScreenshotPortalWithColorPicking;
+    }
+    return false;
+}
+
 QT_END_NAMESPACE
+
+#include "qgenericunixservices.moc"
diff --git a/src/platformsupport/services/genericunix/qgenericunixservices_p.h b/src/platformsupport/services/genericunix/qgenericunixservices_p.h
index 8ac3de6f03..30924e64bd 100644
--- a/src/platformsupport/services/genericunix/qgenericunixservices_p.h
+++ b/src/platformsupport/services/genericunix/qgenericunixservices_p.h
@@ -59,16 +59,21 @@ QT_BEGIN_NAMESPACE
 class QGenericUnixServices : public QPlatformServices
 {
 public:
-    QGenericUnixServices() {}
+    QGenericUnixServices();
 
     QByteArray desktopEnvironment() const override;
 
+    bool hasCapability(Capability capability) const override;
     bool openUrl(const QUrl &url) override;
     bool openDocument(const QUrl &url) override;
+    QPlatformServiceColorPicker *colorPicker(QWindow *parent = nullptr) override;
+
+    virtual QString portalWindowIdentifier(QWindow *window);
 
 private:
     QString m_webBrowser;
     QString m_documentLauncher;
+    bool m_hasScreenshotPortalWithColorPicking = false;
 };
 
 QT_END_NAMESPACE
diff --git a/src/widgets/dialogs/qcolordialog.cpp b/src/widgets/dialogs/qcolordialog.cpp
index 4247731275..cb325be85c 100644
--- a/src/widgets/dialogs/qcolordialog.cpp
+++ b/src/widgets/dialogs/qcolordialog.cpp
@@ -78,7 +78,10 @@
 #include "qwindow.h"
 
 #include "private/qdialog_p.h"
+#include "private/qguiapplication_p.h"
 
+#include <qpa/qplatformservices.h>
+#include <qpa/qplatformintegration.h>
 #include <algorithm>
 
 QT_BEGIN_NAMESPACE
@@ -1611,6 +1614,20 @@ void QColorDialogPrivate::_q_newStandard(int r, int c)
 void QColorDialogPrivate::_q_pickScreenColor()
 {
     Q_Q(QColorDialog);
+
+    auto *platformServices = QGuiApplicationPrivate::platformIntegration()->services();
+    if (platformServices->hasCapability(QPlatformServices::Capability::ColorPicking)) {
+        if (auto *colorPicker = platformServices->colorPicker(q->windowHandle())) {
+            q->connect(colorPicker, &QPlatformServiceColorPicker::colorPicked, q,
+                       [q, colorPicker](const QColor &color) {
+                           colorPicker->deleteLater();
+                           q->setCurrentColor(color);
+                       });
+            colorPicker->pickColor();
+            return;
+        }
+    }
+
     if (!colorPickingEventFilter)
         colorPickingEventFilter = new QColorPickingEventFilter(this, q);
     q->installEventFilter(colorPickingEventFilter);
-- 
2.40.1