i3status_rs/
icons.rs

1use crate::errors::*;
2use crate::util;
3use serde::Deserialize;
4use std::collections::HashMap;
5
6#[derive(Deserialize, Debug, Clone)]
7#[serde(try_from = "IconsConfigRaw")]
8pub struct Icons(pub HashMap<String, Icon>);
9
10#[derive(Deserialize, Debug, Clone)]
11#[serde(untagged)]
12pub enum Icon {
13    Single(String),
14    Progression(Vec<String>),
15}
16
17impl From<&'static str> for Icon {
18    fn from(value: &'static str) -> Self {
19        Self::Single(value.into())
20    }
21}
22
23impl<const N: usize> From<[&str; N]> for Icon {
24    fn from(value: [&str; N]) -> Self {
25        Self::Progression(value.iter().map(|s| s.to_string()).collect())
26    }
27}
28
29impl Default for Icons {
30    fn default() -> Self {
31        // "none" icon set
32        Self(map! {
33            "backlight" => "BRIGHT",
34            "bat" => "BAT",
35            "bat_charging" => "CHG",
36            "bat_not_available" => "BAT N/A",
37            "bell" => "ON",
38            "bell-slash" => "OFF",
39            "bluetooth" => "BT",
40            "calendar" => "CAL",
41            "cogs" => "LOAD",
42            "cpu" => "CPU",
43            "cpu_boost_on" => "BOOST ON",
44            "cpu_boost_off" => "BOOST OFF",
45            "disk_drive" => "DISK",
46            "docker" => "DOCKER",
47            "github" => "GITHUB",
48            "gpu" => "GPU",
49            "headphones" => "HEAD",
50            "hueshift" => "HUE",
51            "joystick" => "JOY",
52            "keyboard" => "KBD",
53            "mail" => "MAIL",
54            "memory_mem" => "MEM",
55            "memory_swap" => "SWAP",
56            "mouse" => "MOUSE",
57            "music" => "MUSIC",
58            "music_next" => ">",
59            "music_pause" => "||",
60            "music_play" => ">",
61            "music_prev" => "<",
62            "net_bridge" => "BRIDGE",
63            "net_cellular" => [
64                                "NO SIGNAL",
65                                "0 BARS",
66                                "1 BAR",
67                                "2 BARS",
68                                "3 BARS",
69                                "4 BARS",
70                              ],
71            "net_down" => "DOWN",
72            "net_loopback" => "LO",
73            "net_modem" => "MODEM",
74            "net_up" => "UP ",
75            "net_vpn" => "VPN",
76            "net_wired" => "ETH",
77            "net_wireless" => "WLAN",
78            "notification" => "NOTIF",
79            "phone" => "PHONE",
80            "phone_disconnected" => "PHONE",
81            "ping" => "PING",
82            "pomodoro" => "POMODORO",
83            "pomodoro_break" => "BREAK",
84            "pomodoro_paused" => "PAUSED",
85            "pomodoro_started" => "STARTED",
86            "pomodoro_stopped" => "STOPPED",
87            "resolution" => "RES",
88            "scratchpad" => "[]",
89            "tasks" => "TSK",
90            "tea" => "TEA",
91            "thermometer" => "TEMP",
92            "time" => "TIME",
93            "toggle_off" => "OFF",
94            "toggle_on" => "ON",
95            "unknown" => "??",
96            "update" => "UPD",
97            "uptime" => "UP",
98            "volume" => "VOL",
99            "volume_muted" => "VOL MUTED",
100            "microphone" => "MIC",
101            "microphone_muted" => "MIC MUTED",
102            "weather_clouds_night" => "CLOUDY",
103            "weather_clouds" => "CLOUDY",
104            "weather_default" => "WEATHER",
105            "weather_fog_night" => "FOG",
106            "weather_fog" => "FOG",
107            "weather_moon" => "MOONY",
108            "weather_rain_night" => "RAIN",
109            "weather_rain" => "RAIN",
110            "weather_snow" => "SNOW",
111            "weather_sun" => "SUNNY",
112            "weather_thunder_night" => "STORM",
113            "weather_thunder" => "STORM",
114            "webcam" => "CAM",
115            "xrandr" => "SCREEN"
116        })
117    }
118}
119
120impl Icons {
121    pub fn from_file(file: &str) -> Result<Self> {
122        if file == "none" {
123            Ok(Icons::default())
124        } else {
125            let file = util::find_file(file, Some("icons"), Some("toml"))
126                .or_error(|| format!("Icon set '{file}' not found"))?;
127            Ok(Icons(util::deserialize_toml_file(file)?))
128        }
129    }
130
131    pub fn apply_overrides(&mut self, overrides: HashMap<String, Icon>) {
132        self.0.extend(overrides);
133    }
134
135    pub fn get(&self, icon: &'_ str, value: Option<f64>) -> Option<&str> {
136        match (self.0.get(icon)?, value) {
137            (Icon::Single(icon), _) => Some(icon),
138            (Icon::Progression(prog), _) if prog.is_empty() => None,
139            (Icon::Progression(prog), None) => Some(prog.last().unwrap()),
140            (Icon::Progression(prog), Some(value)) => {
141                let index = ((value * prog.len() as f64) as usize).clamp(0, prog.len() - 1);
142                Some(prog[index].as_str())
143            }
144        }
145    }
146}
147
148#[derive(Deserialize, Default)]
149#[serde(deny_unknown_fields, default)]
150struct IconsConfigRaw {
151    icons: Option<String>,
152    overrides: Option<HashMap<String, Icon>>,
153}
154
155impl TryFrom<IconsConfigRaw> for Icons {
156    type Error = Error;
157
158    fn try_from(raw: IconsConfigRaw) -> Result<Self, Self::Error> {
159        let mut icons = Self::from_file(raw.icons.as_deref().unwrap_or("none"))?;
160        if let Some(overrides) = raw.overrides {
161            for icon in overrides {
162                icons.0.insert(icon.0, icon.1);
163            }
164        }
165        Ok(icons)
166    }
167}