i3status_rs/
blocks.rs

1//! The collection of blocks
2//!
3//! Blocks are defined as a [TOML array of tables](https://github.com/toml-lang/toml/blob/main/toml.md#user-content-array-of-tables): `[[block]]`
4//!
5//! Key | Description | Default
6//! ----|-------------|----------
7//! `block` | Name of the i3status-rs block you want to use. See [modules](#modules) below for valid block names. | -
8//! `signal` | Signal value that causes an update for this block with `0` corresponding to `-SIGRTMIN+0` and the largest value being `-SIGRTMAX` | None
9//! `if_command` | Only display the block if the supplied command returns 0 on startup. | None
10//! `merge_with_next` | If true this will group the block with the next one, so rendering such as alternating_tint will apply to the whole group | `false`
11//! `icons_format` | Overrides global `icons_format` | None
12//! `error_format` | Overrides global `error_format` | None
13//! `error_fullscreen_format` | Overrides global `error_fullscreen_format` | None
14//! `error_interval` | How long to wait until restarting the block after an error occurred. | `5`
15//! `[block.theme_overrides]` | Same as the top-level config option, but for this block only. Refer to `Themes and Icons` below. | None
16//! `[block.icons_overrides]` | Same as the top-level config option, but for this block only. Refer to `Themes and Icons` below. | None
17//! `[[block.click]]` | Set or override click action for the block. See below for details. | Block default / None
18//!
19//! Per block click configuration `[[block.click]]`:
20//!
21//! Key | Description | Default
22//! ----|-------------|----------
23//! `button` | `left`, `middle`, `right`, `up`, `down`, `forward`, `back` or [`double_left`](MouseButton). | -
24//! `widget` | To which part of the block this entry applies (accepts regex) | `"block"`
25//! `cmd` | Command to run when the mouse button event is detected. | None
26//! `action` | Which block action to trigger | None
27//! `sync` | Whether to wait for command to exit or not. | `false`
28//! `update` | Whether to update the block on click. | `false`
29
30mod 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/// An error which originates from a block
209#[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    /// Sends the widget to be displayed.
229    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    /// Hides the block. Send new widget to make it visible again.
239    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    /// Sends the error to be displayed.
249    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}