use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::os::unix::ffi::OsStrExt;
use std::{ffi::CString, path::Path};
use zbus::zvariant::{DeserializeDict, SerializeDict, Type};
use super::{HandleToken, DESTINATION, PATH};
use crate::{helpers::call_request_method, Error, WindowIdentifier};
#[derive(Serialize, Deserialize, Type, Clone, Debug)]
pub struct FileFilter(String, Vec<(FilterType, String)>);
#[derive(Serialize_repr, Clone, Deserialize_repr, PartialEq, Debug, Type)]
#[repr(u8)]
enum FilterType {
GlobPattern = 0,
MimeType = 1,
}
impl FileFilter {
pub fn new(label: &str) -> Self {
Self(label.to_string(), vec![])
}
#[must_use]
pub fn mimetype(mut self, mimetype: &str) -> Self {
self.1.push((FilterType::MimeType, mimetype.to_string()));
self
}
#[must_use]
pub fn glob(mut self, pattern: &str) -> Self {
self.1.push((FilterType::GlobPattern, pattern.to_string()));
self
}
}
#[derive(Serialize, Deserialize, Type, Clone, 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_string(),
label.to_string(),
vec![],
initial_selection.to_string(),
)
}
#[must_use]
pub fn insert(mut self, key: &str, value: &str) -> Self {
self.2.push((key.to_string(), value.to_string()));
self
}
pub fn id(&self) -> &str {
&self.0
}
pub fn label(&self) -> &str {
&self.1
}
pub fn initial_selection(&self) -> &str {
&self.3
}
}
#[derive(SerializeDict, DeserializeDict, Type, Clone, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct OpenFileOptions {
handle_token: HandleToken,
accept_label: Option<String>,
modal: Option<bool>,
multiple: Option<bool>,
directory: Option<bool>,
filters: Vec<FileFilter>,
current_filter: Option<FileFilter>,
choices: Vec<Choice>,
}
impl OpenFileOptions {
#[must_use]
pub fn accept_label(mut self, accept_label: &str) -> Self {
self.accept_label = Some(accept_label.to_string());
self
}
#[must_use]
pub fn modal(mut self, modal: bool) -> Self {
self.modal = Some(modal);
self
}
#[must_use]
pub fn multiple(mut self, multiple: bool) -> Self {
self.multiple = Some(multiple);
self
}
#[must_use]
pub fn directory(mut self, directory: bool) -> Self {
self.directory = Some(directory);
self
}
#[must_use]
pub fn add_filter(mut self, filter: FileFilter) -> Self {
self.filters.push(filter);
self
}
#[must_use]
pub fn current_filter(mut self, current_filter: FileFilter) -> Self {
self.current_filter = Some(current_filter);
self
}
#[must_use]
pub fn add_choice(mut self, choice: Choice) -> Self {
self.choices.push(choice);
self
}
}
#[derive(SerializeDict, DeserializeDict, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct SaveFileOptions {
handle_token: HandleToken,
accept_label: Option<String>,
modal: Option<bool>,
current_name: Option<String>,
current_folder: Option<Vec<u8>>,
current_file: Option<Vec<u8>>,
filters: Vec<FileFilter>,
current_filter: Option<FileFilter>,
choices: Vec<Choice>,
}
impl SaveFileOptions {
#[must_use]
pub fn accept_label(mut self, accept_label: &str) -> Self {
self.accept_label = Some(accept_label.to_string());
self
}
#[must_use]
pub fn current_name(mut self, current_name: &str) -> Self {
self.current_name = Some(current_name.to_string());
self
}
#[must_use]
pub fn current_folder(mut self, current_folder: impl AsRef<Path> + Type + Serialize) -> Self {
let cstr = CString::new(current_folder.as_ref().as_os_str().as_bytes())
.expect("`current_folder` should not be null terminated");
self.current_folder = Some(cstr.into_bytes_with_nul());
self
}
#[must_use]
pub fn current_file(mut self, current_file: impl AsRef<Path> + Type + Serialize) -> Self {
let cstr = CString::new(current_file.as_ref().as_os_str().as_bytes())
.expect("`current_file` should not be null terminated");
self.current_file = Some(cstr.into_bytes_with_nul());
self
}
#[must_use]
pub fn modal(mut self, modal: bool) -> Self {
self.modal = Some(modal);
self
}
#[must_use]
pub fn add_filter(mut self, filter: FileFilter) -> Self {
self.filters.push(filter);
self
}
#[must_use]
pub fn current_filter(mut self, current_filter: FileFilter) -> Self {
self.current_filter = Some(current_filter);
self
}
#[must_use]
pub fn add_choice(mut self, choice: Choice) -> Self {
self.choices.push(choice);
self
}
}
#[derive(SerializeDict, DeserializeDict, Type, Debug, Default)]
#[zvariant(signature = "dict")]
pub struct SaveFilesOptions {
handle_token: HandleToken,
accept_label: Option<String>,
modal: Option<bool>,
choices: Vec<Choice>,
current_folder: Option<Vec<u8>>,
files: Option<Vec<Vec<u8>>>,
}
impl SaveFilesOptions {
#[must_use]
pub fn accept_label(mut self, accept_label: &str) -> Self {
self.accept_label = Some(accept_label.to_string());
self
}
#[must_use]
pub fn modal(mut self, modal: bool) -> Self {
self.modal = Some(modal);
self
}
#[must_use]
pub fn add_choice(mut self, choice: Choice) -> Self {
self.choices.push(choice);
self
}
#[must_use]
pub fn current_folder(mut self, current_folder: impl AsRef<Path> + Type + Serialize) -> Self {
let cstr = CString::new(current_folder.as_ref().as_os_str().as_bytes())
.expect("`current_folder` should not be null terminated");
self.current_folder = Some(cstr.into_bytes_with_nul());
self
}
#[must_use]
pub fn files(mut self, files: &[impl AsRef<Path> + Type + Serialize]) -> Self {
self.files = Some(
files
.iter()
.map(|s| {
let cstr = CString::new(s.as_ref().as_os_str().as_bytes())
.expect("`files` should not be null terminated");
cstr.into_bytes_with_nul()
})
.collect(),
);
self
}
}
#[derive(Debug, Type, SerializeDict, Clone, DeserializeDict)]
#[zvariant(signature = "dict")]
pub struct SelectedFiles {
uris: Vec<String>,
choices: Option<Vec<(String, String)>>,
}
impl SelectedFiles {
pub fn uris(&self) -> &[String] {
self.uris.as_slice()
}
pub fn choices(&self) -> &[(String, String)] {
self.choices.as_deref().unwrap_or_default()
}
}
#[derive(Debug)]
#[doc(alias = "org.freedesktop.portal.FileChooser")]
pub struct FileChooserProxy<'a>(zbus::Proxy<'a>);
impl<'a> FileChooserProxy<'a> {
pub async fn new(connection: &zbus::Connection) -> Result<FileChooserProxy<'a>, Error> {
let proxy = zbus::ProxyBuilder::new_bare(connection)
.interface("org.freedesktop.portal.FileChooser")?
.path(PATH)?
.destination(DESTINATION)?
.build()
.await?;
Ok(Self(proxy))
}
pub fn inner(&self) -> &zbus::Proxy<'_> {
&self.0
}
#[doc(alias = "OpenFile")]
#[doc(alias = "xdp_portal_open_file")]
pub async fn open_file(
&self,
identifier: &WindowIdentifier,
title: &str,
options: OpenFileOptions,
) -> Result<SelectedFiles, Error> {
call_request_method(
self.inner(),
&options.handle_token,
"OpenFile",
&(&identifier, title, &options),
)
.await
}
#[doc(alias = "SaveFile")]
#[doc(alias = "xdp_portal_save_file")]
pub async fn save_file(
&self,
identifier: &WindowIdentifier,
title: &str,
options: SaveFileOptions,
) -> Result<SelectedFiles, Error> {
call_request_method(
self.inner(),
&options.handle_token,
"SaveFile",
&(&identifier, title, &options),
)
.await
}
#[doc(alias = "SaveFiles")]
#[doc(alias = "xdp_portal_save_files")]
pub async fn save_files(
&self,
identifier: &WindowIdentifier,
title: &str,
options: SaveFilesOptions,
) -> Result<SelectedFiles, Error> {
call_request_method(
self.inner(),
&options.handle_token,
"SaveFiles",
&(&identifier, title, &options),
)
.await
}
}