Skip to content

Commit 060b316

Browse files
committed
wip: send dismiss requests and display notifications
1 parent c456673 commit 060b316

File tree

5 files changed

+330
-15
lines changed

5 files changed

+330
-15
lines changed

cosmic-applet-notifications/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ license = "GPL-3.0-or-later"
66

77
[dependencies]
88
anyhow = "1"
9-
icon-loader = { version = "0.3.6", features = ["gtk"] }
109
libcosmic.workspace = true
1110
cosmic-time.workspace = true
1211
cosmic-applet = { path = "../applet" }
1312
nix = "0.26"
1413
tokio = { version = "1.24.1", features = ["sync", "rt", "tracing", "macros", "net", "io-util", "io-std"] }
1514
cosmic-notifications-util = { git = "https://github.com/pop-os/cosmic-notifications" }
15+
cosmic-notifications-config = { git = "https://github.com/pop-os/cosmic-notifications" }
1616
tracing = "0.1"
1717
ron = "0.8"
1818
sendfd = { version = "0.4", features = [ "tokio" ] }
1919
bytemuck = "1"
2020
tracing-subscriber = "0.3"
21+
zbus = "3.14"

cosmic-applet-notifications/src/main.rs

Lines changed: 184 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
mod subscriptions;
22

3+
use cosmic::cosmic_config::{config_subscription, Config, CosmicConfigEntry};
34
use cosmic::iced::wayland::popup::{destroy_popup, get_popup};
45
use cosmic::iced::{
56
widget::{button, column, row, text, Row, Space},
67
window, Alignment, Application, Color, Command, Length, Subscription,
78
};
9+
use cosmic::iced_core::image;
10+
use cosmic::iced_widget::button::StyleSheet;
811
use cosmic_applet::{applet_button_theme, CosmicAppletHelper};
912

1013
use cosmic::iced_style::application::{self, Appearance};
1114

12-
use cosmic::iced_widget::Button;
13-
use cosmic::theme::Svg;
15+
use cosmic::iced_widget::{horizontal_space, scrollable, Column};
16+
use cosmic::theme::{Button, Svg};
1417
use cosmic::widget::{divider, icon};
1518
use cosmic::Renderer;
1619
use cosmic::{Element, Theme};
20+
use cosmic_notifications_config::NotificationsConfig;
21+
use cosmic_notifications_util::{AppletEvent, Notification};
1722
use cosmic_time::{anim, chain, id, once_cell::sync::Lazy, Instant, Timeline};
18-
use cosmic_notifications_util::AppletEvent;
19-
use tracing::info;
23+
use std::borrow::Cow;
2024
use std::process;
25+
use tokio::sync::mpsc::Sender;
26+
use tracing::info;
2127

22-
pub fn main() -> cosmic::iced::Result {
28+
#[tokio::main(flavor = "current_thread")]
29+
pub async fn main() -> cosmic::iced::Result {
2330
tracing_subscriber::fmt::init();
2431

2532
info!("Notifications applet");
@@ -34,12 +41,14 @@ static DO_NOT_DISTURB: Lazy<id::Toggler> = Lazy::new(id::Toggler::unique);
3441
struct Notifications {
3542
applet_helper: CosmicAppletHelper,
3643
theme: Theme,
44+
config: NotificationsConfig,
45+
config_helper: Option<Config>,
3746
icon_name: String,
3847
popup: Option<window::Id>,
3948
id_ctr: u128,
40-
do_not_disturb: bool,
41-
notifications: Vec<Vec<String>>,
49+
notifications: Vec<Notification>,
4250
timeline: Timeline,
51+
dbus_sender: Option<Sender<subscriptions::dbus::Input>>,
4352
}
4453

4554
#[derive(Debug, Clone)]
@@ -50,7 +59,10 @@ enum Message {
5059
Ignore,
5160
Frame(Instant),
5261
Theme(Theme),
53-
NotificationEvent(AppletEvent)
62+
NotificationEvent(AppletEvent),
63+
Config(NotificationsConfig),
64+
DbusEvent(subscriptions::dbus::Output),
65+
Dismissed(u32),
5466
}
5567

5668
impl Application for Notifications {
@@ -62,11 +74,30 @@ impl Application for Notifications {
6274
fn new(_flags: ()) -> (Notifications, Command<Message>) {
6375
let applet_helper = CosmicAppletHelper::default();
6476
let theme = applet_helper.theme();
77+
let helper = Config::new(
78+
cosmic_notifications_config::ID,
79+
NotificationsConfig::version(),
80+
)
81+
.ok();
82+
83+
let config: NotificationsConfig = helper
84+
.as_ref()
85+
.map(|helper| {
86+
NotificationsConfig::get_entry(helper).unwrap_or_else(|(errors, config)| {
87+
for err in errors {
88+
tracing::error!("{:?}", err);
89+
}
90+
config
91+
})
92+
})
93+
.unwrap_or_default();
6594
(
6695
Notifications {
6796
applet_helper,
6897
theme,
6998
icon_name: "notification-alert-symbolic".to_string(),
99+
config_helper: helper,
100+
config,
70101
..Default::default()
71102
},
72103
Command::none(),
@@ -95,10 +126,25 @@ impl Application for Notifications {
95126
fn subscription(&self) -> Subscription<Message> {
96127
Subscription::batch(vec![
97128
self.applet_helper.theme_subscription(0).map(Message::Theme),
129+
config_subscription::<u64, NotificationsConfig>(
130+
0,
131+
cosmic_notifications_config::ID.into(),
132+
NotificationsConfig::version(),
133+
)
134+
.map(|(_, res)| match res {
135+
Ok(config) => Message::Config(config),
136+
Err((errors, config)) => {
137+
for err in errors {
138+
tracing::error!("{:?}", err);
139+
}
140+
Message::Config(config)
141+
}
142+
}),
98143
self.timeline
99144
.as_subscription()
100145
.map(|(_, now)| Message::Frame(now)),
101-
subscriptions::notifications::notifications().map(Message::NotificationEvent)
146+
subscriptions::dbus::proxy().map(Message::DbusEvent),
147+
subscriptions::notifications::notifications().map(Message::NotificationEvent),
102148
])
103149
}
104150

@@ -132,7 +178,12 @@ impl Application for Notifications {
132178
}
133179
Message::DoNotDisturb(chain, b) => {
134180
self.timeline.set_chain(chain).start();
135-
self.do_not_disturb = b;
181+
self.config.do_not_disturb = b;
182+
if let Some(helper) = &self.config_helper {
183+
if let Err(err) = self.config.write_entry(helper) {
184+
tracing::error!("{:?}", err);
185+
}
186+
}
136187
Command::none()
137188
}
138189
Message::Settings => {
@@ -144,6 +195,28 @@ impl Application for Notifications {
144195
Command::none()
145196
}
146197
Message::Ignore => Command::none(),
198+
Message::Config(config) => {
199+
self.config = config;
200+
Command::none()
201+
}
202+
Message::Dismissed(id) => {
203+
self.notifications.retain(|n| n.id != id);
204+
if let Some(tx) = &self.dbus_sender {
205+
let tx = tx.clone();
206+
tokio::spawn(async move {
207+
if let Err(err) = tx.send(subscriptions::dbus::Input::Dismiss(id)).await {
208+
tracing::error!("{:?}", err);
209+
}
210+
});
211+
}
212+
Command::none()
213+
}
214+
Message::DbusEvent(e) => match e {
215+
subscriptions::dbus::Output::Ready(tx) => {
216+
self.dbus_sender.replace(tx);
217+
Command::none()
218+
}
219+
},
147220
}
148221
}
149222

@@ -158,7 +231,7 @@ impl Application for Notifications {
158231
DO_NOT_DISTURB,
159232
&self.timeline,
160233
String::from("Do Not Disturb"),
161-
self.do_not_disturb,
234+
self.config.do_not_disturb,
162235
Message::DoNotDisturb
163236
)
164237
.width(Length::Fill)]
@@ -176,7 +249,103 @@ impl Application for Notifications {
176249
]
177250
.spacing(12)
178251
} else {
179-
row![text("TODO: make app worky with notifications")]
252+
let mut notifs = Vec::with_capacity(self.notifications.len());
253+
254+
for n in &self.notifications {
255+
let summary = text(if n.summary.len() > 24 {
256+
Cow::from(format!(
257+
"{:.26}...",
258+
n.summary.lines().next().unwrap_or_default()
259+
))
260+
} else {
261+
Cow::from(&n.summary)
262+
})
263+
.size(18);
264+
let urgency = n.urgency();
265+
266+
notifs.push(
267+
cosmic::widget::button(Button::Custom {
268+
active: Box::new(move |t| {
269+
let style = if urgency > 1 {
270+
Button::Primary
271+
} else {
272+
Button::Secondary
273+
};
274+
let cosmic = t.cosmic();
275+
let mut a = t.active(&style);
276+
a.border_radius = 8.0.into();
277+
a.background = Some(Color::from(cosmic.bg_color()).into());
278+
a.border_color = Color::from(cosmic.bg_divider());
279+
a.border_width = 1.0;
280+
a
281+
}),
282+
hover: Box::new(move |t| {
283+
let style = if urgency > 1 {
284+
Button::Primary
285+
} else {
286+
Button::Secondary
287+
};
288+
let cosmic = t.cosmic();
289+
let mut a = t.hovered(&style);
290+
a.border_radius = 8.0.into();
291+
a.background = Some(Color::from(cosmic.bg_color()).into());
292+
a.border_color = Color::from(cosmic.bg_divider());
293+
a.border_width = 1.0;
294+
a
295+
}),
296+
})
297+
.custom(vec![column!(
298+
match n.image() {
299+
Some(cosmic_notifications_util::Image::File(path)) => {
300+
row![icon(path.as_path(), 32), summary]
301+
.spacing(8)
302+
.align_items(Alignment::Center)
303+
}
304+
Some(cosmic_notifications_util::Image::Name(name)) => {
305+
row![icon(name.as_str(), 32), summary]
306+
.spacing(8)
307+
.align_items(Alignment::Center)
308+
}
309+
Some(cosmic_notifications_util::Image::Data {
310+
width,
311+
height,
312+
data,
313+
}) => {
314+
let handle =
315+
image::Handle::from_pixels(*width, *height, data.clone());
316+
row![icon(handle, 32), summary]
317+
.spacing(8)
318+
.align_items(Alignment::Center)
319+
}
320+
None => row![summary],
321+
},
322+
text(if n.body.len() > 38 {
323+
Cow::from(format!(
324+
"{:.40}...",
325+
n.body.lines().next().unwrap_or_default()
326+
))
327+
} else {
328+
Cow::from(&n.summary)
329+
})
330+
.size(14),
331+
horizontal_space(Length::Fixed(300.0)),
332+
)
333+
.spacing(8)
334+
.into()])
335+
.on_press(Message::Dismissed(n.id))
336+
.into(),
337+
);
338+
}
339+
340+
row!(scrollable(
341+
Column::with_children(notifs)
342+
.spacing(8)
343+
.width(Length::Shrink)
344+
.height(Length::Shrink),
345+
)
346+
.width(Length::Shrink)
347+
.height(Length::Fixed(400.0)))
348+
.width(Length::Shrink)
180349
};
181350

182351
let main_content = column![
@@ -201,7 +370,9 @@ impl Application for Notifications {
201370
}
202371

203372
// todo put into libcosmic doing so will fix the row_button's boarder radius
204-
fn row_button(mut content: Vec<Element<Message>>) -> Button<Message, Renderer> {
373+
fn row_button(
374+
mut content: Vec<Element<Message>>,
375+
) -> cosmic::iced::widget::Button<Message, Renderer> {
205376
content.insert(0, Space::with_width(Length::Fixed(24.0)).into());
206377
content.push(Space::with_width(Length::Fixed(24.0)).into());
207378

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,83 @@
1-
// TODO connect as a client and send actions / dismissals
1+
use crate::subscriptions::dbus_proxy::NotificationsProxy;
2+
use cosmic::{
3+
iced::{
4+
futures::{self, SinkExt},
5+
subscription,
6+
},
7+
iced_futures::Subscription,
8+
};
9+
use tokio::sync::mpsc::{channel, Receiver, Sender};
10+
use tracing::{error, warn};
11+
use zbus::Connection;
12+
13+
#[derive(Debug)]
14+
pub enum State {
15+
Ready,
16+
WaitingForNotificationEvent(Connection, Receiver<Input>),
17+
Finished,
18+
}
19+
20+
#[derive(Debug, Clone, Copy)]
21+
pub enum Input {
22+
Dismiss(u32),
23+
}
24+
25+
#[derive(Debug, Clone)]
26+
pub enum Output {
27+
Ready(Sender<Input>),
28+
}
29+
30+
pub fn proxy() -> Subscription<Output> {
31+
struct SomeWorker;
32+
33+
subscription::channel(
34+
std::any::TypeId::of::<SomeWorker>(),
35+
50,
36+
|mut output| async move {
37+
let mut state = State::Ready;
38+
39+
loop {
40+
match &mut state {
41+
State::Ready => {
42+
let (sender, receiver) = channel(10);
43+
let Ok(conn) = Connection::session().await else {
44+
error!("Failed to connect to session bus");
45+
state = State::Finished;
46+
continue;
47+
};
48+
if let Err(err) = output.send(Output::Ready(sender)).await {
49+
error!("Failed to send sender: {}", err);
50+
state = State::Finished;
51+
continue;
52+
}
53+
54+
state = State::WaitingForNotificationEvent(conn, receiver);
55+
}
56+
State::WaitingForNotificationEvent(conn, rx) => {
57+
let Ok(proxy) = NotificationsProxy::new(&conn).await else {
58+
error!("Failed to create proxy from session connection");
59+
state = State::Finished;
60+
continue;
61+
};
62+
63+
match rx.recv().await {
64+
Some(Input::Dismiss(id)) => {
65+
if let Err(err) = proxy.close_notification(id).await {
66+
error!("Failed to close notification: {}", err);
67+
}
68+
}
69+
None => {
70+
warn!("Notification event channel closed");
71+
state = State::Finished;
72+
continue;
73+
}
74+
}
75+
}
76+
State::Finished => {
77+
let () = futures::future::pending().await;
78+
}
79+
}
80+
}
81+
},
82+
)
83+
}

0 commit comments

Comments
 (0)