mod format; mod icon; mod notification; use std::io::Write; use std::process::{Command, Stdio}; fn main() -> Result<(), Box> { let notifications = notification::history()?; if notifications.is_empty() { return Ok(()); } let entries: Vec = notifications .iter() .map(|n| { let app_name = n.app_name.as_deref().unwrap_or("unknown"); let summary = n.summary.as_deref().unwrap_or(""); let body = n.body.as_deref().unwrap_or(""); let text = format::entry_text(app_name, summary, body); match icon::resolve( n.app_icon.as_deref(), n.desktop_entry.as_deref(), n.app_name.as_deref(), ) { Some(path) => format!("img:{}:text:{text}", path.display()), None => text, } }) .collect(); let Some(index) = prompt_selection(&entries)? else { return Ok(()); }; if let Some(notification) = notifications.get(index) { notification::invoke(notification.id)?; } Ok(()) } /// Run wofi in dmenu mode with the given entries and return the selected /// index, or `None` if the user cancelled (Escape / clicked outside). fn prompt_selection(entries: &[String]) -> Result, Box> { let mut child = Command::new("wofi") .args([ "--dmenu", "-p", "Notifications", "-D", "dmenu-print_line_num=true", ]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; let mut stdin = child.stdin.take().expect("stdin was piped"); stdin.write_all(entries.join("\n").as_bytes())?; drop(stdin); let output = child.wait_with_output()?; if !output.status.success() { return Ok(None); } let selection = String::from_utf8(output.stdout)?; let selection = selection.trim(); if selection.is_empty() { return Ok(None); } Ok(selection.parse::().ok()) }