1mod prelude;
31
32use futures::future::FutureExt as _;
33use futures::stream::FuturesUnordered;
34use serde::de::{self, Deserialize};
35use tokio::sync::{Notify, mpsc};
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 as _;
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 backlight,
148 battery,
149 bluetooth,
150 calendar,
151 cpu,
152 custom,
153 custom_dbus,
154 disk_iostats,
155 disk_space,
156 docker,
157 external_ip,
158 focused_window,
159 github,
160 hueshift,
161 kdeconnect,
162 load,
163 #[cfg(feature = "maildir")]
164 maildir,
165 menu,
166 memory,
167 music,
168 net,
169 notify,
170 #[cfg(feature = "notmuch")]
171 notmuch,
172 nvidia_gpu,
173 packages,
174 pomodoro,
175 privacy,
176 rofication,
177 service_status,
178 scratchpad,
179 sound,
180 speedtest,
181 keyboard_layout,
182 taskwarrior,
183 temperature,
184 time,
185 tea_timer,
186 toggle,
187 uptime,
188 vpn,
189 watson,
190 weather,
191 xrandr,
192);
193
194#[derive(Debug, thiserror::Error)]
196#[error("In block {}: {}", .block_name, .error)]
197pub struct BlockError {
198 pub block_id: usize,
199 pub block_name: &'static str,
200 pub error: Error,
201}
202
203pub type BlockAction = Cow<'static, str>;
204
205#[derive(Clone)]
206pub struct CommonApi {
207 pub(crate) id: usize,
208 pub(crate) update_request: Arc<Notify>,
209 pub(crate) request_sender: mpsc::UnboundedSender<Request>,
210 pub(crate) error_interval: Duration,
211}
212
213impl CommonApi {
214 pub fn set_widget(&self, widget: Widget) -> Result<()> {
216 self.request_sender
217 .send(Request {
218 block_id: self.id,
219 cmd: RequestCmd::SetWidget(widget),
220 })
221 .error("Failed to send Request")
222 }
223
224 pub fn hide(&self) -> Result<()> {
226 self.request_sender
227 .send(Request {
228 block_id: self.id,
229 cmd: RequestCmd::UnsetWidget,
230 })
231 .error("Failed to send Request")
232 }
233
234 pub fn set_error(&self, error: Error) -> Result<()> {
236 self.request_sender
237 .send(Request {
238 block_id: self.id,
239 cmd: RequestCmd::SetError(error),
240 })
241 .error("Failed to send Request")
242 }
243
244 pub fn set_default_actions(
245 &self,
246 actions: &'static [(MouseButton, Option<&'static str>, &'static str)],
247 ) -> Result<()> {
248 self.request_sender
249 .send(Request {
250 block_id: self.id,
251 cmd: RequestCmd::SetDefaultActions(actions),
252 })
253 .error("Failed to send Request")
254 }
255
256 pub fn get_actions(&self) -> Result<mpsc::UnboundedReceiver<BlockAction>> {
257 let (tx, rx) = mpsc::unbounded_channel();
258 self.request_sender
259 .send(Request {
260 block_id: self.id,
261 cmd: RequestCmd::SubscribeToActions(tx),
262 })
263 .error("Failed to send Request")?;
264 Ok(rx)
265 }
266
267 pub async fn wait_for_update_request(&self) {
268 self.update_request.notified().await;
269 }
270}