i3status_rs/blocks/
maildir.rs

1//! Unread mail. Only supports maildir format.
2//!
3//! Note that you need to enable `maildir` feature to use this block:
4//! ```sh
5//! cargo build --release --features maildir
6//! ```
7//!
8//! # Configuration
9//!
10//! Key | Values | Default
11//! ----|--------|--------
12//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $status "`
13//! `inboxes` | List of maildir inboxes to look for mails in. Supports path/glob expansions (e.g. `~` and `*`). | **Required**
14//! `threshold_warning` | Number of unread mails where state is set to warning. | `1`
15//! `threshold_critical` | Number of unread mails where state is set to critical. | `10`
16//! `interval` | Update interval, in seconds. | `5`
17//! `display_type` | Which part of the maildir to count: `"new"`, `"cur"`, or `"all"`. | `"new"`
18//!
19//! Placeholder  | Value                  | Type   | Unit
20//! -------------|------------------------|--------|-----
21//! `icon`       | A static icon          | Icon   | -
22//! `status`     | Number of emails       | Number | -
23//!
24//! # Examples
25//!
26//! ```toml
27//! [[block]]
28//! block = "maildir"
29//! interval = 60
30//! inboxes = ["~/mail/local", "~/maildir/account1/*"]
31//! threshold_warning = 1
32//! threshold_critical = 10
33//! display_type = "new"
34//! ```
35//!
36//! # Icons Used
37//! - `mail`
38
39use super::prelude::*;
40use maildir::Maildir;
41use std::path::PathBuf;
42
43#[derive(Deserialize, Debug, SmartDefault)]
44#[serde(deny_unknown_fields, default)]
45pub struct Config {
46    pub format: FormatConfig,
47    #[default(5.into())]
48    pub interval: Seconds,
49    pub inboxes: Vec<String>,
50    #[default(1)]
51    pub threshold_warning: usize,
52    #[default(10)]
53    pub threshold_critical: usize,
54    #[default(MailType::New)]
55    pub display_type: MailType,
56}
57
58fn expand_inbox(inbox: &str) -> Result<impl Iterator<Item = PathBuf>> {
59    let expanded = shellexpand::full(inbox).error("Failed to expand inbox")?;
60    let paths = glob::glob(&expanded).error("Glob expansion failed")?;
61    Ok(paths.filter_map(|p| p.ok()))
62}
63
64pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
65    let format = config.format.with_default(" $icon $status ")?;
66
67    let mut inboxes = Vec::with_capacity(config.inboxes.len());
68    for inbox in &config.inboxes {
69        inboxes.extend(expand_inbox(inbox)?.map(Maildir::from));
70    }
71
72    loop {
73        let mut newmails = 0;
74        for inbox in &inboxes {
75            // TODO: spawn_blocking?
76            newmails += match config.display_type {
77                MailType::New => inbox.count_new(),
78                MailType::Cur => inbox.count_cur(),
79                MailType::All => inbox.count_new() + inbox.count_cur(),
80            };
81        }
82
83        let mut widget = Widget::new().with_format(format.clone());
84        widget.state = if newmails >= config.threshold_critical {
85            State::Critical
86        } else if newmails >= config.threshold_warning {
87            State::Warning
88        } else {
89            State::Idle
90        };
91        widget.set_values(map!(
92            "icon" => Value::icon("mail"),
93            "status" => Value::number(newmails)
94        ));
95        api.set_widget(widget)?;
96
97        select! {
98            _ = sleep(config.interval.0) => (),
99            _ = api.wait_for_update_request() => (),
100        }
101    }
102}
103
104#[derive(Clone, Debug, Deserialize)]
105#[serde(rename_all = "lowercase")]
106pub enum MailType {
107    New,
108    Cur,
109    All,
110}