use dbus::arg::{RefArg, Variant}; use dbus::blocking::Connection; use dbus::message::MatchRule; use dbus::strings::Interface; use dbus::MessageType::Signal; use dbus::{Message, Path}; use std::collections::HashMap; use std::fmt; use std::time::Duration; // Structure of a notification struct Notif { app_name: String, replace_id: Option, ico: String, summary: String, body: String, actions: Vec<(String, String)>, hints: HashMap, expir_timeout: Option, } // Function to print the contents of the notification impl fmt::Display for Notif { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "AppName: {}", &self.app_name)?; match self.replace_id { Some(notif_id) => writeln!(f, "ReplaceId: {notif_id}")?, None => writeln!(f, "None")?, } write!( f, "Icon: {}\nSummary: {}\nBody: {}\nActions:\n", &self.ico, &self.summary, &self.body )?; for (key, label) in &self.actions { writeln!(f, "\t{key}: {label}")?; } writeln!(f, "Hints:")?; for (key, value) in &self.hints { writeln!(f, "\t{key}: {value}")?; } match self.expir_timeout { Some(millisec) => writeln!(f, "Expiration Timeout: {millisec}")?, None => writeln!(f, "None")?, } Ok(()) // Return Ok to indicate success } } // Summary should be generally <= 40 chars, as per the specification. fn truncate_summary(notif: &mut Notif) { if notif.summary.len() > 40 { notif.summary.truncate(39); notif.summary.push('…'); } } // Callback function fn handle_notif(signal: &dbus::Message) -> Result<(), dbus::Error> { // Extract all items as variants let items = signal.get_items(); // Helper functions to safely extract data let get_string = |item: &dyn RefArg| item.as_str().unwrap_or_default().to_string(); let get_u32 = |item: &dyn RefArg| item.as_u64().unwrap_or_default() as u32; let get_i32 = |item: &dyn RefArg| item.as_i64().unwrap_or_default() as i32; // Extract the fields from the message let app_name = get_string(&items[0]); let replace_id = get_u32(&items[1]); let ico = get_string(&items[2]); let summary = get_string(&items[3]); let body = get_string(&items[4]); // Extract actions as Vec let actions = items[5].as_array().unwrap_or(&[]).iter() .filter_map(|v| v.as_str().map(|s| s.to_string())) .collect(); // Extract hints as HashMap let hints = items[6].as_dict().unwrap_or(&[]).iter() .map(|(k, v)| (get_string(k), get_string(v))) .collect(); let expir_timeout = get_i32(&items[7]); // Convert actions from Vec to Vec<(String, String)> let notif_actions = actions.chunks(2) .map(|chunk| if chunk.len() == 2 { (chunk[0].clone(), chunk[1].clone()) } else { (chunk[0].clone(), "".to_string()) }) .collect(); let mut notif = Notif { app_name, replace_id: Some(replace_id), ico, summary, body, actions: notif_actions, hints: notif_hints, expir_timeout: if expir_timeout >= 0 { Some(expir_timeout as u32) } else { None }, }; truncate_summary(&mut notif); println!("{}", notif); Ok(()) } fn main() -> Result<(), dbus::Error> { // Connect to the system bus let conn = Connection::new_session()?; // Get the object path of the notification service //let object_path = "/org/freedesktop/Notifications"; //let member = "Notify"; // Get the interface name of the notification service //let interface_name = "org.freedesktop.Notifications".to_string(); // Create a match rule to listen for the notifications let match_rule = MatchRule::new_signal("org.freedesktop.Notifications".into(), "Notify".into()); // Add the match rule to the connection conn.add_match(match_rule, |msg, _conn, _| { if let Err(e) = handle_notif(&msg) { eprintln!("Error handling notification: {:?}", e); } true // Returning true keeps the callback active })?; // Wait for signals loop { conn.process(Duration::from_millis(1000))?; } Ok(()) }