The release profile builds with panic = "abort", so a panic in any helper
thread took the whole dashboard down:
- backlight: WalkDir yields /sys/class/backlight itself, so detect() was
always true and new() picked the class dir as the device, then panicked
reading brightness. Use min_depth(1) and tolerate read errors.
- battery: detect() only checked that UPower's DisplayDevice answered,
which it does on every machine; use IsPresent. The monitor thread now
returns an error instead of unwrapping, and publishes its first reading.
- pulseaudio: a failed connection panicked ("damn"); clicks before the
first sink query unwrapped a None channel map. Both are now handled, and
the connect loop no longer busy-spins.
- bar widget: don't underflow when narrower than the label column.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
113 lines
2.9 KiB
Rust
113 lines
2.9 KiB
Rust
use std::{
|
|
fs::{self, OpenOptions},
|
|
io::{Error, ErrorKind, Write},
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use walkdir::WalkDir;
|
|
|
|
use crate::{
|
|
color::Color,
|
|
event::PointerButton,
|
|
fonts::FontMap,
|
|
widgets::bar_widget::{BarWidget, BarWidgetImpl},
|
|
};
|
|
|
|
fn read_file_as_u64(path: &Path) -> Result<u64, Error> {
|
|
let mut s = fs::read_to_string(path)?;
|
|
s.pop();
|
|
s.parse::<u64>()
|
|
.map_err(|_e| Error::new(ErrorKind::Other, "unable to parse value"))
|
|
}
|
|
|
|
fn write_file_as_u64(path: &Path, value: u64) -> Result<(), Error> {
|
|
let mut file = OpenOptions::new().write(true).open(path)?;
|
|
file.write_fmt(format_args!("{}", value))
|
|
}
|
|
|
|
pub struct Backlight {
|
|
device_path: PathBuf,
|
|
cur: u64,
|
|
max: u64,
|
|
dirty: bool,
|
|
}
|
|
|
|
/// First device under /sys/class/backlight, if any. `min_depth(1)` skips the
|
|
/// class directory itself, which otherwise got picked up as a "device".
|
|
fn find_device() -> Option<PathBuf> {
|
|
WalkDir::new("/sys/class/backlight")
|
|
.min_depth(1)
|
|
.max_depth(1)
|
|
.into_iter()
|
|
.flatten()
|
|
.map(|e| e.path().to_path_buf())
|
|
.next()
|
|
}
|
|
|
|
impl Backlight {
|
|
fn update(&mut self) {
|
|
if let Ok(cur) = read_file_as_u64(self.device_path.join("brightness").as_path()) {
|
|
self.cur = cur;
|
|
}
|
|
if let Ok(max) = read_file_as_u64(self.device_path.join("max_brightness").as_path()) {
|
|
self.max = max;
|
|
}
|
|
}
|
|
|
|
pub fn new(path: Option<&str>, fm: &mut FontMap, font: &'static str, size: f32) -> BarWidget {
|
|
let device_path = match path {
|
|
Some(path) => Some(Path::new("/sys/class/backlight").join(path)),
|
|
None => find_device(),
|
|
};
|
|
let mut dev = Backlight {
|
|
device_path: device_path.expect("did not find backlight device"),
|
|
cur: 0,
|
|
max: 0,
|
|
dirty: true,
|
|
};
|
|
|
|
dev.update();
|
|
BarWidget::new(Box::new(dev), fm, font, size)
|
|
}
|
|
|
|
pub fn detect() -> bool {
|
|
find_device().is_some()
|
|
}
|
|
|
|
pub fn set(&mut self, brightness: f32) {
|
|
let val = (self.max as f32 * brightness.clamp(0.0, 1.0)) as u64;
|
|
let _ = write_file_as_u64(self.device_path.join("brightness").as_path(), val);
|
|
if let Ok(cur) = read_file_as_u64(self.device_path.join("brightness").as_path()) {
|
|
self.cur = cur;
|
|
}
|
|
}
|
|
}
|
|
|
|
impl BarWidgetImpl for Backlight {
|
|
fn name(&self) -> &'static str {
|
|
"backlight"
|
|
}
|
|
fn get_dirty(&self) -> bool {
|
|
self.dirty
|
|
}
|
|
fn value(&mut self) -> f32 {
|
|
if self.max == 0 || self.cur > self.max {
|
|
// what.
|
|
return 1.0;
|
|
}
|
|
|
|
self.dirty = false;
|
|
self.cur as f32 / self.max as f32
|
|
}
|
|
fn color(&self) -> Color {
|
|
Color::WHITE
|
|
}
|
|
fn click(&mut self, pos: f32, btn: PointerButton) {
|
|
self.dirty = true;
|
|
match btn {
|
|
PointerButton::Left => self.set(pos),
|
|
_ => (),
|
|
};
|
|
}
|
|
}
|