mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 23:27:09 +02:00
Add XDG Desktop Portal based file dialog implementation from Qt
This allows to use portal dialogs more flexibly (e.g. fallback mechanism) This also allows to have any changes we want for portal dialogs without patchig Qt No more need to override QT_QPA_PLATFORM to use portal dialogs
This commit is contained in:
parent
ce1b94eb16
commit
36acf60f7e
6 changed files with 700 additions and 33 deletions
|
@ -840,6 +840,8 @@ PRIVATE
|
|||
platform/linux/linux_open_with_dialog.h
|
||||
platform/linux/linux_wayland_integration.cpp
|
||||
platform/linux/linux_wayland_integration.h
|
||||
platform/linux/linux_xdp_file_dialog.cpp
|
||||
platform/linux/linux_xdp_file_dialog.h
|
||||
platform/linux/linux_xlib_helper.cpp
|
||||
platform/linux/linux_xlib_helper.h
|
||||
platform/linux/file_utilities_linux.cpp
|
||||
|
@ -1132,6 +1134,8 @@ if (LINUX AND DESKTOP_APP_DISABLE_DBUS_INTEGRATION)
|
|||
platform/linux/linux_gsd_media_keys.h
|
||||
platform/linux/linux_notification_service_watcher.cpp
|
||||
platform/linux/linux_notification_service_watcher.h
|
||||
platform/linux/linux_xdp_file_dialog.cpp
|
||||
platform/linux/linux_xdp_file_dialog.h
|
||||
platform/linux/notifications_manager_linux.cpp
|
||||
)
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "platform/linux/linux_gtk_integration.h"
|
||||
#include "platform/linux/specific_linux.h"
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
#include "platform/linux/linux_xdp_file_dialog.h"
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
||||
#include <QtGui/QDesktopServices>
|
||||
|
||||
extern "C" {
|
||||
|
@ -77,6 +81,18 @@ bool Get(
|
|||
if (parent) {
|
||||
parent = parent->window();
|
||||
}
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (XDP::Use(type)) {
|
||||
return XDP::Get(
|
||||
parent,
|
||||
files,
|
||||
remoteContent,
|
||||
caption,
|
||||
filter,
|
||||
type,
|
||||
startFile);
|
||||
}
|
||||
#endif // !DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
if (const auto integration = GtkIntegration::Instance()) {
|
||||
if (integration->fileDialogSupported()
|
||||
&& integration->useFileDialog(type)) {
|
||||
|
|
|
@ -640,24 +640,10 @@ bool Supported() {
|
|||
}
|
||||
|
||||
bool Use(Type type) {
|
||||
// use gtk file dialog on gtk-based desktop environments
|
||||
// or if QT_QPA_PLATFORMTHEME=(gtk2|gtk3)
|
||||
// or if portals are used and operation is to open folder
|
||||
// and portal doesn't support folder choosing
|
||||
const auto sandboxedOrCustomPortal = InFlatpak()
|
||||
|| InSnap()
|
||||
|| UseXDGDesktopPortal();
|
||||
|
||||
const auto neededForPortal = (type == Type::ReadFolder)
|
||||
&& !CanOpenDirectoryWithPortal();
|
||||
|
||||
const auto neededNonForced = DesktopEnvironment::IsGtkBased()
|
||||
|| (sandboxedOrCustomPortal && neededForPortal);
|
||||
|
||||
const auto excludeNonForced = sandboxedOrCustomPortal && !neededForPortal;
|
||||
|
||||
return IsGtkIntegrationForced()
|
||||
|| (neededNonForced && !excludeNonForced);
|
||||
|| DesktopEnvironment::IsGtkBased()
|
||||
// use as a fallback for portal dialog
|
||||
|| UseXDGDesktopPortal();
|
||||
}
|
||||
|
||||
bool Get(
|
||||
|
|
536
Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp
Normal file
536
Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp
Normal file
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "platform/linux/linux_xdp_file_dialog.h"
|
||||
|
||||
#include "platform/linux/specific_linux.h"
|
||||
#include "storage/localstorage.h"
|
||||
#include "base/qt_adapters.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>
|
||||
|
||||
namespace Platform {
|
||||
namespace FileDialog {
|
||||
namespace XDP {
|
||||
namespace {
|
||||
|
||||
const char *filterRegExp =
|
||||
"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$";
|
||||
|
||||
QStringList makeFilterList(const QString &filter) {
|
||||
QString f(filter);
|
||||
|
||||
if (f.isEmpty())
|
||||
return QStringList();
|
||||
|
||||
QString sep(QLatin1String(";;"));
|
||||
int i = f.indexOf(sep, 0);
|
||||
if (i == -1) {
|
||||
if (f.indexOf(QLatin1Char('\n'), 0) != -1) {
|
||||
sep = QLatin1Char('\n');
|
||||
i = f.indexOf(sep, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return f.split(sep);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Use(Type type) {
|
||||
return UseXDGDesktopPortal()
|
||||
&& (type != Type::ReadFolder || CanOpenDirectoryWithPortal());
|
||||
}
|
||||
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile) {
|
||||
XDPFileDialog dialog(parent, caption, QString(), filter);
|
||||
|
||||
dialog.setModal(true);
|
||||
if (type == Type::ReadFile || type == Type::ReadFiles) {
|
||||
dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
} else if (type == Type::ReadFolder) {
|
||||
dialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.setOption(QFileDialog::ShowDirsOnly);
|
||||
} else {
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
}
|
||||
if (startFile.isEmpty() || startFile.at(0) != '/') {
|
||||
startFile = cDialogLastPath() + '/' + startFile;
|
||||
}
|
||||
dialog.selectFile(startFile);
|
||||
|
||||
int res = dialog.exec();
|
||||
|
||||
QString path = dialog.directory().path();
|
||||
if (path != cDialogLastPath()) {
|
||||
cSetDialogLastPath(path);
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
if (res == QDialog::Accepted) {
|
||||
QStringList selectedFilesStrings;
|
||||
ranges::transform(
|
||||
dialog.selectedFiles(),
|
||||
ranges::back_inserter(selectedFilesStrings),
|
||||
[](const QUrl &url) { return url.path(); });
|
||||
|
||||
if (type == Type::ReadFiles) {
|
||||
files = selectedFilesStrings;
|
||||
} else {
|
||||
files = selectedFilesStrings.mid(0, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
files = QStringList();
|
||||
remoteContent = QByteArray();
|
||||
return false;
|
||||
}
|
||||
|
||||
QDBusArgument &operator <<(QDBusArgument &arg, const XDPFileDialog::FilterCondition &filterCondition) {
|
||||
arg.beginStructure();
|
||||
arg << filterCondition.type << filterCondition.pattern;
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
|
||||
const QDBusArgument &operator >>(const QDBusArgument &arg, XDPFileDialog::FilterCondition &filterCondition) {
|
||||
uint type;
|
||||
QString filterPattern;
|
||||
arg.beginStructure();
|
||||
arg >> type >> filterPattern;
|
||||
filterCondition.type = (XDPFileDialog::ConditionType)type;
|
||||
filterCondition.pattern = filterPattern;
|
||||
arg.endStructure();
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
QDBusArgument &operator <<(QDBusArgument &arg, const XDPFileDialog::Filter filter) {
|
||||
arg.beginStructure();
|
||||
arg << filter.name << filter.filterConditions;
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
|
||||
const QDBusArgument &operator >>(const QDBusArgument &arg, XDPFileDialog::Filter &filter) {
|
||||
QString name;
|
||||
XDPFileDialog::FilterConditionList filterConditions;
|
||||
arg.beginStructure();
|
||||
arg >> name >> filterConditions;
|
||||
filter.name = name;
|
||||
filter.filterConditions = filterConditions;
|
||||
arg.endStructure();
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
class XDPFileDialogPrivate {
|
||||
public:
|
||||
XDPFileDialogPrivate() {
|
||||
}
|
||||
|
||||
WId winId = 0;
|
||||
bool directoryMode = false;
|
||||
bool modal = false;
|
||||
bool multipleFiles = false;
|
||||
bool saveFile = false;
|
||||
QString acceptLabel;
|
||||
QString directory;
|
||||
QString title;
|
||||
QStringList nameFilters;
|
||||
QStringList mimeTypesFilters;
|
||||
// maps user-visible name for portal to full name filter
|
||||
QMap<QString, QString> userVisibleToNameFilter;
|
||||
QString selectedMimeTypeFilter;
|
||||
QString selectedNameFilter;
|
||||
QStringList selectedFiles;
|
||||
};
|
||||
|
||||
XDPFileDialog::XDPFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter)
|
||||
: QDialog(parent)
|
||||
, d_ptr(new XDPFileDialogPrivate())
|
||||
, _windowTitle(caption)
|
||||
, _initialDirectory(directory) {
|
||||
Q_D(XDPFileDialog);
|
||||
|
||||
auto filters = makeFilterList(filter);
|
||||
const int numFilters = filters.count();
|
||||
_nameFilters.reserve(numFilters);
|
||||
for (int i = 0; i < numFilters; ++i) {
|
||||
_nameFilters << filters[i].simplified();
|
||||
}
|
||||
|
||||
accepted(
|
||||
) | rpl::start_with_next([=] {
|
||||
Q_EMIT accept();
|
||||
}, _lifetime);
|
||||
|
||||
rejected(
|
||||
) | rpl::start_with_next([=] {
|
||||
Q_EMIT reject();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
XDPFileDialog::~XDPFileDialog() {
|
||||
}
|
||||
|
||||
void XDPFileDialog::initializeDialog() {
|
||||
Q_D(XDPFileDialog);
|
||||
|
||||
if (_fileMode == QFileDialog::ExistingFiles)
|
||||
d->multipleFiles = true;
|
||||
|
||||
if (_fileMode == QFileDialog::Directory || _options.testFlag(QFileDialog::ShowDirsOnly))
|
||||
d->directoryMode = true;
|
||||
|
||||
#if 0 // it is commented in GtkFileDialog for some reason, do the same
|
||||
if (options()->isLabelExplicitlySet(QFileDialog::Accept))
|
||||
d->acceptLabel = options()->labelText(QFileDialog::Accept);
|
||||
#endif
|
||||
|
||||
if (!_windowTitle.isEmpty())
|
||||
d->title = _windowTitle;
|
||||
|
||||
if (_acceptMode == QFileDialog::AcceptSave)
|
||||
d->saveFile = true;
|
||||
|
||||
if (!_nameFilters.isEmpty())
|
||||
d->nameFilters = _nameFilters;
|
||||
|
||||
#if 0 // what is the right way to implement this?
|
||||
if (!options()->mimeTypeFilters().isEmpty())
|
||||
d->mimeTypesFilters = options()->mimeTypeFilters();
|
||||
|
||||
if (!options()->initiallySelectedMimeTypeFilter().isEmpty())
|
||||
d->selectedMimeTypeFilter = options()->initiallySelectedMimeTypeFilter();
|
||||
#endif
|
||||
|
||||
const auto initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front();
|
||||
if (!initialNameFilter.isEmpty())
|
||||
d->selectedNameFilter = initialNameFilter;
|
||||
|
||||
setDirectory(_initialDirectory);
|
||||
}
|
||||
|
||||
void XDPFileDialog::openPortal() {
|
||||
Q_D(XDPFileDialog);
|
||||
|
||||
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, 16);
|
||||
|
||||
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);
|
||||
options.insert(QLatin1String("directory"), d->directoryMode);
|
||||
|
||||
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;
|
||||
auto selectedFilterIndex = filterList.size() - 1;
|
||||
|
||||
d->userVisibleToNameFilter.clear();
|
||||
|
||||
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;
|
||||
|
||||
if (!d->selectedMimeTypeFilter.isEmpty() && d->selectedMimeTypeFilter == mimeTypefilter)
|
||||
selectedFilterIndex = filterList.size() - 1;
|
||||
}
|
||||
} else if (!d->nameFilters.isEmpty()) {
|
||||
for (const QString &nameFilter : d->nameFilters) {
|
||||
// Do parsing:
|
||||
// Supported format is ("Images (*.png *.jpg)")
|
||||
QRegularExpression regexp(QString::fromLatin1(filterRegExp));
|
||||
QRegularExpressionMatch match = regexp.match(nameFilter);
|
||||
if (match.hasMatch()) {
|
||||
QString userVisibleName = match.captured(1);
|
||||
QStringList filterStrings = match.captured(2).split(QLatin1Char(' '), base::QStringSkipEmptyParts);
|
||||
|
||||
if (filterStrings.isEmpty()) {
|
||||
LOG(("XDP File Dialog Error: Filter %1 is empty and will be ignored.").arg(userVisibleName));
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
d->userVisibleToNameFilter.insert(userVisibleName, nameFilter);
|
||||
|
||||
if (!d->selectedNameFilter.isEmpty() && d->selectedNameFilter == nameFilter)
|
||||
selectedFilterIndex = filterList.size() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!filterList.isEmpty())
|
||||
options.insert(QLatin1String("filters"), QVariant::fromValue(filterList));
|
||||
|
||||
if (selectedFilterIndex != -1)
|
||||
options.insert(QLatin1String("current_filter"), QVariant::fromValue(filterList[selectedFilterIndex]));
|
||||
|
||||
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()) {
|
||||
_reject.fire({});
|
||||
} else {
|
||||
QDBusConnection::sessionBus().connect(
|
||||
nullptr,
|
||||
reply.value().path(),
|
||||
QLatin1String("org.freedesktop.portal.Request"),
|
||||
QLatin1String("Response"),
|
||||
this,
|
||||
SLOT(gotResponse(uint,QVariantMap)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool XDPFileDialog::defaultNameFilterDisables() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void XDPFileDialog::setDirectory(const QUrl &directory) {
|
||||
Q_D(XDPFileDialog);
|
||||
|
||||
d->directory = directory.path();
|
||||
}
|
||||
|
||||
QUrl XDPFileDialog::directory() const {
|
||||
Q_D(const XDPFileDialog);
|
||||
|
||||
return d->directory;
|
||||
}
|
||||
|
||||
void XDPFileDialog::selectFile(const QUrl &filename) {
|
||||
Q_D(XDPFileDialog);
|
||||
|
||||
d->selectedFiles << filename.path();
|
||||
}
|
||||
|
||||
QList<QUrl> XDPFileDialog::selectedFiles() const {
|
||||
Q_D(const XDPFileDialog);
|
||||
|
||||
QList<QUrl> files;
|
||||
for (const QString &file : d->selectedFiles) {
|
||||
files << QUrl(file);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
void XDPFileDialog::setFilter() {
|
||||
Q_D(XDPFileDialog);
|
||||
}
|
||||
|
||||
void XDPFileDialog::selectMimeTypeFilter(const QString &filter) {
|
||||
Q_D(XDPFileDialog);
|
||||
}
|
||||
|
||||
QString XDPFileDialog::selectedMimeTypeFilter() const {
|
||||
Q_D(const XDPFileDialog);
|
||||
return d->selectedMimeTypeFilter;
|
||||
}
|
||||
|
||||
void XDPFileDialog::selectNameFilter(const QString &filter) {
|
||||
Q_D(XDPFileDialog);
|
||||
}
|
||||
|
||||
QString XDPFileDialog::selectedNameFilter() const {
|
||||
Q_D(const XDPFileDialog);
|
||||
return d->selectedNameFilter;
|
||||
}
|
||||
|
||||
int XDPFileDialog::exec() {
|
||||
Q_D(XDPFileDialog);
|
||||
|
||||
bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose);
|
||||
setAttribute(Qt::WA_DeleteOnClose, false);
|
||||
|
||||
bool wasShowModal = testAttribute(Qt::WA_ShowModal);
|
||||
setAttribute(Qt::WA_ShowModal, true);
|
||||
setResult(0);
|
||||
|
||||
show();
|
||||
|
||||
QPointer<QDialog> guard = this;
|
||||
|
||||
// HACK we have to avoid returning until we emit that the dialog was accepted or rejected
|
||||
QEventLoop loop;
|
||||
rpl::lifetime lifetime;
|
||||
|
||||
accepted(
|
||||
) | rpl::start_with_next([&] {
|
||||
loop.quit();
|
||||
}, lifetime);
|
||||
|
||||
rejected(
|
||||
) | rpl::start_with_next([&] {
|
||||
loop.quit();
|
||||
}, lifetime);
|
||||
|
||||
loop.exec();
|
||||
|
||||
if (guard.isNull())
|
||||
return QDialog::Rejected;
|
||||
|
||||
setAttribute(Qt::WA_ShowModal, wasShowModal);
|
||||
|
||||
return result();
|
||||
}
|
||||
|
||||
void XDPFileDialog::setVisible(bool visible) {
|
||||
if (visible) {
|
||||
if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
} else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr);
|
||||
} else {
|
||||
hideHelper();
|
||||
}
|
||||
|
||||
// Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below
|
||||
// updates the state correctly, but skips showing the non-native version:
|
||||
setAttribute(Qt::WA_DontShowOnScreen);
|
||||
|
||||
QDialog::setVisible(visible);
|
||||
}
|
||||
|
||||
void XDPFileDialog::hideHelper() {
|
||||
Q_D(XDPFileDialog);
|
||||
}
|
||||
|
||||
void XDPFileDialog::showHelper(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) {
|
||||
Q_D(XDPFileDialog);
|
||||
|
||||
initializeDialog();
|
||||
|
||||
d->modal = windowModality != Qt::NonModal;
|
||||
d->winId = parent ? parent->winId() : 0;
|
||||
|
||||
openPortal();
|
||||
}
|
||||
|
||||
void XDPFileDialog::gotResponse(uint response, const QVariantMap &results) {
|
||||
Q_D(XDPFileDialog);
|
||||
|
||||
if (!response) {
|
||||
if (results.contains(QLatin1String("uris")))
|
||||
d->selectedFiles = results.value(QLatin1String("uris")).toStringList();
|
||||
|
||||
if (results.contains(QLatin1String("current_filter"))) {
|
||||
const Filter selectedFilter = qdbus_cast<Filter>(results.value(QStringLiteral("current_filter")));
|
||||
if (!selectedFilter.filterConditions.empty() && selectedFilter.filterConditions[0].type == MimeType) {
|
||||
// s.a. XDPFileDialog::openPortal which basically does the inverse
|
||||
d->selectedMimeTypeFilter = selectedFilter.filterConditions[0].pattern;
|
||||
d->selectedNameFilter.clear();
|
||||
} else {
|
||||
d->selectedNameFilter = d->userVisibleToNameFilter.value(selectedFilter.name);
|
||||
d->selectedMimeTypeFilter.clear();
|
||||
}
|
||||
}
|
||||
_accept.fire({});
|
||||
} else {
|
||||
_reject.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> XDPFileDialog::accepted() {
|
||||
return _accept.events();
|
||||
}
|
||||
|
||||
rpl::producer<> XDPFileDialog::rejected() {
|
||||
return _reject.events();
|
||||
}
|
||||
|
||||
} // namespace XDP
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
136
Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h
Normal file
136
Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "core/file_utilities.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QVector>
|
||||
|
||||
namespace Platform {
|
||||
namespace FileDialog {
|
||||
namespace XDP {
|
||||
|
||||
class XDPFileDialogPrivate;
|
||||
using Type = ::FileDialog::internal::Type;
|
||||
|
||||
bool Use(Type type = Type::ReadFile);
|
||||
bool Get(
|
||||
QPointer<QWidget> parent,
|
||||
QStringList &files,
|
||||
QByteArray &remoteContent,
|
||||
const QString &caption,
|
||||
const QString &filter,
|
||||
Type type,
|
||||
QString startFile);
|
||||
|
||||
// This is a patched copy of file dialog from qxdgdesktopportal theme plugin.
|
||||
// It allows using XDP file dialog flexibly,
|
||||
// without relying on QT_QPA_PLATFORMTHEME variable.
|
||||
//
|
||||
// XDP file dialog is a dialog obtained via a DBus service
|
||||
// provided by the current desktop environment.
|
||||
class XDPFileDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PRIVATE(XDPFileDialog)
|
||||
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;
|
||||
|
||||
XDPFileDialog(
|
||||
QWidget *parent = nullptr,
|
||||
const QString &caption = QString(),
|
||||
const QString &directory = QString(),
|
||||
const QString &filter = QString());
|
||||
~XDPFileDialog();
|
||||
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
void setWindowTitle(const QString &windowTitle) {
|
||||
_windowTitle = windowTitle;
|
||||
}
|
||||
void setAcceptMode(QFileDialog::AcceptMode acceptMode) {
|
||||
_acceptMode = acceptMode;
|
||||
}
|
||||
void setFileMode(QFileDialog::FileMode fileMode) {
|
||||
_fileMode = fileMode;
|
||||
}
|
||||
void setOption(QFileDialog::Option option, bool on = true) {
|
||||
if (on) {
|
||||
_options |= option;
|
||||
} else {
|
||||
_options &= ~option;
|
||||
}
|
||||
}
|
||||
|
||||
bool defaultNameFilterDisables() const;
|
||||
QUrl directory() const;
|
||||
void setDirectory(const QUrl &directory);
|
||||
void selectFile(const QUrl &filename);
|
||||
QList<QUrl> selectedFiles() const;
|
||||
void setFilter();
|
||||
void selectNameFilter(const QString &filter);
|
||||
QString selectedNameFilter() const;
|
||||
void selectMimeTypeFilter(const QString &filter);
|
||||
QString selectedMimeTypeFilter() const;
|
||||
|
||||
int exec() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void gotResponse(uint response, const QVariantMap &results);
|
||||
|
||||
private:
|
||||
void initializeDialog();
|
||||
void openPortal();
|
||||
|
||||
void showHelper(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent);
|
||||
void hideHelper();
|
||||
|
||||
rpl::producer<> accepted();
|
||||
rpl::producer<> rejected();
|
||||
|
||||
QScopedPointer<XDPFileDialogPrivate> d_ptr;
|
||||
|
||||
// Options
|
||||
QFileDialog::Options _options;
|
||||
QString _windowTitle = "Choose file";
|
||||
QString _initialDirectory;
|
||||
QStringList _initialFiles;
|
||||
QStringList _nameFilters;
|
||||
QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen;
|
||||
QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile;
|
||||
|
||||
rpl::event_stream<> _accept;
|
||||
rpl::event_stream<> _reject;
|
||||
rpl::lifetime _lifetime;
|
||||
};
|
||||
|
||||
} // namespace XDP
|
||||
} // namespace FileDialog
|
||||
} // namespace Platform
|
||||
|
||||
Q_DECLARE_METATYPE(Platform::FileDialog::XDP::XDPFileDialog::FilterCondition);
|
||||
Q_DECLARE_METATYPE(Platform::FileDialog::XDP::XDPFileDialog::FilterConditionList);
|
||||
Q_DECLARE_METATYPE(Platform::FileDialog::XDP::XDPFileDialog::Filter);
|
||||
Q_DECLARE_METATYPE(Platform::FileDialog::XDP::XDPFileDialog::FilterList);
|
||||
|
|
@ -29,8 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QtWidgets/QDesktopWidget>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QProcess>
|
||||
#include <QtCore/QVersionNumber>
|
||||
#include <QtCore/QLibraryInfo>
|
||||
#include <QtGui/QWindow>
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
|
@ -596,18 +594,16 @@ bool AreQtPluginsBundled() {
|
|||
bool UseXDGDesktopPortal() {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
static const auto Result = [&] {
|
||||
const auto onlyIn = AreQtPluginsBundled()
|
||||
// it is handled by Qt for flatpak and snap
|
||||
&& !InFlatpak()
|
||||
&& !InSnap();
|
||||
if (InFlatpak() || InSnap()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto envVar = qEnvironmentVariableIsSet("TDESKTOP_USE_PORTAL");
|
||||
const auto portalPresent = IsXDGDesktopPortalPresent();
|
||||
const auto neededForKde = DesktopEnvironment::IsKDE()
|
||||
&& IsXDGDesktopPortalKDEPresent();
|
||||
|
||||
return onlyIn
|
||||
&& portalPresent
|
||||
return portalPresent
|
||||
&& (neededForKde || envVar);
|
||||
}();
|
||||
|
||||
|
@ -620,12 +616,7 @@ bool UseXDGDesktopPortal() {
|
|||
bool CanOpenDirectoryWithPortal() {
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
static const auto Result = [&] {
|
||||
#ifdef DESKTOP_APP_QT_PATCHED
|
||||
return FileChooserPortalVersion() >= 3;
|
||||
#else // DESKTOP_APP_QT_PATCHED
|
||||
return QLibraryInfo::version() >= QVersionNumber(5, 15, 0)
|
||||
&& FileChooserPortalVersion() >= 3;
|
||||
#endif // !DESKTOP_APP_QT_PATCHED
|
||||
}();
|
||||
|
||||
return Result;
|
||||
|
@ -1037,14 +1028,12 @@ void start() {
|
|||
}
|
||||
|
||||
#ifndef DESKTOP_APP_DISABLE_DBUS_INTEGRATION
|
||||
// this can give us a chance to use
|
||||
// a proper file dialog for current session
|
||||
// Tell the user when XDP file dialog is used
|
||||
DEBUG_LOG(("Checking for XDG Desktop Portal..."));
|
||||
if (IsXDGDesktopPortalPresent()) {
|
||||
DEBUG_LOG(("XDG Desktop Portal is present!"));
|
||||
if (UseXDGDesktopPortal()) {
|
||||
LOG(("Using XDG Desktop Portal."));
|
||||
qputenv("QT_QPA_PLATFORMTHEME", "xdgdesktopportal");
|
||||
} else {
|
||||
DEBUG_LOG(("Not using XDG Desktop Portal."));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue