start working on terminal launcher

This commit is contained in:
Joseph Montanaro 2023-08-02 19:57:37 -07:00
parent 60c24e3ee4
commit 89bc74e644
7 changed files with 134 additions and 0 deletions

12
src-tauri/Cargo.lock generated
View File

@ -1066,6 +1066,7 @@ dependencies = [
"tauri-plugin-single-instance", "tauri-plugin-single-instance",
"thiserror", "thiserror",
"tokio", "tokio",
"which",
] ]
[[package]] [[package]]
@ -5145,6 +5146,17 @@ dependencies = [
"windows-metadata", "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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -46,6 +46,7 @@ clap = { version = "3.2.23", features = ["derive"] }
is-terminal = "0.4.7" is-terminal = "0.4.7"
argon2 = { version = "0.5.0", features = ["std"] } argon2 = { version = "0.5.0", features = ["std"] }
chacha20poly1305 = { version = "0.10.1", features = ["std"] } chacha20poly1305 = { version = "0.10.1", features = ["std"] }
which = "4.4.0"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode

View File

@ -42,6 +42,7 @@ pub fn run() -> tauri::Result<()> {
ipc::save_credentials, ipc::save_credentials,
ipc::get_config, ipc::get_config,
ipc::save_config, ipc::save_config,
ipc::launch_terminal,
]) ])
.setup(|app| rt::block_on(setup(app))) .setup(|app| rt::block_on(setup(app)))
.build(tauri::generate_context!())? .build(tauri::generate_context!())?

View File

@ -9,6 +9,16 @@ use sqlx::SqlitePool;
use crate::errors::*; 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<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AppConfig { pub struct AppConfig {
#[serde(default = "default_listen_addr")] #[serde(default = "default_listen_addr")]
@ -21,6 +31,7 @@ pub struct AppConfig {
pub start_minimized: bool, pub start_minimized: bool,
#[serde(default = "default_start_on_login")] #[serde(default = "default_start_on_login")]
pub start_on_login: bool, pub start_on_login: bool,
pub terminal: Option<TermConfig>,
} }
@ -32,6 +43,7 @@ impl Default for AppConfig {
rehide_ms: default_rehide_ms(), rehide_ms: default_rehide_ms(),
start_minimized: default_start_minimized(), start_minimized: default_start_minimized(),
start_on_login: default_start_on_login(), start_on_login: default_start_on_login(),
terminal: None,
} }
} }
} }

View File

@ -6,6 +6,7 @@ use crate::credentials::{Session,BaseCredentials};
use crate::errors::*; use crate::errors::*;
use crate::clientinfo::Client; use crate::clientinfo::Client;
use crate::state::AppState; use crate::state::AppState;
use crate::terminal;
#[derive(Clone, Debug, Serialize, Deserialize)] #[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}"))?; .map_err(|e| format!("Error saving config: {e}"))?;
Ok(()) Ok(())
} }
#[tauri::command]
pub async fn launch_terminal(base: bool) -> bool {
match terminal::launch(base).await {
Ok(_) => true,
Err(_) => false,
}
}

View File

@ -7,4 +7,5 @@ mod clientinfo;
mod ipc; mod ipc;
mod state; mod state;
mod server; mod server;
mod terminal;
mod tray; mod tray;

97
src-tauri/src/terminal.rs Normal file
View File

@ -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::<AppState>();
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::<AppState>();
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<TermConfig> {
#[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![],
},
];
}