use std::path::Path;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use zbus::zvariant::{
Optional, Type,
as_value::{self, optional},
};
use super::{HandleToken, Request};
use crate::{Error, FilePath, Uri, WindowIdentifier, proxy::Proxy};
#[derive(Clone, Serialize, Deserialize, Type, Debug, PartialEq)]
pub struct FileFilter(String, Vec<(FilterType, String)>);
#[derive(Clone, Serialize_repr, Deserialize_repr, Debug, Type, PartialEq)]
#[repr(u32)]
enum FilterType {
GlobPattern = 0,
MimeType = 1,
}
impl FilterType {
fn is_mimetype(&self) -> bool {
matches!(self, FilterType::MimeType)
}
fn is_pattern(&self) -> bool {
matches!(self, FilterType::GlobPattern)
}
}
impl FileFilter {
pub fn new(label: &str) -> Self {
Self(label.to_owned(), vec![])
}
#[must_use]
pub fn mimetype(mut self, mimetype: &str) -> Self {
self.1.push((FilterType::MimeType, mimetype.to_owned()));
self
}
#[must_use]
pub fn glob(mut self, pattern: &str) -> Self {
self.1.push((FilterType::GlobPattern, pattern.to_owned()));
self
}
}
impl FileFilter {
pub fn label(&self) -> &str {
&self.0
}
pub fn mimetype_filters(&self) -> Vec<&str> {
self.1
.iter()
.filter_map(|(type_, string)| type_.is_mimetype().then_some(string.as_str()))
.collect()
}
pub fn pattern_filters(&self) -> Vec<&str> {
self.1
.iter()
.filter_map(|(type_, string)| type_.is_pattern().then_some(string.as_str()))
.collect()
}
}
#[derive(Clone, Serialize, Deserialize, Type, Debug)]
pub struct Choice(String, String, Vec<(String, String)>, String);
impl Choice {
pub fn boolean(id: &str, label: &str, state: bool) -> Self {
Self::new(id, label, &state.to_string())
}
pub fn new(id: &str, label: &str, initial_selection: &str) -> Self {
Self(
id.to_owned(),
label.to_owned(),
vec![],
initial_selection.to_owned(),
)
}
#[must_use]
pub fn insert(mut self, key: &str, value: &str) -> Self {
self.2.push((key.to_owned(), value.to_owned()));
self
}
pub fn id(&self) -> &str {
&self.0
}
pub fn label(&self) -> &str {
&self.1
}
pub fn pairs(&self) -> Vec<(&str, &str)> {
self.2
.iter()
.map(|(x, y)| (x.as_str(), y.as_str()))
.collect::<Vec<_>>()
}
pub fn initial_selection(&self) -> &str {
&self.3
}
}
#[derive(Serialize, Deserialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct OpenFileOptions {
#[serde(with = "as_value", skip_deserializing)]
handle_token: HandleToken,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
accept_label: Option<String>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
modal: Option<bool>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
multiple: Option<bool>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
directory: Option<bool>,
#[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")]
filters: Vec<FileFilter>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
current_filter: Option<FileFilter>,
#[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")]
choices: Vec<Choice>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
current_folder: Option<FilePath>,
}
impl OpenFileOptions {
#[must_use]
pub fn set_accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[cfg(feature = "backend")]
pub fn accept_label(&self) -> Option<&str> {
self.accept_label.as_deref()
}
#[must_use]
pub fn set_modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.modal = modal.into();
self
}
#[cfg(feature = "backend")]
pub fn modal(&self) -> Option<bool> {
self.modal
}
#[must_use]
pub fn set_multiple(mut self, multiple: impl Into<Option<bool>>) -> Self {
self.multiple = multiple.into();
self
}
#[cfg(feature = "backend")]
pub fn multiple(&self) -> Option<bool> {
self.multiple
}
#[must_use]
pub fn set_directory(mut self, directory: impl Into<Option<bool>>) -> Self {
self.directory = directory.into();
self
}
#[cfg(feature = "backend")]
pub fn directory(&self) -> Option<bool> {
self.directory
}
#[must_use]
pub fn set_filters(mut self, filters: impl IntoIterator<Item = FileFilter>) -> Self {
self.filters = filters.into_iter().collect();
self
}
#[cfg(feature = "backend")]
pub fn filters(&self) -> &[FileFilter] {
&self.filters
}
#[must_use]
pub fn set_current_filter(mut self, current_filter: impl Into<Option<FileFilter>>) -> Self {
self.current_filter = current_filter.into();
self
}
#[cfg(feature = "backend")]
pub fn current_filter(&self) -> Option<&FileFilter> {
self.current_filter.as_ref()
}
#[must_use]
pub fn set_choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.choices = choices.into_iter().collect();
self
}
#[cfg(feature = "backend")]
pub fn choices(&self) -> &[Choice] {
&self.choices
}
#[must_use]
pub fn set_current_folder(mut self, current_folder: impl Into<Option<FilePath>>) -> Self {
self.current_folder = current_folder.into();
self
}
#[cfg(feature = "backend")]
pub fn current_folder(&self) -> Option<&FilePath> {
self.current_folder.as_ref()
}
}
#[derive(Serialize, Deserialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct SaveFileOptions {
#[serde(with = "as_value", skip_deserializing)]
handle_token: HandleToken,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
accept_label: Option<String>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
modal: Option<bool>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
current_name: Option<String>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
current_folder: Option<FilePath>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
current_file: Option<FilePath>,
#[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")]
filters: Vec<FileFilter>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
current_filter: Option<FileFilter>,
#[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")]
choices: Vec<Choice>,
}
impl SaveFileOptions {
#[must_use]
pub fn set_accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[cfg(feature = "backend")]
pub fn accept_label(&self) -> Option<&str> {
self.accept_label.as_deref()
}
#[must_use]
pub fn set_modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.modal = modal.into();
self
}
#[cfg(feature = "backend")]
pub fn modal(&self) -> Option<bool> {
self.modal
}
#[must_use]
pub fn set_current_name<'a>(mut self, current_name: impl Into<Option<&'a str>>) -> Self {
self.current_name = current_name.into().map(ToOwned::to_owned);
self
}
#[cfg(feature = "backend")]
pub fn current_name(&self) -> Option<&str> {
self.current_name.as_deref()
}
#[must_use]
pub fn set_current_folder(mut self, current_folder: impl Into<Option<FilePath>>) -> Self {
self.current_folder = current_folder.into();
self
}
#[cfg(feature = "backend")]
pub fn current_folder(&self) -> Option<&FilePath> {
self.current_folder.as_ref()
}
#[must_use]
pub fn set_current_file(mut self, current_file: impl Into<Option<FilePath>>) -> Self {
self.current_file = current_file.into();
self
}
#[cfg(feature = "backend")]
pub fn current_file(&self) -> Option<&FilePath> {
self.current_file.as_ref()
}
#[must_use]
pub fn set_filters(mut self, filters: impl IntoIterator<Item = FileFilter>) -> Self {
self.filters = filters.into_iter().collect();
self
}
#[cfg(feature = "backend")]
pub fn filters(&self) -> &[FileFilter] {
&self.filters
}
#[must_use]
pub fn set_current_filter(mut self, current_filter: impl Into<Option<FileFilter>>) -> Self {
self.current_filter = current_filter.into();
self
}
#[cfg(feature = "backend")]
pub fn current_filter(&self) -> Option<&FileFilter> {
self.current_filter.as_ref()
}
#[must_use]
pub fn set_choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.choices = choices.into_iter().collect();
self
}
#[cfg(feature = "backend")]
pub fn choices(&self) -> &[Choice] {
&self.choices
}
}
#[derive(Serialize, Deserialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct SaveFilesOptions {
#[serde(with = "as_value", skip_deserializing)]
handle_token: HandleToken,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
accept_label: Option<String>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
modal: Option<bool>,
#[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")]
choices: Vec<Choice>,
#[serde(default, with = "optional", skip_serializing_if = "Option::is_none")]
current_folder: Option<FilePath>,
#[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")]
files: Vec<FilePath>,
}
impl SaveFilesOptions {
#[must_use]
pub fn set_accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[cfg(feature = "backend")]
pub fn accept_label(&self) -> Option<&str> {
self.accept_label.as_deref()
}
#[must_use]
pub fn set_modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.modal = modal.into();
self
}
#[cfg(feature = "backend")]
pub fn modal(&self) -> Option<bool> {
self.modal
}
#[must_use]
pub fn set_choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.choices = choices.into_iter().collect();
self
}
#[cfg(feature = "backend")]
pub fn choices(&self) -> &[Choice] {
&self.choices
}
#[must_use]
pub fn set_current_folder(mut self, current_folder: impl Into<Option<FilePath>>) -> Self {
self.current_folder = current_folder.into();
self
}
#[cfg(feature = "backend")]
pub fn current_folder(&self) -> Option<&FilePath> {
self.current_folder.as_ref()
}
#[must_use]
pub fn set_files(mut self, files: impl IntoIterator<Item = FilePath>) -> Self {
self.files = files.into_iter().collect();
self
}
#[cfg(feature = "backend")]
pub fn files(&self) -> &[FilePath] {
&self.files
}
}
#[derive(Serialize, Deserialize, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct SelectedFiles {
#[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")]
uris: Vec<Uri>,
#[serde(default, with = "as_value", skip_serializing_if = "Vec::is_empty")]
choices: Vec<(String, String)>,
#[serde(
default,
with = "optional",
skip_serializing_if = "Option::is_none",
skip_deserializing
)]
current_filter: Option<FileFilter>,
#[serde(
default,
with = "optional",
skip_serializing_if = "Option::is_none",
skip_deserializing
)]
writable: Option<bool>,
}
impl SelectedFiles {
pub fn open_file() -> OpenFileRequest {
OpenFileRequest::default()
}
pub fn save_file() -> SaveFileRequest {
SaveFileRequest::default()
}
pub fn save_files() -> SaveFilesRequest {
SaveFilesRequest::default()
}
pub fn uris(&self) -> &[Uri] {
self.uris.as_slice()
}
pub fn choices(&self) -> &[(String, String)] {
&self.choices
}
#[cfg(feature = "backend")]
pub fn uri(mut self, value: Uri) -> Self {
self.uris.push(value);
self
}
#[cfg(feature = "backend")]
pub fn choice(mut self, choice_key: &str, choice_value: &str) -> Self {
self.choices
.push((choice_key.to_owned(), choice_value.to_owned()));
self
}
#[cfg(feature = "backend")]
pub fn current_filter(mut self, value: impl Into<Option<FileFilter>>) -> Self {
self.current_filter = value.into();
self
}
#[cfg(feature = "backend")]
pub fn writable(mut self, value: impl Into<Option<bool>>) -> Self {
self.writable = value.into();
self
}
}
#[doc(alias = "org.freedesktop.portal.FileChooser")]
pub struct FileChooserProxy(Proxy<'static>);
impl FileChooserProxy {
pub async fn new() -> Result<Self, Error> {
let proxy = Proxy::new_desktop("org.freedesktop.portal.FileChooser").await?;
Ok(Self(proxy))
}
pub async fn with_connection(connection: zbus::Connection) -> Result<Self, Error> {
let proxy =
Proxy::new_desktop_with_connection(connection, "org.freedesktop.portal.FileChooser")
.await?;
Ok(Self(proxy))
}
pub fn version(&self) -> u32 {
self.0.version()
}
#[doc(alias = "OpenFile")]
pub async fn open_file(
&self,
identifier: Option<&WindowIdentifier>,
title: &str,
options: OpenFileOptions,
) -> Result<Request<SelectedFiles>, Error> {
let identifier = Optional::from(identifier);
self.0
.request(
&options.handle_token,
"OpenFile",
&(identifier, title, &options),
)
.await
}
#[doc(alias = "SaveFile")]
pub async fn save_file(
&self,
identifier: Option<&WindowIdentifier>,
title: &str,
options: SaveFileOptions,
) -> Result<Request<SelectedFiles>, Error> {
let identifier = Optional::from(identifier);
self.0
.request(
&options.handle_token,
"SaveFile",
&(identifier, title, &options),
)
.await
}
#[doc(alias = "SaveFiles")]
pub async fn save_files(
&self,
identifier: Option<&WindowIdentifier>,
title: &str,
options: SaveFilesOptions,
) -> Result<Request<SelectedFiles>, Error> {
let identifier = Optional::from(identifier);
self.0
.request(
&options.handle_token,
"SaveFiles",
&(identifier, title, &options),
)
.await
}
}
impl std::ops::Deref for FileChooserProxy {
type Target = zbus::Proxy<'static>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Default)]
#[doc(alias = "xdp_portal_open_file")]
pub struct OpenFileRequest {
identifier: Option<WindowIdentifier>,
title: String,
options: OpenFileOptions,
connection: Option<zbus::Connection>,
}
impl OpenFileRequest {
#[must_use]
pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
self.identifier = identifier.into();
self
}
#[must_use]
pub fn title<'a>(mut self, title: impl Into<Option<&'a str>>) -> Self {
self.title = title.into().map(ToOwned::to_owned).unwrap_or_default();
self
}
#[must_use]
pub fn accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.options.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[must_use]
pub fn modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.options.modal = modal.into();
self
}
#[must_use]
pub fn multiple(mut self, multiple: impl Into<Option<bool>>) -> Self {
self.options.multiple = multiple.into();
self
}
#[must_use]
pub fn directory(mut self, directory: impl Into<Option<bool>>) -> Self {
self.options.directory = directory.into();
self
}
#[must_use]
pub fn filter(mut self, filter: FileFilter) -> Self {
self.options.filters.push(filter);
self
}
#[must_use]
pub fn filters(mut self, filters: impl IntoIterator<Item = FileFilter>) -> Self {
self.options.filters = filters.into_iter().collect();
self
}
#[must_use]
pub fn current_filter(mut self, current_filter: impl Into<Option<FileFilter>>) -> Self {
self.options.current_filter = current_filter.into();
self
}
#[must_use]
pub fn choice(mut self, choice: Choice) -> Self {
self.options.choices.push(choice);
self
}
#[must_use]
pub fn choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.options.choices = choices.into_iter().collect();
self
}
pub fn current_folder<P: AsRef<Path>>(
mut self,
current_folder: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.current_folder = current_folder
.into()
.map(|c| FilePath::new(c))
.transpose()?;
Ok(self)
}
#[must_use]
pub fn connection(mut self, connection: Option<zbus::Connection>) -> Self {
self.connection = connection;
self
}
pub async fn send(self) -> Result<Request<SelectedFiles>, Error> {
let proxy = if let Some(connection) = self.connection {
FileChooserProxy::with_connection(connection).await?
} else {
FileChooserProxy::new().await?
};
proxy
.open_file(self.identifier.as_ref(), &self.title, self.options)
.await
}
}
#[derive(Debug, Default)]
#[doc(alias = "xdp_portal_save_files")]
pub struct SaveFilesRequest {
identifier: Option<WindowIdentifier>,
title: String,
options: SaveFilesOptions,
connection: Option<zbus::Connection>,
}
impl SaveFilesRequest {
#[must_use]
pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
self.identifier = identifier.into();
self
}
#[must_use]
pub fn title<'a>(mut self, title: impl Into<Option<&'a str>>) -> Self {
self.title = title.into().map(ToOwned::to_owned).unwrap_or_default();
self
}
#[must_use]
pub fn accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.options.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[must_use]
pub fn modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.options.modal = modal.into();
self
}
#[must_use]
pub fn choice(mut self, choice: Choice) -> Self {
self.options.choices.push(choice);
self
}
#[must_use]
pub fn choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.options.choices = choices.into_iter().collect();
self
}
pub fn current_folder<P: AsRef<Path>>(
mut self,
current_folder: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.current_folder = current_folder
.into()
.map(|c| FilePath::new(c))
.transpose()?;
Ok(self)
}
pub fn files<P: IntoIterator<Item = impl AsRef<Path>>>(
mut self,
files: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
if let Some(f) = files.into() {
self.options.files = f
.into_iter()
.map(|s| FilePath::new(s))
.collect::<Result<Vec<_>, _>>()?;
}
Ok(self)
}
#[must_use]
pub fn connection(mut self, connection: Option<zbus::Connection>) -> Self {
self.connection = connection;
self
}
pub async fn send(self) -> Result<Request<SelectedFiles>, Error> {
let proxy = if let Some(connection) = self.connection {
FileChooserProxy::with_connection(connection).await?
} else {
FileChooserProxy::new().await?
};
proxy
.save_files(self.identifier.as_ref(), &self.title, self.options)
.await
}
}
#[derive(Debug, Default)]
#[doc(alias = "xdp_portal_save_file")]
pub struct SaveFileRequest {
identifier: Option<WindowIdentifier>,
title: String,
options: SaveFileOptions,
connection: Option<zbus::Connection>,
}
impl SaveFileRequest {
#[must_use]
pub fn identifier(mut self, identifier: impl Into<Option<WindowIdentifier>>) -> Self {
self.identifier = identifier.into();
self
}
#[must_use]
pub fn title<'a>(mut self, title: impl Into<Option<&'a str>>) -> Self {
self.title = title.into().map(ToOwned::to_owned).unwrap_or_default();
self
}
#[must_use]
pub fn accept_label<'a>(mut self, accept_label: impl Into<Option<&'a str>>) -> Self {
self.options.accept_label = accept_label.into().map(ToOwned::to_owned);
self
}
#[must_use]
pub fn modal(mut self, modal: impl Into<Option<bool>>) -> Self {
self.options.modal = modal.into();
self
}
#[must_use]
pub fn current_name<'a>(mut self, current_name: impl Into<Option<&'a str>>) -> Self {
self.options.current_name = current_name.into().map(ToOwned::to_owned);
self
}
pub fn current_folder<P: AsRef<Path>>(
mut self,
current_folder: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.current_folder = current_folder
.into()
.map(|c| FilePath::new(c))
.transpose()?;
Ok(self)
}
pub fn current_file<P: AsRef<Path>>(
mut self,
current_file: impl Into<Option<P>>,
) -> Result<Self, crate::Error> {
self.options.current_file = current_file.into().map(|c| FilePath::new(c)).transpose()?;
Ok(self)
}
#[must_use]
pub fn filter(mut self, filter: FileFilter) -> Self {
self.options.filters.push(filter);
self
}
#[must_use]
pub fn filters(mut self, filters: impl IntoIterator<Item = FileFilter>) -> Self {
self.options.filters = filters.into_iter().collect();
self
}
#[must_use]
pub fn current_filter(mut self, current_filter: impl Into<Option<FileFilter>>) -> Self {
self.options.current_filter = current_filter.into();
self
}
#[must_use]
pub fn choice(mut self, choice: Choice) -> Self {
self.options.choices.push(choice);
self
}
#[must_use]
pub fn choices(mut self, choices: impl IntoIterator<Item = Choice>) -> Self {
self.options.choices = choices.into_iter().collect();
self
}
#[must_use]
pub fn connection(mut self, connection: Option<zbus::Connection>) -> Self {
self.connection = connection;
self
}
pub async fn send(self) -> Result<Request<SelectedFiles>, Error> {
let proxy = if let Some(connection) = self.connection {
FileChooserProxy::with_connection(connection).await?
} else {
FileChooserProxy::new().await?
};
proxy
.save_file(self.identifier.as_ref(), &self.title, self.options)
.await
}
}