aboutsummaryrefslogtreecommitdiff
path: root/crates/dinggerd/src/notify.rs
blob: 9fcfcde3c4d29ffc73ce6cce2249085861ee8302 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//! Notifications and sounds, mirroring herdr's macOS delivery.

use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU64, Ordering};

use tracing::{debug, warn};

pub enum NotificationKind {
    NeedsAttention,
    Finished,
}

impl NotificationKind {
    pub fn title(&self) -> &'static str {
        match self {
            Self::NeedsAttention => "needs attention",
            Self::Finished => "finished",
        }
    }

    /// herdr's bundled notification sounds; `request` for blockers, `done`
    /// for completions.
    fn sound_bytes(&self) -> &'static [u8] {
        match self {
            Self::NeedsAttention => include_bytes!("../assets/request.mp3"),
            Self::Finished => include_bytes!("../assets/done.mp3"),
        }
    }
}

static SOUND_SEQ: AtomicU64 = AtomicU64::new(0);

pub fn play_sound(kind: &NotificationKind) {
    // Sounds are embedded; afplay needs a file, so stage the bytes in the
    // system temp dir.
    let path = std::env::temp_dir().join(format!(
        "dingger-{}-{}.mp3",
        std::process::id(),
        SOUND_SEQ.fetch_add(1, Ordering::Relaxed)
    ));
    if let Err(err) = std::fs::write(&path, kind.sound_bytes()) {
        warn!(%err, "failed to stage notification sound");
        return;
    }
    match Command::new("afplay")
        .arg(&path)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn()
    {
        // Fire and forget: never let audio latency block the watch loop.
        Ok(mut child) => {
            std::thread::spawn(move || {
                let _ = child.wait();
                let _ = std::fs::remove_file(&path);
            });
        }
        Err(err) => {
            warn!(%err, "failed to play notification sound");
            let _ = std::fs::remove_file(&path);
        }
    }
}

pub fn send_notification(agent_label: &str, kind: &NotificationKind, context: &str) {
    debug!(agent = agent_label, kind = kind.title(), context, "notifying");
    let title = format!("{agent_label} {}", kind.title());
    let notification = mac_notification_sys::Notification::new()
        .title(title.as_str())
        .message(context)
        .send();
    if let Err(err) = notification {
        warn!(%err, "failed to deliver macOS notification");
    }
}