Skip to main content

void/
sound.rs

1use std::process::Command;
2#[cfg(target_os = "windows")]
3use std::time::Duration;
4
5use notify_rust::Notification;
6
7fn spawn_sound(f: impl FnOnce() + Send + 'static) {
8    std::thread::spawn(f);
9}
10
11#[derive(Debug, Clone, Copy)]
12pub enum NotifyKind {
13    FocusComplete,
14    BreakComplete,
15    SessionSkipped,
16    Info,
17}
18
19#[cfg(target_os = "windows")]
20fn beep_windows(freq: u32, duration_ms: u32) {
21    let _ = Command::new("powershell")
22        .args([
23            "-NoProfile",
24            "-Command",
25            &format!("[console]::Beep({},{})", freq, duration_ms),
26        ])
27        .output();
28}
29
30#[cfg(target_os = "macos")]
31fn beep_macos() {
32    let _ = Command::new("afplay")
33        .args(["/System/Library/Sounds/Glass.aiff"])
34        .output();
35}
36
37#[cfg(all(unix, not(target_os = "macos")))]
38fn beep_linux() {
39    let _ = Command::new("sh").args(["-c", "printf '\\a'"]).output();
40}
41
42pub fn play_finish() {
43    spawn_sound(|| {
44        #[cfg(target_os = "windows")]
45        {
46            beep_windows(880, 200);
47            std::thread::sleep(Duration::from_millis(120));
48            beep_windows(1175, 350);
49        }
50        #[cfg(target_os = "macos")]
51        {
52            beep_macos();
53        }
54        #[cfg(all(unix, not(target_os = "macos")))]
55        {
56            beep_linux();
57            let _ = Command::new("paplay")
58                .args(["/usr/share/sounds/freedesktop/stereo/complete.oga"])
59                .output();
60        }
61    });
62}
63
64pub fn play_pause() {
65    spawn_sound(|| {
66        #[cfg(target_os = "windows")]
67        {
68            beep_windows(440, 120);
69        }
70        #[cfg(target_os = "macos")]
71        {
72            let _ = Command::new("afplay")
73                .args(["/System/Library/Sounds/Tink.aiff"])
74                .output();
75        }
76        #[cfg(all(unix, not(target_os = "macos")))]
77        {
78            let _ = Command::new("printf").arg("%b").arg("\u{7}").output();
79        }
80    });
81}
82
83pub fn play_start() {
84    spawn_sound(|| {
85        #[cfg(target_os = "windows")]
86        {
87            beep_windows(660, 100);
88        }
89        #[cfg(target_os = "macos")]
90        {
91            let _ = Command::new("afplay")
92                .args(["/System/Library/Sounds/Pop.aiff"])
93                .output();
94        }
95        #[cfg(all(unix, not(target_os = "macos")))]
96        {
97            let _ = Command::new("printf").arg("%b").arg("\u{7}").output();
98        }
99    });
100}
101
102pub fn notify(title: &str, body: &str) {
103    notify_typed(NotifyKind::Info, title, body);
104}
105
106pub fn notify_typed(kind: NotifyKind, title: &str, body: &str) {
107    let title = title.to_string();
108    let body = body.to_string();
109    std::thread::spawn(move || {
110        let mut n = Notification::new();
111        n.summary(&title).body(&body).timeout(8000);
112
113        #[cfg(target_os = "macos")]
114        {
115            let subtitle = match kind {
116                NotifyKind::FocusComplete => "Focus session",
117                NotifyKind::BreakComplete => "Break time",
118                NotifyKind::SessionSkipped => "Session skipped",
119                NotifyKind::Info => "Void",
120            };
121            n.subtitle(subtitle);
122        }
123
124        #[cfg(all(unix, not(target_os = "macos")))]
125        {
126            use notify_rust::Urgency;
127            n.appname("Void");
128            match kind {
129                NotifyKind::FocusComplete => {
130                    n.urgency(Urgency::Normal);
131                }
132                NotifyKind::BreakComplete | NotifyKind::SessionSkipped => {
133                    n.urgency(Urgency::Low);
134                }
135                NotifyKind::Info => {}
136            }
137        }
138
139        #[cfg(target_os = "windows")]
140        let _ = kind;
141
142        if let Err(e) = n.show() {
143            eprintln!("Void notification error: {e}");
144        }
145    });
146}