Skip to content

Commit e83960a

Browse files
authored
Merge pull request #1356 from cfsmp3/master
Create rofication block
2 parents 9d53d1c + 20bad44 commit e83960a

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

doc/blocks.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ You may find that the block you desire is not in the list below. In that case, f
4444
- [Nvidia Gpu](#nvidia-gpu)
4545
- [Pacman](#pacman)
4646
- [Pomodoro](#pomodoro)
47+
- [Rofication](#rofication)
4748
- [Sound](#sound)
4849
- [Speed Test](#speed-test)
4950
- [Taskwarrior](#taskwarrior)
@@ -1550,6 +1551,40 @@ Key | Values | Required | Default
15501551

15511552
###### [↥ back to top](#list-of-available-blocks)
15521553

1554+
## Rofication
1555+
1556+
Creates a block with shows the number of pending notifications in rofication-daemon. A different color is used is there are critical notications. Left clicking the block opens the GUI.
1557+
1558+
#### Examples
1559+
1560+
```toml
1561+
[[block]]
1562+
block = "rofication"
1563+
interval = 1
1564+
socket_path = "/tmp/rofi_notification_daemon"
1565+
```
1566+
1567+
#### Options
1568+
1569+
Key | Values | Required | Default
1570+
----|--------|----------|--------
1571+
`interval` | Refresh rate in seconds. | No | `1`
1572+
`format` | A string to customise the output of this block. See below for placeholders. Text may need to be escaped, refer to [Escaping Text](#escaping-text). | No | `"{num}"`
1573+
`socket_path` | Socket path for the rofication daemon. | No | "/tmp/rofi_notification_daemon"
1574+
1575+
### Available Format Keys
1576+
1577+
Key | Value | Type
1578+
-----|-------|-----
1579+
`{num}` | Number of pending notifications | Integer
1580+
1581+
#### Icons Used
1582+
1583+
- `bell`
1584+
- `bell-slash`
1585+
1586+
###### [↥ back to top](#list-of-available-blocks)
1587+
15531588
## Sound
15541589

15551590
Creates a block which displays the volume level (according to PulseAudio or ALSA). Right click to toggle mute, scroll to adjust volume.

src/blocks.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub mod notmuch;
2828
pub mod nvidia_gpu;
2929
pub mod pacman;
3030
pub mod pomodoro;
31+
pub mod rofication;
3132
pub mod sound;
3233
pub mod speedtest;
3334
pub mod taskwarrior;
@@ -70,6 +71,7 @@ use self::notmuch::*;
7071
use self::nvidia_gpu::*;
7172
use self::pacman::*;
7273
use self::pomodoro::*;
74+
use self::rofication::*;
7375
use self::sound::*;
7476
use self::speedtest::*;
7577
use self::taskwarrior::*;
@@ -269,6 +271,7 @@ pub fn create_block(
269271
"nvidia_gpu" => block!(NvidiaGpu, id, block_config, shared_config, update_request),
270272
"pacman" => block!(Pacman, id, block_config, shared_config, update_request),
271273
"pomodoro" => block!(Pomodoro, id, block_config, shared_config, update_request),
274+
"rofication" => block!(Rofication, id, block_config, shared_config, update_request),
272275
"sound" => block!(Sound, id, block_config, shared_config, update_request),
273276
"speedtest" => block!(SpeedTest, id, block_config, shared_config, update_request),
274277
"taskwarrior" => block!(Taskwarrior, id, block_config, shared_config, update_request),

src/blocks/rofication.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
use std::str::FromStr;
2+
use std::time::Duration;
3+
4+
use crossbeam_channel::Sender;
5+
use serde_derive::Deserialize;
6+
7+
use std::io::Read;
8+
use std::io::Write;
9+
use std::os::unix::net::UnixStream;
10+
use std::path::Path;
11+
12+
use crate::blocks::{Block, ConfigBlock, Update};
13+
use crate::config::SharedConfig;
14+
use crate::de::deserialize_duration;
15+
use crate::errors::*;
16+
use crate::formatting::value::Value;
17+
use crate::formatting::FormatTemplate;
18+
use crate::protocol::i3bar_event::I3BarEvent;
19+
use crate::protocol::i3bar_event::MouseButton;
20+
use crate::scheduler::Task;
21+
use crate::subprocess::spawn_child_async;
22+
use crate::widgets::text::TextWidget;
23+
use crate::widgets::I3BarWidget;
24+
use crate::widgets::State;
25+
26+
#[derive(Debug)]
27+
struct RotificationStatus {
28+
num: u64,
29+
crit: u64,
30+
}
31+
32+
pub struct Rofication {
33+
id: usize,
34+
text: TextWidget,
35+
update_interval: Duration,
36+
format: FormatTemplate,
37+
38+
// UNIX socket to read from
39+
pub socket_path: String,
40+
}
41+
42+
#[derive(Deserialize, Debug, Clone)]
43+
#[serde(deny_unknown_fields, default)]
44+
pub struct RoficationConfig {
45+
/// Update interval in seconds
46+
#[serde(deserialize_with = "deserialize_duration")]
47+
pub interval: Duration,
48+
49+
// UNIX socket to read from
50+
pub socket_path: String,
51+
52+
/// Format override
53+
pub format: FormatTemplate,
54+
}
55+
56+
impl Default for RoficationConfig {
57+
fn default() -> Self {
58+
Self {
59+
interval: Duration::from_secs(1),
60+
socket_path: "/tmp/rofi_notification_daemon".to_string(),
61+
format: FormatTemplate::default(),
62+
}
63+
}
64+
}
65+
66+
impl ConfigBlock for Rofication {
67+
type Config = RoficationConfig;
68+
69+
fn new(
70+
id: usize,
71+
block_config: Self::Config,
72+
shared_config: SharedConfig,
73+
_tx_update_request: Sender<Task>,
74+
) -> Result<Self> {
75+
let text = TextWidget::new(id, 0, shared_config.clone())
76+
.with_text("?")
77+
.with_icon("bell")?
78+
.with_state(State::Good);
79+
80+
Ok(Rofication {
81+
id,
82+
update_interval: block_config.interval,
83+
text,
84+
socket_path: block_config.socket_path,
85+
format: block_config.format.with_default("{num}")?,
86+
})
87+
}
88+
}
89+
90+
impl Block for Rofication {
91+
fn update(&mut self) -> Result<Option<Update>> {
92+
match rofication_status(&self.socket_path) {
93+
Ok(status) => {
94+
let values = map!(
95+
"num" => Value::from_string(status.num.to_string()),
96+
);
97+
98+
self.text.set_texts(self.format.render(&values)?);
99+
if status.crit > 0 {
100+
self.text.set_state(State::Critical)
101+
} else {
102+
if status.num > 0 {
103+
self.text.set_state(State::Warning)
104+
} else {
105+
self.text.set_state(State::Good)
106+
}
107+
}
108+
self.text.set_icon("bell")?;
109+
}
110+
Err(_) => {
111+
self.text.set_text("?".to_string());
112+
self.text.set_state(State::Critical);
113+
self.text.set_icon("bell-slash")?;
114+
}
115+
}
116+
117+
Ok(Some(self.update_interval.into()))
118+
}
119+
120+
fn view(&self) -> Vec<&dyn I3BarWidget> {
121+
vec![&self.text]
122+
}
123+
124+
fn click(&mut self, event: &I3BarEvent) -> Result<()> {
125+
if event.button == MouseButton::Left {
126+
spawn_child_async("rofication-gui", &[])
127+
.block_error("rofication", "could not spawn gui")?;
128+
}
129+
Ok(())
130+
}
131+
132+
fn id(&self) -> usize {
133+
self.id
134+
}
135+
}
136+
137+
fn rofication_status(socket_path: &str) -> Result<RotificationStatus> {
138+
let socket = Path::new(socket_path);
139+
// Connect to socket
140+
let mut stream = match UnixStream::connect(&socket) {
141+
Err(_) => {
142+
return Err(BlockError(
143+
"rofication".to_string(),
144+
"Failed to connect to socket".to_string(),
145+
))
146+
}
147+
Ok(stream) => stream,
148+
};
149+
150+
// Request count
151+
match stream.write(b"num\n") {
152+
Err(_) => {
153+
return Err(BlockError(
154+
"rofication".to_string(),
155+
"Failed to write to socket".to_string(),
156+
))
157+
}
158+
Ok(_) => {}
159+
};
160+
161+
// Response must be two comma separated integers: regular and critical
162+
let mut buffer = String::new();
163+
match stream.read_to_string(&mut buffer) {
164+
Err(_) => {
165+
return Err(BlockError(
166+
"rofication".to_string(),
167+
"Failed to read from socket".to_string(),
168+
))
169+
}
170+
Ok(_) => {}
171+
};
172+
173+
let values = buffer.split(',').collect::<Vec<&str>>();
174+
if values.len() != 2 {
175+
return Err(BlockError(
176+
"rofication".to_string(),
177+
"Format error".to_string(),
178+
));
179+
}
180+
181+
let num = match values[0].parse::<u64>() {
182+
Ok(num) => num,
183+
Err(_) => {
184+
return Err(BlockError(
185+
"rofication".to_string(),
186+
"Failed to parse num".to_string(),
187+
))
188+
}
189+
};
190+
let crit = match values[1].parse::<u64>() {
191+
Ok(crit) => crit,
192+
Err(_) => {
193+
return Err(BlockError(
194+
"rofication".to_string(),
195+
"Failed to parse crit".to_string(),
196+
))
197+
}
198+
};
199+
200+
Ok(RotificationStatus { num, crit })
201+
}

0 commit comments

Comments
 (0)