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}