tmux_applets/
mem.rs

1use std::fmt;
2use std::fs;
3
4use colorsys::Rgb;
5
6use crate::common::{parse_colour_param, pct_value_hsl};
7
8#[derive(Debug, PartialEq)]
9pub enum MemAppletError {
10    MemInfoUnavailable,
11}
12
13impl std::error::Error for MemAppletError {}
14
15impl fmt::Display for MemAppletError {
16    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17        write!(f, "MemAppletError: {:?}", self)
18    }
19}
20
21type Result<T> = std::result::Result<T, MemAppletError>;
22
23#[derive(Debug, PartialEq)]
24pub struct MemInfo {
25    pub total: u32,
26    pub used: u32,
27    pub available: u32,
28}
29
30const MEM_INFO_PATH: &str = "/proc/meminfo";
31
32fn read_meminfo() -> Result<MemInfo> {
33    let data = fs::read_to_string(MEM_INFO_PATH).or(Err(MemAppletError::MemInfoUnavailable))?;
34
35    let mut info = MemInfo { available: 0, total: 0, used: 0 };
36
37    for line in data.lines() {
38        if info.total != 0 && info.available != 0 {
39            break;
40        }
41
42        let mut parts = line.split_whitespace();
43        let Some(key) = parts.next() else { continue };
44        let Some(value) = parts.next() else { continue };
45
46        match key {
47            "MemTotal:" => {
48                info.total = value.parse::<u32>().unwrap_or(0);
49            }
50            "MemAvailable:" => {
51                info.available = value.parse::<u32>().unwrap_or(0);
52            }
53            _ => continue,
54        }
55    }
56
57    if info.available > 0 && info.total > 0 {
58        info.used = info.total - info.available
59    }
60
61    Ok(info)
62}
63
64fn normalise_mem_usage(info: &MemInfo) -> f32 {
65    info.used as f32 / info.total as f32
66}
67
68pub fn applet(args: &[String]) -> Result<()> {
69    let mut colour_s: Option<f32> = None;
70    let mut colour_l: Option<f32> = None;
71    let mut show_pct_text: bool = false;
72
73    for arg in args {
74        if arg == "pct-text" {
75            show_pct_text = true;
76            continue;
77        }
78        if let Some(s) = parse_colour_param(arg, "s") {
79            if (0.0..=100.0).contains(&s) {
80                colour_s = Some(s);
81            } else {
82                eprintln!("Saturation {s} out of range [0, 100.0]");
83            }
84        };
85        if let Some(l) = parse_colour_param(arg, "l") {
86            if (0.0..=100.0).contains(&l) {
87                colour_l = Some(l);
88            } else {
89                eprintln!("Lightness {l} out of range [0, 100.0]");
90            }
91        }
92    }
93
94    let info = read_meminfo()?;
95    let norm = normalise_mem_usage(&info);
96
97    eprintln!("Mem Info: {:?} Pct: {:.2}", info, norm);
98
99    let c = pct_value_hsl(norm, colour_s, colour_l);
100    let rgb = Rgb::from(&c);
101
102    if show_pct_text {
103        let val = format!("{:.0}", ((norm * 100.0).round()));
104        print!("#[bg={}]{:>2}", rgb.to_hex_string(), val);
105    } else {
106        print!("#[bg={}]  ", rgb.to_hex_string());
107    }
108    println!("#[default]");
109
110    Ok(())
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_read_mem_info() {
119        let m = read_meminfo();
120        assert_eq!(true, m.is_ok());
121        let m = m.unwrap();
122        assert_ne!(0, m.total);
123        assert_ne!(0, m.available);
124        assert_ne!(0, m.used);
125        println!("mem_info: {:?}", m);
126    }
127}