Files
fluxo-rs/src/modules/dnd.rs
T
nvrl 31072bc645
Release / Build and Release (push) Has been cancelled
refactor + feature flags
2026-04-04 00:18:22 +02:00

175 lines
5.9 KiB
Rust

use crate::config::Config;
use crate::error::Result;
use crate::modules::WaybarModule;
use crate::output::WaybarOutput;
use crate::state::{AppReceivers, DndState};
use futures::StreamExt;
use tokio::sync::watch;
use tracing::{debug, error, info};
use zbus::{Connection, fdo::PropertiesProxy, proxy};
pub struct DndModule;
impl WaybarModule for DndModule {
async fn run(
&self,
config: &Config,
state: &AppReceivers,
args: &[&str],
) -> Result<WaybarOutput> {
let action = args.first().unwrap_or(&"show");
if *action == "toggle" {
let connection =
Connection::session()
.await
.map_err(|e| crate::error::FluxoError::Module {
module: "dnd",
message: format!("DBus connection failed: {}", e),
})?;
// Try toggling SwayNC
if let Ok(proxy) = SwayncControlProxy::new(&connection).await {
if let Ok(is_dnd) = proxy.dnd().await {
let _ = proxy.set_dnd(!is_dnd).await;
return Ok(WaybarOutput::default());
}
}
// Try toggling Dunst
if let Ok(proxy) = DunstControlProxy::new(&connection).await {
if let Ok(is_paused) = proxy.paused().await {
let _ = proxy.set_paused(!is_paused).await;
return Ok(WaybarOutput::default());
}
}
return Err(crate::error::FluxoError::Module {
module: "dnd",
message: "No supported notification daemon found to toggle".to_string(),
});
}
let is_dnd = state.dnd.borrow().is_dnd;
if is_dnd {
Ok(WaybarOutput {
text: config.dnd.format_dnd.clone(),
tooltip: Some("Do Not Disturb: On".to_string()),
class: Some("dnd".to_string()),
percentage: None,
})
} else {
Ok(WaybarOutput {
text: config.dnd.format_normal.clone(),
tooltip: Some("Do Not Disturb: Off".to_string()),
class: Some("normal".to_string()),
percentage: None,
})
}
}
}
pub struct DndDaemon;
#[proxy(
interface = "org.erikreider.swaync.control",
default_service = "org.erikreider.swaync.control",
default_path = "/org/erikreider/swaync/control"
)]
trait SwayncControl {
#[zbus(property)]
fn dnd(&self) -> zbus::Result<bool>;
#[zbus(property)]
fn set_dnd(&self, value: bool) -> zbus::Result<()>;
}
#[proxy(
interface = "org.dunstproject.cmd0",
default_service = "org.freedesktop.Notifications",
default_path = "/org/freedesktop/Notifications"
)]
trait DunstControl {
#[zbus(property)]
fn paused(&self) -> zbus::Result<bool>;
#[zbus(property)]
fn set_paused(&self, value: bool) -> zbus::Result<()>;
}
impl DndDaemon {
pub fn new() -> Self {
Self
}
pub fn start(&self, tx: watch::Sender<DndState>) {
tokio::spawn(async move {
loop {
if let Err(e) = Self::listen_loop(&tx).await {
error!("DND listener error: {}", e);
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
}
}
});
}
async fn listen_loop(tx: &watch::Sender<DndState>) -> anyhow::Result<()> {
let connection = Connection::session().await?;
info!("Connected to D-Bus for DND monitoring");
// Try SwayNC
if let Ok(proxy) = SwayncControlProxy::new(&connection).await {
if let Ok(is_dnd) = proxy.dnd().await {
debug!("Found SwayNC, using it for DND state.");
let _ = tx.send(DndState { is_dnd });
if let Ok(props_proxy) = PropertiesProxy::builder(&connection)
.destination("org.erikreider.swaync.control")?
.path("/org/erikreider/swaync/control")?
.build()
.await
{
let mut stream = props_proxy.receive_properties_changed().await?;
while let Some(signal) = stream.next().await {
let args = signal.args()?;
if args.interface_name == "org.erikreider.swaync.control"
&& let Some(val) = args.changed_properties.get("dnd")
&& let Ok(is_dnd) = bool::try_from(val)
{
let _ = tx.send(DndState { is_dnd });
}
}
}
}
}
// Try Dunst
if let Ok(proxy) = DunstControlProxy::new(&connection).await {
if let Ok(is_dnd) = proxy.paused().await {
debug!("Found Dunst, using it for DND state.");
let _ = tx.send(DndState { is_dnd });
if let Ok(props_proxy) = PropertiesProxy::builder(&connection)
.destination("org.freedesktop.Notifications")?
.path("/org/freedesktop/Notifications")?
.build()
.await
{
let mut stream = props_proxy.receive_properties_changed().await?;
while let Some(signal) = stream.next().await {
let args = signal.args()?;
if args.interface_name == "org.dunstproject.cmd0"
&& let Some(val) = args.changed_properties.get("paused")
&& let Ok(is_dnd) = bool::try_from(val)
{
let _ = tx.send(DndState { is_dnd });
}
}
}
}
}
Err(anyhow::anyhow!("DND stream ended or daemon not found"))
}
}