1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Instant;
use crossbeam_channel::Sender;
use dbus::ffidisp::stdintf::org_freedesktop_dbus::{Properties, PropertiesPropertiesChanged};
use dbus::ffidisp::{BusType, Connection};
use dbus::message::SignalArgs;
use serde_derive::Deserialize;
use crate::blocks::{Block, ConfigBlock, Update};
use crate::config::SharedConfig;
use crate::errors::*;
use crate::formatting::value::Value;
use crate::formatting::FormatTemplate;
use crate::input::{I3BarEvent, MouseButton};
use crate::scheduler::Task;
use crate::util::pseudo_uuid;
use crate::widgets::text::TextWidget;
use crate::widgets::I3BarWidget;
pub struct Notify {
id: usize,
paused: Arc<Mutex<i64>>,
format: FormatTemplate,
output: TextWidget,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(deny_unknown_fields, default)]
pub struct NotifyConfig {
pub format: String,
}
impl Default for NotifyConfig {
fn default() -> Self {
Self {
format: "".into(),
}
}
}
impl ConfigBlock for Notify {
type Config = NotifyConfig;
fn new(
id: usize,
block_config: Self::Config,
shared_config: SharedConfig,
send: Sender<Task>,
) -> Result<Self> {
let notify_id = pseudo_uuid();
let c = Connection::get_private(BusType::Session).block_error(
"notify",
&"Failed to establish D-Bus connection".to_string(),
)?;
let p = c.with_path(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
5000,
);
let initial_state: bool = p.get("org.dunstproject.cmd0", "paused").block_error(
"notify",
&"Failed to get dunst state. Is it running?".to_string(),
)?;
let icon = if initial_state { "bell-slash" } else { "bell" };
#[allow(clippy::mutex_atomic)]
let state = Arc::new(Mutex::new(initial_state as i64));
let state_copy = state.clone();
thread::Builder::new()
.name("notify".into())
.spawn(move || {
let c = Connection::get_private(BusType::Session)
.expect("Failed to establish D-Bus connection in thread");
let matched_signal = PropertiesPropertiesChanged::match_str(
Some(&"org.freedesktop.Notifications".into()),
None,
);
c.add_match(&matched_signal).unwrap();
loop {
for msg in c.incoming(1000) {
if let Some(signal) = PropertiesPropertiesChanged::from_message(&msg) {
let value = signal.changed_properties.get("paused").unwrap();
let status = &value.0.as_i64().unwrap();
let mut paused = state_copy.lock().unwrap();
*paused = *status;
send.send(Task {
id,
update_time: Instant::now(),
})
.unwrap();
}
}
}
})
.unwrap();
Ok(Notify {
id,
paused: state,
format: FormatTemplate::from_string(&block_config.format)?,
output: TextWidget::new(notify_id, 0, shared_config).with_icon(icon)?,
})
}
}
impl Block for Notify {
fn id(&self) -> usize {
self.id
}
fn update(&mut self) -> Result<Option<Update>> {
let paused = *self
.paused
.lock()
.block_error("notify", "failed to acquire lock for `state`")?;
let values = map!(
"state" => Value::from_string(paused.to_string())
);
self.output.set_text(self.format.render(&values)?);
let icon = if paused == 1 { "bell-slash" } else { "bell" };
self.output.set_icon(icon)?;
Ok(None)
}
fn view(&self) -> Vec<&dyn I3BarWidget> {
vec![&self.output]
}
fn click(&mut self, e: &I3BarEvent) -> Result<()> {
if let MouseButton::Left = e.button {
let c = Connection::get_private(BusType::Session).block_error(
"notify",
&"Failed to establish D-Bus connection".to_string(),
)?;
let p = c.with_path(
"org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
5000,
);
let paused = *self
.paused
.lock()
.block_error("notify", "failed to acquire lock")?;
if paused == 1 {
p.set("org.dunstproject.cmd0", "paused", false)
.block_error("notify", &"Failed to query D-Bus".to_string())?;
} else {
p.set("org.dunstproject.cmd0", "paused", true)
.block_error("notify", &"Failed to query D-Bus".to_string())?;
}
}
Ok(())
}
}