168 lines
5.4 KiB
Rust
168 lines
5.4 KiB
Rust
use std::sync::Arc;
|
|
use tokio::process::{Child, Command};
|
|
use tokio::sync::Mutex;
|
|
use tokio_util::sync::CancellationToken;
|
|
|
|
type ProcessHandle = Arc<Mutex<Option<Child>>>;
|
|
|
|
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<ProcessHandle>) {
|
|
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),
|
|
}
|
|
}
|