From 78065350be5458855df65f1f6ab3f78767866e21 Mon Sep 17 00:00:00 2001 From: candifloss Date: Wed, 18 Sep 2024 23:52:45 +0530 Subject: [PATCH] formats module, CloseNotification method, modules for Notification --- src/formats.rs | 45 +++++++++++++++++++++ src/main.rs | 53 +++++++++++++++---------- src/notification.rs | 97 ++++++++++++++++++++------------------------- 3 files changed, 120 insertions(+), 75 deletions(-) create mode 100644 src/formats.rs diff --git a/src/formats.rs b/src/formats.rs new file mode 100644 index 0000000..8b0d421 --- /dev/null +++ b/src/formats.rs @@ -0,0 +1,45 @@ +use crate::notification::Notification; + +impl Notification { + // Plain string format, useful for printing + pub fn plain(&self) -> String { + // Actions + let mut actions: String = String::new(); + if self.actions().is_empty() { + actions = "None".to_string(); + } else { + for actn in &self.actions() { + actions = actions + "\n\t" + actn.0.as_str() + ": " + actn.1.as_str(); + } + } + // Default action + let default_action: String = match self.default_action() { + None => "None".to_string(), + Some(actn) => actn.0, + }; + // Hints + let mut hints: String = String::new(); + if self.hints().is_empty() { + hints = "None".to_string(); + } else { + for (key, value) in self.hints() { + hints = hints + "\n\t" + key + ": " + &value.to_string(); + } + } + + // Return the notification as a plain string + let plain_string = format!("App Name: {}\nReplace ID: {}\nIcon: {}\nSummary: {}\nBody: {}\nActions: {}\nHints: {}\nExpiration Timeout: {}\nUrgency: {}\nDefault Action: {}", + &self.app_name(), + &self.replace_id().to_string(), + &self.icon(), + &self.summary(), + &self.body(), + actions, + hints, + &self.expir_timeout().to_string(), + &self.urgency(), + default_action, + ); + plain_string + } +} diff --git a/src/main.rs b/src/main.rs index 07b6188..97dd609 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod formats; mod notification; use notification::to_notif; @@ -5,53 +6,63 @@ use futures_util::stream::TryStreamExt; use zbus::{Connection, Result}; const SERVER_NAME: &str = "SNot"; // Server software name -const VENDOR: &str = "candifloss.cc"; // Server sw author -const VERSION: &str = "0.1.0"; // Server version -const SPEC_VERSION: &str = "0.42"; // DBus Spec version +const VENDOR: &str = "candifloss.cc"; // Server software vendor +const VERSION: &str = "0.1.0"; // Server software version +const SPEC_VERSION: &str = "0.42"; // DBus specification version #[tokio::main] async fn main() -> Result<()> { let connection = Connection::session().await?; connection - .request_name("org.freedesktop.Notifications") // Requesting dbus for this service name. Any other services/progs using this name should be stopped/disabled before this + .request_name("org.freedesktop.Notifications") // Requesting dbus for this service name. Any other services/procs using this name should be stopped/disabled before this .await?; let mut stream = zbus::MessageStream::from(&connection); // Convert connection to a MessageStream, yields Message items + // Iterate on the message stream while let Some(msg) = stream.try_next().await? { - // Check if the message is a method call to the "Notify" method + // Check the method calls in the received message's header if let Some(member) = msg.header().member() { - let member_name = member.as_str(); + let member_name = member.as_str(); // Convert to &str for the match match member_name { "GetServerInformation" => { - // Respond with server information: (name, vendor, version, dbus_spec_version) + // Client requested server information. Respond with: (Server_name, author, software_version, dbus_spec_version) let response = (SERVER_NAME, VENDOR, VERSION, SPEC_VERSION); connection.reply(&msg, &response).await?; println!("Request received: {member}\n\tName: {SERVER_NAME}, Vendor: {VENDOR}, Version: {VERSION}, DBus spec version: {SPEC_VERSION}"); - // Remove this later + // Remove this LATER } "GetCapabilities" => { - // Respond with supported capabilities - let capabilities = vec!["actions", "body", "body-hyperlinks"]; + // Client requested server capabilities. Respond with the supported capabilities + let capabilities = vec!["actions", "body", "body-hyperlinks"]; // Add more LATER connection.reply(&msg, &capabilities).await?; println!("Request received: {member}\n\tCapabilities: {capabilities:?}"); - // Remove this later + // Remove this LATER } "Notify" => { - // Handle new received notif - println!("New notification!"); + // New notification received. Now, respond to the client with a notification ID + let notification_id: u32 = 1; // This could be incremented or generated. DO IT LATER + connection.reply(&msg, ¬ification_id).await?; // The client waits for this response in order to disconnect + // Get the body of the message let msg_body = msg.body(); - // get the app_name, summary, body, etc. from the msg_body + // Convert the msg body to a Notification object let notif = to_notif(&msg_body)?; - // Print the notif - //print_notif(¬if); - println!("{}", ¬if.plain()); - // Done. Respond to the client with a notification ID - let notification_id: u32 = 1; // This could be incremented or generated. Do it l8r - connection.reply(&msg, ¬ification_id).await?; // The client will disconnect when it gets this response + // Handle the notif + println!("New notification!\n{}", ¬if.plain()); // Print the plain version + } + "CloseNotification" => { + // Client sent a close signal. Extract notification ID of the notif to be closed from the message body + let notification_id: u32 = msg.body().deserialize()?; + + // Tracking notifications by their IDs, closing them, and other features may be implemented later + // close_notification(notification_id); + println!("Closing notification with ID: {notification_id}"); + + // Respond to the client, acknowledging the closure + connection.reply(&msg, &()).await?; } _ => { - println!("Unhandled method: {member}"); // Other methods are irrelevant or not handled at this stage of development + println!("Unhandled method: {member}"); // Other methods are either irrelevant or unhandled at this stage of development } } } diff --git a/src/notification.rs b/src/notification.rs index f0cb57b..778c8e6 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -23,20 +23,25 @@ pub struct Notification { } impl Notification { - // Obtain urgency - pub fn urgency(&self) -> String { - match self.hints.get("urgency") { - Some(value) => { - // Attempt to convert OwnedValue to u8 - match u8::try_from(value) { - Ok(0) => "Low".to_string(), - Ok(1) => "Normal".to_string(), - Ok(2) => "Critical".to_string(), - _ => "Other".to_string(), // There are no accepted values other that these 3 - } - } - None => "Unknown".to_string(), // This possibly never occurs, or something might be wrong - } + // App name + pub fn app_name(&self) -> String { + self.app_name.clone() + } + // Replace ID + pub fn replace_id(&self) -> u32 { + self.replace_id + } + // Icon + pub fn icon(&self) -> String { + self.icon.clone() + } + // Summary + pub fn summary(&self) -> String { + self.summary.clone() + } + // Body + pub fn body(&self) -> String { + self.body.clone() } // Key:Val pairs of actions @@ -59,6 +64,16 @@ impl Notification { actions } + // Hints + pub fn hints(&self) -> &HashMap { + &self.hints + } + + // Expiry timeout + pub fn expir_timeout(&self) -> i32 { + self.expir_timeout + } + // Default action pub fn default_action(&self) -> Option<(String, String)> { if self.actions().is_empty() { @@ -68,55 +83,29 @@ impl Notification { } } - // Plain string format, useful for printing - pub fn plain(&self) -> String { - // Actions - let mut actions: String = String::new(); - if self.actions().is_empty() { - actions = "None".to_string(); - } else { - for actn in &self.actions() { - actions = actions + "\n\t" + actn.0.as_str() + ": " + actn.1.as_str(); + // Urgency + pub fn urgency(&self) -> String { + match self.hints.get("urgency") { + Some(value) => { + // Attempt to convert OwnedValue to u8 + match u8::try_from(value) { + Ok(0) => "Low".to_string(), + Ok(1) => "Normal".to_string(), + Ok(2) => "Critical".to_string(), + _ => "Other".to_string(), // There are no accepted values other that these 3 + } } + None => "Unknown".to_string(), // This possibly never occurs, or something might be wrong } - // Default action - let def_action: String = match self.default_action() { - None => "None".to_string(), - Some(actn) => actn.0, - }; - // Hints - let mut hints: String = String::new(); - if self.hints.is_empty() { - hints = "None".to_string(); - } else { - for (key, value) in &self.hints { - hints = hints + "\n\t" + key + ": " + &value.to_string(); - } - } - - // Return the notification as a plain string - let plain_string = format!("App Name: {}\nReplace ID: {}\nIcon: {}\nSummary: {}\nBody: {}\nActions: {}\nHints: {}\nExpiration Timeout: {}\nUrgency: {}\nDefault Action: {}", - &self.app_name, - &self.replace_id.to_string(), - &self.icon, - &self.summary, - &self.body, - actions, - hints, - &self.expir_timeout, - &self.urgency(), - def_action, - ); - plain_string } } // Convert the DBus message body into Notification pub fn to_notif(msg_body: &Body) -> Result { - // Deserialized body into a tuple + // Deserialize body into a tuple let (app_name, replace_id, icon, summary, body, actions, hints, expir_timeout) = msg_body.deserialize()?; - // Make a Notification object from the obtained tuple + // Form a Notification object from the obtained tuple Ok(Notification { app_name, replace_id,