Verified Commit 56c136cc authored by Jonah Brüchert's avatar Jonah Brüchert 🌳
Browse files

Implement forwarding file dialog requests to xdg-desktop portal

This is activated per default on mobile devices.
Contains a code copy from the xdgdesktopportal Qt platform theme.
The file can be found in
src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp
of the qtbase source tree.

A few fixes to the file were necessary to fix compilation with our compiler settings:
 * explicit QUrl from QString conversion
 * explicitly convert c string to QString
 * Don't initialize QString from nullptr, use {} instead

The portal forwarding can be activated by setting
the PLASMA_INTEGRATION_USE_PORTAL environment variable to 1
parent 15c79263
......@@ -46,6 +46,7 @@ set(platformThemeSRCS
../src/platformtheme/kfiletreeview.cpp
../src/platformtheme/kwaylandintegration.cpp
../src/platformtheme/x11integration.cpp
../src/platformtheme/qxdgdesktopportalfiledialog.cpp
${dbus_interface}
)
......
......@@ -30,6 +30,7 @@ set(platformtheme_SRCS
kwaylandintegration.cpp
x11integration.cpp
main.cpp
qxdgdesktopportalfiledialog.cpp
)
qt5_add_dbus_interface(platformtheme_SRCS org.kde.StatusNotifierWatcher.xml statusnotifierwatcher_interface)
......@@ -54,7 +55,6 @@ target_link_libraries(KDEPlasmaPlatformTheme
KF5::WaylandClient
XCB::XCB
${QT5PLATFORMSUPPORT_LIBS}
)
if(HAVE_X11)
......
......@@ -49,6 +49,7 @@
#include <KIO/Global>
#include "qdbusmenubar_p.h"
#include "qxdgdesktopportalfiledialog_p.h"
static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME");
static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH");
......@@ -321,6 +322,9 @@ QPlatformDialogHelper *KdePlatformTheme::createPlatformDialogHelper(QPlatformThe
{
switch (type) {
case QPlatformTheme::FileDialog:
if (useXdgDesktopPortal()) {
return new QXdgDesktopPortalFileDialog;
}
return new KDEPlatformFileDialogHelper;
case QPlatformTheme::FontDialog:
case QPlatformTheme::ColorDialog:
......@@ -390,3 +394,9 @@ void KdePlatformTheme::setQtQuickControlsTheme()
}
QQuickStyle::setStyle(QLatin1String("org.kde.desktop"));
}
bool KdePlatformTheme::useXdgDesktopPortal()
{
static bool usePortal = qEnvironmentVariableIntValue("PLASMA_INTEGRATION_USE_PORTAL") == 1;
return usePortal;
}
......@@ -64,6 +64,8 @@ private:
static void setWindowProperty(QWindow *window, const QByteArray &name, const QByteArray &value);
static bool useXdgDesktopPortal();
KHintsSettings *m_hints;
KFontSettingsData *m_fontsData;
QScopedPointer<KWaylandIntegration> m_kwaylandIntegration;
......
/****************************************************************************
**
** Copyright (C) 2017-2018 Red Hat, Inc
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qxdgdesktopportalfiledialog_p.h"
#include <QtCore/qeventloop.h>
#include <QtDBus/QtDBus>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QFile>
#include <QMetaType>
#include <QMimeType>
#include <QMimeDatabase>
#include <QRandomGenerator>
#include <QWindow>
QT_BEGIN_NAMESPACE
QDBusArgument &operator <<(QDBusArgument &arg, const QXdgDesktopPortalFileDialog::FilterCondition &filterCondition)
{
arg.beginStructure();
arg << filterCondition.type << filterCondition.pattern;
arg.endStructure();
return arg;
}
const QDBusArgument &operator >>(const QDBusArgument &arg, QXdgDesktopPortalFileDialog::FilterCondition &filterCondition)
{
uint type;
QString filterPattern;
arg.beginStructure();
arg >> type >> filterPattern;
filterCondition.type = (QXdgDesktopPortalFileDialog::ConditionType)type;
filterCondition.pattern = filterPattern;
arg.endStructure();
return arg;
}
QDBusArgument &operator <<(QDBusArgument &arg, const QXdgDesktopPortalFileDialog::Filter filter)
{
arg.beginStructure();
arg << filter.name << filter.filterConditions;
arg.endStructure();
return arg;
}
const QDBusArgument &operator >>(const QDBusArgument &arg, QXdgDesktopPortalFileDialog::Filter &filter)
{
QString name;
QXdgDesktopPortalFileDialog::FilterConditionList filterConditions;
arg.beginStructure();
arg >> name >> filterConditions;
filter.name = name;
filter.filterConditions = filterConditions;
arg.endStructure();
return arg;
}
class QXdgDesktopPortalFileDialogPrivate
{
public:
QXdgDesktopPortalFileDialogPrivate(QPlatformFileDialogHelper *nativeFileDialog)
: nativeFileDialog(nativeFileDialog)
{ }
WId winId = 0;
bool modal = false;
bool multipleFiles = false;
bool saveFile = false;
QString acceptLabel;
QString directory;
QString title;
QStringList nameFilters;
QStringList mimeTypesFilters;
QStringList selectedFiles;
QPlatformFileDialogHelper *nativeFileDialog = nullptr;
};
QXdgDesktopPortalFileDialog::QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog)
: QPlatformFileDialogHelper()
, d_ptr(new QXdgDesktopPortalFileDialogPrivate(nativeFileDialog))
{
Q_D(QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog) {
connect(d->nativeFileDialog, SIGNAL(accept()), this, SIGNAL(accept()));
connect(d->nativeFileDialog, SIGNAL(reject()), this, SIGNAL(reject()));
}
}
QXdgDesktopPortalFileDialog::~QXdgDesktopPortalFileDialog()
{
}
void QXdgDesktopPortalFileDialog::initializeDialog()
{
Q_D(QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog)
d->nativeFileDialog->setOptions(options());
if (options()->fileMode() == QFileDialogOptions::ExistingFiles)
d->multipleFiles = true;
if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept))
d->acceptLabel = options()->labelText(QFileDialogOptions::Accept);
if (!options()->windowTitle().isEmpty())
d->title = options()->windowTitle();
if (options()->acceptMode() == QFileDialogOptions::AcceptSave)
d->saveFile = true;
if (!options()->nameFilters().isEmpty())
d->nameFilters = options()->nameFilters();
if (!options()->mimeTypeFilters().isEmpty())
d->mimeTypesFilters = options()->mimeTypeFilters();
setDirectory(options()->initialDirectory());
}
void QXdgDesktopPortalFileDialog::openPortal()
{
Q_D(const QXdgDesktopPortalFileDialog);
QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.portal.Desktop"),
QLatin1String("/org/freedesktop/portal/desktop"),
QLatin1String("org.freedesktop.portal.FileChooser"),
d->saveFile ? QLatin1String("SaveFile") : QLatin1String("OpenFile"));
QString parentWindowId = QLatin1String("x11:") + QString::number(d->winId);
QVariantMap options;
if (!d->acceptLabel.isEmpty())
options.insert(QLatin1String("accept_label"), d->acceptLabel);
options.insert(QLatin1String("modal"), d->modal);
options.insert(QLatin1String("multiple"), d->multipleFiles);
if (d->saveFile) {
if (!d->directory.isEmpty())
options.insert(QLatin1String("current_folder"), QFile::encodeName(d->directory).append('\0'));
if (!d->selectedFiles.isEmpty())
options.insert(QLatin1String("current_file"), QFile::encodeName(d->selectedFiles.first()).append('\0'));
}
// Insert filters
qDBusRegisterMetaType<FilterCondition>();
qDBusRegisterMetaType<FilterConditionList>();
qDBusRegisterMetaType<Filter>();
qDBusRegisterMetaType<FilterList>();
FilterList filterList;
if (!d->mimeTypesFilters.isEmpty()) {
for (const QString &mimeTypefilter : d->mimeTypesFilters) {
QMimeDatabase mimeDatabase;
QMimeType mimeType = mimeDatabase.mimeTypeForName(mimeTypefilter);
// Creates e.g. (1, "image/png")
FilterCondition filterCondition;
filterCondition.type = MimeType;
filterCondition.pattern = mimeTypefilter;
// Creates e.g. [((1, "image/png"))]
FilterConditionList filterConditions;
filterConditions << filterCondition;
// Creates e.g. [("Images", [((1, "image/png"))])]
Filter filter;
filter.name = mimeType.comment();
filter.filterConditions = filterConditions;
filterList << filter;
}
} else if (!d->nameFilters.isEmpty()) {
for (const QString &filter : d->nameFilters) {
// Do parsing:
// Supported format is ("Images (*.png *.jpg)")
QRegularExpression regexp(QString::fromLatin1(QPlatformFileDialogHelper::filterRegExp));
QRegularExpressionMatch match = regexp.match(filter);
if (match.hasMatch()) {
QString userVisibleName = match.captured(1);
QStringList filterStrings = match.captured(2).split(QLatin1Char(' '), Qt::SkipEmptyParts);
FilterConditionList filterConditions;
for (const QString &filterString : filterStrings) {
FilterCondition filterCondition;
filterCondition.type = GlobalPattern;
filterCondition.pattern = filterString;
filterConditions << filterCondition;
}
Filter filter;
filter.name = userVisibleName;
filter.filterConditions = filterConditions;
filterList << filter;
}
}
}
if (!filterList.isEmpty())
options.insert(QLatin1String("filters"), QVariant::fromValue(filterList));
options.insert(QLatin1String("handle_token"), QStringLiteral("qt%1").arg(QRandomGenerator::global()->generate()));
// TODO choices a(ssa(ss)s)
// List of serialized combo boxes to add to the file chooser.
message << parentWindowId << d->title << options;
QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<QDBusObjectPath> reply = *watcher;
if (reply.isError()) {
Q_EMIT reject();
} else {
QDBusConnection::sessionBus().connect({},
reply.value().path(),
QLatin1String("org.freedesktop.portal.Request"),
QLatin1String("Response"),
this,
SLOT(gotResponse(uint,QVariantMap)));
}
});
}
bool QXdgDesktopPortalFileDialog::defaultNameFilterDisables() const
{
return false;
}
void QXdgDesktopPortalFileDialog::setDirectory(const QUrl &directory)
{
Q_D(QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog) {
d->nativeFileDialog->setOptions(options());
d->nativeFileDialog->setDirectory(directory);
}
d->directory = directory.path();
}
QUrl QXdgDesktopPortalFileDialog::directory() const
{
Q_D(const QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly))
return d->nativeFileDialog->directory();
return QUrl(d->directory);
}
void QXdgDesktopPortalFileDialog::selectFile(const QUrl &filename)
{
Q_D(QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog) {
d->nativeFileDialog->setOptions(options());
d->nativeFileDialog->selectFile(filename);
}
d->selectedFiles << filename.path();
}
QList<QUrl> QXdgDesktopPortalFileDialog::selectedFiles() const
{
Q_D(const QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly))
return d->nativeFileDialog->selectedFiles();
QList<QUrl> files;
for (const QString &file : d->selectedFiles) {
files << QUrl(file);
}
return files;
}
void QXdgDesktopPortalFileDialog::setFilter()
{
Q_D(QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog) {
d->nativeFileDialog->setOptions(options());
d->nativeFileDialog->setFilter();
}
}
void QXdgDesktopPortalFileDialog::selectNameFilter(const QString &filter)
{
Q_D(QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog) {
d->nativeFileDialog->setOptions(options());
d->nativeFileDialog->selectNameFilter(filter);
}
}
QString QXdgDesktopPortalFileDialog::selectedNameFilter() const
{
// TODO
return QString();
}
void QXdgDesktopPortalFileDialog::exec()
{
Q_D(QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) {
d->nativeFileDialog->exec();
return;
}
// HACK we have to avoid returning until we emit that the dialog was accepted or rejected
QEventLoop loop;
loop.connect(this, SIGNAL(accept()), SLOT(quit()));
loop.connect(this, SIGNAL(reject()), SLOT(quit()));
loop.exec();
}
void QXdgDesktopPortalFileDialog::hide()
{
Q_D(QXdgDesktopPortalFileDialog);
if (d->nativeFileDialog)
d->nativeFileDialog->hide();
}
bool QXdgDesktopPortalFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
{
Q_D(QXdgDesktopPortalFileDialog);
initializeDialog();
d->modal = windowModality != Qt::NonModal;
d->winId = parent ? parent->winId() : 0;
if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly))
return d->nativeFileDialog->show(windowFlags, windowModality, parent);
openPortal();
return true;
}
void QXdgDesktopPortalFileDialog::gotResponse(uint response, const QVariantMap &results)
{
Q_D(QXdgDesktopPortalFileDialog);
if (!response) {
if (results.contains(QLatin1String("uris")))
d->selectedFiles = results.value(QLatin1String("uris")).toStringList();
Q_EMIT accept();
} else {
Q_EMIT reject();
}
}
QT_END_NAMESPACE
/****************************************************************************
**
** Copyright (C) 2017-2018 Red Hat, Inc
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QXDGDESKTOPPORTALFILEDIALOG_P_H
#define QXDGDESKTOPPORTALFILEDIALOG_P_H
#include <qpa/qplatformdialoghelper.h>
#include <QVector>
QT_BEGIN_NAMESPACE
class QXdgDesktopPortalFileDialogPrivate;
class QXdgDesktopPortalFileDialog : public QPlatformFileDialogHelper
{
Q_OBJECT
Q_DECLARE_PRIVATE(QXdgDesktopPortalFileDialog)
public:
enum ConditionType : uint {
GlobalPattern = 0,
MimeType = 1
};
// Filters a(sa(us))
// Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]
struct FilterCondition {
ConditionType type;
QString pattern; // E.g. '*ico' or 'image/png'
};
typedef QVector<FilterCondition> FilterConditionList;
struct Filter {
QString name; // E.g. 'Images' or 'Text
FilterConditionList filterConditions; // E.g. [(0, '*.ico'), (1, 'image/png')] or [(0, '*.txt')]
};
typedef QVector<Filter> FilterList;
QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog = nullptr);
~QXdgDesktopPortalFileDialog();
bool defaultNameFilterDisables() const override;
QUrl directory() const override;
void setDirectory(const QUrl &directory) override;
void selectFile(const QUrl &filename) override;
QList<QUrl> selectedFiles() const override;
void setFilter() override;
void selectNameFilter(const QString &filter) override;
QString selectedNameFilter() const override;
void exec() override;
bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override;
void hide() override;
private Q_SLOTS:
void gotResponse(uint response, const QVariantMap &results);
private:
void initializeDialog();
void openPortal();
QScopedPointer<QXdgDesktopPortalFileDialogPrivate> d_ptr;
};
QT_END_NAMESPACE
Q_DECLARE_METATYPE(QXdgDesktopPortalFileDialog::FilterCondition)
Q_DECLARE_METATYPE(QXdgDesktopPortalFileDialog::FilterConditionList)
Q_DECLARE_METATYPE(QXdgDesktopPortalFileDialog::Filter)
Q_DECLARE_METATYPE(QXdgDesktopPortalFileDialog::FilterList)
#endif // QXDGDESKTOPPORTALFILEDIALOG_P_H
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment