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}