Skip to content

Commit

Permalink
ColorPicker: use XDG Desktop Portal on Wayland TODO
Browse files Browse the repository at this point in the history
TODO: show error message when not supported
  • Loading branch information
gfgit committed Jul 12, 2024
1 parent 89cc494 commit dd5fdfa
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 14 deletions.
156 changes: 142 additions & 14 deletions plugin-colorpicker/colorpicker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
#include <QScreen>
#include <QSvgRenderer>

#include <QDBusConnection>
#include <QDBusPendingReply>

//NOTE: Xlib.h defines Bool which conflicts with QJsonValue::Type enum
#include <X11/Xlib.h>
#undef Bool
Expand Down Expand Up @@ -77,6 +80,33 @@ void ColorPicker::realign()
mWidget.update(panel()->lineCount() <= 1 ? panel()->isHorizontal() : !panel()->isHorizontal());
}

void ColorPicker::queryXDGSupport()
{
if (qEnvironmentVariableIntValue("QT_NO_XDG_DESKTOP_PORTAL") > 0) {
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(
QLatin1String("org.freedesktop.portal.Desktop"),
QLatin1String("/org/freedesktop/portal/desktop"),
QLatin1String("org.freedesktop.DBus.Properties"),
QLatin1String("Get"));
message << QLatin1String("org.freedesktop.portal.Screenshot")
<< QLatin1String("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;
});

//TODO: show error tooltip if not supported
//NOTE: on Wayland we cannot pick color without it
}


ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent)
{
Expand Down Expand Up @@ -108,7 +138,8 @@ ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent)
layout->addWidget(mColorButton);
setLayout(layout);

connect(mPickerButton, &QToolButton::clicked, this, &ColorPickerWidget::captureMouse);
connect(mPickerButton, &QToolButton::clicked, this, &ColorPickerWidget::startCapturingColor);

connect(mColorButton, &QToolButton::clicked, this, [&]()
{
buildMenu();
Expand Down Expand Up @@ -162,29 +193,86 @@ void ColorPickerWidget::mouseReleaseEvent(QMouseEvent *event)
qWarning() << "WAYLAND does not support grabbing windows";
}

mColorButton->setColor(col);
paste(col.name());
setCapturedColor(col);

if (mColorsList.contains(col))
mCapturing = false;
releaseMouse();

if (!mPickerButton->contentsRect().contains(mapFromGlobal(QCursor::pos())))
{
mColorsList.move(mColorsList.indexOf(col), 0);
QApplication::sendEvent(mPickerButton, new QEvent(QEvent::Leave));
}
else
}

void ColorPickerWidget::startCapturingColor()
{
//NOTE: see qt6 `src/gui/platform/unix/qgenericunixservices.cpp`

// 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")))
{
mColorsList.prepend(col);
// On Wayland use XDG Desktop Portal

QString m_parentWindowId; //TODO

QDBusMessage message = QDBusMessage::createMethodCall(
QLatin1String("org.freedesktop.portal.Desktop"),
QLatin1String("/org/freedesktop/portal/desktop"),
QLatin1String("org.freedesktop.portal.Screenshot"),
QLatin1String("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()));
setCapturedColor({});
} else {
QDBusConnection::sessionBus().connect(
QLatin1String("org.freedesktop.portal.Desktop"),
reply.value().path(),
QLatin1String("org.freedesktop.portal.Request"),
QLatin1String("Response"),
this,
// clang-format off
SLOT(gotColorResponse(uint,QVariantMap))
// clang-format on
);
}
});
}

if (mColorsList.size() > 10)
else if (qGuiApp->nativeInterface<QNativeInterface::QX11Application>())
{
mColorsList.removeLast();
// On X11 grab mouse and let `mouseReleaseEvent()` retrieve color
captureMouse();
}
}

mCapturing = false;
releaseMouse();
void ColorPickerWidget::setCapturedColor(const QColor &color)
{
mColorButton->setColor(color);
paste(color.name());

if (!mPickerButton->contentsRect().contains(mapFromGlobal(QCursor::pos())))
if (mColorsList.contains(color))
{
QApplication::sendEvent(mPickerButton, new QEvent(QEvent::Leave));
mColorsList.move(mColorsList.indexOf(color), 0);
}
else
{
mColorsList.prepend(color);
}

if (mColorsList.size() > 10)
{
mColorsList.removeLast();
}
}

Expand All @@ -195,6 +283,46 @@ void ColorPickerWidget::captureMouse()
mCapturing = true;
}

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;
}

void ColorPickerWidget::gotColorResponse(uint result, const QVariantMap &map)
{
auto colorProp = QStringLiteral("color");

if (result != 0)
return;
if (map.contains(colorProp))
{
XDGDesktopColor color{};
map.value(colorProp).value<QDBusArgument>() >> color;
setCapturedColor(color.toQColor());
}
else
{
setCapturedColor({});
}
}


QIcon ColorPickerWidget::colorIcon(QColor color)
{
Expand Down
8 changes: 8 additions & 0 deletions plugin-colorpicker/colorpicker.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ class ColorPickerWidget : public QWidget
void mouseReleaseEvent(QMouseEvent *event);

private slots:
void startCapturingColor();
void setCapturedColor(const QColor& color);
void captureMouse();
void gotColorResponse(uint result, const QVariantMap& map);

private:
static const QString svgIcon;
Expand Down Expand Up @@ -91,8 +94,13 @@ class ColorPicker : public QObject, public ILXQtPanelPlugin

virtual void realign() override;

private:
void queryXDGSupport();

private:
ColorPickerWidget mWidget;

bool m_hasScreenshotPortalWithColorPicking = false;
};

class ColorPickerLibrary: public QObject, public ILXQtPanelPluginLibrary
Expand Down

0 comments on commit dd5fdfa

Please sign in to comment.