use std::sync::Arc; use tokio::process::{Child, Command}; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; type ProcessHandle = Arc>>; pub async fn run() { let qjackctl_handle: ProcessHandle = Arc::new(Mutex::new(None)); let audio_engine_handle: ProcessHandle = Arc::new(Mutex::new(None)); let gui_handle: ProcessHandle = Arc::new(Mutex::new(None)); let simulator_handle: ProcessHandle = Arc::new(Mutex::new(None)); let cancel_token = CancellationToken::new(); // Set up signal handling for graceful shutdown let cleanup_handles = vec![ qjackctl_handle.clone(), audio_engine_handle.clone(), gui_handle.clone(), simulator_handle.clone(), ]; let cleanup_token = cancel_token.clone(); tokio::spawn(async move { tokio::signal::ctrl_c() .await .expect("Failed to listen for ctrl+c"); println!("Received Ctrl+C, shutting down..."); cleanup_token.cancel(); cleanup_processes(cleanup_handles).await; stop_jack_daemon().await; }); // Start processes let qjackctl = spawn_qjackctl().await; tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; let audio_engine = spawn_audio_engine().await; let gui = spawn_gui().await; let simulator = spawn_simulator().await; // Store handles for cleanup *qjackctl_handle.lock().await = Some(qjackctl); *audio_engine_handle.lock().await = Some(audio_engine); *gui_handle.lock().await = Some(gui); *simulator_handle.lock().await = Some(simulator); // Get references back for waiting let mut qjackctl = qjackctl_handle.lock().await.take().unwrap(); let mut audio_engine = audio_engine_handle.lock().await.take().unwrap(); let mut gui = gui_handle.lock().await.take().unwrap(); let mut simulator = simulator_handle.lock().await.take().unwrap(); // Wait for any process to exit or cancellation tokio::select! { result = qjackctl.wait() => { println!("qjackctl exited: {:?}", result); kill_process(&mut audio_engine, "audio_engine").await; kill_process(&mut gui, "gui").await; kill_process(&mut simulator, "simulator").await; stop_jack_daemon().await; } result = audio_engine.wait() => { println!("audio_engine exited: {:?}", result); kill_process(&mut qjackctl, "qjackctl").await; kill_process(&mut gui, "gui").await; kill_process(&mut simulator, "simulator").await; stop_jack_daemon().await; } result = gui.wait() => { println!("gui exited: {:?}", result); kill_process(&mut qjackctl, "qjackctl").await; kill_process(&mut audio_engine, "audio_engine").await; kill_process(&mut simulator, "simulator").await; stop_jack_daemon().await; } result = simulator.wait() => { println!("simulator exited: {:?}", result); kill_process(&mut qjackctl, "qjackctl").await; kill_process(&mut audio_engine, "audio_engine").await; kill_process(&mut gui, "gui").await; stop_jack_daemon().await; } _ = cancel_token.cancelled() => { println!("Shutdown requested"); kill_process(&mut qjackctl, "qjackctl").await; kill_process(&mut audio_engine, "audio_engine").await; kill_process(&mut gui, "gui").await; kill_process(&mut simulator, "simulator").await; stop_jack_daemon().await; } } println!("All processes stopped"); } async fn cleanup_processes(handles: Vec) { for handle in handles { if let Some(mut child) = handle.lock().await.take() { let _ = child.kill().await; } } } async fn stop_jack_daemon() { println!("Stopping JACK daemon..."); #[cfg(target_os = "windows")] let (jack, args) = ("taskkill", vec!["/f", "/im", "jackd.exe"]); #[cfg(target_os = "linux")] let (jack, args) = ("pkill", vec!["-f", "jackd"]); Command::new(jack) .args(args) .output() .await .expect("Failed to stop JACK daemon"); } async fn spawn_qjackctl() -> Child { #[cfg(target_os = "windows")] let qjackctl = "C:\\Program Files\\JACK2\\qjackctl\\qjackctl.exe"; #[cfg(target_os = "linux")] let qjackctl = "qjackctl"; Command::new(qjackctl) .arg("/start-server") .spawn() .expect("Could not start qjackctl") } async fn spawn_audio_engine() -> Child { Command::new("cargo") .args([ "run", "--release", "--bin", "audio_engine", "--", "--config-path", "config", ]) .spawn() .expect("Could not start audio engine") } async fn spawn_gui() -> Child { Command::new("cargo") .args(["run", "--release", "--bin", "gui"]) .spawn() .expect("Could not start gui") } async fn spawn_simulator() -> Child { Command::new("cargo") .args(["run", "--release", "--bin", "simulator"]) .spawn() .expect("Could not start simulator") } async fn kill_process(child: &mut Child, name: &str) { match child.kill().await { Ok(()) => println!("Killed {}", name), Err(e) => eprintln!("Failed to kill {}: {}", name, e), } }