void-focus 0.3.0-alpha.6

A feature-rich terminal focus timer with task tracking
Documentation
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}");
        }
    });
}