diff --git a/Cargo.toml b/Cargo.toml
index f626906..7a45564 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2024"
[dependencies]
openaction = "2.6"
discord-ipc-rust = { git = "https://github.com/nekename/discord-ipc-rust.git" }
+base64 = "0.22"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.52", features = ["rt-multi-thread", "macros"] }
diff --git a/src/actions/notifications.rs b/src/actions/notifications.rs
index cab4172..b78e836 100644
--- a/src/actions/notifications.rs
+++ b/src/actions/notifications.rs
@@ -5,15 +5,51 @@ use discord_ipc_rust::models::send::commands::{SelectTextChannelArgs, SentComman
use openaction::{Action, ActionUuid, Instance, OpenActionResult, async_trait};
use serde::{Deserialize, Serialize};
-pub async fn update_title(instance: &Instance) -> OpenActionResult<()> {
+const NOTIFICATION_SVG: &str = include_str!("../../assets/actions/notifications.svg");
+
+fn generate_badge_xml(count: usize) -> String {
+ if count == 0 {
+ return String::new();
+ }
+ let title = format!("{}", count);
+
+ if count < 100 {
+ format!(
+ r#"
+ {}"#,
+ title
+ )
+ } else {
+ let digit_count = title.len() as i32;
+ let width = 20 + (digit_count * 9);
+ let rect_x = 105 - (width / 2);
+ format!(
+ r#"
+ {}"#,
+ rect_x, width, title
+ )
+ }
+}
+
+pub async fn update_image(instance: &Instance) -> OpenActionResult<()> {
+ use base64::{Engine, prelude::BASE64_STANDARD};
+
let cache = notification_cache().read().await;
- let title = format!("{}", cache.len());
+ let count = cache.len();
+ let final_svg = if count > 0 {
+ let badge_xml = generate_badge_xml(count);
+ NOTIFICATION_SVG.replace("", &format!("{}\n", badge_xml))
+ } else {
+ NOTIFICATION_SVG.to_string()
+ };
- if let Err(e) = instance.set_title(Some(title), None).await {
+ let b64_encoded = BASE64_STANDARD.encode(final_svg.as_bytes());
+ let data_url = format!("data:image/svg+xml;base64,{}", b64_encoded);
+
+ if let Err(e) = instance.set_image(Some(data_url), None).await {
log::error!("Failed to update notifications action title: {}", e);
instance.show_alert().await?;
}
-
Ok(())
}
@@ -44,7 +80,7 @@ impl Action for NotificationsAction {
instance: &Instance,
_settings: &Self::Settings,
) -> OpenActionResult<()> {
- update_title(instance).await
+ update_image(instance).await
}
async fn key_down(
@@ -56,7 +92,7 @@ impl Action for NotificationsAction {
NotificationsActionType::DoNothing => return Ok(()),
NotificationsActionType::Clear => {
notification_cache().write().await.clear();
- update_title(instance).await?;
+ update_image(instance).await?;
return Ok(());
}
NotificationsActionType::OpenAndClear => {
@@ -78,7 +114,7 @@ impl Action for NotificationsAction {
return Ok(());
};
- update_title(instance).await?;
+ update_image(instance).await?;
let mut client_lock = discord_client().write().await;
let Some(client) = client_lock.as_mut() else {
diff --git a/src/rpc_events.rs b/src/rpc_events.rs
index b0efed5..c9a0c1b 100644
--- a/src/rpc_events.rs
+++ b/src/rpc_events.rs
@@ -132,7 +132,7 @@ async fn handle_select_voice_channel(channel_id: Option) {
async fn handle_notification(notification: NotificationCreateData) {
crate::cache::add_notification_to_cache(notification).await;
for instance in visible_instances(crate::actions::NotificationsAction::UUID).await {
- let _ = crate::actions::notifications::update_title(&instance).await;
+ let _ = crate::actions::notifications::update_image(&instance).await;
}
}