Skip to content

Commit

Permalink
make process start async
Browse files Browse the repository at this point in the history
  • Loading branch information
mmertama committed Apr 3, 2023
1 parent 66327a8 commit 7a69eb5
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 47 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ GemGui lets write platform independent UI applications with Rust, and combines p

GemGui itself does not contain an application window. The UI by default uses native system browser. However that is fully configurable per application e.g. to utilize Python webview or browser in kiosk-mode.

The Python webview can be installed using Pip - see [PyPi](https://pypi.org/project/pywebview/0.5/)

GemGui is absolute Rust rewrite of [Gempyre C++ GUI Library](https://github.com/mmertama/Gempyre).

Available at [crates.io](https://crates.io/).
Expand Down
5 changes: 3 additions & 2 deletions gemgui/res/pyclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,9 @@ def exit_f():
await ws.close()
return
except websockets.ConnectionClosedError as e:
print(f"Connection closed: {ws_uri} due {e}")
destroy_window()
if not window_destroyed:
print(f"Connection closed: {ws_uri} due {e}")
destroy_window()
return

doc = receive.result()
Expand Down
2 changes: 1 addition & 1 deletion gemgui/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ impl Element {
pub async fn rect<T>(&self) -> Result<Rect<T>>
where T: FromStr + Clone + Copy {
let result = self.query("bounding_rect", &vec![]).await;
let err = |e: &str| Err(GemGuiError::Err(format!("Bad value {e}")));
let err = |e: &str| GemGuiError::error(format!("Bad value {e}"));
match result {
Ok(value) => {
match crate::value_to_string_map(value) {
Expand Down
12 changes: 10 additions & 2 deletions gemgui/src/gemgui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
//!
//! There are few ways to initiate main
//! 1) Use [tokio](https://docs.rs/tokio/latest/tokio/attr.main.html)
//!
//! ```no_run
//! # use gemgui::GemGuiError;
//! # use gemgui::Result;
Expand All @@ -37,6 +38,7 @@
//! }
//!```
//! 2) Use [application](application)
//!
//! ```no_run
//! # use gemgui::GemGuiError;
//! # use gemgui::Result;
Expand All @@ -53,6 +55,10 @@
//! }
//!```
//! 3) [window application](window_application)
//!
//! Window application uses Python webview, it to can be installed using Pip,
//! see [PyPi](https://pypi.org/project/pywebview/0.5/)
//!
//! ```no_run
//! # use gemgui::GemGuiError;
//! # use gemgui::Result;
Expand Down Expand Up @@ -460,8 +466,10 @@ impl GemGuiError {
GemGuiError::Err(err.to_string())
}

fn error<T>(err: &str) -> Result<T> {
Err(GemGuiError::new(err))
fn error<T, Str>(err: Str) -> Result<T>
where Str: Into<String> {
let err = err.into();
Err(GemGuiError::new(&err))
}
}

Expand Down
12 changes: 6 additions & 6 deletions gemgui/src/graphics/bitmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ impl Bitmap {
let result = image.as_rgb8();
match result {
Some(im) => Ok(Self::from_rgb(im)),
None => Err(GemGuiError::Err("Bad image".to_string())),
None => GemGuiError::error("Bad image".to_string()),
}
}
}
Expand All @@ -156,10 +156,10 @@ impl Bitmap {
Ok(reader) => {
match reader.decode() {
Ok(image) => Self::from_image(image),
Err(e) => Err(GemGuiError::Err(format!("Bad format, {e}"))),
Err(e) => GemGuiError::error(format!("Bad format, {e}")),
}
},
Err(e) => Err(GemGuiError::Err(format!("Image file not found: {filename}, {e}"))),
Err(e) => GemGuiError::error(format!("Image file not found: {filename}, {e}")),
}
}

Expand All @@ -178,10 +178,10 @@ impl Bitmap {
Ok(reader) => {
match reader.decode() {
Ok(image) => Self::from_image(image),
Err(e) => Err(GemGuiError::Err(format!("Bad format, {e}"))),
Err(e) => GemGuiError::error(format!("Bad format, {e}")),
}
},
Err(e) => Err(GemGuiError::Err(format!("Expected an image format, {e:#?}"))),
Err(e) => GemGuiError::error(format!("Expected an image format, {e:#?}")),
}
}

Expand Down Expand Up @@ -387,7 +387,7 @@ impl Bitmap {
}
match rgba.save(filename) {
Ok(_) => Ok(()),
Err(e) => Err(GemGuiError::Err(format!("{e}")))
Err(e) => GemGuiError::error(format!("{e}"))
}
}

Expand Down
78 changes: 55 additions & 23 deletions gemgui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ include!(concat!(env!("OUT_DIR"), "/generated.rs"));
// batch commands
pub (crate) static BATCH_BEGIN: &str = "batch_begin";
pub (crate) static BATCH_END: &str = "batch_end";
pub (crate) static CLOSE_REQUEST: &str = "close_request";


// ##known issues:##
Expand Down Expand Up @@ -360,7 +361,7 @@ impl Gui {

pub fn new(user_map : Filemap, index_html: &str, port: u16) -> Result<Self> {
if ! port_scanner::local_port_available(port) {
return Err(GemGuiError::Err(format!("Port {port} is not available")));
return GemGuiError::error(format!("Port {port} is not available"));
}
let mut filemap = user_map;
for resource in RESOURCES {
Expand All @@ -373,7 +374,7 @@ impl Gui {
}

if ! filemap.contains_key(index_html) {
return Err(GemGuiError::Err(format!("Error {index_html}, not found")));
return GemGuiError::error(format!("Error {index_html}, not found"));
}

let filemap = Arc::new(Mutex::new(filemap));
Expand Down Expand Up @@ -414,7 +415,7 @@ impl Gui {
format!("http://127.0.0.1:{}/{}", self.server.port(), self.index_html)
}

async fn run_process(cmd: (String, Vec<String>)) -> bool {
async fn run_process(cmd: (String, Vec<String>)) -> Result<bool> {

let output = Command::new(&cmd.0)
.args(&cmd.1)
Expand All @@ -427,21 +428,27 @@ impl Gui {
Ok(mut child) => {
match child.try_wait() {
Ok(status) => match status {
None => return true,
Some(err) => eprintln!("Spawn early exit: {err}"),
None => Ok(true),
Some(err) => {
if err.code().unwrap_or(0) != 0 {
eprintln!("Spawned process {} not running {err}", cmd.0);
Ok(false)
} else {
Ok(true) // OSX uses 'open' app to spawn browser, hence it may have ended, we just rely on error code
}
},
},
Err(err) => eprintln!("Spawn failed: {err}"),
};
Err(err) => GemGuiError::error(format!("Spawn process failed: {err}")),
}
}, // here we get handle to spawned UI - not used now as exit is done nicely
Err(e) => {
if cmd.1.is_empty() {
eprintln!("Error while spawning call:'{}' error:{} - URL is missing!", cmd.0, e);
GemGuiError::error(format!("Error while spawning call:'{}' error:{} - URL is missing!", cmd.0, e))
} else {
eprintln!("Error while spawning call:'{}' params:'{:#?}' error:{}", cmd.0, cmd.1, e);
GemGuiError::error(format!("Error while spawning call:'{}' params:'{:#?}' error:{}", cmd.0, cmd.1, e))
}
}
};
false
}
}


Expand Down Expand Up @@ -513,23 +520,48 @@ impl Gui {
true
}

fn default_start_cmd(&self) -> Result<(String, Vec<String>)> {
let start_cmd = utils::html_file_launch_cmd();
if start_cmd.is_none() {
return GemGuiError::error("Cannot find a default application");
}
let mut start_cmd = start_cmd.unwrap();
start_cmd.1.push(self.address());
Ok(start_cmd)
}


/// Start event loop
pub async fn run(&mut self) -> Result<()> {
static DEFAULT_ERROR: &str = "Cannot fallback to default";

let default_cmd = self.default_start_cmd();

let cmd = match &self.start_cmd {
Some(v) => v.clone(),
None => {
let start_cmd = utils::html_file_launch_cmd();
if start_cmd.is_none() {
return GemGuiError::error("Cannot find a default application");
}
let mut start_cmd = start_cmd.unwrap();
start_cmd.1.push(self.address());
start_cmd
},
None => default_cmd.clone().expect(DEFAULT_ERROR),
};

let on_start = move |_| {Self::run_process(cmd)};
let on_start = move |_| async move {
let success = match Self::run_process(cmd.clone()).await {
Ok(success) => {
if ! success {
let default_cmd = default_cmd.expect(DEFAULT_ERROR);
if cmd.0 != default_cmd.0.clone() {
let default_ok = Self::run_process(default_cmd).await.unwrap_or_else(|e| panic!("{e}"));
eprintln!("Requested UI failed, falling back to default: {default_ok}");
}
}
true
},
Err(err) => panic!("{err}"),
};
success
};

//let on_start = move |_| {Self::run_process(cmd)};


let server_wait = self.start_server(on_start).await;
if server_wait.is_none() {
return GemGuiError::error("Starting server failed");
Expand All @@ -550,11 +582,11 @@ impl Gui {
Ok(m) => {
match m._type.as_str() {
"keepalive" => {
println!("keep alive");
// println!("keep alive");
},
"uiready" => UiData::entered(&self.ui),
"start_request" => self.start_handler(),
"close_request" => {
"close_request" => { // whaaat CLOSE_REQUEST cannot be used!
self.exit(); // send exit to all windows - then go
break; },
"event" => self.event_handler(m),
Expand Down
21 changes: 18 additions & 3 deletions gemgui/src/ui/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::JSMessageTx;
use crate::JSType;
use crate::ui::BATCH_BEGIN;
use crate::ui::BATCH_END;
use crate::ui::CLOSE_REQUEST;
use crate::ui::utils::get_extension_from_filename;
use crate::ui_data::ROOT_ID;

Expand Down Expand Up @@ -78,11 +79,25 @@ impl MsgTx {
// sends message from element to socket server
impl MsgTx {
async fn do_send(tx: BroadcastSender<Message>, msg: String) {
tx.send(Message::text(msg)).unwrap_or_else(|e|{eprintln!("Fatal {e}"); 0});
tx.send(Message::text(&msg)).unwrap_or_else(|e| {
// somewhat heavy error handing only upon error - then we look if
// it was on close_exit and ignore if channels are already closed
let obj = serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(&msg);
if obj.is_err()
|| !obj.as_ref().unwrap().contains_key("type")
|| obj.as_ref().unwrap()["type"].to_string() != format!("\"{CLOSE_REQUEST}\"")
|| tx.receiver_count() != 0 {
eprintln!("Channel error {e} on {msg} tx count: {}", tx.receiver_count());
}
0
});
}

async fn do_send_bin(tx: BroadcastSender<Message>, msg: Vec<u8>) {
tx.send(Message::binary(msg)).unwrap_or_else(|e|{eprintln!("Fatal {e}"); 0});
tx.send(Message::binary(msg)).unwrap_or_else(|e|{
eprintln!("Channel error {e}");
0
});
}
}

Expand Down Expand Up @@ -216,7 +231,7 @@ impl WSServer {
eprintln!("Closed code:{} std:{}", cf.0, cf.1);
}
}
let close = String::from("{\"type\": \"close_request\"}");
let close = format!("{{\"type\": \"{CLOSE_REQUEST}\"}}");
subscription_sender.send(close).await.unwrap();
break;
} else if msg.is_ping() {
Expand Down
2 changes: 1 addition & 1 deletion gemgui/src/ui_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ impl UiData {
Ok(())
},
None => {
Err(GemGuiError::Err(format!("Warning timer {id} not found")))
GemGuiError::error(format!("Warning timer {id} not found"))
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions gemgui/src/ui_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl UiRef {
if value.is_number() {
Ok(value.as_f64().unwrap() as f32)
} else {
Err(GemGuiError::Err(format!("Not a number {value}")))
GemGuiError::error(format!("Not a number {value}"))
}
}

Expand All @@ -65,7 +65,7 @@ impl UiRef {
if value.is_boolean() {
Ok(value.as_bool().unwrap())
} else {
GemGuiError::error(&format!("Not a bool {value}"))
GemGuiError::error(format!("Not a bool {value}"))
}
}

Expand Down Expand Up @@ -261,7 +261,7 @@ impl UiRef {

match rx.recv().await {
Some(_) => element,
None => GemGuiError::error(&format!("Element {id} not constructed"))
None => GemGuiError::error(format!("Element {id} not constructed"))
}
}

Expand Down Expand Up @@ -291,7 +291,7 @@ impl UiRef {

match value {
Ok(v) => Ok(v),
Err(e) => Err(GemGuiError::Err(format!("Query error {e}")))
Err(e) => GemGuiError::error(format!("Query error {e}"))
}
}

Expand Down
10 changes: 5 additions & 5 deletions gemgui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ pub async fn open_file(ui: &UiRef, dir: &Path, filters: &[(&str, std::vec::Vec<&
let path = Path::new(&file_name);
return Ok(path.to_path_buf());
}
Err(GemGuiError::Err("Invalid type".to_string()))
GemGuiError::error("Invalid type".to_string())
}


Expand Down Expand Up @@ -252,7 +252,7 @@ pub async fn open_files(ui: &UiRef, dir: &Path, filters: &[(&str, std::vec::Vec<

return Ok(paths);
}
Err(GemGuiError::Err("Invalid type".to_string()))
GemGuiError::error("Invalid type".to_string())
}


Expand All @@ -276,7 +276,7 @@ pub async fn open_dir(ui: &UiRef, dir: &Path) -> Result<PathBuf, GemGuiError> {
let path = Path::new(&file_name);
return Ok(path.to_path_buf());
}
Err(GemGuiError::Err("Invalid type".to_string()))
GemGuiError::error("Invalid type".to_string())
}


Expand Down Expand Up @@ -306,7 +306,7 @@ pub async fn save_file(ui: &UiRef, dir: &Path, filters: &[(&str, std::vec::Vec<&
let path = Path::new(&file_name);
return Ok(path.to_path_buf());
}
Err(GemGuiError::Err("Invalid type".to_string()))
GemGuiError::error("Invalid type".to_string())

}

Expand Down Expand Up @@ -340,6 +340,6 @@ async fn dialog(ui: &UiRef, dialog_type: DialogType, dialog_params: JSMap) -> R
_ => Ok(DialogValue::FileName(value.as_str().expect("Not a string").to_string()))
}
},
Err(e) => Err(GemGuiError::Err(format!("Extension error {e}")))
Err(e) => GemGuiError::error(format!("Extension error {e}"))
}
}

0 comments on commit 7a69eb5

Please sign in to comment.