diff --git a/Cargo.lock b/Cargo.lock index 5ddb22e3..a909f6d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -880,7 +880,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -894,7 +894,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "quote", "syn 1.0.109", @@ -916,7 +916,7 @@ dependencies = [ [[package]] name = "cosmic-notifications-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-notifications#5691ae9f999990b2c37f7dc38d8c04a0178c1dca" +source = "git+https://github.com/pop-os/cosmic-notifications#b0566b96b39d636f7228eb9daf2f3ec2ab2fa362" dependencies = [ "cosmic-config", "serde", @@ -925,10 +925,13 @@ dependencies = [ [[package]] name = "cosmic-notifications-util" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-notifications#5691ae9f999990b2c37f7dc38d8c04a0178c1dca" +source = "git+https://github.com/pop-os/cosmic-notifications#b0566b96b39d636f7228eb9daf2f3ec2ab2fa362" dependencies = [ "bytemuck", + "fast_image_resize", "serde", + "tracing", + "zbus", ] [[package]] @@ -989,7 +992,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "anyhow", "cosmic-config", @@ -1004,8 +1007,8 @@ dependencies = [ [[package]] name = "cosmic-time" version = "0.2.0" -source = "git+https://github.com/pop-os/cosmic-time?rev=39c96ac#39c96ac8b3c11aeb5a4fe8bc962a89013f3f27b7" dependencies = [ + "float-cmp", "libcosmic", "once_cell", ] @@ -1549,6 +1552,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" +[[package]] +name = "fast_image_resize" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc789a40040e11bbe4ba31ca319406805a12fe3f8d71314bbc4bd076602ad55a" +dependencies = [ + "num-traits", + "thiserror", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -2285,7 +2298,7 @@ dependencies = [ [[package]] name = "iced" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "iced_accessibility", "iced_core", @@ -2300,7 +2313,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "accesskit", "accesskit_unix", @@ -2309,7 +2322,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "bitflags 1.3.2", "iced_accessibility", @@ -2324,7 +2337,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.6.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "futures", "iced_core", @@ -2337,7 +2350,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.8.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2354,7 +2367,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2366,7 +2379,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "iced_accessibility", "iced_core", @@ -2378,7 +2391,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "enum-repr", "float-cmp", @@ -2400,7 +2413,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.8.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "iced_core", "once_cell", @@ -2410,7 +2423,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "bytemuck", "cosmic-text", @@ -2428,7 +2441,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2449,7 +2462,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "iced_renderer", "iced_runtime", @@ -2715,7 +2728,7 @@ checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/#56d24b2372ed699115fee777b4811c60419e2f66" +source = "git+https://github.com/pop-os/libcosmic/#f17d52f37f06f4d35c2feb65a5da03516d374acf" dependencies = [ "apply", "cosmic-config", diff --git a/Cargo.toml b/Cargo.toml index e73203b6..2319f05a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,10 @@ libcosmic = { git = "https://github.com/pop-os/libcosmic", default-features = fa [profile.release] lto = "thin" # lto = "fat" + +# [patch."https://github.com/pop-os/cosmic-time"] +# cosmic-time = { path = "../cosmic-time" } +# [patch."https://github.com/pop-os/libcosmic"] +# libcosmic = { path = "../libcosmic" } +# [patch."https://github.com/pop-os/cosmic-config"] +# cosmic-config = { path = "../libcosmic/cosmic-config" } diff --git a/cosmic-applet-notifications/Cargo.toml b/cosmic-applet-notifications/Cargo.toml index aa884291..a4c38738 100644 --- a/cosmic-applet-notifications/Cargo.toml +++ b/cosmic-applet-notifications/Cargo.toml @@ -13,12 +13,14 @@ nix = "0.26" tokio = { version = "1.24.1", features = ["sync", "rt", "tracing", "macros", "net", "io-util", "io-std"] } cosmic-notifications-util = { git = "https://github.com/pop-os/cosmic-notifications" } cosmic-notifications-config = { git = "https://github.com/pop-os/cosmic-notifications" } +# cosmic-notifications-util = { path = "../../cosmic-notifications-daemon/cosmic-notifications-util" } +# cosmic-notifications-config = { path = "../../cosmic-notifications-daemon/cosmic-notifications-config" } tracing = "0.1" ron = "0.8" sendfd = { version = "0.4", features = [ "tokio" ] } bytemuck = "1" tracing-subscriber = "0.3" -zbus = "3.14" +zbus = {version = "3.14", features = [ "tokio" ]} # Application i18n i18n-embed = { version = "0.13.4", features = ["fluent-system", "desktop-requester"] } i18n-embed-fl = "0.6.4" diff --git a/cosmic-applet-notifications/i18n/en/cosmic_applet_notifications.ftl b/cosmic-applet-notifications/i18n/en/cosmic_applet_notifications.ftl index 78268654..0c45c122 100644 --- a/cosmic-applet-notifications/i18n/en/cosmic_applet_notifications.ftl +++ b/cosmic-applet-notifications/i18n/en/cosmic_applet_notifications.ftl @@ -6,6 +6,7 @@ minutes-ago = { NUMBER($duration) -> [1] 1 Minute Ago *[other] {$duration} Minutes Ago } +show-more = Show {$more} More clear-all = Clear All Notifications do-not-disturb = Do Not Disturb notification-settings = Notification Settings... diff --git a/cosmic-applet-notifications/src/main.rs b/cosmic-applet-notifications/src/main.rs index d0548523..7d50dad8 100644 --- a/cosmic-applet-notifications/src/main.rs +++ b/cosmic-applet-notifications/src/main.rs @@ -10,18 +10,17 @@ use cosmic::iced::{ }; use cosmic::iced_core::alignment::Horizontal; use cosmic::iced_core::image; -use cosmic::iced_widget::button::StyleSheet; use cosmic_applet::{applet_button_theme, CosmicAppletHelper}; use cosmic::iced_style::application::{self, Appearance}; use cosmic::iced_widget::{horizontal_rule, scrollable, Column}; -use cosmic::theme::{Button, Svg}; +use cosmic::theme::Svg; use cosmic::widget::{container, icon}; use cosmic::Renderer; use cosmic::{Element, Theme}; use cosmic_notifications_config::NotificationsConfig; -use cosmic_notifications_util::{AppletEvent, Notification}; +use cosmic_notifications_util::Notification; use cosmic_time::{anim, chain, id, once_cell::sync::Lazy, Instant, Timeline}; use std::borrow::Cow; use std::collections::HashMap; @@ -51,9 +50,24 @@ struct Notifications { icon_name: String, popup: Option, id_ctr: u128, - notifications: Vec, + // notifications: Vec, timeline: Timeline, dbus_sender: Option>, + cards: Vec<(id::Cards, Vec, bool, String)>, +} + +impl Notifications { + fn update_cards(&mut self, id: id::Cards) { + if let Some((id, _, card_value, _)) = self.cards.iter_mut().find(|c| c.0 == id) { + let chain = if *card_value { + chain::Cards::on(id.clone(), 1.) + } else { + chain::Cards::off(id.clone(), 1.) + }; + self.timeline.set_chain(chain); + self.timeline.start(); + } + } } #[derive(Debug, Clone)] @@ -64,11 +78,12 @@ enum Message { Ignore, Frame(Instant), Theme(Theme), - NotificationEvent(AppletEvent), + NotificationEvent(Notification), Config(NotificationsConfig), DbusEvent(subscriptions::dbus::Output), Dismissed(u32), - ClearAll, + ClearAll(String), + CardsToggled(String, bool), } impl Application for Notifications { @@ -201,20 +216,31 @@ impl Application for Notifications { let _ = process::Command::new("cosmic-settings notifications").spawn(); Command::none() } - Message::NotificationEvent(e) => { - match e { - AppletEvent::Notification(n) => { - self.notifications.push(n); - } - AppletEvent::Replace(n) => { - if let Some(old) = self.notifications.iter_mut().find(|n| n.id == n.id) { - *old = n; - } - } - AppletEvent::Closed(id) => { - self.notifications.retain(|n| n.id != id); + Message::NotificationEvent(n) => { + info!("{}", &n.app_name); + if let Some(c) = self + .cards + .iter_mut() + .find(|c| c.1.iter().any(|notif| n.app_name == notif.app_name)) + { + if let Some(notif) = c.1.iter_mut().find(|notif| n.id == notif.id) { + *notif = n; + } else { + c.1.push(n); + c.3 = fl!( + "show-more", + HashMap::from_iter(vec![("more", c.1.len().saturating_sub(1))]) + ); } + } else { + self.cards.push(( + id::Cards::new(n.app_name.clone()), + vec![n], + false, + fl!("show-more", HashMap::from_iter(vec![("more", "1")])), + )); } + Command::none() } Message::Ignore => Command::none(), @@ -223,7 +249,10 @@ impl Application for Notifications { Command::none() } Message::Dismissed(id) => { - self.notifications.retain(|n| n.id != id); + info!("dismissed {}", id); + for c in &mut self.cards { + c.1.retain(|n| n.id != id); + } if let Some(tx) = &self.dbus_sender { let tx = tx.clone(); tokio::spawn(async move { @@ -239,20 +268,51 @@ impl Application for Notifications { self.dbus_sender.replace(tx); Command::none() } + subscriptions::dbus::Output::CloseEvent(id) => { + for c in &mut self.cards { + c.1.retain(|n| n.id != id); + c.3 = fl!( + "show-more", + HashMap::from_iter(vec![("more", c.1.len().saturating_sub(1))]) + ); + } + Command::none() + } }, - Message::ClearAll => { - for n in self.notifications.drain(..) { - if let Some(tx) = &self.dbus_sender { - let tx = tx.clone(); - tokio::spawn(async move { - if let Err(err) = - tx.send(subscriptions::dbus::Input::Dismiss(n.id)).await - { - tracing::error!("{:?}", err); - } - }); + Message::ClearAll(app_name) => { + if let Some(pos) = self + .cards + .iter_mut() + .position(|c| c.1.iter().any(|notif| app_name == notif.app_name)) + { + for n in self.cards.remove(pos).1 { + if let Some(tx) = &self.dbus_sender { + let tx = tx.clone(); + tokio::spawn(async move { + if let Err(err) = + tx.send(subscriptions::dbus::Input::Dismiss(n.id)).await + { + tracing::error!("{:?}", err); + } + }); + } } } + + Command::none() + } + Message::CardsToggled(name, expanded) => { + let id = if let Some((id, _, n_expanded, _)) = self + .cards + .iter_mut() + .find(|c| c.1.iter().any(|notif| name == notif.app_name)) + { + *n_expanded = expanded; + id.clone() + } else { + return Command::none(); + }; + self.update_cards(id); Command::none() } } @@ -278,7 +338,7 @@ impl Application for Notifications { let settings = row_button(vec![text(fl!("notification-settings")).into()]) .on_press(Message::Settings); - let notifications = if self.notifications.len() == 0 { + let notifications = if self.cards.is_empty() { row![container( column![text_icon(&self.icon_name, 40), "No Notifications"] .align_items(Alignment::Center) @@ -287,109 +347,110 @@ impl Application for Notifications { .align_x(Horizontal::Center)] .spacing(12) } else { - let mut notifs: Vec> = Vec::with_capacity(self.notifications.len()); - notifs.push( - column![cosmic::widget::button(Button::Text) - .custom(vec![text(fl!("clear-all")).size(14).into()]) - .on_press(Message::ClearAll)] - .align_items(Alignment::End) - .width(Length::Fill) - .into(), - ); - for n in self.notifications.iter().rev() { - let app_name = text(if n.app_name.len() > 24 { - Cow::from(format!( - "{:.26}...", - n.app_name.lines().next().unwrap_or_default() - )) - } else { - Cow::from(&n.app_name) - }) - .size(12) - .width(Length::Fill); - let urgency = n.urgency(); + let mut notifs: Vec> = Vec::with_capacity(self.cards.len()); - let duration_since = text(duration_ago_msg(n)).size(12); - - notifs.push( - cosmic::widget::button(Button::Custom { - active: Box::new(move |t| { - let style = if urgency > 1 { - Button::Primary - } else { - Button::Secondary - }; - let mut a = t.active(&style); - a.border_radius = 8.0.into(); - a - }), - hover: Box::new(move |t| { - let style = if urgency > 1 { - Button::Primary - } else { - Button::Secondary - }; - let mut a = t.hovered(&style); - a.border_radius = 8.0.into(); - a - }), - }) - .custom(vec![column!( - match n.image() { - Some(cosmic_notifications_util::Image::File(path)) => { - row![icon(path.as_path(), 16), app_name, duration_since] - .spacing(8) - .align_items(Alignment::Center) - } - Some(cosmic_notifications_util::Image::Name(name)) => { - row![icon(name.as_str(), 16), app_name, duration_since] - .spacing(8) - .align_items(Alignment::Center) - } - Some(cosmic_notifications_util::Image::Data { - width, - height, - data, - }) => { - let handle = - image::Handle::from_pixels(*width, *height, data.clone()); - row![icon(handle, 16), app_name, duration_since] - .spacing(8) - .align_items(Alignment::Center) - } - None => row![app_name, duration_since], - }, - column![ - text(if n.summary.len() > 77 { + for c in self.cards.iter().rev() { + if c.1.is_empty() { + continue; + } + let name = c.1[0].app_name.clone(); + let notif_elems: Vec<_> = + c.1.iter() + .rev() + .map(|n| { + let app_name = text(if n.app_name.len() > 24 { Cow::from(format!( - "{:.80}...", - n.summary.lines().next().unwrap_or_default() + "{:.26}...", + n.app_name.lines().next().unwrap_or_default() )) } else { - Cow::from(&n.summary) + Cow::from(&n.app_name) }) - .width(Length::Fill) - .size(14), - text(if n.body.len() > 77 { - Cow::from(format!( - "{:.80}...", - n.body.lines().next().unwrap_or_default() - )) - } else { - Cow::from(&n.body) - }) - .width(Length::Fill) .size(12) - ] - ) - .spacing(8) - .into()]) - .padding(16) - .on_press(Message::Dismissed(n.id)) - .width(Length::Fill) - .into(), + .width(Length::Fill); + + let duration_since = text(duration_ago_msg(n)).size(12); + + let close_notif = + button(icon("window-close-symbolic", 16).style(Svg::Symbolic)) + .on_press(Message::Dismissed(n.id)) + .style(cosmic::theme::Button::Text); + Element::from( + column!( + match n.image() { + Some(cosmic_notifications_util::Image::File(path)) => { + row![ + icon(path.as_path(), 16), + app_name, + duration_since, + close_notif + ] + .spacing(8) + .align_items(Alignment::Center) + } + Some(cosmic_notifications_util::Image::Name(name)) => { + row![ + icon(name.as_str(), 16), + app_name, + duration_since, + close_notif + ] + .spacing(8) + .align_items(Alignment::Center) + } + Some(cosmic_notifications_util::Image::Data { + width, + height, + data, + }) => { + let handle = image::Handle::from_pixels( + *width, + *height, + data.clone(), + ); + row![ + icon(handle, 16), + app_name, + duration_since, + close_notif + ] + .spacing(8) + .align_items(Alignment::Center) + } + None => row![app_name, duration_since, close_notif] + .spacing(8) + .align_items(Alignment::Center), + }, + column![ + text(n.summary.lines().next().unwrap_or_default()) + .width(Length::Fill) + .size(14), + text(n.body.lines().next().unwrap_or_default()) + .width(Length::Fill) + .size(12) + ] + ) + .width(Length::Fill), + ) + }) + .collect(); + let card_list = anim!( + //cards + c.0.clone(), + &self.timeline, + notif_elems, + Message::ClearAll(name.clone()), + move |_, e| Message::CardsToggled(name.clone(), e), + &c.3, + "Show Less", + // &format!("Show {} More", c.1.len().saturating_sub(1)), + "Clear All", + None, + c.2, ); + notifs.push(card_list.into()); } + row!(scrollable( Column::with_children(notifs) .spacing(8) diff --git a/cosmic-applet-notifications/src/subscriptions/dbus.rs b/cosmic-applet-notifications/src/subscriptions/dbus.rs index 326b9f6f..97ecb941 100644 --- a/cosmic-applet-notifications/src/subscriptions/dbus.rs +++ b/cosmic-applet-notifications/src/subscriptions/dbus.rs @@ -1,4 +1,4 @@ -use crate::subscriptions::dbus_proxy::NotificationsProxy; +use crate::subscriptions::freedesktop_proxy::NotificationsProxy; use cosmic::{ iced::{ futures::{self, SinkExt}, @@ -8,23 +8,25 @@ use cosmic::{ }; use tokio::sync::mpsc::{channel, Receiver, Sender}; use tracing::{error, warn}; -use zbus::Connection; +use zbus::{export::futures_util::StreamExt, Connection}; #[derive(Debug)] pub enum State { Ready, - WaitingForNotificationEvent(Connection, Receiver), + WaitingForNotificationEvent(NotificationsProxy<'static>, Receiver), Finished, } #[derive(Debug, Clone, Copy)] pub enum Input { Dismiss(u32), + CloseEvent(u32), } #[derive(Debug, Clone)] pub enum Output { Ready(Sender), + CloseEvent(u32), } pub fn proxy() -> Subscription { @@ -45,34 +47,54 @@ pub fn proxy() -> Subscription { state = State::Finished; continue; }; - if let Err(err) = output.send(Output::Ready(sender)).await { - error!("Failed to send sender: {}", err); - state = State::Finished; - continue; - } - state = State::WaitingForNotificationEvent(conn, receiver); - } - State::WaitingForNotificationEvent(conn, rx) => { let Ok(proxy) = NotificationsProxy::new(&conn).await else { error!("Failed to create proxy from session connection"); state = State::Finished; continue; }; - - match rx.recv().await { - Some(Input::Dismiss(id)) => { - if let Err(err) = proxy.close_notification(id).await { - error!("Failed to close notification: {}", err); - } + let tx = sender.clone(); + if let Err(err) = output.send(Output::Ready(sender)).await { + error!("Failed to send sender: {}", err); + state = State::Finished; + continue; + } + state = match proxy.receive_notification_closed().await { + Ok(mut s) => { + tokio::spawn(async move { + while let Some(msg) = s.next().await { + let Ok(id) = msg.args().map(|args| args.id) else { + continue; + }; + _ = tx.send(Input::CloseEvent(id)).await; + } + }); + State::WaitingForNotificationEvent(proxy, receiver) } - None => { - warn!("Notification event channel closed"); - state = State::Finished; - continue; + Err(err) => { + error!( + "failed to get a stream of signals for notifications. {}", + err + ); + State::Finished + } + }; + } + State::WaitingForNotificationEvent(proxy, rx) => match rx.recv().await { + Some(Input::Dismiss(id)) => { + if let Err(err) = proxy.close_notification(id).await { + error!("Failed to close notification: {}", err); } } - } + Some(Input::CloseEvent(id)) => { + _ = output.send(Output::CloseEvent(id)).await; + } + None => { + warn!("Notification event channel closed"); + state = State::Finished; + continue; + } + }, State::Finished => { let () = futures::future::pending().await; } diff --git a/cosmic-applet-notifications/src/subscriptions/dbus_proxy.rs b/cosmic-applet-notifications/src/subscriptions/freedesktop_proxy.rs similarity index 100% rename from cosmic-applet-notifications/src/subscriptions/dbus_proxy.rs rename to cosmic-applet-notifications/src/subscriptions/freedesktop_proxy.rs diff --git a/cosmic-applet-notifications/src/subscriptions/mod.rs b/cosmic-applet-notifications/src/subscriptions/mod.rs index 21fbf7aa..11313be8 100644 --- a/cosmic-applet-notifications/src/subscriptions/mod.rs +++ b/cosmic-applet-notifications/src/subscriptions/mod.rs @@ -1,3 +1,3 @@ pub mod dbus; -mod dbus_proxy; +mod freedesktop_proxy; pub mod notifications; diff --git a/cosmic-applet-notifications/src/subscriptions/notifications.rs b/cosmic-applet-notifications/src/subscriptions/notifications.rs index 9e5ee67e..6c40fd4a 100644 --- a/cosmic-applet-notifications/src/subscriptions/notifications.rs +++ b/cosmic-applet-notifications/src/subscriptions/notifications.rs @@ -1,28 +1,28 @@ use cosmic::{ - iced::{ - futures::{self, SinkExt}, - subscription, - }, + iced::{futures, subscription}, iced_futures::Subscription, }; -use cosmic_notifications_util::AppletEvent; -use sendfd::RecvWithFd; -use std::os::unix::io::{FromRawFd, RawFd}; -use tokio::{ - io::{self, AsyncBufReadExt, BufReader}, - net::UnixStream, +use cosmic_notifications_util::Notification; +use std::{ + collections::HashMap, + os::unix::io::{FromRawFd, RawFd}, +}; + +use tracing::{error, info}; +use zbus::{ + dbus_proxy, + export::futures_util::{SinkExt, StreamExt}, + ConnectionBuilder, }; -use tracing::{error, info, warn}; #[derive(Debug)] pub enum State { Ready, - WaitingForDaemon(UnixStream), - WaitingForNotificationEvent(UnixStream), + WaitingForNotificationEvent(NotificationsAppletProxy<'static>), Finished, } -pub fn notifications() -> Subscription { +pub fn notifications() -> Subscription { struct SomeWorker; subscription::channel( @@ -34,97 +34,45 @@ pub fn notifications() -> Subscription { loop { match &mut state { State::Ready => { - info!("Reading COSMIC_NOTIFICATIONS env var"); - let Ok(Some(raw_fd)) = std::env::var("COSMIC_NOTIFICATIONS") - .map(|fd| fd.parse::().ok()) else - { - error!("Failed to parse COSMIC_NOTIFICATIONS env var"); - state = State::Finished; - continue; - }; - - let stream = unsafe { std::os::unix::net::UnixStream::from_raw_fd(raw_fd) }; - let Ok(stream) = UnixStream::from_std(stream) else { - error!("Failed to convert std stream to unix stream"); - state = State::Finished; - continue; - }; - state = State::WaitingForDaemon(stream); - - } - State::WaitingForDaemon(stream) => { - info!("Waiting for panel to send us a stream"); - if let Err(err) = stream.readable().await { - error!("Failed to wait for stream to be readable {}", err); - state = State::Finished; - continue; - }; - - // we are expecting a single RawFd from the panel on this stream and the applet id - let mut buf = [0u8; 4]; - let mut fd_buf = [0i32; 1]; - - match stream.recv_with_fd(&mut buf, &mut fd_buf) { - Ok((data_cnt, fd_cnt)) => { - if data_cnt == 0 && fd_cnt == 0 { - warn!("Received EOF from panel"); - state = State::Finished; - - continue; - } - if data_cnt != 4 || fd_cnt != 1 { - error!( - "Invalid data received from panel {} {}", - data_cnt, fd_cnt - ); - state = State::Finished; - - continue; - } - let notif_stream = unsafe { - std::os::unix::net::UnixStream::from_raw_fd(fd_buf[0]) - }; - let Ok(notif_stream) = UnixStream::from_std(notif_stream) else { - error!("Failed to convert raw fd to unix stream"); - state = State::Finished; - continue; - }; - - state = State::WaitingForNotificationEvent(notif_stream); - } - Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - continue; - } + state = match get_proxy().await { + Ok(p) => State::WaitingForNotificationEvent(p), Err(err) => { - error!("Failed to receive fd from panel: {}", err); - state = State::Finished; - + error!("Failed to connect to notifications daemon {}", err); + State::Finished + } + }; + } + State::WaitingForNotificationEvent(proxy) => { + info!("Waiting for notification events..."); + let mut signal = match proxy.receive_notify().await { + Ok(s) => s, + Err(err) => { + error!( + "failed to get a stream of signals for notifications. {}", + err + ); continue; } + }; + while let Some(msg) = signal.next().await { + info!("Notification event"); + let Some(args) = msg.args().into_iter().next() else { + error!("Failed to get arguments from notification signal."); + break; + }; + let notification = Notification::new( + args.app_name, + args.id, + args.app_icon, + args.summary, + args.body, + args.actions, + args.hints, + args.expire_timeout, + ); + _ = output.send(notification).await; } } - State::WaitingForNotificationEvent(stream) => { - info!("Waiting for notification event"); - let reader = BufReader::new(stream); - // todo read messages - - let mut lines = reader.lines(); - while let Ok(Some(line)) = lines.next_line().await { - if line.is_empty() { - warn!("Received empty line from notification stream. The notification daemon probably crashed, so we will exit."); - std::process::exit(1); - } - if let Ok(event) = ron::de::from_str::(line.as_str()) { - if let Err(_err) = output.send(event).await { - error!("Error sending event"); - } - } else { - error!("Failed to deserialize event from notification stream"); - } - } - warn!("Notification stream closed. The notification daemon probably crashed, so we will exit."); - std::process::exit(1); - } State::Finished => { let () = futures::future::pending().await; } @@ -133,3 +81,37 @@ pub fn notifications() -> Subscription { }, ) } + +#[dbus_proxy( + default_service = "com.system76.NotificationsApplet", + interface = "com.system76.NotificationsApplet", + default_path = "/com/system76/NotificationsApplet" +)] +trait NotificationsApplet { + #[dbus_proxy(signal)] + fn notify( + &self, + app_name: &str, + id: u32, + app_icon: &str, + summary: &str, + body: &str, + actions: Vec<&str>, + hints: HashMap<&str, zbus::zvariant::Value<'_>>, + expire_timeout: i32, + ) -> zbus::Result<()>; +} + +async fn get_proxy() -> anyhow::Result> { + let raw_fd = std::env::var("COSMIC_NOTIFICATIONS")?; + let raw_fd = raw_fd.parse::()?; + + let stream = unsafe { std::os::unix::net::UnixStream::from_raw_fd(raw_fd) }; + stream.set_nonblocking(true)?; + let stream = tokio::net::UnixStream::from_std(stream)?; + let conn = ConnectionBuilder::socket(stream).p2p().build().await?; + info!("Applet connection created"); + let proxy = NotificationsAppletProxy::new(&conn).await?; + + Ok(proxy) +}