i3status_rs/blocks/backlight.rs
1//! The brightness of a backlight device
2//!
3//! This block reads brightness information directly from the filesystem, so it works under both
4//! X11 and Wayland. The block uses `inotify` to listen for changes in the device's brightness
5//! directly, so there is no need to set an update interval. This block uses DBus to set brightness
6//! level using the mouse wheel, but will [fallback to sysfs](#d-bus-fallback) if `systemd-logind` is not used.
7//!
8//! # Root scaling
9//!
10//! Some devices expose raw values that are best handled with nonlinear scaling. The human perception of lightness is close to the cube root of relative luminance, so settings for `root_scaling` between 2.4 and 3.0 are worth trying. For devices with few discrete steps this should be 1.0 (linear). More information: <https://en.wikipedia.org/wiki/Lightness>
11//!
12//! # Configuration
13//!
14//! Key | Values | Default
15//! ----|--------|--------
16//! `device` | A regex to match against `/sys/class/backlight` devices to read brightness information from (can match 1 or more devices). When there is no `device` specified, this block will display information for all devices found in the `/sys/class/backlight` directory. | Default device
17//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $brightness "`
18//! `missing_format` | A string to customise the output of this block. No placeholders available | `" no backlight devices "`
19//! `step_width` | The brightness increment to use when scrolling, in percent | `5`
20//! `minimum` | The minimum brightness that can be scrolled down to | `5`
21//! `maximum` | The maximum brightness that can be scrolled up to | `100`
22//! `cycle` | The brightnesses to cycle through on each click | `[minimum, maximum]`
23//! `root_scaling` | Scaling exponent reciprocal (ie. root) | `1.0`
24//! `invert_icons` | Invert icons' ordering, useful if you have colorful emoji | `false`
25//! `ddcci_sleep_multiplier` | [See ddcutil documentation](https://www.ddcutil.com/performance_options/#option-sleep-multiplier) | `1.0`
26//! `ddcci_max_tries_write_read` | The maximum number of times to attempt writing to or reading from a ddcci monitor | `10`
27//!
28//! Placeholder | Value | Type | Unit
29//! -------------|-------------------------------------------|--------|---------------
30//! `icon` | Icon based on backlight's state | Icon | -
31//! `brightness` | Current brightness | Number | %
32//!
33//! Action | Default button
34//! ------------------|---------------
35//! `cycle` | Left
36//! `brightness_up` | Wheel Up
37//! `brightness_down` | Wheel Down
38//!
39//! # Example
40//!
41//! ```toml
42//! [[block]]
43//! block = "backlight"
44//! device = "intel_backlight"
45//! ```
46//!
47//! Hide missing backlight:
48//!
49//! ```toml
50//! [[block]]
51//! block = "backlight"
52//! missing_format = ""
53//! ```
54//!
55//! # calibright
56//!
57//! Additional display brightness calibration can be set in `$XDG_CONFIG_HOME/calibright/config.toml`
58//! See <https://github.com/bim9262/calibright> for more details.
59//! This block will override any global config set in `$XDG_CONFIG_HOME/calibright/config.toml`
60//!
61//! # D-Bus Fallback
62//!
63//! If you don't use `systemd-logind` i3status-rust will attempt to set the brightness
64//! using sysfs. In order to do this you'll need to have write permission.
65//! You can do this by writing a `udev` rule for your system.
66//!
67//! First, check that your user is a member of the "video" group using the
68//! `groups` command. Then add a rule in the `/etc/udev/rules.d/` directory
69//! containing the following, for example in `backlight.rules`:
70//!
71//! ```text
72//! ACTION=="add", SUBSYSTEM=="backlight", GROUP="video", MODE="0664"
73//! ```
74//!
75//! This will allow the video group to modify all backlight devices. You will
76//! also need to restart for this rule to take effect.
77//!
78//! # Icons Used
79//! - `backlight` (as a progression)
80
81use std::sync::Arc;
82
83use calibright::{CalibrightBuilder, CalibrightConfig, CalibrightError, DeviceConfig};
84
85use super::prelude::*;
86
87#[derive(Deserialize, Debug, SmartDefault)]
88#[serde(deny_unknown_fields, default)]
89pub struct Config {
90 pub device: Option<String>,
91 pub format: FormatConfig,
92 pub missing_format: FormatConfig,
93 #[default(5.0)]
94 pub step_width: f64,
95 #[default(5.0)]
96 pub minimum: f64,
97 #[default(100.0)]
98 pub maximum: f64,
99 pub cycle: Option<Vec<f64>>,
100 pub invert_icons: bool,
101 //Calibright config settings
102 pub root_scaling: Option<f64>,
103 pub ddcci_sleep_multiplier: Option<f64>,
104 pub ddcci_max_tries_write_read: Option<u8>,
105}
106
107pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
108 let mut actions = api.get_actions()?;
109 api.set_default_actions(&[
110 (MouseButton::Left, None, "cycle"),
111 (MouseButton::WheelUp, None, "brightness_up"),
112 (MouseButton::WheelDown, None, "brightness_down"),
113 ])?;
114
115 let format = config.format.with_default(" $icon $brightness ")?;
116 let missing_format = config
117 .missing_format
118 .with_default(" no backlight devices ")?;
119
120 let default_cycle = &[config.minimum, config.maximum];
121 let mut cycle = config
122 .cycle
123 .as_deref()
124 .unwrap_or(default_cycle)
125 .iter()
126 .map(|x| x / 100.0)
127 .cycle();
128
129 let step_width = config.step_width / 100.0;
130 let minimum = config.minimum / 100.0;
131 let maximum = config.maximum / 100.0;
132
133 let mut calibright_defaults = DeviceConfig::default();
134
135 if let Some(root_scaling) = config.root_scaling {
136 calibright_defaults.root_scaling = root_scaling;
137 }
138
139 if let Some(ddcci_sleep_multiplier) = config.ddcci_sleep_multiplier {
140 calibright_defaults.ddcci_sleep_multiplier = ddcci_sleep_multiplier;
141 }
142
143 if let Some(ddcci_max_tries_write_read) = config.ddcci_max_tries_write_read {
144 calibright_defaults.ddcci_max_tries_write_read = ddcci_max_tries_write_read;
145 }
146
147 let calibright_config = CalibrightConfig::new_with_defaults(&calibright_defaults)
148 .await
149 .error("calibright config error")?;
150
151 let mut calibright = CalibrightBuilder::new()
152 .with_device_regex(config.device.as_deref().unwrap_or("."))
153 .with_config(calibright_config)
154 .with_poll_interval(api.error_interval)
155 .build()
156 .await
157 .error("Failed to init calibright")?;
158
159 // This is used to display the error, if there is one
160 let mut block_error: Option<CalibrightError> = None;
161
162 let mut brightness = calibright
163 .get_brightness()
164 .await
165 .map_err(|e| block_error = Some(e))
166 .unwrap_or_default();
167
168 loop {
169 match block_error {
170 Some(CalibrightError::NoDevices) => {
171 let widget = Widget::new()
172 .with_format(missing_format.clone())
173 .with_state(State::Critical);
174 api.set_widget(widget)?;
175 }
176 Some(e) => {
177 api.set_error(Error {
178 message: None,
179 cause: Some(Arc::new(e)),
180 })?;
181 }
182 None => {
183 let mut widget = Widget::new().with_format(format.clone());
184 let mut icon_value = brightness;
185 if config.invert_icons {
186 icon_value = 1.0 - icon_value;
187 }
188 widget.set_values(map! {
189 "icon" => Value::icon_progression("backlight", icon_value),
190 "brightness" => Value::percents((brightness * 100.0).round())
191 });
192 api.set_widget(widget)?;
193 }
194 }
195
196 loop {
197 select! {
198 // Calibright can recover from errors, just keep reading the next event.
199 _ = calibright.next() => {
200 block_error = calibright
201 .get_brightness()
202 .await
203 .map(|new_brightness| {brightness = new_brightness;})
204 .err();
205
206 break;
207 },
208 Some(action) = actions.recv() => match action.as_ref() {
209 "cycle" => {
210 if let Some(cycle_brightness) = cycle.next() {
211 brightness = cycle_brightness;
212 block_error = calibright
213 .set_brightness(brightness)
214 .await
215 .err();
216 break;
217
218 }
219 }
220 "brightness_up" => {
221 brightness = (brightness + step_width).clamp(minimum, maximum);
222 block_error = calibright
223 .set_brightness(brightness)
224 .await
225 .err();
226 break;
227 }
228 "brightness_down" => {
229 brightness = (brightness - step_width).clamp(minimum, maximum);
230 block_error = calibright
231 .set_brightness(brightness)
232 .await
233 .err();
234 break;
235 }
236 _ => (),
237 }
238 }
239 }
240 }
241}