731 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			731 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
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/platform_file_utilities.h"
 | 
						|
#include "platform/linux/linux_desktop_environment.h"
 | 
						|
#include "platform/linux/specific_linux.h"
 | 
						|
#include "base/platform/base_platform_info.h"
 | 
						|
#include "base/platform/linux/base_linux_glibmm_helper.h"
 | 
						|
#include "storage/localstorage.h"
 | 
						|
#include "base/openssl_help.h"
 | 
						|
#include "base/qt_adapters.h"
 | 
						|
 | 
						|
#include <QtCore/QMimeType>
 | 
						|
#include <QtCore/QMimeDatabase>
 | 
						|
#include <QtGui/QWindow>
 | 
						|
#include <QtWidgets/QFileDialog>
 | 
						|
 | 
						|
#include <glibmm.h>
 | 
						|
#include <giomm.h>
 | 
						|
 | 
						|
namespace Platform {
 | 
						|
namespace FileDialog {
 | 
						|
namespace XDP {
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kXDGDesktopPortalService = "org.freedesktop.portal.Desktop"_cs;
 | 
						|
constexpr auto kXDGDesktopPortalObjectPath = "/org/freedesktop/portal/desktop"_cs;
 | 
						|
constexpr auto kXDGDesktopPortalFileChooserInterface = "org.freedesktop.portal.FileChooser"_cs;
 | 
						|
constexpr auto kPropertiesInterface = "org.freedesktop.DBus.Properties"_cs;
 | 
						|
 | 
						|
const char *filterRegExp =
 | 
						|
"^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$";
 | 
						|
 | 
						|
auto QStringListToStd(const QStringList &list) {
 | 
						|
	std::vector<Glib::ustring> result;
 | 
						|
	ranges::transform(
 | 
						|
		list,
 | 
						|
		ranges::back_inserter(result),
 | 
						|
		&QString::toStdString);
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
auto MakeFilterList(const QString &filter) {
 | 
						|
	std::vector<Glib::ustring> result;
 | 
						|
	QString f(filter);
 | 
						|
 | 
						|
	if (f.isEmpty()) {
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
 | 
						|
	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);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	ranges::transform(
 | 
						|
		f.split(sep),
 | 
						|
		ranges::back_inserter(result),
 | 
						|
		[](const QString &string) {
 | 
						|
			return string.simplified().toStdString();
 | 
						|
		});
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
std::optional<uint> FileChooserPortalVersion() {
 | 
						|
	try {
 | 
						|
		const auto connection = Gio::DBus::Connection::get_sync(
 | 
						|
			Gio::DBus::BusType::BUS_TYPE_SESSION);
 | 
						|
 | 
						|
		auto reply = connection->call_sync(
 | 
						|
			std::string(kXDGDesktopPortalObjectPath),
 | 
						|
			std::string(kPropertiesInterface),
 | 
						|
			"Get",
 | 
						|
			base::Platform::MakeGlibVariant(std::tuple{
 | 
						|
				Glib::ustring(
 | 
						|
					std::string(kXDGDesktopPortalFileChooserInterface)),
 | 
						|
				Glib::ustring("version"),
 | 
						|
			}),
 | 
						|
			std::string(kXDGDesktopPortalService));
 | 
						|
 | 
						|
		return base::Platform::GlibVariantCast<uint>(
 | 
						|
			base::Platform::GlibVariantCast<Glib::VariantBase>(
 | 
						|
				reply.get_child(0)));
 | 
						|
	} catch (const Glib::Error &e) {
 | 
						|
		static const auto NotSupportedErrors = {
 | 
						|
			"org.freedesktop.DBus.Error.Disconnected",
 | 
						|
			"org.freedesktop.DBus.Error.ServiceUnknown",
 | 
						|
		};
 | 
						|
 | 
						|
		const auto errorName = Gio::DBus::ErrorUtils::get_remote_error(e);
 | 
						|
		if (ranges::contains(NotSupportedErrors, errorName)) {
 | 
						|
			return std::nullopt;
 | 
						|
		}
 | 
						|
 | 
						|
		LOG(("XDP File Dialog Error: %1").arg(
 | 
						|
			QString::fromStdString(e.what())));
 | 
						|
	} catch (const std::exception &e) {
 | 
						|
		LOG(("XDP File Dialog Error: %1").arg(
 | 
						|
			QString::fromStdString(e.what())));
 | 
						|
	}
 | 
						|
 | 
						|
	return std::nullopt;
 | 
						|
}
 | 
						|
 | 
						|
// 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, public sigc::trackable {
 | 
						|
public:
 | 
						|
	enum ConditionType : uint {
 | 
						|
		GlobalPattern = 0,
 | 
						|
		MimeType = 1
 | 
						|
	};
 | 
						|
	// Filters a(sa(us))
 | 
						|
	// Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]
 | 
						|
	typedef std::tuple<uint, Glib::ustring> FilterCondition;
 | 
						|
	typedef std::vector<FilterCondition> FilterConditionList;
 | 
						|
	typedef std::tuple<Glib::ustring, FilterConditionList> Filter;
 | 
						|
	typedef std::vector<Filter> FilterList;
 | 
						|
 | 
						|
	XDPFileDialog(
 | 
						|
		QWidget *parent = nullptr,
 | 
						|
		const QString &caption = QString(),
 | 
						|
		const QString &directory = QString(),
 | 
						|
		const QString &nameFilter = QString(),
 | 
						|
		const QStringList &mimeTypeFilters = QStringList());
 | 
						|
	~XDPFileDialog();
 | 
						|
 | 
						|
	void setVisible(bool visible) override;
 | 
						|
 | 
						|
	void setWindowTitle(const QString &windowTitle) {
 | 
						|
		_windowTitle = windowTitle.toStdString();
 | 
						|
	}
 | 
						|
	void setAcceptLabel(const QString &acceptLabel) {
 | 
						|
		_acceptLabel = acceptLabel.toStdString();
 | 
						|
	}
 | 
						|
	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:
 | 
						|
	void openPortal();
 | 
						|
	void gotResponse(
 | 
						|
		const Glib::RefPtr<Gio::DBus::Connection> &connection,
 | 
						|
		const Glib::ustring &sender_name,
 | 
						|
		const Glib::ustring &object_path,
 | 
						|
		const Glib::ustring &interface_name,
 | 
						|
		const Glib::ustring &signal_name,
 | 
						|
		const Glib::VariantContainerBase ¶meters);
 | 
						|
 | 
						|
	void showHelper(
 | 
						|
		Qt::WindowFlags windowFlags,
 | 
						|
		Qt::WindowModality windowModality,
 | 
						|
		QWindow *parent);
 | 
						|
	void hideHelper();
 | 
						|
 | 
						|
	rpl::producer<> accepted();
 | 
						|
	rpl::producer<> rejected();
 | 
						|
 | 
						|
	Glib::RefPtr<Gio::DBus::Connection> _dbusConnection;
 | 
						|
	Glib::RefPtr<Gio::Cancellable> _cancellable;
 | 
						|
	uint _requestSignalId = 0;
 | 
						|
 | 
						|
	// Options
 | 
						|
	WId _winId = 0;
 | 
						|
	QFileDialog::Options _options;
 | 
						|
	QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen;
 | 
						|
	QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile;
 | 
						|
	bool _modal = false;
 | 
						|
	Glib::ustring _windowTitle = "Choose file";
 | 
						|
	Glib::ustring _acceptLabel;
 | 
						|
	Glib::ustring _directory;
 | 
						|
	std::vector<Glib::ustring> _nameFilters;
 | 
						|
	std::vector<Glib::ustring> _mimeTypesFilters;
 | 
						|
	// maps user-visible name for portal to full name filter
 | 
						|
	std::map<Glib::ustring, Glib::ustring> _userVisibleToNameFilter;
 | 
						|
	Glib::ustring _selectedMimeTypeFilter;
 | 
						|
	Glib::ustring _selectedNameFilter;
 | 
						|
	std::vector<Glib::ustring> _selectedFiles;
 | 
						|
 | 
						|
	rpl::event_stream<> _accept;
 | 
						|
	rpl::event_stream<> _reject;
 | 
						|
	rpl::lifetime _lifetime;
 | 
						|
};
 | 
						|
 | 
						|
XDPFileDialog::XDPFileDialog(
 | 
						|
		QWidget *parent,
 | 
						|
		const QString &caption,
 | 
						|
		const QString &directory,
 | 
						|
		const QString &nameFilter,
 | 
						|
		const QStringList &mimeTypeFilters)
 | 
						|
: QDialog(parent)
 | 
						|
, _windowTitle(caption.toStdString())
 | 
						|
, _directory(directory.toStdString())
 | 
						|
, _nameFilters(MakeFilterList(nameFilter))
 | 
						|
, _mimeTypesFilters(QStringListToStd(mimeTypeFilters))
 | 
						|
, _selectedMimeTypeFilter(!_mimeTypesFilters.empty()
 | 
						|
		? _mimeTypesFilters[0]
 | 
						|
		: Glib::ustring())
 | 
						|
, _selectedNameFilter(!_nameFilters.empty()
 | 
						|
		? _nameFilters[0]
 | 
						|
		: Glib::ustring()) {
 | 
						|
	accepted(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		accept();
 | 
						|
	}, _lifetime);
 | 
						|
 | 
						|
	rejected(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		reject();
 | 
						|
	}, _lifetime);
 | 
						|
}
 | 
						|
 | 
						|
XDPFileDialog::~XDPFileDialog() {
 | 
						|
	if (_cancellable) {
 | 
						|
		_cancellable->cancel();
 | 
						|
	}
 | 
						|
 | 
						|
	if (_dbusConnection && _requestSignalId != 0) {
 | 
						|
		_dbusConnection->signal_unsubscribe(_requestSignalId);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void XDPFileDialog::openPortal() {
 | 
						|
	std::stringstream parentWindowId;
 | 
						|
 | 
						|
	if (IsX11()) {
 | 
						|
		parentWindowId << "x11:" << std::hex << _winId;
 | 
						|
	}
 | 
						|
 | 
						|
	std::map<Glib::ustring, Glib::VariantBase> options;
 | 
						|
	if (!_acceptLabel.empty()) {
 | 
						|
		options["accept_label"] = Glib::Variant<Glib::ustring>::create(
 | 
						|
			_acceptLabel);
 | 
						|
	}
 | 
						|
 | 
						|
	options["modal"] = Glib::Variant<bool>::create(_modal);
 | 
						|
	options["multiple"] = Glib::Variant<bool>::create(
 | 
						|
		_fileMode == QFileDialog::ExistingFiles);
 | 
						|
 | 
						|
	options["directory"] = Glib::Variant<bool>::create(
 | 
						|
		_fileMode == QFileDialog::Directory
 | 
						|
			|| _options.testFlag(QFileDialog::ShowDirsOnly));
 | 
						|
 | 
						|
	if (_acceptMode == QFileDialog::AcceptSave) {
 | 
						|
		if (!_directory.empty()) {
 | 
						|
			options["current_folder"] = Glib::Variant<std::string>::create(
 | 
						|
				_directory +'\0');
 | 
						|
		}
 | 
						|
 | 
						|
		if (!_selectedFiles.empty()) {
 | 
						|
			options["current_file"] = Glib::Variant<std::string>::create(
 | 
						|
				_selectedFiles[0] + '\0');
 | 
						|
 | 
						|
			options["current_name"] = Glib::Variant<Glib::ustring>::create(
 | 
						|
				Glib::path_get_basename(_selectedFiles[0]));
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Insert filters
 | 
						|
	FilterList filterList;
 | 
						|
	auto selectedFilterIndex = filterList.size() - 1;
 | 
						|
 | 
						|
	_userVisibleToNameFilter.clear();
 | 
						|
 | 
						|
	if (!_mimeTypesFilters.empty()) {
 | 
						|
		for (const auto &mimeTypeFilter : _mimeTypesFilters) {
 | 
						|
			const auto mimeType = QMimeDatabase().mimeTypeForName(
 | 
						|
				QString::fromStdString(mimeTypeFilter));
 | 
						|
 | 
						|
			// Creates e.g. (1, "image/png")
 | 
						|
			const auto filterCondition = FilterCondition{
 | 
						|
				MimeType,
 | 
						|
				mimeTypeFilter,
 | 
						|
			};
 | 
						|
 | 
						|
			// Creates e.g. [("Images", [((1, "image/png"))])]
 | 
						|
			filterList.push_back({
 | 
						|
				mimeType.comment().toStdString(),
 | 
						|
				FilterConditionList{filterCondition},
 | 
						|
			});
 | 
						|
 | 
						|
			if (!_selectedMimeTypeFilter.empty()
 | 
						|
				&& _selectedMimeTypeFilter == mimeTypeFilter) {
 | 
						|
				selectedFilterIndex = filterList.size() - 1;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else if (!_nameFilters.empty()) {
 | 
						|
		for (const auto &nameFilter : _nameFilters) {
 | 
						|
			// Do parsing:
 | 
						|
			// Supported format is ("Images (*.png *.jpg)")
 | 
						|
			const QRegularExpression regexp(
 | 
						|
				QString::fromLatin1(filterRegExp));
 | 
						|
 | 
						|
			const QRegularExpressionMatch match = regexp.match(
 | 
						|
				QString::fromStdString(nameFilter));
 | 
						|
 | 
						|
			if (match.hasMatch()) {
 | 
						|
				const auto userVisibleName = match.captured(1).toStdString();
 | 
						|
				const auto filterStrings = QStringListToStd(
 | 
						|
					match.captured(2).split(
 | 
						|
						QLatin1Char(' '),
 | 
						|
						base::QStringSkipEmptyParts));
 | 
						|
 | 
						|
				if (filterStrings.empty()) {
 | 
						|
					LOG((
 | 
						|
						"XDP File Dialog Error: "
 | 
						|
						"Filter %1 is empty and will be ignored.")
 | 
						|
						.arg(QString::fromStdString(userVisibleName)));
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
 | 
						|
				FilterConditionList filterConditions;
 | 
						|
				for (const auto &filterString : filterStrings) {
 | 
						|
					filterConditions.push_back({
 | 
						|
						GlobalPattern,
 | 
						|
						filterString,
 | 
						|
					});
 | 
						|
				}
 | 
						|
 | 
						|
				filterList.push_back({
 | 
						|
					userVisibleName,
 | 
						|
					filterConditions,
 | 
						|
				});
 | 
						|
 | 
						|
				_userVisibleToNameFilter[userVisibleName] = nameFilter;
 | 
						|
 | 
						|
				if (!_selectedNameFilter.empty()
 | 
						|
					&& _selectedNameFilter == nameFilter) {
 | 
						|
					selectedFilterIndex = filterList.size() - 1;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!filterList.empty()) {
 | 
						|
		options["filters"] = Glib::Variant<FilterList>::create(filterList);
 | 
						|
	}
 | 
						|
 | 
						|
	if (selectedFilterIndex != -1) {
 | 
						|
		options["current_filter"] = Glib::Variant<Filter>::create(
 | 
						|
			filterList[selectedFilterIndex]);
 | 
						|
	}
 | 
						|
 | 
						|
	const auto handleToken = Glib::ustring("tdesktop")
 | 
						|
		+ std::to_string(openssl::RandomValue<uint>());
 | 
						|
 | 
						|
	options["handle_token"] = Glib::Variant<Glib::ustring>::create(
 | 
						|
		handleToken);
 | 
						|
 | 
						|
	// TODO choices a(ssa(ss)s)
 | 
						|
	// List of serialized combo boxes to add to the file chooser.
 | 
						|
 | 
						|
	try {
 | 
						|
		_dbusConnection = Gio::DBus::Connection::get_sync(
 | 
						|
			Gio::DBus::BusType::BUS_TYPE_SESSION);
 | 
						|
 | 
						|
		auto uniqueName = _dbusConnection->get_unique_name();
 | 
						|
		uniqueName.erase(0, 1);
 | 
						|
		uniqueName.replace(uniqueName.find('.'), 1, 1, '_');
 | 
						|
 | 
						|
		const auto requestPath = Glib::ustring(
 | 
						|
				"/org/freedesktop/portal/desktop/request/")
 | 
						|
			+ uniqueName
 | 
						|
			+ '/'
 | 
						|
			+ handleToken;
 | 
						|
 | 
						|
		_requestSignalId = _dbusConnection->signal_subscribe(
 | 
						|
			sigc::mem_fun(this, &XDPFileDialog::gotResponse),
 | 
						|
			{},
 | 
						|
			"org.freedesktop.portal.Request",
 | 
						|
			"Response",
 | 
						|
			requestPath);
 | 
						|
 | 
						|
		// synchronize functor deletion by this cancellable
 | 
						|
		_cancellable = Gio::Cancellable::create();
 | 
						|
 | 
						|
		_dbusConnection->call(
 | 
						|
			std::string(kXDGDesktopPortalObjectPath),
 | 
						|
			std::string(kXDGDesktopPortalFileChooserInterface),
 | 
						|
			_acceptMode == QFileDialog::AcceptSave
 | 
						|
				? "SaveFile"
 | 
						|
				: "OpenFile",
 | 
						|
			base::Platform::MakeGlibVariant(std::tuple{
 | 
						|
				Glib::ustring(parentWindowId.str()),
 | 
						|
				_windowTitle,
 | 
						|
				options,
 | 
						|
			}),
 | 
						|
			[=](const Glib::RefPtr<Gio::AsyncResult> &result) {
 | 
						|
				try {
 | 
						|
					_dbusConnection->call_finish(result);
 | 
						|
				} catch (const Glib::Error &e) {
 | 
						|
					LOG(("XDP File Dialog Error: %1").arg(
 | 
						|
						QString::fromStdString(e.what())));
 | 
						|
 | 
						|
					crl::on_main([=] {
 | 
						|
						_reject.fire({});
 | 
						|
					});
 | 
						|
				}
 | 
						|
			},
 | 
						|
			_cancellable,
 | 
						|
			std::string(kXDGDesktopPortalService));
 | 
						|
	} catch (const Glib::Error &e) {
 | 
						|
		LOG(("XDP File Dialog Error: %1").arg(
 | 
						|
			QString::fromStdString(e.what())));
 | 
						|
 | 
						|
		_reject.fire({});
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool XDPFileDialog::defaultNameFilterDisables() const {
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
void XDPFileDialog::setDirectory(const QUrl &directory) {
 | 
						|
	_directory = directory.path().toStdString();
 | 
						|
}
 | 
						|
 | 
						|
QUrl XDPFileDialog::directory() const {
 | 
						|
	return QString::fromStdString(_directory);
 | 
						|
}
 | 
						|
 | 
						|
void XDPFileDialog::selectFile(const QUrl &filename) {
 | 
						|
	_selectedFiles.push_back(filename.path().toStdString());
 | 
						|
}
 | 
						|
 | 
						|
QList<QUrl> XDPFileDialog::selectedFiles() const {
 | 
						|
	QList<QUrl> files;
 | 
						|
	ranges::transform(
 | 
						|
		_selectedFiles,
 | 
						|
		ranges::back_inserter(files),
 | 
						|
		[](const Glib::ustring &string) {
 | 
						|
			return QUrl(QString::fromStdString(string));
 | 
						|
		});
 | 
						|
	return files;
 | 
						|
}
 | 
						|
 | 
						|
void XDPFileDialog::setFilter() {
 | 
						|
}
 | 
						|
 | 
						|
void XDPFileDialog::selectMimeTypeFilter(const QString &filter) {
 | 
						|
}
 | 
						|
 | 
						|
QString XDPFileDialog::selectedMimeTypeFilter() const {
 | 
						|
	return QString::fromStdString(_selectedMimeTypeFilter);
 | 
						|
}
 | 
						|
 | 
						|
void XDPFileDialog::selectNameFilter(const QString &filter) {
 | 
						|
}
 | 
						|
 | 
						|
QString XDPFileDialog::selectedNameFilter() const {
 | 
						|
	return QString::fromStdString(_selectedNameFilter);
 | 
						|
}
 | 
						|
 | 
						|
int XDPFileDialog::exec() {
 | 
						|
	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() {
 | 
						|
}
 | 
						|
 | 
						|
void XDPFileDialog::showHelper(
 | 
						|
		Qt::WindowFlags windowFlags,
 | 
						|
		Qt::WindowModality windowModality,
 | 
						|
		QWindow *parent) {
 | 
						|
	_modal = windowModality != Qt::NonModal;
 | 
						|
	_winId = parent ? parent->winId() : 0;
 | 
						|
 | 
						|
	openPortal();
 | 
						|
}
 | 
						|
 | 
						|
void XDPFileDialog::gotResponse(
 | 
						|
		const Glib::RefPtr<Gio::DBus::Connection> &connection,
 | 
						|
		const Glib::ustring &sender_name,
 | 
						|
		const Glib::ustring &object_path,
 | 
						|
		const Glib::ustring &interface_name,
 | 
						|
		const Glib::ustring &signal_name,
 | 
						|
		const Glib::VariantContainerBase ¶meters) {
 | 
						|
	try {
 | 
						|
		auto parametersCopy = parameters;
 | 
						|
 | 
						|
		const auto response = base::Platform::GlibVariantCast<uint>(
 | 
						|
			parametersCopy.get_child(0));
 | 
						|
 | 
						|
		const auto results = base::Platform::GlibVariantCast<
 | 
						|
			std::map<
 | 
						|
				Glib::ustring,
 | 
						|
				Glib::VariantBase
 | 
						|
			>>(parametersCopy.get_child(1));
 | 
						|
 | 
						|
		if (!response) {
 | 
						|
			if (const auto i = results.find("uris"); i != end(results)) {
 | 
						|
				_selectedFiles = base::Platform::GlibVariantCast<
 | 
						|
					std::vector<Glib::ustring>>(i->second);
 | 
						|
 | 
						|
				_directory = _selectedFiles.empty()
 | 
						|
					? Glib::ustring()
 | 
						|
					: Glib::ustring(
 | 
						|
						Glib::path_get_dirname(_selectedFiles.back()));
 | 
						|
			}
 | 
						|
 | 
						|
			if (const auto i = results.find("current_filter");
 | 
						|
				i != end(results)) {
 | 
						|
				auto selectedFilter = base::Platform::GlibVariantCast<
 | 
						|
					Filter>(i->second);
 | 
						|
 | 
						|
				if (!std::get<1>(selectedFilter).empty()
 | 
						|
					&& std::get<0>(
 | 
						|
						std::get<1>(selectedFilter)[0]) == MimeType) {
 | 
						|
					// s.a. XDPFileDialog::openPortal
 | 
						|
					// which basically does the inverse
 | 
						|
					_selectedMimeTypeFilter = std::get<1>(
 | 
						|
						std::get<1>(selectedFilter)[0]);
 | 
						|
					_selectedNameFilter.clear();
 | 
						|
				} else {
 | 
						|
					_selectedNameFilter =
 | 
						|
						_userVisibleToNameFilter[std::get<0>(selectedFilter)];
 | 
						|
					_selectedMimeTypeFilter.clear();
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			_accept.fire({});
 | 
						|
		} else {
 | 
						|
			_reject.fire({});
 | 
						|
		}
 | 
						|
	} catch (const std::exception &e) {
 | 
						|
		LOG(("XDP File Dialog Error: %1").arg(
 | 
						|
			QString::fromStdString(e.what())));
 | 
						|
 | 
						|
		_reject.fire({});
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<> XDPFileDialog::accepted() {
 | 
						|
	return _accept.events();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<> XDPFileDialog::rejected() {
 | 
						|
	return _reject.events();
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
bool Use(Type type) {
 | 
						|
	static const auto ShouldUse = [&] {
 | 
						|
		const auto envVar = qEnvironmentVariableIsSet("TDESKTOP_USE_PORTAL");
 | 
						|
		const auto confined = InFlatpak() || InSnap();
 | 
						|
		const auto notGtkBased = !DesktopEnvironment::IsGtkBased();
 | 
						|
 | 
						|
		return confined || notGtkBased || envVar;
 | 
						|
	}();
 | 
						|
 | 
						|
	static const auto Version = FileChooserPortalVersion();
 | 
						|
 | 
						|
	return ShouldUse
 | 
						|
		&& Version.has_value()
 | 
						|
		&& (type != Type::ReadFolder || *Version >= 3);
 | 
						|
}
 | 
						|
 | 
						|
bool Get(
 | 
						|
		QPointer<QWidget> parent,
 | 
						|
		QStringList &files,
 | 
						|
		QByteArray &remoteContent,
 | 
						|
		const QString &caption,
 | 
						|
		const QString &filter,
 | 
						|
		Type type,
 | 
						|
		QString startFile) {
 | 
						|
	static const auto docRegExp = QRegularExpression("^/run/user/\\d+/doc");
 | 
						|
	if (cDialogLastPath().isEmpty()
 | 
						|
		|| cDialogLastPath().contains(docRegExp)) {
 | 
						|
		InitLastPath();
 | 
						|
	}
 | 
						|
 | 
						|
	XDPFileDialog dialog(parent, caption, QString(), filter, QStringList());
 | 
						|
 | 
						|
	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.setDirectory(QFileInfo(startFile).absoluteDir().absolutePath());
 | 
						|
	dialog.selectFile(startFile);
 | 
						|
 | 
						|
	const auto res = dialog.exec();
 | 
						|
 | 
						|
	if (type != Type::ReadFolder) {
 | 
						|
		// Save last used directory for all queries except directory choosing.
 | 
						|
		const auto path = dialog.directory().path();
 | 
						|
		if (!path.isEmpty()
 | 
						|
			&& !path.contains(docRegExp)
 | 
						|
			&& 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;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace XDP
 | 
						|
} // namespace FileDialog
 | 
						|
} // namespace Platform
 |