use crate::alert::Alert;
use crate::units::Units;
pub const SECONDS: u64 = 30;
pub fn glyph(alert: Alert) -> &'static str {
match alert {
Alert::Stale => "\u{f0150}",
_ => "\u{f0026}",
}
}
fn label(alert: Alert) -> &'static str {
match alert {
Alert::Stale => "NO DATA",
other => other.label(),
}
}
pub fn should_show(alerts: &crate::config::Alerts, alert: Alert) -> bool {
alerts.osd && alert.is_urgent()
}
pub fn payload(
alert: Alert,
sgv: Option<f64>,
units: Units,
content: bool,
seconds: u64,
) -> String {
let message = if !content {
"sugarrush alert".to_string()
} else {
match sgv {
Some(v) => format!("{} {} {}", label(alert), units.format(v), units.label()),
None => label(alert).to_string(),
}
};
serde_json::json!({
"icon": glyph(alert),
"message": message,
"duration": seconds.saturating_mul(1000),
})
.to_string()
}
pub fn show(payload: &str) -> bool {
let mut cmd = std::process::Command::new("omarchy-shell");
cmd.args(["osd", "show", payload]);
if std::env::var_os("OMARCHY_PATH").is_none() {
cmd.env("OMARCHY_PATH", "/usr/share/omarchy");
}
let Ok(out) = cmd.stdin(std::process::Stdio::null()).output() else {
return false;
};
String::from_utf8_lossy(&out.stdout).trim() == "ok"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_payload_names_the_state_and_the_reading() {
let json: serde_json::Value = serde_json::from_str(&payload(
Alert::UrgentLow,
Some(54.0),
Units::Mgdl,
true,
30,
))
.expect("payload is JSON");
assert_eq!(json["message"], "URGENT LOW 54 mg/dL");
assert_eq!(json["duration"], 30_000);
assert_eq!(json["icon"], glyph(Alert::UrgentLow));
}
#[test]
fn only_urgent_alerts_reach_the_osd() {
let mut alerts = crate::config::Alerts::default();
assert!(alerts.osd, "the OSD is on by default");
assert!(should_show(&alerts, Alert::UrgentLow));
assert!(should_show(&alerts, Alert::Stale));
assert!(!should_show(&alerts, Alert::Low), "a low is not urgent");
assert!(!should_show(&alerts, Alert::High), "a high is not urgent");
alerts.osd = false;
assert!(
!should_show(&alerts, Alert::UrgentLow),
"the setting is off"
);
}
#[test]
fn the_worst_case_message_fits_the_osd() {
let widest = payload(Alert::UrgentHigh, Some(288.0), Units::Mgdl, true, 30);
let json: serde_json::Value = serde_json::from_str(&widest).unwrap();
let message = json["message"].as_str().unwrap();
assert!(
message.chars().count() <= 21,
"{message} is {} characters and will elide",
message.chars().count()
);
}
#[test]
fn a_stale_alarm_is_not_dressed_as_a_glucose_alarm() {
assert_ne!(glyph(Alert::Stale), glyph(Alert::UrgentLow));
assert_eq!(label(Alert::Stale), "NO DATA");
assert!(
label(Alert::Stale).chars().count() <= 21,
"the notification's own wording does not fit here"
);
}
#[test]
fn a_content_free_payload_carries_no_reading() {
let json: serde_json::Value = serde_json::from_str(&payload(
Alert::UrgentLow,
Some(54.0),
Units::Mgdl,
false,
30,
))
.expect("payload is JSON");
let message = json["message"].as_str().unwrap();
assert!(!message.contains("54"), "leaked the reading: {message}");
assert_eq!(message, "sugarrush alert");
}
}