From 36acf60f7e63386b4ba500540281b224c7c2977b Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Fri, 5 Feb 2021 02:06:21 +0400 Subject: [PATCH] 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 --- Telegram/CMakeLists.txt | 4 + .../platform/linux/file_utilities_linux.cpp | 16 + .../platform/linux/linux_gtk_file_dialog.cpp | 20 +- .../platform/linux/linux_xdp_file_dialog.cpp | 536 ++++++++++++++++++ .../platform/linux/linux_xdp_file_dialog.h | 136 +++++ .../platform/linux/specific_linux.cpp | 21 +- 6 files changed, 700 insertions(+), 33 deletions(-) create mode 100644 Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp create mode 100644 Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 663453b10..85f82779c 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -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 ) diff --git a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp b/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp index d13b80488..b9ebef8c0 100644 --- a/Telegram/SourceFiles/platform/linux/file_utilities_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/file_utilities_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 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)) { diff --git a/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp index d9adb924d..f03fcdaea 100644 --- a/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_gtk_file_dialog.cpp @@ -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( diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp new file mode 100644 index 000000000..5d53d1ab2 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp @@ -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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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 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 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(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + 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 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 XDPFileDialog::selectedFiles() const { + Q_D(const XDPFileDialog); + + QList 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 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(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 diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h new file mode 100644 index 000000000..57bc43e56 --- /dev/null +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h @@ -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 +#include + +namespace Platform { +namespace FileDialog { +namespace XDP { + +class XDPFileDialogPrivate; +using Type = ::FileDialog::internal::Type; + +bool Use(Type type = Type::ReadFile); +bool Get( + QPointer 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 FilterConditionList; + + struct Filter { + QString name; // E.g. 'Images' or 'Text + FilterConditionList filterConditions;; // E.g. [(0, '*.ico'), (1, 'image/png')] or [(0, '*.txt')] + }; + typedef QVector 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 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 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); + diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 6f2acd99e..cc091af3f 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -29,8 +29,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include #include #include -#include -#include #include #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.")); }