Skip to content

Commit

Permalink
Thread based zenity backend (#165)
Browse files Browse the repository at this point in the history
  • Loading branch information
PolyMeilex authored Dec 8, 2023
1 parent 81aa5d6 commit 94e3941
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 82 deletions.
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ documentation = "https://docs.rs/rfd"
default = ["gtk3"]
file-handle-inner = []
gtk3 = ["gtk-sys", "glib-sys", "gobject-sys"]
xdg-portal = ["ashpd", "urlencoding", "pollster", "async-io", "futures-util"]
xdg-portal = ["ashpd", "urlencoding", "pollster"]
common-controls-v6 = ["windows-sys/Win32_UI_Controls"]

[dev-dependencies]
Expand All @@ -41,10 +41,6 @@ windows-sys = { version = "0.48", features = [
] }

[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
# Make sure that this is in sync with zbus, to avoid duplicated deps
async-io = { version = "1.3", optional = true }
futures-util = { version = "0.3", optional = true, default-features = false, features = ["io"] }

# XDG Desktop Portal
ashpd = { version = "0.6", optional = true }
urlencoding = { version = "2.1.0", optional = true }
Expand Down
55 changes: 55 additions & 0 deletions src/backend/linux/async_command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::{
io,
pin::Pin,
sync::{Arc, Mutex},
task::{Context, Poll, Waker},
};

struct State {
waker: Option<Waker>,
data: Option<io::Result<std::process::Output>>,
}

pub struct AsyncCommand {
state: Arc<Mutex<State>>,
}

impl AsyncCommand {
pub fn spawn(mut command: std::process::Command) -> Self {
let state = Arc::new(Mutex::new(State {
waker: None,
data: None,
}));

std::thread::spawn({
let state = state.clone();
move || {
let output = command.output();

let mut state = state.lock().unwrap();
state.data = Some(output);

if let Some(waker) = state.waker.take() {
waker.wake();
}
}
});

Self { state }
}
}

impl std::future::Future for AsyncCommand {
type Output = io::Result<std::process::Output>;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut state = self.state.lock().unwrap();

if state.data.is_some() {
Poll::Ready(state.data.take().unwrap())
} else {
state.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}
26 changes: 0 additions & 26 deletions src/backend/linux/child_stdout.rs

This file was deleted.

2 changes: 1 addition & 1 deletion src/backend/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
mod child_stdout;
mod async_command;
pub(crate) mod zenity;
89 changes: 41 additions & 48 deletions src/backend/linux/zenity.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
use futures_util::AsyncReadExt;
use std::{
error::Error,
fmt::Display,
path::PathBuf,
process::{Command, Stdio},
time::Duration,
};
use std::{error::Error, fmt::Display, path::PathBuf, process::Command};

use super::child_stdout::ChildStdout;
use crate::{file_dialog::Filter, message_dialog::{MessageButtons, MessageLevel}, FileDialog, MessageDialogResult};
use crate::{
file_dialog::Filter,
message_dialog::{MessageButtons, MessageLevel},
FileDialog, MessageDialogResult,
};

#[derive(Debug)]
pub enum ZenityError {
Io(std::io::Error),
StdOutNotFound,
FromUtf8Error(std::string::FromUtf8Error),
}

impl Error for ZenityError {}
Expand All @@ -22,7 +18,7 @@ impl Display for ZenityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ZenityError::Io(io) => write!(f, "{io}"),
ZenityError::StdOutNotFound => write!(f, "Stdout not found"),
ZenityError::FromUtf8Error(err) => err.fmt(f),
}
}
}
Expand All @@ -33,6 +29,12 @@ impl From<std::io::Error> for ZenityError {
}
}

impl From<std::string::FromUtf8Error> for ZenityError {
fn from(value: std::string::FromUtf8Error) -> Self {
Self::FromUtf8Error(value)
}
}

pub type ZenityResult<T> = Result<T, ZenityError>;

fn command() -> Command {
Expand Down Expand Up @@ -60,24 +62,11 @@ fn add_filename(command: &mut Command, file_name: &Option<String>) {
}
}

async fn run(mut command: Command) -> ZenityResult<Option<String>> {
let mut process = command.stdout(Stdio::piped()).spawn()?;

let stdout = process.stdout.take().ok_or(ZenityError::StdOutNotFound)?;
let mut stdout = ChildStdout::new(stdout)?;

let mut buffer = String::new();
stdout.read_to_string(&mut buffer).await?;

let status = loop {
if let Some(status) = process.try_wait()? {
break status;
}

async_io::Timer::after(Duration::from_millis(1)).await;
};
async fn run(command: Command) -> ZenityResult<Option<String>> {
let res = super::async_command::AsyncCommand::spawn(command).await?;
let buffer = String::from_utf8(res.stdout)?;

Ok((status.success() || !buffer.is_empty()).then_some(buffer))
Ok((res.status.success() || !buffer.is_empty()).then_some(buffer))
}

#[allow(unused)]
Expand Down Expand Up @@ -176,7 +165,11 @@ pub async fn message(
})
}

pub async fn question(btns: &MessageButtons, title: &str, description: &str) -> ZenityResult<MessageDialogResult> {
pub async fn question(
btns: &MessageButtons,
title: &str,
description: &str,
) -> ZenityResult<MessageDialogResult> {
let mut command = command();
command.args(["--question", "--title", title, "--text", description]);

Expand All @@ -188,43 +181,43 @@ pub async fn question(btns: &MessageButtons, title: &str, description: &str) ->
MessageButtons::OkCancelCustom(ok, cancel) => {
command.args(["--ok-label", ok.as_str()]);
command.args(["--cancel-label", cancel.as_str()]);
},
}
MessageButtons::YesNoCancel => {
command.args(["--extra-button", "No"]);
command.args(["--cancel-label", "Cancel"]);
},
}
MessageButtons::YesNoCancelCustom(yes, no, cancel) => {
command.args(["--ok-label", yes.as_str()]);
command.args(["--cancel-label", cancel.as_str()]);
command.args(["--extra-button", no.as_str()]);
},
}
_ => {}
}

run(command).await.map(|res| match btns {
MessageButtons::OkCancel => match res {
Some(_) => MessageDialogResult::Ok,
None => MessageDialogResult::Cancel,
}
},
MessageButtons::YesNo => match res {
Some(_) => MessageDialogResult::Yes,
None => MessageDialogResult::No,
}
},
MessageButtons::OkCancelCustom(ok, cancel) => match res {
Some(_) => MessageDialogResult::Custom(ok.clone()),
None => MessageDialogResult::Custom(cancel.clone()),
}
},
MessageButtons::YesNoCancel => match res {
Some(output) if output.is_empty() => MessageDialogResult::Yes,
Some(_) => MessageDialogResult::No,
None => MessageDialogResult::Cancel,
}
},
MessageButtons::YesNoCancelCustom(yes, no, cancel) => match res {
Some(output) if output.is_empty() => MessageDialogResult::Custom(yes.clone()),
Some(_) => MessageDialogResult::Custom(no.clone()),
None => MessageDialogResult::Custom(cancel.clone()),
}
_ => MessageDialogResult::Cancel
},
_ => MessageDialogResult::Cancel,
})
}

Expand All @@ -235,21 +228,21 @@ mod tests {
#[test]
#[ignore]
fn message() {
async_io::block_on(super::message(
pollster::block_on(super::message(
&crate::message_dialog::MessageLevel::Info,
&crate::message_dialog::MessageButtons::Ok,
"hi",
"me",
))
.unwrap();
async_io::block_on(super::message(
pollster::block_on(super::message(
&crate::message_dialog::MessageLevel::Warning,
&crate::message_dialog::MessageButtons::Ok,
"hi",
"me",
))
.unwrap();
async_io::block_on(super::message(
pollster::block_on(super::message(
&crate::message_dialog::MessageLevel::Error,
&crate::message_dialog::MessageButtons::Ok,
"hi",
Expand All @@ -261,13 +254,13 @@ mod tests {
#[test]
#[ignore]
fn question() {
async_io::block_on(super::question(
pollster::block_on(super::question(
&crate::message_dialog::MessageButtons::OkCancel,
"hi",
"me",
))
.unwrap();
async_io::block_on(super::question(
pollster::block_on(super::question(
&crate::message_dialog::MessageButtons::YesNo,
"hi",
"me",
Expand All @@ -278,28 +271,28 @@ mod tests {
#[test]
#[ignore]
fn pick_file() {
let path = async_io::block_on(super::pick_file(&FileDialog::default())).unwrap();
let path = pollster::block_on(super::pick_file(&FileDialog::default())).unwrap();
dbg!(path);
}

#[test]
#[ignore]
fn pick_files() {
let path = async_io::block_on(super::pick_files(&FileDialog::default())).unwrap();
let path = pollster::block_on(super::pick_files(&FileDialog::default())).unwrap();
dbg!(path);
}

#[test]
#[ignore]
fn pick_folder() {
let path = async_io::block_on(super::pick_folder(&FileDialog::default())).unwrap();
let path = pollster::block_on(super::pick_folder(&FileDialog::default())).unwrap();
dbg!(path);
}

#[test]
#[ignore]
fn save_file() {
let path = async_io::block_on(super::save_file(&FileDialog::default())).unwrap();
let path = pollster::block_on(super::save_file(&FileDialog::default())).unwrap();
dbg!(path);
}
}

0 comments on commit 94e3941

Please sign in to comment.