From 89bc74e644d2fd5d5add36543f464b84d44ec93a Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Wed, 2 Aug 2023 19:57:37 -0700 Subject: [PATCH] start working on terminal launcher --- src-tauri/Cargo.lock | 12 +++++ src-tauri/Cargo.toml | 1 + src-tauri/src/app.rs | 1 + src-tauri/src/config.rs | 12 +++++ src-tauri/src/ipc.rs | 10 ++++ src-tauri/src/lib.rs | 1 + src-tauri/src/terminal.rs | 97 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 134 insertions(+) create mode 100644 src-tauri/src/terminal.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 09746c8..1579058 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1066,6 +1066,7 @@ dependencies = [ "tauri-plugin-single-instance", "thiserror", "tokio", + "which", ] [[package]] @@ -5145,6 +5146,17 @@ dependencies = [ "windows-metadata", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index bbd2b7e..e1e2a4f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -46,6 +46,7 @@ clap = { version = "3.2.23", features = ["derive"] } is-terminal = "0.4.7" argon2 = { version = "0.5.0", features = ["std"] } chacha20poly1305 = { version = "0.10.1", features = ["std"] } +which = "4.4.0" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/app.rs b/src-tauri/src/app.rs index 603190d..0d2fe0a 100644 --- a/src-tauri/src/app.rs +++ b/src-tauri/src/app.rs @@ -42,6 +42,7 @@ pub fn run() -> tauri::Result<()> { ipc::save_credentials, ipc::get_config, ipc::save_config, + ipc::launch_terminal, ]) .setup(|app| rt::block_on(setup(app))) .build(tauri::generate_context!())? diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index cd82565..ead3552 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -9,6 +9,16 @@ use sqlx::SqlitePool; use crate::errors::*; +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TermConfig { + pub name: String, + // we call it exec because it isn't always the actual path, + // in some cases it's just the name and relies on path-searching + pub exec: PathBuf, + pub args: Vec, +} + + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct AppConfig { #[serde(default = "default_listen_addr")] @@ -21,6 +31,7 @@ pub struct AppConfig { pub start_minimized: bool, #[serde(default = "default_start_on_login")] pub start_on_login: bool, + pub terminal: Option, } @@ -32,6 +43,7 @@ impl Default for AppConfig { rehide_ms: default_rehide_ms(), start_minimized: default_start_minimized(), start_on_login: default_start_on_login(), + terminal: None, } } } diff --git a/src-tauri/src/ipc.rs b/src-tauri/src/ipc.rs index ea6a62c..8cd3b36 100644 --- a/src-tauri/src/ipc.rs +++ b/src-tauri/src/ipc.rs @@ -6,6 +6,7 @@ use crate::credentials::{Session,BaseCredentials}; use crate::errors::*; use crate::clientinfo::Client; use crate::state::AppState; +use crate::terminal; #[derive(Clone, Debug, Serialize, Deserialize)] @@ -78,3 +79,12 @@ pub async fn save_config(config: AppConfig, app_state: State<'_, AppState>) -> R .map_err(|e| format!("Error saving config: {e}"))?; Ok(()) } + + +#[tauri::command] +pub async fn launch_terminal(base: bool) -> bool { + match terminal::launch(base).await { + Ok(_) => true, + Err(_) => false, + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a594d9b..2c1fbeb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,4 +7,5 @@ mod clientinfo; mod ipc; mod state; mod server; +mod terminal; mod tray; diff --git a/src-tauri/src/terminal.rs b/src-tauri/src/terminal.rs new file mode 100644 index 0000000..de539a6 --- /dev/null +++ b/src-tauri/src/terminal.rs @@ -0,0 +1,97 @@ +use std::process::Command; + +use tauri::Manager; + +use crate::app::APP; +use crate::config::TermConfig; +use crate::credentials::Session; +use crate::errors::*; +use crate::state::AppState; + + +pub async fn launch(use_base: bool) -> Result<(), ExecError> { + // we may have multiple candidates, because we might be on unix and + // we don't have a good way of detecting for sure what default to use + let state = APP.get().unwrap().state::(); + let config = state.config.read().await; + let _ = match config.terminal { + Some(ref term) => launch_term(term, use_base).await, + None => launch_default(use_base).await, + }?; + Ok(()) +} + + +async fn launch_term(term: &TermConfig, use_base: bool) -> Result<(), std::io::Error> { + // do all this in a block so we don't hold the lock any longer than necessary + let mut cmd = Command::new(&term.exec); + cmd.args(&term.args); + { + // note: if called from launch(), there is already a lock being held on state.config + // don't read that here or we will deadlock + let state = APP.get().unwrap().state::(); + let app_session = state.session.read().await; + let (base_creds, session_creds) = match *app_session { + Session::Locked(_) | Session::Empty => todo!(), + Session::Unlocked{ref base, ref session} => (base, session), + }; + + if use_base { + cmd.env("AWS_ACCESS_KEY_ID", &base_creds.access_key_id); + cmd.env("AWS_SECRET_ACCESS_KEY", &base_creds.secret_access_key); + } + else { + cmd.env("AWS_ACCESS_KEY_ID", &session_creds.access_key_id); + cmd.env("AWS_SECRET_ACCESS_KEY", &session_creds.secret_access_key); + cmd.env("AWS_SESSION_TOKEN", &session_creds.token); + } + } + + let _ = cmd.spawn()?; + Ok(()) +} + + +async fn launch_default(use_base: bool) -> Result<(), std::io::Error> { + let defaults = default_terms(); + let last_idx = defaults.len() - 1; + for (i, candidate) in defaults.iter().enumerate() { + match launch_term(candidate, use_base).await { + Ok(_) => return Ok(()), + Err(e) => { + if std::io::ErrorKind::NotFound == e.kind() && i < last_idx { + continue; + } + return Err(e); + } + } + } + // we only continue the loop if there are further iterations left, so this is safe + unreachable!() +} + + +pub fn default_terms() -> Vec { + #[cfg(windows)] + return vec![ + TermConfig { + name: "powershell.exe".into(), + exec: "conhost.exe".into(), + args: vec!["powershell.exe".into()] + } + ]; + + #[cfg(unix)] + return vec![ + TermConfig { + name: "gnome-terminal".into(), + exec: "gnome-terminal".into(), + args: vec![], + }, + TermConfig { + name: "konsole".into(), + exec: "konsole".into(), + args: vec![], + }, + ]; +} \ No newline at end of file