use std::process::Command;
#[cfg(target_os = "windows")]
use std::time::Duration;
use notify_rust::Notification;
fn spawn_sound(f: impl FnOnce() + Send + 'static) {
std::thread::spawn(f);
}
#[derive(Debug, Clone, Copy)]
pub enum NotifyKind {
FocusComplete,
BreakComplete,
SessionSkipped,
Info,
}
#[cfg(target_os = "windows")]
fn beep_windows(freq: u32, duration_ms: u32) {
let _ = Command::new("powershell")
.args([
"-NoProfile",
"-Command",
&format!("[console]::Beep({},{})", freq, duration_ms),
])
.output();
}
#[cfg(target_os = "macos")]
fn beep_macos() {
let _ = Command::new("afplay")
.args(["/System/Library/Sounds/Glass.aiff"])
.output();
}
#[cfg(all(unix, not(target_os = "macos")))]
fn beep_linux() {
let _ = Command::new("sh").args(["-c", "printf '\\a'"]).output();
}
pub fn play_finish() {
spawn_sound(|| {
#[cfg(target_os = "windows")]
{
beep_windows(880, 200);
std::thread::sleep(Duration::from_millis(120));
beep_windows(1175, 350);
}
#[cfg(target_os = "macos")]
{
beep_macos();
}
#[cfg(all(unix, not(target_os = "macos")))]
{
beep_linux();
let _ = Command::new("paplay")
.args(["/usr/share/sounds/freedesktop/stereo/complete.oga"])
.output();
}
});
}
pub fn play_pause() {
spawn_sound(|| {
#[cfg(target_os = "windows")]
{
beep_windows(440, 120);
}
#[cfg(target_os = "macos")]
{
let _ = Command::new("afplay")
.args(["/System/Library/Sounds/Tink.aiff"])
.output();
}
#[cfg(all(unix, not(target_os = "macos")))]
{
let _ = Command::new("printf").arg("%b").arg("\u{7}").output();
}
});
}
pub fn play_start() {
spawn_sound(|| {
#[cfg(target_os = "windows")]
{
beep_windows(660, 100);
}
#[cfg(target_os = "macos")]
{
let _ = Command::new("afplay")
.args(["/System/Library/Sounds/Pop.aiff"])
.output();
}
#[cfg(all(unix, not(target_os = "macos")))]
{
let _ = Command::new("printf").arg("%b").arg("\u{7}").output();
}
});
}
pub fn notify(title: &str, body: &str) {
notify_typed(NotifyKind::Info, title, body);
}
pub fn notify_typed(kind: NotifyKind, title: &str, body: &str) {
let title = title.to_string();
let body = body.to_string();
std::thread::spawn(move || {
let mut n = Notification::new();
n.summary(&title).body(&body).timeout(8000);
#[cfg(target_os = "macos")]
{
let subtitle = match kind {
NotifyKind::FocusComplete => "Focus session",
NotifyKind::BreakComplete => "Break time",
NotifyKind::SessionSkipped => "Session skipped",
NotifyKind::Info => "Void",
};
n.subtitle(subtitle);
}
#[cfg(all(unix, not(target_os = "macos")))]
{
use notify_rust::Urgency;
n.appname("Void");
match kind {
NotifyKind::FocusComplete => {
n.urgency(Urgency::Normal);
}
NotifyKind::BreakComplete | NotifyKind::SessionSkipped => {
n.urgency(Urgency::Low);
}
NotifyKind::Info => {}
}
}
#[cfg(target_os = "windows")]
let _ = kind;
if let Err(e) = n.show() {
eprintln!("Void notification error: {e}");
}
});
}