diff --git a/crates/desktop/src/launcher.rs b/crates/desktop/src/launcher.rs index f798161..f4dfedc 100644 --- a/crates/desktop/src/launcher.rs +++ b/crates/desktop/src/launcher.rs @@ -1,4 +1,5 @@ -use async_compat::Compat; +use std::{path::Path, time::Duration}; + use directories::ProjectDirs; use slint::PlatformError; use tracing::info; @@ -17,33 +18,7 @@ pub fn setup() -> Result { }; let lock_file = proj_dirs.data_local_dir().join(".rx.is.alive"); - if lock_file.exists() { - eprintln!("Another instance of the application is already running."); - let api_port = std::fs::read_to_string(&lock_file).unwrap_or_else(|_| { - eprintln!("Failed to read lock file"); - std::fs::remove_file(&lock_file).unwrap_or_else(|_| { - eprintln!("Failed to remove lock file"); - }); - std::process::exit(1); - }); - eprintln!("Notify the other instance to raise..."); - slint::spawn_local(Compat::new(async move {})).expect("Failed to spawn thread"); - let client = reqwest::blocking::Client::new(); - match client - .post(format!("http://127.0.0.1:{api_port}/popup")) - .header("User-Agent", format!("wsrx/{}", env!("CARGO_PKG_VERSION"))) - .send() - { - Ok(_) => { - eprintln!("Notification sent."); - } - Err(e) => { - eprintln!("Failed to send notification: {e}, removing lock file."); - std::fs::remove_file(&lock_file).unwrap_or_else(|_| { - eprintln!("Failed to remove lock file"); - }); - } - } + if lock_file.exists() && try_focus_existing_instance(&lock_file) { std::process::exit(0); } @@ -64,10 +39,82 @@ pub fn setup() -> Result { Ok(ui) } -pub fn shutdown(ui: &slint::Weak) { - let window = ui.upgrade().unwrap(); - bridges::settings::save_config(&window); - daemon::save_scopes(ui); +fn try_focus_existing_instance(lock_file: &Path) -> bool { + eprintln!("Detected existing instance lock file. Trying to notify running app..."); + + let Some(api_port) = read_lock_file_port(lock_file) else { + return false; + }; + + match notify_existing_instance(api_port) { + Ok(()) => { + eprintln!("Notification sent."); + true + } + Err(err) => { + eprintln!("Failed to notify existing app: {err}. Removing stale lock file."); + remove_lock_file(lock_file); + false + } + } +} + +fn remove_lock_file(lock_file: &Path) { + std::fs::remove_file(lock_file).unwrap_or_else(|err| { + if err.kind() != std::io::ErrorKind::NotFound { + eprintln!("Failed to remove lock file: {err}"); + } + }); +} + +fn read_lock_file_port(lock_file: &Path) -> Option { + match std::fs::read_to_string(lock_file) { + Ok(port) => match port.trim().parse::() { + Ok(port) => Some(port), + Err(err) => { + eprintln!("Invalid lock file content: {err}. Removing stale lock file."); + remove_lock_file(lock_file); + None + } + }, + Err(err) => { + eprintln!("Failed to read lock file: {err}. Removing stale lock file."); + remove_lock_file(lock_file); + None + } + } +} + +fn notify_existing_instance(api_port: u16) -> Result<(), String> { + let client = reqwest::blocking::Client::builder() + .no_proxy() + .timeout(Duration::from_secs(2)) + .build() + .unwrap_or_else(|err| { + eprintln!( + "Failed to create loopback HTTP client: {err}. Falling back to default client." + ); + reqwest::blocking::Client::new() + }); + + let response = client + .post(format!("http://127.0.0.1:{api_port}/popup")) + .header("User-Agent", format!("wsrx/{}", env!("CARGO_PKG_VERSION"))) + .send() + .map_err(|err| err.to_string())?; + + if response.status().is_success() { + Ok(()) + } else { + Err(format!("unexpected response status {}", response.status())) + } +} + +pub fn cleanup_runtime_state(ui: &slint::Weak) { + if let Some(window) = ui.upgrade() { + bridges::settings::save_config(&window); + daemon::save_scopes(ui); + } let proj_dirs = match ProjectDirs::from("org", "xdsec", "wsrx") { Some(dirs) => dirs, @@ -77,15 +124,20 @@ pub fn shutdown(ui: &slint::Weak) { } }; - let log_dir = proj_dirs.data_local_dir().join("logs"); + cleanup_runtime_files(proj_dirs.data_local_dir()); +} + +pub fn shutdown(ui: &slint::Weak) { + cleanup_runtime_state(ui); + std::process::exit(0); +} + +fn cleanup_runtime_files(data_local_dir: &Path) { + let log_dir = data_local_dir.join("logs"); std::fs::remove_dir_all(log_dir).unwrap_or_else(|_| { eprintln!("Failed to remove log directory"); }); - let lock_file = proj_dirs.data_local_dir().join(".rx.is.alive"); - std::fs::remove_file(lock_file).unwrap_or_else(|_| { - eprintln!("Failed to remove lock file"); - }); - - std::process::exit(0); + let lock_file = data_local_dir.join(".rx.is.alive"); + remove_lock_file(&lock_file); } diff --git a/crates/desktop/src/main.rs b/crates/desktop/src/main.rs index 0afd870..5b62825 100644 --- a/crates/desktop/src/main.rs +++ b/crates/desktop/src/main.rs @@ -31,9 +31,11 @@ fn main() -> Result<(), Box> { // Create the main window. let ui = launcher::setup()?; + let ui_weak = ui.as_weak(); ui.run().ok(); - drop(console_guard); + launcher::cleanup_runtime_state(&ui_weak); drop(file_guard); + drop(console_guard); drop(ui); Ok(())