Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use XDG Desktop Portal on Linux & BSDs #41

Merged
merged 10 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ repository = "https://github.com/PolyMeilex/rfd"
documentation = "https://docs.rs/rfd"

[features]
default = ["parent"]
default = ["parent", "gtk3"]
parent = ["raw-window-handle"]
file-handle-inner = []
gtk3 = ["gtk-sys", "glib-sys", "gobject-sys", "lazy_static"]

[dev-dependencies]
futures = "0.3.12"
Expand All @@ -35,11 +36,14 @@ windows = { version = "0.30.0", features = [
"Win32_UI_WindowsAndMessaging",
] }

[target.'cfg(any(target_os = "freebsd", target_os = "linux"))'.dependencies]
gtk-sys = { version = "0.15.1", features = ["v3_20"] }
glib-sys = "0.15.1"
gobject-sys = "0.15.1"
lazy_static = "1.4.0"
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
ashpd = "0.2.0-beta-1"
pollster = "0.2"
log = "0.4"
gtk-sys = { version = "0.15.1", features = ["v3_20"], optional = true }
glib-sys = { version = "0.15.1", optional = true }
gobject-sys = { version = "0.15.1", optional = true }
lazy_static = { version = "1.4.0", optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2.69"
Expand Down
15 changes: 15 additions & 0 deletions examples/msg.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
fn main() {
let res = "";
#[cfg(any(
target_os = "windows",
target_os = "macos",
all(
any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "gtk3"
)
))]
PolyMeilex marked this conversation as resolved.
Show resolved Hide resolved
let res = rfd::MessageDialog::new()
.set_title("Msg!")
.set_description("Description!")
Expand Down
22 changes: 21 additions & 1 deletion src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,34 @@ use std::future::Future;
use std::path::PathBuf;
use std::pin::Pin;

#[cfg(any(target_os = "freebsd", target_os = "linux"))]
#[cfg(all(
any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "gtk3"
))]
mod gtk3;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_arch = "wasm32")]
mod wasm;
#[cfg(target_os = "windows")]
mod win_cid;
#[cfg(all(
any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "openbsd"
),
not(feature = "gtk3")
))]
mod xdg_desktop_portal;

//
// Sync
Expand Down
216 changes: 216 additions & 0 deletions src/backend/xdg_desktop_portal.rs
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should provide a feature for that in ashpd

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// 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)
})
}
}
12 changes: 12 additions & 0 deletions src/file_handle/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,15 @@ impl From<PathBuf> for FileHandle {
Self(path)
}
}

impl From<FileHandle> for PathBuf {
fn from(file_handle: FileHandle) -> Self {
PathBuf::from(file_handle.path())
}
}

impl From<&FileHandle> for PathBuf {
fn from(file_handle: &FileHandle) -> Self {
PathBuf::from(file_handle.path())
}
}
30 changes: 30 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,36 @@ pub use file_dialog::FileDialog;

pub use file_dialog::AsyncFileDialog;

#[cfg(any(
target_os = "windows",
target_os = "macos",
target_family = "wasm",
all(
any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "gtk3"
)
))]
mod message_dialog;

#[cfg(any(
target_os = "windows",
target_os = "macos",
target_family = "wasm",
all(
any(
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "openbsd"
),
feature = "gtk3"
)
))]
pub use message_dialog::{AsyncMessageDialog, MessageButtons, MessageDialog, MessageLevel};