i3status_rs/blocks/
xrandr.rs
1use super::prelude::*;
45use crate::subprocess::spawn_shell;
46use regex::RegexSet;
47use tokio::process::Command;
48
49#[derive(Deserialize, Debug, SmartDefault)]
50#[serde(deny_unknown_fields, default)]
51pub struct Config {
52 #[default(5.into())]
53 pub interval: Seconds,
54 pub format: FormatConfig,
55 #[default(5)]
56 pub step_width: u32,
57 pub invert_icons: bool,
58}
59
60pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
61 let mut actions = api.get_actions()?;
62 api.set_default_actions(&[
63 (MouseButton::Left, None, "cycle_outputs"),
64 (MouseButton::WheelUp, None, "brightness_up"),
65 (MouseButton::WheelDown, None, "brightness_down"),
66 ])?;
67
68 let format = config
69 .format
70 .with_default(" $icon $display $brightness_icon $brightness ")?;
71
72 let mut cur_indx = 0;
73 let mut timer = config.interval.timer();
74
75 loop {
76 let mut monitors = get_monitors().await?;
77 if cur_indx > monitors.len() {
78 cur_indx = 0;
79 }
80
81 loop {
82 let mut widget = Widget::new().with_format(format.clone());
83
84 if let Some(mon) = monitors.get(cur_indx) {
85 let mut icon_value = (mon.brightness as f64) / 100.0;
86 if config.invert_icons {
87 icon_value = 1.0 - icon_value;
88 }
89 widget.set_values(map! {
90 "display" => Value::text(mon.name.clone()),
91 "brightness" => Value::percents(mon.brightness),
92 "brightness_icon" => Value::icon_progression("backlight", icon_value),
93 "resolution" => Value::text(mon.resolution.clone()),
94 "icon" => Value::icon("xrandr"),
95 "res_icon" => Value::icon("resolution"),
96 });
97 }
98 api.set_widget(widget)?;
99
100 select! {
101 _ = timer.tick() => break,
102 _ = api.wait_for_update_request() => break,
103 Some(action) = actions.recv() => match action.as_ref() {
104 "cycle_outputs" => {
105 cur_indx = (cur_indx + 1) % monitors.len();
106 }
107 "brightness_up" => {
108 if let Some(monitor) = monitors.get_mut(cur_indx) {
109 let bright = (monitor.brightness + config.step_width).min(100);
110 monitor.set_brightness(bright);
111 }
112 }
113 "brightness_down" => {
114 if let Some(monitor) = monitors.get_mut(cur_indx) {
115 let bright = monitor.brightness.saturating_sub(config.step_width);
116 monitor.set_brightness(bright);
117 }
118 }
119 _ => (),
120 }
121 }
122 }
123 }
124}
125
126struct Monitor {
127 name: String,
128 brightness: u32,
129 resolution: String,
130}
131
132impl Monitor {
133 fn set_brightness(&mut self, brightness: u32) {
134 let _ = spawn_shell(&format!(
135 "xrandr --output {} --brightness {}",
136 self.name,
137 brightness as f64 / 100.0
138 ));
139 self.brightness = brightness;
140 }
141}
142
143async fn get_monitors() -> Result<Vec<Monitor>> {
144 let mut monitors = Vec::new();
145
146 let active_monitors = Command::new("xrandr")
147 .arg("--listactivemonitors")
148 .output()
149 .await
150 .error("Failed to collect active xrandr monitors")?
151 .stdout;
152 let active_monitors =
153 String::from_utf8(active_monitors).error("xrandr produced non-UTF8 output")?;
154
155 let regex = active_monitors
156 .lines()
157 .filter_map(|line| line.split_ascii_whitespace().last())
158 .map(|name| format!("{name} connected"))
159 .chain(Some("Brightness:".into()));
160 let regex = RegexSet::new(regex).error("Failed to create RegexSet")?;
161
162 let monitors_info = Command::new("xrandr")
163 .arg("--verbose")
164 .output()
165 .await
166 .error("Failed to collect xrandr monitors info")?
167 .stdout;
168 let monitors_info =
169 String::from_utf8(monitors_info).error("xrandr produced non-UTF8 output")?;
170
171 let mut it = monitors_info.lines().filter(|line| regex.is_match(line));
172
173 while let (Some(line1), Some(line2)) = (it.next(), it.next()) {
174 let mut tokens = line1.split_ascii_whitespace().peekable();
175 let name = tokens.next().error("Failed to parse xrandr output")?.into();
176 let _ = tokens.next();
177
178 let _ = tokens.next_if_eq(&"primary");
180
181 let resolution = tokens
182 .next()
183 .and_then(|x| x.split('+').next())
184 .error("Failed to parse xrandr output")?
185 .into();
186
187 let brightness = (line2
188 .split(':')
189 .nth(1)
190 .error("Failed to parse xrandr output")?
191 .trim()
192 .parse::<f64>()
193 .error("Failed to parse xrandr output")?
194 * 100.0) as u32;
195
196 monitors.push(Monitor {
197 name,
198 brightness,
199 resolution,
200 });
201 }
202
203 Ok(monitors)
204}