i3status_rs/blocks/
disk_space.rs

1//! Disk usage statistics
2//!
3//! # Configuration
4//!
5//! Key | Values | Default
6//! ----|--------|--------
7//! `path` | Path to collect information from. Supports path expansions e.g. `~`. | `"/"`
8//! `interval` | Update time in seconds | `20`
9//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $available "`
10//! `format_alt` | If set, block will switch between `format` and `format_alt` on every click | `None`
11//! `warning` | A value which will trigger warning block state | `20.0`
12//! `alert` | A value which will trigger critical block state | `10.0`
13//! `info_type` | Determines which information will affect the block state. Possible values are `"available"`, `"free"` and `"used"` | `"available"`
14//! `alert_unit` | The unit of `alert` and `warning` options. If not set, percents are used. Possible values are `"B"`, `"KB"`, `"KiB"`, `"MB"`, `"MiB"`, `"GB"`, `"Gib"`, `"TB"` and `"TiB"` | `None`
15//!
16//! Placeholder  | Value                                                              | Type   | Unit
17//! -------------|--------------------------------------------------------------------|--------|-------
18//! `icon`       | A static icon                                                      | Icon   | -
19//! `path`       | The value of `path` option                                         | Text   | -
20//! `percentage` | Free or used percentage. Depends on `info_type`                    | Number | %
21//! `total`      | Total disk space                                                   | Number | Bytes
22//! `used`       | Used disk space                                                    | Number | Bytes
23//! `free`       | Free disk space                                                    | Number | Bytes
24//! `available`  | Available disk space (free disk space minus reserved system space) | Number | Bytes
25//!
26//! Action          | Description                               | Default button
27//! ----------------|-------------------------------------------|---------------
28//! `toggle_format` | Toggles between `format` and `format_alt` | Left
29//!
30//! # Examples
31//!
32//! ```toml
33//! [[block]]
34//! block = "disk_space"
35//! info_type = "available"
36//! alert_unit = "GB"
37//! alert = 10.0
38//! warning = 15.0
39//! format = " $icon $available "
40//! format_alt = " $icon $available / $total "
41//! ```
42//!
43//! Update block on right click:
44//!
45//! ```toml
46//! [[block]]
47//! block = "disk_space"
48//! [[block.click]]
49//! button = "right"
50//! update = true
51//! ```
52//!
53//! Show the block only if less than 10GB is available:
54//!
55//! ```toml
56//! [[block]]
57//! block = "disk_space"
58//! format = " $free.eng(range:..10e9) |"
59//! ```
60//!
61//! # Icons Used
62//! - `disk_drive`
63
64// make_log_macro!(debug, "disk_space");
65
66use super::prelude::*;
67use crate::formatting::prefix::Prefix;
68use nix::sys::statvfs::statvfs;
69
70#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)]
71#[serde(rename_all = "lowercase")]
72pub enum InfoType {
73    #[default]
74    Available,
75    Free,
76    Used,
77}
78
79#[derive(Deserialize, Debug, SmartDefault)]
80#[serde(deny_unknown_fields, default)]
81pub struct Config {
82    #[default("/".into())]
83    pub path: ShellString,
84    pub info_type: InfoType,
85    pub format: FormatConfig,
86    pub format_alt: Option<FormatConfig>,
87    pub alert_unit: Option<String>,
88    #[default(20.into())]
89    pub interval: Seconds,
90    #[default(20.0)]
91    pub warning: f64,
92    #[default(10.0)]
93    pub alert: f64,
94}
95
96pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
97    let mut actions = api.get_actions()?;
98    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
99
100    let mut format = config.format.with_default(" $icon $available ")?;
101    let mut format_alt = match &config.format_alt {
102        Some(f) => Some(f.with_default("")?),
103        None => None,
104    };
105
106    let unit = match config.alert_unit.as_deref() {
107        // Decimal
108        Some("TB") => Some(Prefix::Tera),
109        Some("GB") => Some(Prefix::Giga),
110        Some("MB") => Some(Prefix::Mega),
111        Some("KB") => Some(Prefix::Kilo),
112        // Binary
113        Some("TiB") => Some(Prefix::Tebi),
114        Some("GiB") => Some(Prefix::Gibi),
115        Some("MiB") => Some(Prefix::Mebi),
116        Some("KiB") => Some(Prefix::Kibi),
117        // Byte
118        Some("B") => Some(Prefix::One),
119        // Unknown
120        Some(x) => return Err(Error::new(format!("Unknown unit: '{x}'"))),
121        None => None,
122    };
123
124    let path = config.path.expand()?;
125
126    let mut timer = config.interval.timer();
127
128    loop {
129        let mut widget = Widget::new().with_format(format.clone());
130
131        let statvfs = statvfs(&*path).error("failed to retrieve statvfs")?;
132
133        // Casting to be compatible with 32-bit systems
134        #[allow(clippy::unnecessary_cast)]
135        let (total, used, available, free) = {
136            let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64);
137            let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64))
138                * (statvfs.fragment_size() as u64);
139            let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64);
140            let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64);
141            (total, used, available, free)
142        };
143
144        let result = match config.info_type {
145            InfoType::Available => available,
146            InfoType::Free => free,
147            InfoType::Used => used,
148        } as f64;
149
150        let percentage = result / (total as f64) * 100.;
151        widget.set_values(map! {
152            "icon" => Value::icon("disk_drive"),
153            "path" => Value::text(path.to_string()),
154            "percentage" => Value::percents(percentage),
155            "total" => Value::bytes(total as f64),
156            "used" => Value::bytes(used as f64),
157            "available" => Value::bytes(available as f64),
158            "free" => Value::bytes(free as f64),
159        });
160
161        // Send percentage to alert check if we don't want absolute alerts
162        let alert_val_in_config_units = match unit {
163            Some(p) => p.apply(result),
164            None => percentage,
165        };
166
167        // Compute state
168        widget.state = match config.info_type {
169            InfoType::Used => {
170                if alert_val_in_config_units >= config.alert {
171                    State::Critical
172                } else if alert_val_in_config_units >= config.warning {
173                    State::Warning
174                } else {
175                    State::Idle
176                }
177            }
178            InfoType::Free | InfoType::Available => {
179                if alert_val_in_config_units <= config.alert {
180                    State::Critical
181                } else if alert_val_in_config_units <= config.warning {
182                    State::Warning
183                } else {
184                    State::Idle
185                }
186            }
187        };
188
189        api.set_widget(widget)?;
190
191        loop {
192            select! {
193                _ = timer.tick() => break,
194                _ = api.wait_for_update_request() => break,
195                Some(action) = actions.recv() => match action.as_ref() {
196                    "toggle_format" => {
197                        if let Some(format_alt) = &mut format_alt {
198                            std::mem::swap(format_alt, &mut format);
199                            break;
200                        }
201                    }
202                    _ => (),
203                }
204            }
205        }
206    }
207}