use crate::config::DeviceConfig;
use crate::consts::*;
use crate::errors::*;
use crate::util::*;
use std::cmp::max;
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::time::Duration;
#[cfg(feature = "watch")]
use std::time::Instant;
use tokio::fs::OpenOptions;
use tokio::io::AsyncWriteExt as _;
use tokio::time::sleep;
use zbus::Connection;
make_log_macro!(debug, "calibright_device");
#[zbus::proxy(
interface = "org.freedesktop.login1.Session",
default_service = "org.freedesktop.login1",
default_path = "/org/freedesktop/login1/session/auto"
)]
trait Session {
fn set_brightness(&self, subsystem: &str, name: &str, brightness: u32) -> zbus::Result<()>;
}
pub struct Device {
pub device_name: OsString,
pub read_brightness_file: PathBuf,
write_brightness_file: PathBuf,
raw_brightness: u32,
max_brightness: u32,
dbus_proxy: SessionProxy<'static>,
config: DeviceConfig,
#[cfg(feature = "watch")]
updated_at: Instant,
}
impl Device {
pub async fn new(device_name: &str, config: DeviceConfig) -> Result<Self> {
let device_path = PathBuf::from(DEVICES_PATH).join(device_name);
let dbus_conn = Connection::system().await?;
let mut s = Self {
read_brightness_file: device_path.join({
if device_path
.file_name()
.and_then(OsStr::to_str)
.is_some_and(|file_name| file_name.starts_with("amdgpu_bl"))
{
FILE_BRIGHTNESS_AMD
} else {
FILE_BRIGHTNESS
}
}),
write_brightness_file: device_path.join(FILE_BRIGHTNESS_WRITE),
device_name: device_name.into(),
raw_brightness: 0,
max_brightness: 0,
dbus_proxy: SessionProxy::new(&dbus_conn).await?,
config,
#[cfg(feature = "watch")]
updated_at: Instant::now(),
};
s.raw_brightness = s.read_brightness_raw(&s.read_brightness_file).await?;
s.max_brightness = s
.read_brightness_raw(&device_path.join(FILE_MAX_BRIGHTNESS))
.await?;
Ok(s)
}
async fn read_brightness_raw(&self, device_file: &Path) -> Result<u32> {
let val = match read_file(device_file).await {
Ok(v) => Ok(v),
Err(_) => {
for i in 1..self.config.ddcci_max_tries_write_read {
debug!("retry {i} reading brightness");
sleep(Duration::from_millis(
(40.0 * self.config.ddcci_sleep_multiplier).round() as u64,
))
.await;
if let Ok(val) = read_file(device_file).await {
return Ok(val.parse()?);
}
}
Err(CalibrightError::Other(
"Failed to read brightness file, check your ddcci settings".into(),
))
}
};
Ok(val?.parse()?)
}
pub async fn get_brightness(&mut self) -> Result<f64> {
self.raw_brightness = self.read_brightness_raw(&self.read_brightness_file).await?;
let brightness_ratio = (self.raw_brightness as f64 / self.max_brightness as f64)
.powf(self.config.root_scaling.recip());
scale_to_clamped_absolute(
brightness_ratio,
self.config.calibration[0],
self.config.calibration[1],
)
}
pub async fn set_brightness(&mut self, value: f64) -> Result<()> {
let value = scale_to_clamped_relative(
value,
self.config.calibration[0],
self.config.calibration[1],
)?;
let ratio = value.powf(self.config.root_scaling);
self.raw_brightness = max(1, (ratio * (self.max_brightness as f64)).round() as u32);
match self
.dbus_proxy
.set_brightness(
"backlight",
&self.device_name.to_string_lossy(),
self.raw_brightness,
)
.await
{
Ok(()) => Ok(()),
Err(e) => {
debug!("{}", e.to_string());
let mut file = OpenOptions::new()
.write(true)
.truncate(true)
.open(&self.write_brightness_file)
.await?;
Ok(file
.write_all(self.raw_brightness.to_string().as_bytes())
.await?)
}
}
.map(|_| {
#[cfg(feature = "watch")]
{
self.updated_at = Instant::now();
}
})
}
#[cfg(feature = "watch")]
pub fn get_last_set_ago(&self) -> Duration {
self.updated_at.elapsed()
}
}