-
Notifications
You must be signed in to change notification settings - Fork 57
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add suport for XDG Desktop Portal on Linux & BSDs (#41)
* use XDG Desktop Portal on Linux & BSDs This new backend does not support MessageDialog nor AsyncMessageDialog because there is no corresponding API in the XDG Desktop Portal. The GTK backend is still available with the new `gtk3` Cargo feature. Fixes #36 * replace smol with pollster pollster is smaller than smol * rename unwrap_or_warn to ok_or_warn * reuse async functions to implement sync functions * replace Option::ok with ok_or_warn * impl From<FileHandle> for PathBuf to reduce code duplication * cargo fmt * factor out file_chooser_proxy function * add link to ashpd issue for RawWindowHandle bilelmoussaoui/ashpd#40 * make gtk3 a default feature
- Loading branch information
Showing
6 changed files
with
304 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
use std::path::PathBuf; | ||
|
||
use crate::backend::DialogFutureType; | ||
use crate::file_dialog::Filter; | ||
use crate::{FileDialog, FileHandle}; | ||
|
||
use ashpd::desktop::file_chooser::{ | ||
FileChooserProxy, FileFilter, OpenFileOptions, SaveFileOptions, | ||
}; | ||
// TODO: convert raw_window_handle::RawWindowHandle to ashpd::WindowIdentifier | ||
// https://github.com/bilelmoussaoui/ashpd/issues/40 | ||
use ashpd::{zbus, WindowIdentifier}; | ||
|
||
use log::warn; | ||
use pollster::block_on; | ||
|
||
// | ||
// Utility functions | ||
// | ||
|
||
fn add_filters_to_open_file_options( | ||
filters: Vec<Filter>, | ||
mut options: OpenFileOptions, | ||
) -> OpenFileOptions { | ||
for filter in &filters { | ||
let mut ashpd_filter = FileFilter::new(&filter.name); | ||
for file_extension in &filter.extensions { | ||
ashpd_filter = ashpd_filter.glob(&format!("*.{}", file_extension)); | ||
} | ||
options = options.add_filter(ashpd_filter); | ||
} | ||
options | ||
} | ||
|
||
fn add_filters_to_save_file_options( | ||
filters: Vec<Filter>, | ||
mut options: SaveFileOptions, | ||
) -> SaveFileOptions { | ||
for filter in &filters { | ||
let mut ashpd_filter = FileFilter::new(&filter.name); | ||
for file_extension in &filter.extensions { | ||
ashpd_filter = ashpd_filter.glob(&format!("*.{}", file_extension)); | ||
} | ||
options = options.add_filter(ashpd_filter); | ||
} | ||
options | ||
} | ||
|
||
// refer to https://github.com/flatpak/xdg-desktop-portal/issues/213 | ||
fn uri_to_pathbuf(uri: &str) -> Option<PathBuf> { | ||
uri.strip_prefix("file://").map(PathBuf::from) | ||
} | ||
|
||
fn ok_or_warn<T, E: std::fmt::Debug>(result: Result<T, E>) -> Option<T> { | ||
match result { | ||
Err(e) => { | ||
warn!("{:?}", e); | ||
None | ||
} | ||
Ok(t) => Some(t), | ||
} | ||
} | ||
|
||
async fn file_chooser_proxy<'a>() -> Option<FileChooserProxy<'a>> { | ||
let connection = ok_or_warn(zbus::Connection::session().await)?; | ||
ok_or_warn(FileChooserProxy::new(&connection).await) | ||
} | ||
|
||
// | ||
// File Picker | ||
// | ||
|
||
use crate::backend::FilePickerDialogImpl; | ||
impl FilePickerDialogImpl for FileDialog { | ||
fn pick_file(self) -> Option<PathBuf> { | ||
block_on(self.pick_file_async()).map(PathBuf::from) | ||
} | ||
|
||
fn pick_files(self) -> Option<Vec<PathBuf>> { | ||
block_on(self.pick_files_async()) | ||
.map(|vec_file_handle| vec_file_handle.iter().map(PathBuf::from).collect()) | ||
} | ||
} | ||
|
||
use crate::backend::AsyncFilePickerDialogImpl; | ||
impl AsyncFilePickerDialogImpl for FileDialog { | ||
fn pick_file_async(self) -> DialogFutureType<Option<FileHandle>> { | ||
Box::pin(async { | ||
let proxy = file_chooser_proxy().await?; | ||
let mut options = OpenFileOptions::default() | ||
.accept_label("Pick file") | ||
.multiple(false); | ||
options = add_filters_to_open_file_options(self.filters, options); | ||
let selected_files = proxy | ||
.open_file( | ||
&WindowIdentifier::default(), | ||
&self.title.unwrap_or_else(|| "Pick a file".to_string()), | ||
options, | ||
) | ||
.await; | ||
if selected_files.is_err() { | ||
return None; | ||
} | ||
uri_to_pathbuf(&selected_files.unwrap().uris()[0]).map(FileHandle::from) | ||
}) | ||
} | ||
|
||
fn pick_files_async(self) -> DialogFutureType<Option<Vec<FileHandle>>> { | ||
Box::pin(async { | ||
let proxy = file_chooser_proxy().await?; | ||
let mut options = OpenFileOptions::default() | ||
.accept_label("Pick file(s)") | ||
.multiple(true); | ||
options = add_filters_to_open_file_options(self.filters, options); | ||
let selected_files = proxy | ||
.open_file( | ||
&WindowIdentifier::default(), | ||
&self | ||
.title | ||
.unwrap_or_else(|| "Pick one or more files".to_string()), | ||
options, | ||
) | ||
.await; | ||
if selected_files.is_err() { | ||
return None; | ||
} | ||
let selected_files = selected_files | ||
.unwrap() | ||
.uris() | ||
.iter() | ||
.filter_map(|string| uri_to_pathbuf(string)) | ||
.map(FileHandle::from) | ||
.collect::<Vec<FileHandle>>(); | ||
if selected_files.is_empty() { | ||
return None; | ||
} | ||
Some(selected_files) | ||
}) | ||
} | ||
} | ||
|
||
// | ||
// Folder Picker | ||
// | ||
|
||
use crate::backend::FolderPickerDialogImpl; | ||
impl FolderPickerDialogImpl for FileDialog { | ||
fn pick_folder(self) -> Option<PathBuf> { | ||
block_on(self.pick_folder_async()).map(PathBuf::from) | ||
} | ||
} | ||
|
||
use crate::backend::AsyncFolderPickerDialogImpl; | ||
impl AsyncFolderPickerDialogImpl for FileDialog { | ||
fn pick_folder_async(self) -> DialogFutureType<Option<FileHandle>> { | ||
Box::pin(async { | ||
let proxy = file_chooser_proxy().await?; | ||
let mut options = OpenFileOptions::default() | ||
.accept_label("Pick folder") | ||
.multiple(false) | ||
.directory(true); | ||
options = add_filters_to_open_file_options(self.filters, options); | ||
let selected_files = proxy | ||
.open_file( | ||
&WindowIdentifier::default(), | ||
&self.title.unwrap_or_else(|| "Pick a folder".to_string()), | ||
options, | ||
) | ||
.await; | ||
if selected_files.is_err() { | ||
return None; | ||
} | ||
uri_to_pathbuf(&selected_files.unwrap().uris()[0]).map(FileHandle::from) | ||
}) | ||
} | ||
} | ||
|
||
// | ||
// File Save | ||
// | ||
|
||
use crate::backend::FileSaveDialogImpl; | ||
impl FileSaveDialogImpl for FileDialog { | ||
fn save_file(self) -> Option<PathBuf> { | ||
block_on(self.save_file_async()).map(PathBuf::from) | ||
} | ||
} | ||
|
||
use crate::backend::AsyncFileSaveDialogImpl; | ||
impl AsyncFileSaveDialogImpl for FileDialog { | ||
fn save_file_async(self) -> DialogFutureType<Option<FileHandle>> { | ||
Box::pin(async { | ||
let proxy = file_chooser_proxy().await?; | ||
let mut options = SaveFileOptions::default().accept_label("Save"); | ||
options = add_filters_to_save_file_options(self.filters, options); | ||
if let Some(file_name) = self.file_name { | ||
options = options.current_name(&file_name); | ||
} | ||
// TODO: impl zvariant::Type for PathBuf? | ||
// if let Some(dir) = self.starting_directory { | ||
// options.current_folder(dir); | ||
// } | ||
let selected_files = proxy | ||
.save_file( | ||
&WindowIdentifier::default(), | ||
&self.title.unwrap_or_else(|| "Save file".to_string()), | ||
options, | ||
) | ||
.await; | ||
if selected_files.is_err() { | ||
return None; | ||
} | ||
uri_to_pathbuf(&selected_files.unwrap().uris()[0]).map(FileHandle::from) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters