1use std::sync::atomic::{AtomicU64, Ordering};
11use std::time::Instant;
12
13use tracing::debug;
14
15const COOLDOWN_SECONDS: u64 = 30;
17
18static LAST_PLAYED_MS: AtomicU64 = AtomicU64::new(0);
20
21static START: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
23
24fn now_ms() -> u64 {
25 let start = START.get_or_init(Instant::now);
26 start.elapsed().as_millis() as u64
27}
28
29pub fn play_finish_sound() {
34 let now = now_ms();
35 let last = LAST_PLAYED_MS.load(Ordering::Relaxed);
36 if now.saturating_sub(last) < COOLDOWN_SECONDS * 1000 {
37 return;
38 }
39 LAST_PLAYED_MS.store(now, Ordering::Relaxed);
40
41 if let Err(e) = play_platform_sound() {
42 debug!("Failed to play finish sound: {e}");
43 }
44}
45
46fn play_platform_sound() -> Result<(), String> {
47 #[cfg(target_os = "macos")]
48 {
49 std::process::Command::new("afplay")
50 .arg("/System/Library/Sounds/Glass.aiff")
51 .stdout(std::process::Stdio::null())
52 .stderr(std::process::Stdio::null())
53 .spawn()
54 .map_err(|e| e.to_string())?;
55 Ok(())
56 }
57
58 #[cfg(target_os = "linux")]
59 {
60 let players = ["paplay", "aplay", "play", "cvlc"];
61 let sounds = [
62 "/usr/share/sounds/freedesktop/stereo/complete.oga",
63 "/usr/share/sounds/gnome/default/alerts/glass.ogg",
64 "/usr/share/sounds/alsa/Front_Center.wav",
65 ];
66
67 for player in &players {
68 let which = std::process::Command::new("which").arg(player).output();
69 if let Ok(output) = which
70 && output.status.success()
71 {
72 for sound in &sounds {
73 if std::path::Path::new(sound).exists() {
74 let mut cmd = std::process::Command::new(player);
75 if *player == "cvlc" {
76 cmd.arg("--play-and-exit");
77 }
78 cmd.arg(sound)
79 .stdout(std::process::Stdio::null())
80 .stderr(std::process::Stdio::null())
81 .spawn()
82 .map_err(|e| e.to_string())?;
83 return Ok(());
84 }
85 }
86 }
87 }
88
89 print!("\x07");
91 Ok(())
92 }
93
94 #[cfg(target_os = "windows")]
95 {
96 let sound_path = r"C:\Windows\Media\notify.wav";
97 let ps_cmd = format!("(New-Object Media.SoundPlayer '{sound_path}').PlaySync()");
98 std::process::Command::new("powershell")
99 .args(["-Command", &ps_cmd])
100 .stdout(std::process::Stdio::null())
101 .stderr(std::process::Stdio::null())
102 .spawn()
103 .map_err(|e| e.to_string())?;
104 Ok(())
105 }
106
107 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
108 {
109 print!("\x07");
110 Ok(())
111 }
112}
113
114#[cfg(test)]
115#[path = "sound_tests.rs"]
116mod tests;