diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a86f9..78c7ce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Change Log ## [Unreleased] +- Use zenity as a fallback for xdgp - Update `raw-window-handle` to 0.6. - Update `winit` in example to 0.29. - Update `ashpd` to 0.8. diff --git a/src/backend/linux/zenity.rs b/src/backend/linux/zenity.rs index c8e0541..5d55e6b 100644 --- a/src/backend/linux/zenity.rs +++ b/src/backend/linux/zenity.rs @@ -69,7 +69,6 @@ async fn run(command: Command) -> ZenityResult> { Ok((res.status.success() || !buffer.is_empty()).then_some(buffer)) } -#[allow(unused)] pub async fn pick_file(dialog: &FileDialog) -> ZenityResult> { let mut command = command(); command.arg("--file-selection"); @@ -85,7 +84,6 @@ pub async fn pick_file(dialog: &FileDialog) -> ZenityResult> { }) } -#[allow(unused)] pub async fn pick_files(dialog: &FileDialog) -> ZenityResult> { let mut command = command(); command.args(["--file-selection", "--multiple"]); @@ -98,11 +96,10 @@ pub async fn pick_files(dialog: &FileDialog) -> ZenityResult> { let list = buffer.trim().split('|').map(PathBuf::from).collect(); list }) - .unwrap_or(Vec::new()) + .unwrap_or_default() }) } -#[allow(unused)] pub async fn pick_folder(dialog: &FileDialog) -> ZenityResult> { let mut command = command(); command.args(["--file-selection", "--directory"]); @@ -118,7 +115,22 @@ pub async fn pick_folder(dialog: &FileDialog) -> ZenityResult> { }) } -#[allow(unused)] +pub async fn pick_folders(dialog: &FileDialog) -> ZenityResult> { + let mut command = command(); + command.args(["--file-selection", "--directory", "--multiple"]); + + add_filters(&mut command, &dialog.filters); + add_filename(&mut command, &dialog.file_name); + + run(command).await.map(|res| { + res.map(|buffer| { + let list = buffer.trim().split('|').map(PathBuf::from).collect(); + list + }) + .unwrap_or_default() + }) +} + pub async fn save_file(dialog: &FileDialog) -> ZenityResult> { let mut command = command(); command.args(["--file-selection", "--save", "--confirm-overwrite"]); diff --git a/src/backend/xdg_desktop_portal.rs b/src/backend/xdg_desktop_portal.rs index 0bbabad..9f4d3e8 100644 --- a/src/backend/xdg_desktop_portal.rs +++ b/src/backend/xdg_desktop_portal.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use super::linux::zenity; use crate::backend::DialogFutureType; use crate::file_dialog::Filter; use crate::message_dialog::MessageDialog; @@ -9,10 +10,11 @@ use ashpd::desktop::file_chooser::{FileFilter, OpenFileRequest, SaveFileRequest} // TODO: convert raw_window_handle::RawWindowHandle to ashpd::WindowIdentifier // https://github.com/bilelmoussaoui/ashpd/issues/40 +use log::error; use pollster::block_on; -impl From for FileFilter { - fn from(filter: Filter) -> Self { +impl From<&Filter> for FileFilter { + fn from(filter: &Filter) -> Self { let mut ashpd_filter = FileFilter::new(&filter.name); for file_extension in &filter.extensions { ashpd_filter = ashpd_filter.glob(&format!("*.{file_extension}")); @@ -40,49 +42,67 @@ impl FilePickerDialogImpl for FileDialog { use crate::backend::AsyncFilePickerDialogImpl; impl AsyncFilePickerDialogImpl for FileDialog { fn pick_file_async(self) -> DialogFutureType> { - Box::pin(async { - OpenFileRequest::default() + Box::pin(async move { + let res = OpenFileRequest::default() .accept_label("Pick file") .multiple(false) - .title(&*self.title.unwrap_or_else(|| "Pick a file".to_string())) - .filters(self.filters.into_iter().map(From::from)) + .title(self.title.as_deref().unwrap_or("Pick a file")) + .filters(self.filters.iter().map(From::from)) .send() - .await - .ok() - .and_then(|request| request.response().ok()) - .and_then(|response| { - response - .uris() - .get(0) - .and_then(|uri| uri.to_file_path().ok()) - }) - .map(FileHandle::from) + .await; + + if res.is_err() { + match zenity::pick_file(&self).await { + Ok(res) => res, + Err(err) => { + error!("pick_file error {err}"); + return None; + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .and_then(|response| { + response + .uris() + .first() + .and_then(|uri| uri.to_file_path().ok()) + }) + } + .map(FileHandle::from) }) } fn pick_files_async(self) -> DialogFutureType>> { - Box::pin(async { - OpenFileRequest::default() + Box::pin(async move { + let res = OpenFileRequest::default() .accept_label("Pick file(s)") .multiple(true) - .title( - &*self - .title - .unwrap_or_else(|| "Pick one or more files".to_string()), - ) - .filters(self.filters.into_iter().map(From::from)) + .title(self.title.as_deref().unwrap_or("Pick one or more files")) + .filters(self.filters.iter().map(From::from)) .send() - .await - .ok() - .and_then(|request| request.response().ok()) - .map(|response| { - response - .uris() - .iter() - .filter_map(|uri| uri.to_file_path().ok()) - .map(FileHandle::from) - .collect::>() - }) + .await; + + if res.is_err() { + match zenity::pick_files(&self).await { + Ok(res) => Some(res.into_iter().map(FileHandle::from).collect::>()), + Err(err) => { + error!("pick_files error {err}"); + None + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .map(|response| { + response + .uris() + .iter() + .filter_map(|uri| uri.to_file_path().ok()) + .map(FileHandle::from) + .collect::>() + }) + } }) } } @@ -106,51 +126,69 @@ impl FolderPickerDialogImpl for FileDialog { use crate::backend::AsyncFolderPickerDialogImpl; impl AsyncFolderPickerDialogImpl for FileDialog { fn pick_folder_async(self) -> DialogFutureType> { - Box::pin(async { - OpenFileRequest::default() + Box::pin(async move { + let res = OpenFileRequest::default() .accept_label("Pick folder") .multiple(false) .directory(true) - .title(&*self.title.unwrap_or_else(|| "Pick a folder".to_string())) - .filters(self.filters.into_iter().map(From::from)) + .title(self.title.as_deref().unwrap_or("Pick a folder")) + .filters(self.filters.iter().map(From::from)) .send() - .await - .ok() - .and_then(|request| request.response().ok()) - .and_then(|response| { - response - .uris() - .get(0) - .and_then(|uri| uri.to_file_path().ok()) - }) - .map(FileHandle::from) + .await; + + if res.is_err() { + match zenity::pick_folder(&self).await { + Ok(res) => res, + Err(err) => { + error!("pick_folder error {err}"); + return None; + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .and_then(|response| { + response + .uris() + .first() + .and_then(|uri| uri.to_file_path().ok()) + }) + } + .map(FileHandle::from) }) } fn pick_folders_async(self) -> DialogFutureType>> { - Box::pin(async { - OpenFileRequest::default() + Box::pin(async move { + let res = OpenFileRequest::default() .accept_label("Pick folders") .multiple(true) .directory(true) - .title( - &*self - .title - .unwrap_or_else(|| "Pick one or more folders".to_string()), - ) - .filters(self.filters.into_iter().map(From::from)) + .title(self.title.as_deref().unwrap_or("Pick one or more folders")) + .filters(self.filters.iter().map(From::from)) .send() - .await - .ok() - .and_then(|request| request.response().ok()) - .map(|response| { - response - .uris() - .iter() - .filter_map(|uri| uri.to_file_path().ok()) - .map(FileHandle::from) - .collect::>() - }) + .await; + + if res.is_err() { + match zenity::pick_folders(&self).await { + Ok(res) => Some(res.into_iter().map(FileHandle::from).collect::>()), + Err(err) => { + error!("pick_files error {err}"); + None + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .map(|response| { + response + .uris() + .iter() + .filter_map(|uri| uri.to_file_path().ok()) + .map(FileHandle::from) + .collect::>() + }) + } }) } } @@ -170,24 +208,35 @@ use crate::backend::AsyncFileSaveDialogImpl; impl AsyncFileSaveDialogImpl for FileDialog { fn save_file_async(self) -> DialogFutureType> { Box::pin(async move { - SaveFileRequest::default() + let res = SaveFileRequest::default() .accept_label("Save") - .title(&*self.title.unwrap_or_else(|| "Save file".to_string())) + .title(self.title.as_deref().unwrap_or("Save file")) .current_name(self.file_name.as_deref()) - .filters(self.filters.into_iter().map(From::from)) - .current_folder::(self.starting_directory) + .filters(self.filters.iter().map(From::from)) + .current_folder::<&PathBuf>(&self.starting_directory) .expect("File path should not be nul-terminated") .send() - .await - .ok() - .and_then(|request| request.response().ok()) - .and_then(|response| { - response - .uris() - .get(0) - .and_then(|uri| uri.to_file_path().ok()) - }) - .map(FileHandle::from) + .await; + + if res.is_err() { + match zenity::save_file(&self).await { + Ok(res) => res, + Err(err) => { + error!("pick_folder error {err}"); + return None; + } + } + } else { + res.ok() + .and_then(|request| request.response().ok()) + .and_then(|response| { + response + .uris() + .first() + .and_then(|uri| uri.to_file_path().ok()) + }) + } + .map(FileHandle::from) }) } } diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 75d574f..d1a5692 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -30,6 +30,7 @@ pub struct FileDialog { // Oh god, I don't like sending RawWindowHandle between threads but here we go anyways... // fingers crossed unsafe impl Send for FileDialog {} +unsafe impl Sync for FileDialog {} impl FileDialog { /// New file dialog builder diff --git a/src/file_handle/native.rs b/src/file_handle/native.rs index 17b6112..54c91c1 100644 --- a/src/file_handle/native.rs +++ b/src/file_handle/native.rs @@ -177,7 +177,7 @@ impl std::fmt::Debug for FileHandle { impl From for FileHandle { fn from(path: PathBuf) -> Self { - Self(path) + Self::wrap(path) } }