use futures_util::stream::{StreamExt, select};
use log::warn;
use zbus::{Connection, proxy};
use crate::widget::{Command, Msg, Notifications};
use crate::command::CommandReceiver;
use crate::producer::{MsgSender, Producer, ProducerFuture, ProducerResult};
const SWAYNC_NAME: &str = "org.erikreider.swaync.cc";
#[proxy(
interface = "org.erikreider.swaync.cc",
default_service = "org.erikreider.swaync.cc",
default_path = "/org/erikreider/swaync/cc"
)]
trait SwayNc {
#[zbus(no_autostart)]
fn get_subscribe_data(&self) -> zbus::Result<(bool, bool, u32, bool)>;
fn toggle_visibility(&self) -> zbus::Result<()>;
fn toggle_dnd(&self) -> zbus::Result<bool>;
#[zbus(signal)]
fn subscribe_v2(&self, count: u32, dnd: bool, cc_open: bool, inhibited: bool);
}
pub fn notifications_from_swaync(count: u32, dnd: bool) -> Notifications {
Notifications::new(count, dnd)
}
pub struct NotificationsProducer;
impl NotificationsProducer {
pub fn new() -> Self {
Self
}
}
impl Default for NotificationsProducer {
fn default() -> Self {
Self::new()
}
}
impl Producer for NotificationsProducer {
fn name(&self) -> String {
"notifications".to_string()
}
fn run(self: Box<Self>, tx: MsgSender) -> ProducerFuture {
Box::pin(run(tx))
}
}
async fn run(tx: MsgSender) -> ProducerResult {
let conn = Connection::session().await?;
let proxy = SwayNcProxy::new(&conn).await?;
let dbus = zbus::fdo::DBusProxy::new(&conn).await?;
let mut owner_changes = dbus
.receive_name_owner_changed_with_args(&[(0, SWAYNC_NAME)])
.await?;
loop {
let seed = match proxy.get_subscribe_data().await {
Ok((dnd, _cc_open, count, _inhibited)) => Some(notifications_from_swaync(count, dnd)),
Err(_) => None,
};
if tx.send(Msg::Notifications(seed)).is_err() {
return Ok(());
}
enum BusEvent {
State(SubscribeV2),
OwnerChanged,
}
let signals = proxy.receive_subscribe_v2().await?.map(BusEvent::State);
let owners = (&mut owner_changes).map(|_| BusEvent::OwnerChanged);
let mut merged = select(signals, owners);
let mut reseed = false;
while let Some(event) = merged.next().await {
match event {
BusEvent::State(signal) => match signal.args() {
Ok(args) => {
let snapshot = notifications_from_swaync(*args.count(), *args.dnd());
if tx.send(Msg::Notifications(Some(snapshot))).is_err() {
return Ok(());
}
}
Err(e) => warn!("notifications: bad SubscribeV2 args: {e}"),
},
BusEvent::OwnerChanged => {
reseed = true;
break;
}
}
}
if !reseed {
return Ok(());
}
}
}
pub async fn run_commands(mut commands: CommandReceiver) -> ProducerResult {
let conn = Connection::session().await?;
let proxy = SwayNcProxy::new(&conn).await?;
while let Some(command) = commands.recv().await {
let result = match command {
Command::ToggleNotificationPanel => proxy.toggle_visibility().await,
Command::ToggleNotificationsDnd => proxy.toggle_dnd().await.map(|_| ()),
_ => continue,
};
if let Err(e) = result {
warn!("notifications: executing {command:?} failed: {e}");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn readings_normalize_into_snapshots() {
let quiet = notifications_from_swaync(0, false);
assert_eq!(quiet.count(), 0);
assert!(!quiet.dnd());
let busy = notifications_from_swaync(7, true);
assert_eq!(busy.count(), 7);
assert!(busy.dnd());
}
#[test]
fn snapshots_differing_only_in_dropped_fields_are_equal() {
assert_eq!(
notifications_from_swaync(3, false),
notifications_from_swaync(3, false)
);
}
}