732 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			732 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) {
 | |
| 	const auto shouldUse = [&] {
 | |
| 		const auto setting = cFileDialogType() <= ImplementationType::XDP;
 | |
| 		const auto forceSetting = cFileDialogType() == ImplementationType::XDP;
 | |
| 		const auto confined = InFlatpak() || InSnap();
 | |
| 		const auto notGtkBased = !DesktopEnvironment::IsGtkBased();
 | |
| 
 | |
| 		return setting && (confined || notGtkBased || forceSetting);
 | |
| 	}();
 | |
| 
 | |
| 	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
 | 
