187 lines
5.3 KiB
Rust
Raw Normal View History

use std::error::Error;
use std::time::Duration;
use once_cell::sync::OnceCell;
use sqlx::{
SqlitePool,
sqlite::SqlitePoolOptions,
sqlite::SqliteConnectOptions,
};
use tauri::{
App,
AppHandle,
async_runtime as rt,
2024-06-03 01:21:43 -04:00
Manager,
RunEvent,
WindowEvent,
};
use creddy_cli::GlobalArgs;
use crate::{
config::{self, AppConfig},
2024-06-19 05:10:55 -04:00
credentials::AppSession,
ipc,
2024-07-03 14:54:10 -04:00
srv::{creddy_server, agent},
errors::*,
shortcuts,
state::AppState,
tray,
};
pub static APP: OnceCell<AppHandle> = OnceCell::new();
pub fn run(global_args: GlobalArgs) -> tauri::Result<()> {
if let Ok(_) = creddy_cli::show_window(global_args) {
// app is already running, so terminate
return Ok(());
}
tauri::Builder::default()
2024-06-01 20:17:50 -04:00
.plugin(tauri_plugin_global_shortcut::Builder::default().build())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![
ipc::unlock,
2024-06-25 15:19:29 -04:00
ipc::lock,
2024-06-28 11:19:52 -04:00
ipc::reset_session,
2024-06-25 15:19:29 -04:00
ipc::set_passphrase,
ipc::respond,
ipc::get_session_status,
ipc::signal_activity,
2024-06-25 15:19:29 -04:00
ipc::save_credential,
ipc::delete_credential,
ipc::list_credentials,
2024-07-01 06:38:46 -04:00
ipc::sshkey_from_file,
2024-07-03 16:28:12 -04:00
ipc::sshkey_from_private_key,
ipc::get_config,
ipc::save_config,
2023-08-02 19:57:37 -07:00
ipc::launch_terminal,
ipc::get_setup_errors,
ipc::get_devmode,
2024-06-28 20:35:18 -04:00
ipc::exit,
])
.setup(|app| rt::block_on(setup(app)))
.build(tauri::generate_context!())?
2024-06-03 01:21:43 -04:00
.run(|app, run_event| {
if let RunEvent::WindowEvent { event, .. } = run_event {
if let WindowEvent::CloseRequested { api, .. } = event {
let _ = hide_main_window(app);
api.prevent_close();
}
}
});
Ok(())
}
pub async fn connect_db() -> Result<SqlitePool, SetupError> {
let conn_opts = SqliteConnectOptions::new()
.filename(config::get_or_create_db_path()?)
.create_if_missing(true);
let pool_opts = SqlitePoolOptions::new();
let pool: SqlitePool = pool_opts.connect_with(conn_opts).await?;
sqlx::migrate!().run(&pool).await?;
Ok(pool)
}
async fn setup(app: &mut App) -> Result<(), Box<dyn Error>> {
2024-06-01 20:17:50 -04:00
APP.set(app.handle().clone()).unwrap();
tray::setup(app)?;
2023-09-13 11:06:40 -07:00
// get_or_create_db_path doesn't create the actual db file, just the directory
let is_first_launch = !config::get_or_create_db_path()?.exists();
let pool = connect_db().await?;
let mut setup_errors: Vec<String> = vec![];
2023-09-13 11:06:40 -07:00
let mut conf = match AppConfig::load(&pool).await {
2023-09-13 11:06:40 -07:00
Ok(c) => c,
Err(LoadKvError::Invalid(_)) => {
setup_errors.push(
"Could not load configuration from database. Reverting to defaults.".into()
);
2023-09-13 11:06:40 -07:00
AppConfig::default()
},
err => err?,
};
2024-06-19 05:10:55 -04:00
let app_session = AppSession::load(&pool).await?;
2024-07-03 14:54:10 -04:00
creddy_server::serve(app.handle().clone())?;
agent::serve(app.handle().clone())?;
config::set_auto_launch(conf.start_on_login)?;
if let Err(_e) = config::set_auto_launch(conf.start_on_login) {
setup_errors.push("Error: Failed to manage autolaunch.".into());
}
// if hotkeys fail to register, disable them so that this error doesn't have to keep showing up
if let Err(_e) = shortcuts::register_hotkeys(&conf.hotkeys) {
conf.hotkeys.disable_all();
conf.save(&pool).await?;
setup_errors.push("Failed to register hotkeys. Hotkey settings have been disabled.".into());
}
2023-09-13 11:06:40 -07:00
let desktop_is_gnome = std::env::var("XDG_CURRENT_DESKTOP")
.map(|names| names.split(':').any(|n| n == "GNOME"))
.unwrap_or(false);
2023-07-11 16:13:20 -07:00
if !conf.start_minimized || is_first_launch {
show_main_window(&app.handle())?;
}
2024-06-19 05:10:55 -04:00
let state = AppState::new(conf, app_session, pool, setup_errors, desktop_is_gnome);
app.manage(state);
// make sure we do this after managing app state, so that it doesn't panic
2024-06-01 20:17:50 -04:00
start_auto_locker(app.app_handle().clone());
Ok(())
}
fn start_auto_locker(app: AppHandle) {
rt::spawn(async move {
let state = app.state::<AppState>();
loop {
// this gives our session-timeout a minimum resolution of 10s, which seems fine?
let delay = Duration::from_secs(10);
tokio::time::sleep(delay).await;
if state.should_auto_lock().await {
2024-06-03 01:21:43 -04:00
state.lock().await.error_popup("Failed to lock Creddy");
}
}
});
}
pub fn show_main_window(app: &AppHandle) -> Result<(), WindowError> {
2024-06-01 20:17:50 -04:00
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
w.show()?;
let menu = app.state::<tray::MenuItems>();
menu.after_show()?;
Ok(())
}
pub fn hide_main_window(app: &AppHandle) -> Result<(), WindowError> {
2024-06-01 20:17:50 -04:00
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
w.hide()?;
let menu = app.state::<tray::MenuItems>();
menu.after_hide()?;
Ok(())
}
pub fn toggle_main_window(app: &AppHandle) -> Result<(), WindowError> {
2024-06-01 20:17:50 -04:00
let w = app.get_webview_window("main").ok_or(WindowError::NoMainWindow)?;
if w.is_visible()? {
hide_main_window(app)
}
else {
show_main_window(app)
}
}