Files
fluxo-rs/src/modules/backlight.rs
T
nvrl 25ecde96fa
CI / Lint and Test (pull_request) Failing after 1m31s
CI / Version Check (pull_request) Failing after 3s
clippy
2026-04-03 15:01:23 +02:00

155 lines
4.8 KiB
Rust

use crate::config::Config;
use crate::error::Result;
use crate::modules::WaybarModule;
use crate::output::WaybarOutput;
use crate::state::{AppReceivers, BacklightState};
use crate::utils::{TokenValue, format_template};
use notify::{Config as NotifyConfig, Event, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::PathBuf;
use std::sync::mpsc;
use std::time::Duration;
use tokio::sync::watch;
use tracing::{error, info};
pub struct BacklightModule;
impl WaybarModule for BacklightModule {
async fn run(
&self,
config: &Config,
state: &AppReceivers,
_args: &[&str],
) -> Result<WaybarOutput> {
let percentage = state.backlight.borrow().percentage;
let icon = if percentage < 30 {
"󰃞"
} else if percentage < 70 {
"󰃟"
} else {
"󰃠"
};
let text = format_template(
&config.backlight.format,
&[
("percentage", TokenValue::Int(percentage as i64)),
("icon", TokenValue::String(icon.to_string())),
],
);
Ok(WaybarOutput {
text,
tooltip: Some(format!("Brightness: {}%", percentage)),
class: Some("normal".to_string()),
percentage: Some(percentage),
})
}
}
pub struct BacklightDaemon;
impl BacklightDaemon {
pub fn new() -> Self {
Self
}
pub fn start(&self, tx: watch::Sender<BacklightState>) {
std::thread::spawn(move || {
let base_dir = PathBuf::from("/sys/class/backlight");
let mut device_dir = None;
if let Ok(entries) = std::fs::read_dir(&base_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
device_dir = Some(path);
break;
}
}
}
let Some(dir) = device_dir else {
error!("No backlight device found in /sys/class/backlight");
return;
};
info!("Monitoring backlight device: {:?}", dir);
let max_brightness_path = dir.join("max_brightness");
let brightness_path = dir.join("actual_brightness");
let brightness_path_fallback = dir.join("brightness");
let target_file = if brightness_path.exists() {
brightness_path
} else {
brightness_path_fallback
};
let get_percentage = || -> u8 {
let max: f64 = std::fs::read_to_string(&max_brightness_path)
.unwrap_or_default()
.trim()
.parse()
.unwrap_or(100.0);
let current: f64 = std::fs::read_to_string(&target_file)
.unwrap_or_default()
.trim()
.parse()
.unwrap_or(0.0);
if max > 0.0 {
((current / max) * 100.0).round() as u8
} else {
0
}
};
// Initial poll
let _ = tx.send(BacklightState {
percentage: get_percentage(),
});
// Set up notify watcher
let (ev_tx, ev_rx) = mpsc::channel();
let mut watcher = RecommendedWatcher::new(
move |res: notify::Result<Event>| {
if let Ok(event) = res
&& event.kind.is_modify()
{
let _ = ev_tx.send(());
}
},
NotifyConfig::default(),
)
.unwrap();
if let Err(e) = watcher.watch(&target_file, RecursiveMode::NonRecursive) {
error!("Failed to watch backlight file: {}", e);
return;
}
loop {
// Block until an event occurs or a timeout to catch missed events
if ev_rx.recv_timeout(Duration::from_secs(5)).is_ok() {
// Debounce rapid events
std::thread::sleep(Duration::from_millis(50));
while ev_rx.try_recv().is_ok() {}
let _ = tx.send(BacklightState {
percentage: get_percentage(),
});
} else {
// Timeout hit, poll just in case
let current = get_percentage();
if tx.borrow().percentage != current {
let _ = tx.send(BacklightState {
percentage: current,
});
}
}
}
});
}
}