1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-01 10:41:03 +02:00

feat: swaync notifications module

Adds a new module which connects to SwayNC to display information from the notifications deamon.

Resolves #7
This commit is contained in:
Jake Stanger 2024-03-03 22:42:57 +00:00
parent 660dc81e7d
commit 7742a46578
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
15 changed files with 592 additions and 3 deletions

View file

@ -385,6 +385,8 @@ fn add_modules(
ModuleConfig::Launcher(mut module) => add_module!(module, id),
#[cfg(feature = "music")]
ModuleConfig::Music(mut module) => add_module!(module, id),
#[cfg(feature = "notifications")]
ModuleConfig::Notifications(mut module) => add_module!(module, id),
ModuleConfig::Script(mut module) => add_module!(module, id),
#[cfg(feature = "sys_info")]
ModuleConfig::SysInfo(mut module) => add_module!(module, id),

View file

@ -6,6 +6,8 @@ pub mod clipboard;
pub mod compositor;
#[cfg(feature = "music")]
pub mod music;
#[cfg(feature = "notifications")]
pub mod swaync;
#[cfg(feature = "tray")]
pub mod system_tray;
#[cfg(feature = "upower")]
@ -25,6 +27,8 @@ pub struct Clients {
clipboard: Option<Arc<clipboard::Client>>,
#[cfg(feature = "music")]
music: std::collections::HashMap<music::ClientType, Arc<dyn music::MusicClient>>,
#[cfg(feature = "notifications")]
notifications: Option<Arc<swaync::Client>>,
#[cfg(feature = "tray")]
tray: Option<Arc<system_tray::TrayEventReceiver>>,
#[cfg(feature = "upower")]
@ -71,6 +75,15 @@ impl Clients {
.clone()
}
#[cfg(feature = "notifications")]
pub fn notifications(&mut self) -> Arc<swaync::Client> {
self.notifications
.get_or_insert_with(|| {
Arc::new(crate::await_sync(async { swaync::Client::new().await }))
})
.clone()
}
#[cfg(feature = "tray")]
pub fn tray(&mut self) -> Arc<system_tray::TrayEventReceiver> {
self.tray
@ -122,7 +135,7 @@ macro_rules! register_client {
where
TSend: Clone,
{
fn provide(&self) -> Arc<$ty> {
fn provide(&self) -> std::sync::Arc<$ty> {
self.ironbar.clients.borrow_mut().$method()
}
}

111
src/clients/swaync/dbus.rs Normal file
View file

@ -0,0 +1,111 @@
//! # D-Bus interface proxy for: `org.erikreider.swaync.cc`
//!
//! This code was generated by `zbus-xmlgen` `4.0.1` from D-Bus introspection data.
//! Source: `Interface '/org/erikreider/swaync/cc' from service 'org.erikreider.swaync.cc' on session bus`.
//!
//! You may prefer to adapt it, instead of using it verbatim.
//!
//! More information can be found in the [Writing a client proxy] section of the zbus
//! documentation.
//!
//! This type implements the [D-Bus standard interfaces], (`org.freedesktop.DBus.*`) for which the
//! following zbus API can be used:
//!
//! * [`zbus::fdo::PropertiesProxy`]
//! * [`zbus::fdo::IntrospectableProxy`]
//! * [`zbus::fdo::PeerProxy`]
//!
//! Consequently `zbus-xmlgen` did not generate code for the above interfaces.
//!
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
#[zbus::dbus_proxy(
interface = "org.erikreider.swaync.cc",
default_service = "org.erikreider.swaync.cc",
default_path = "/org/erikreider/swaync/cc"
)]
trait SwayNc {
/// AddInhibitor method
fn add_inhibitor(&self, application_id: &str) -> zbus::Result<bool>;
/// ChangeConfigValue method
fn change_config_value(
&self,
name: &str,
value: &zbus::zvariant::Value<'_>,
write_to_file: bool,
path: &str,
) -> zbus::Result<()>;
/// ClearInhibitors method
fn clear_inhibitors(&self) -> zbus::Result<bool>;
/// CloseAllNotifications method
fn close_all_notifications(&self) -> zbus::Result<()>;
/// CloseNotification method
fn close_notification(&self, id: u32) -> zbus::Result<()>;
/// GetDnd method
fn get_dnd(&self) -> zbus::Result<bool>;
/// GetSubscribeData method
fn get_subscribe_data(&self) -> zbus::Result<(bool, bool, u32, bool)>;
/// GetVisibility method
fn get_visibility(&self) -> zbus::Result<bool>;
/// HideLatestNotifications method
fn hide_latest_notifications(&self, close: bool) -> zbus::Result<()>;
/// IsInhibited method
fn is_inhibited(&self) -> zbus::Result<bool>;
/// NotificationCount method
fn notification_count(&self) -> zbus::Result<u32>;
/// NumberOfInhibitors method
fn number_of_inhibitors(&self) -> zbus::Result<u32>;
/// ReloadConfig method
fn reload_config(&self) -> zbus::Result<()>;
/// ReloadCss method
fn reload_css(&self) -> zbus::Result<bool>;
/// RemoveInhibitor method
fn remove_inhibitor(&self, application_id: &str) -> zbus::Result<bool>;
/// SetDnd method
fn set_dnd(&self, state: bool) -> zbus::Result<()>;
/// SetVisibility method
fn set_visibility(&self, visibility: bool) -> zbus::Result<()>;
/// ToggleDnd method
fn toggle_dnd(&self) -> zbus::Result<bool>;
/// ToggleVisibility method
fn toggle_visibility(&self) -> zbus::Result<()>;
/// Subscribe signal
#[dbus_proxy(signal)]
fn subscribe(&self, count: u32, dnd: bool, cc_open: bool) -> zbus::Result<()>;
/// SubscribeV2 signal
#[dbus_proxy(signal)]
fn subscribe_v2(
&self,
count: u32,
dnd: bool,
cc_open: bool,
inhibited: bool,
) -> zbus::Result<()>;
/// Inhibited property
#[dbus_proxy(property)]
fn inhibited(&self) -> zbus::Result<bool>;
#[dbus_proxy(property)]
fn set_inhibited(&self, value: bool) -> zbus::Result<()>;
}

88
src/clients/swaync/mod.rs Normal file
View file

@ -0,0 +1,88 @@
mod dbus;
use crate::{register_client, send, spawn};
use color_eyre::{Report, Result};
use dbus::SwayNcProxy;
use serde::Deserialize;
use tokio::sync::broadcast;
use tracing::{debug, error};
use zbus::export::ordered_stream::OrderedStreamExt;
use zbus::zvariant::Type;
#[derive(Debug, Clone, Copy, Type, Deserialize)]
pub struct Event {
pub count: u32,
pub dnd: bool,
pub cc_open: bool,
pub inhibited: bool,
}
type GetSubscribeData = (bool, bool, u32, bool);
/// Converts the data returned from
/// `get_subscribe_data` into an event for convenience.
impl From<GetSubscribeData> for Event {
fn from((dnd, cc_open, count, inhibited): (bool, bool, u32, bool)) -> Self {
Self {
dnd,
cc_open,
count,
inhibited,
}
}
}
#[derive(Debug)]
pub struct Client {
proxy: SwayNcProxy<'static>,
tx: broadcast::Sender<Event>,
_rx: broadcast::Receiver<Event>,
}
impl Client {
pub async fn new() -> Self {
let dbus = Box::pin(zbus::Connection::session())
.await
.expect("failed to create connection to system bus");
let proxy = SwayNcProxy::new(&dbus).await.unwrap();
let (tx, rx) = broadcast::channel(8);
let mut stream = proxy.receive_subscribe_v2().await.unwrap();
{
let tx = tx.clone();
spawn(async move {
while let Some(ev) = stream.next().await {
let ev = ev.body::<Event>().expect("to deserialize");
debug!("Received event: {ev:?}");
send!(tx, ev);
}
});
}
Self { proxy, tx, _rx: rx }
}
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
self.tx.subscribe()
}
pub async fn state(&self) -> Result<Event> {
debug!("Getting subscribe data (current state)");
match self.proxy.get_subscribe_data().await {
Ok(data) => Ok(data.into()),
Err(err) => Err(Report::new(err)),
}
}
pub async fn toggle_visibility(&self) {
debug!("Toggling visibility");
if let Err(err) = self.proxy.toggle_visibility().await {
error!("{err:?}");
}
}
}
register_client!(Client, notifications);

View file

@ -14,6 +14,8 @@ use crate::modules::label::LabelModule;
use crate::modules::launcher::LauncherModule;
#[cfg(feature = "music")]
use crate::modules::music::MusicModule;
#[cfg(feature = "notifications")]
use crate::modules::notifications::NotificationsModule;
use crate::modules::script::ScriptModule;
#[cfg(feature = "sys_info")]
use crate::modules::sysinfo::SysInfoModule;
@ -47,6 +49,8 @@ pub enum ModuleConfig {
Launcher(Box<LauncherModule>),
#[cfg(feature = "music")]
Music(Box<MusicModule>),
#[cfg(feature = "notifications")]
Notifications(Box<NotificationsModule>),
Script(Box<ScriptModule>),
#[cfg(feature = "sys_info")]
SysInfo(Box<SysInfoModule>),

View file

@ -34,6 +34,8 @@ pub mod label;
pub mod launcher;
#[cfg(feature = "music")]
pub mod music;
#[cfg(feature = "notifications")]
pub mod notifications;
pub mod script;
#[cfg(feature = "sys_info")]
pub mod sysinfo;

View file

@ -0,0 +1,190 @@
use crate::clients::swaync;
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{glib_recv, send_async, spawn, try_send};
use gtk::prelude::*;
use gtk::{Align, Button, Label, Overlay};
use serde::Deserialize;
use tokio::sync::mpsc::Receiver;
use tracing::error;
#[derive(Debug, Deserialize, Clone)]
pub struct NotificationsModule {
#[serde(default = "crate::config::default_true")]
show_count: bool,
#[serde(default)]
icons: Icons,
#[serde(flatten)]
pub common: Option<CommonConfig>,
}
#[derive(Debug, Deserialize, Clone)]
struct Icons {
#[serde(default = "default_icon_closed_none")]
closed_none: String,
#[serde(default = "default_icon_closed_some")]
closed_some: String,
#[serde(default = "default_icon_closed_dnd")]
closed_dnd: String,
#[serde(default = "default_icon_open_none")]
open_none: String,
#[serde(default = "default_icon_open_some")]
open_some: String,
#[serde(default = "default_icon_open_dnd")]
open_dnd: String,
}
impl Default for Icons {
fn default() -> Self {
Self {
closed_none: default_icon_closed_none(),
closed_some: default_icon_closed_some(),
closed_dnd: default_icon_closed_dnd(),
open_none: default_icon_open_none(),
open_some: default_icon_open_some(),
open_dnd: default_icon_open_dnd(),
}
}
}
fn default_icon_closed_none() -> String {
String::from("󰍥")
}
fn default_icon_closed_some() -> String {
String::from("󱥂")
}
fn default_icon_closed_dnd() -> String {
String::from("󱅯")
}
fn default_icon_open_none() -> String {
String::from("󰍡")
}
fn default_icon_open_some() -> String {
String::from("󱥁")
}
fn default_icon_open_dnd() -> String {
String::from("󱅮")
}
impl Icons {
fn icon(&self, value: &swaync::Event) -> &str {
match (value.cc_open, value.count > 0, value.dnd) {
(true, _, true) => &self.open_dnd,
(true, true, false) => &self.open_some,
(true, false, false) => &self.open_none,
(false, _, true) => &self.closed_dnd,
(false, true, false) => &self.closed_some,
(false, false, false) => &self.closed_none,
}
.as_str()
}
}
#[derive(Debug, Clone, Copy)]
pub enum UiEvent {
ToggleVisibility,
}
impl Module<Overlay> for NotificationsModule {
type SendMessage = swaync::Event;
type ReceiveMessage = UiEvent;
fn name() -> &'static str {
"notifications"
}
fn spawn_controller(
&self,
_info: &ModuleInfo,
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
mut rx: Receiver<Self::ReceiveMessage>,
) -> color_eyre::Result<()>
where
<Self as Module<Overlay>>::SendMessage: Clone,
{
let client = context.client::<swaync::Client>();
{
let client = client.clone();
let mut rx = client.subscribe();
let tx = context.tx.clone();
spawn(async move {
let initial_state = client.state().await;
match initial_state {
Ok(ev) => send_async!(tx, ModuleUpdateEvent::Update(ev)),
Err(err) => error!("{err:?}"),
};
while let Ok(ev) = rx.recv().await {
send_async!(tx, ModuleUpdateEvent::Update(ev));
}
});
}
spawn(async move {
while let Some(event) = rx.recv().await {
match event {
UiEvent::ToggleVisibility => client.toggle_visibility().await,
}
}
});
Ok(())
}
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
) -> color_eyre::Result<ModuleParts<Overlay>>
where
<Self as Module<Overlay>>::SendMessage: Clone,
{
let overlay = Overlay::new();
let button = Button::with_label(&self.icons.closed_none);
overlay.add(&button);
let label = Label::builder()
.label("0")
.halign(Align::End)
.valign(Align::Start)
.build();
if self.show_count {
label.add_class("count");
overlay.add_overlay(&label);
}
let ctx = context.controller_tx.clone();
button.connect_clicked(move |_| {
try_send!(ctx, UiEvent::ToggleVisibility);
});
{
let button = button.clone();
glib_recv!(context.subscribe(), ev => {
let icon = self.icons.icon(&ev);
button.set_label(icon);
label.set_label(&ev.count.to_string());
label.set_visible(self.show_count && ev.count > 0);
});
}
Ok(ModuleParts {
widget: overlay,
popup: None,
})
}
}