1mod prelude;
31
32use futures::future::FutureExt;
33use futures::stream::FuturesUnordered;
34use serde::de::{self, Deserialize};
35use tokio::sync::{mpsc, Notify};
36
37use std::borrow::Cow;
38use std::sync::Arc;
39use std::time::Duration;
40
41use crate::click::MouseButton;
42use crate::errors::*;
43use crate::widget::Widget;
44use crate::{BoxedFuture, Request, RequestCmd};
45
46macro_rules! define_blocks {
47 {
48 $(
49 $(#[cfg(feature = $feat: literal)])?
50 $(#[deprecated($($dep_k: ident = $dep_v: literal),+)])?
51 $block: ident $(,)?
52 )*
53 } => {
54 $(
55 $(#[cfg(feature = $feat)])?
56 $(#[cfg_attr(docsrs, doc(cfg(feature = $feat)))])?
57 $(#[deprecated($($dep_k = $dep_v),+)])?
58 pub mod $block;
59 )*
60
61 #[derive(Debug)]
62 pub enum BlockConfig {
63 $(
64 $(#[cfg(feature = $feat)])?
65 #[allow(non_camel_case_types)]
66 #[allow(deprecated)]
67 $block($block::Config),
68 )*
69 Err(&'static str, Error),
70 }
71
72 impl BlockConfig {
73 pub fn name(&self) -> &'static str {
74 match self {
75 $(
76 $(#[cfg(feature = $feat)])?
77 Self::$block { .. } => stringify!($block),
78 )*
79 Self::Err(name, _err) => name,
80 }
81 }
82
83 pub fn spawn(self, api: CommonApi, futures: &mut FuturesUnordered<BoxedFuture<()>>) {
84 match self {
85 $(
86 $(#[cfg(feature = $feat)])?
87 #[allow(deprecated)]
88 Self::$block(config) => futures.push(async move {
89 while let Err(err) = $block::run(&config, &api).await {
90 if api.set_error(err).is_err() {
91 return;
92 }
93 tokio::select! {
94 _ = tokio::time::sleep(api.error_interval) => (),
95 _ = api.wait_for_update_request() => (),
96 }
97 }
98 }.boxed_local()),
99 )*
100 Self::Err(_name, err) => {
101 let _ = api.set_error(Error {
102 message: Some("Configuration error".into()),
103 cause: Some(Arc::new(err)),
104 });
105 },
106 }
107 }
108 }
109
110 impl<'de> Deserialize<'de> for BlockConfig {
111 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
112 where
113 D: de::Deserializer<'de>,
114 {
115 use de::Error;
116
117 let mut table = toml::Table::deserialize(deserializer)?;
118 let block_name = table.remove("block").ok_or_else(|| D::Error::missing_field("block"))?;
119 let block_name = block_name.as_str().ok_or_else(|| D::Error::custom("block must be a string"))?;
120
121 match block_name {
122 $(
123 $(#[cfg(feature = $feat)])?
124 #[allow(deprecated)]
125 stringify!($block) => match $block::Config::deserialize(table) {
126 Ok(config) => Ok(BlockConfig::$block(config)),
127 Err(err) => Ok(BlockConfig::Err(stringify!($block), crate::errors::Error::new(err.to_string()))),
128 }
129 $(
130 #[cfg(not(feature = $feat))]
131 stringify!($block) => Err(D::Error::custom(format!(
132 "block {} is behind a feature gate '{}' which must be enabled at compile time",
133 stringify!($block),
134 $feat,
135 ))),
136 )?
137 )*
138 other => Err(D::Error::custom(format!("unknown block '{other}'")))
139 }
140 }
141 }
142 };
143}
144
145define_blocks!(
146 amd_gpu,
147 #[deprecated(
148 since = "0.33.0",
149 note = "The block has been deprecated in favor of the the packages block"
150 )]
151 apt,
152 backlight,
153 battery,
154 bluetooth,
155 calendar,
156 cpu,
157 custom,
158 custom_dbus,
159 disk_space,
160 #[deprecated(
161 since = "0.33.0",
162 note = "The block has been deprecated in favor of the the packages block"
163 )]
164 dnf,
165 docker,
166 external_ip,
167 focused_window,
168 github,
169 hueshift,
170 kdeconnect,
171 load,
172 #[cfg(feature = "maildir")]
173 maildir,
174 menu,
175 memory,
176 music,
177 net,
178 notify,
179 #[cfg(feature = "notmuch")]
180 notmuch,
181 nvidia_gpu,
182 packages,
183 #[deprecated(
184 since = "0.33.0",
185 note = "The block has been deprecated in favor of the the packages block"
186 )]
187 pacman,
188 pomodoro,
189 privacy,
190 rofication,
191 service_status,
192 scratchpad,
193 sound,
194 speedtest,
195 keyboard_layout,
196 taskwarrior,
197 temperature,
198 time,
199 tea_timer,
200 toggle,
201 uptime,
202 vpn,
203 watson,
204 weather,
205 xrandr,
206);
207
208#[derive(Debug, thiserror::Error)]
210#[error("In block {}: {}", .block_name, .error)]
211pub struct BlockError {
212 pub block_id: usize,
213 pub block_name: &'static str,
214 pub error: Error,
215}
216
217pub type BlockAction = Cow<'static, str>;
218
219#[derive(Clone)]
220pub struct CommonApi {
221 pub(crate) id: usize,
222 pub(crate) update_request: Arc<Notify>,
223 pub(crate) request_sender: mpsc::UnboundedSender<Request>,
224 pub(crate) error_interval: Duration,
225}
226
227impl CommonApi {
228 pub fn set_widget(&self, widget: Widget) -> Result<()> {
230 self.request_sender
231 .send(Request {
232 block_id: self.id,
233 cmd: RequestCmd::SetWidget(widget),
234 })
235 .error("Failed to send Request")
236 }
237
238 pub fn hide(&self) -> Result<()> {
240 self.request_sender
241 .send(Request {
242 block_id: self.id,
243 cmd: RequestCmd::UnsetWidget,
244 })
245 .error("Failed to send Request")
246 }
247
248 pub fn set_error(&self, error: Error) -> Result<()> {
250 self.request_sender
251 .send(Request {
252 block_id: self.id,
253 cmd: RequestCmd::SetError(error),
254 })
255 .error("Failed to send Request")
256 }
257
258 pub fn set_default_actions(
259 &self,
260 actions: &'static [(MouseButton, Option<&'static str>, &'static str)],
261 ) -> Result<()> {
262 self.request_sender
263 .send(Request {
264 block_id: self.id,
265 cmd: RequestCmd::SetDefaultActions(actions),
266 })
267 .error("Failed to send Request")
268 }
269
270 pub fn get_actions(&self) -> Result<mpsc::UnboundedReceiver<BlockAction>> {
271 let (tx, rx) = mpsc::unbounded_channel();
272 self.request_sender
273 .send(Request {
274 block_id: self.id,
275 cmd: RequestCmd::SubscribeToActions(tx),
276 })
277 .error("Failed to send Request")?;
278 Ok(rx)
279 }
280
281 pub async fn wait_for_update_request(&self) {
282 self.update_request.notified().await;
283 }
284}