mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 17:33:19 +01:00
349 lines
12 KiB
Rust
349 lines
12 KiB
Rust
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use config_traits::StdConfig;
|
|
use log::{info, warn};
|
|
use rog_platform::backlight::{Backlight, BacklightType};
|
|
use tokio::sync::Mutex;
|
|
use zbus::fdo::Error as FdoErr;
|
|
use zbus::object_server::SignalEmitter;
|
|
use zbus::{interface, Connection};
|
|
|
|
use crate::config::Config;
|
|
use crate::error::RogError;
|
|
use crate::ASUS_ZBUS_PATH;
|
|
|
|
#[derive(Clone)]
|
|
pub struct CtrlBacklight {
|
|
backlights: Vec<Backlight>,
|
|
config: Arc<Mutex<Config>>,
|
|
}
|
|
|
|
impl CtrlBacklight {
|
|
pub fn new(config: Arc<Mutex<Config>>) -> Result<Self, RogError> {
|
|
let mut backlights = Vec::new();
|
|
|
|
if let Ok(primary) = Backlight::new(BacklightType::Primary) {
|
|
info!("Found primary display backlight");
|
|
backlights.push(primary);
|
|
}
|
|
|
|
if let Ok(screenpad) = Backlight::new(BacklightType::Screenpad) {
|
|
info!("Found screenpad backlight");
|
|
backlights.push(screenpad);
|
|
}
|
|
|
|
if backlights.is_empty() {
|
|
return Err(RogError::MissingFunction("No backlights found".into()));
|
|
}
|
|
|
|
Ok(Self { backlights, config })
|
|
}
|
|
|
|
fn get_backlight(&self, device_type: &BacklightType) -> Option<&Backlight> {
|
|
self.backlights
|
|
.iter()
|
|
.find(|b| b.device_type() == device_type)
|
|
}
|
|
|
|
async fn set_brightness_with_sync(
|
|
&self,
|
|
device_type: &BacklightType,
|
|
level: i32,
|
|
) -> Result<(), FdoErr> {
|
|
let sync = self
|
|
.config
|
|
.lock()
|
|
.await
|
|
.screenpad_sync_primary
|
|
.unwrap_or_default();
|
|
|
|
// If sync is enabled and we're setting screenpad brightness, set primary first
|
|
if sync && *device_type == BacklightType::Screenpad {
|
|
if let Some(primary) = self.get_backlight(&BacklightType::Primary) {
|
|
if let Ok(primary_max) = primary.get_max_brightness() {
|
|
let primary_scaled = level * primary_max / 100;
|
|
let _ = primary.set_brightness(primary_scaled);
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(backlight) = self.get_backlight(device_type) {
|
|
let max = backlight.get_max_brightness().map_err(|e| {
|
|
warn!("Failed to get max brightness: {}", e);
|
|
FdoErr::Failed(format!("Failed to get max brightness: {}", e))
|
|
})?;
|
|
|
|
let gamma = self.config.lock().await.screenpad_gamma.unwrap_or(1.0);
|
|
let scaled = if *device_type == BacklightType::Screenpad {
|
|
// Apply non-linear scaling with the configurable gamma value only for Screenpad
|
|
let normalized_level = level as f32 / 100.0;
|
|
let gamma_corrected = normalized_level.powf(gamma);
|
|
(gamma_corrected * max as f32) as i32
|
|
} else {
|
|
// Linear scaling for other devices
|
|
level * max / 100
|
|
};
|
|
|
|
backlight.set_brightness(scaled).map_err(|e| {
|
|
warn!("Failed to set brightness: {}", e);
|
|
FdoErr::Failed(format!("Failed to set brightness: {}", e))
|
|
})?;
|
|
|
|
// If sync is enabled and we're setting primary brightness, set screenpad
|
|
// afterward
|
|
if sync && *device_type == BacklightType::Primary {
|
|
for other in self
|
|
.backlights
|
|
.iter()
|
|
.filter(|b| b.device_type() != device_type)
|
|
{
|
|
if let Ok(other_max) = other.get_max_brightness() {
|
|
let other_scaled = if other.device_type() == &BacklightType::Screenpad {
|
|
// Apply gamma only to Screenpad
|
|
let normalized_level = level as f32 / 100.0;
|
|
let gamma_corrected = normalized_level.powf(gamma);
|
|
(gamma_corrected * other_max as f32) as i32
|
|
} else {
|
|
// Linear scaling for other devices
|
|
level * other_max / 100
|
|
};
|
|
let _ = other.set_brightness(other_scaled);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
} else {
|
|
Err(FdoErr::NotSupported(format!(
|
|
"Backlight {:?} not found",
|
|
device_type
|
|
)))
|
|
}
|
|
}
|
|
|
|
async fn get_brightness_percent(&self, device_type: &BacklightType) -> Result<i32, FdoErr> {
|
|
if let Some(backlight) = self.get_backlight(device_type) {
|
|
let brightness = backlight.get_brightness().map_err(|e| {
|
|
warn!("Failed to get brightness: {}", e);
|
|
FdoErr::Failed(format!("Failed to get brightness: {}", e))
|
|
})?;
|
|
|
|
let max = backlight.get_max_brightness().map_err(|e| {
|
|
warn!("Failed to get max brightness: {}", e);
|
|
FdoErr::Failed(format!("Failed to get max brightness: {}", e))
|
|
})?;
|
|
|
|
if *device_type == BacklightType::Screenpad {
|
|
let gamma = self.config.lock().await.screenpad_gamma.unwrap_or(1.0);
|
|
let normalized = brightness as f32 / max as f32;
|
|
let corrected = normalized.powf(1.0 / gamma);
|
|
Ok((corrected * 100.0).round() as i32)
|
|
} else {
|
|
Ok(brightness * 100 / max)
|
|
}
|
|
} else {
|
|
Err(FdoErr::NotSupported(format!(
|
|
"Backlight {:?} not found",
|
|
device_type
|
|
)))
|
|
}
|
|
}
|
|
|
|
pub async fn start_watch_primary(&self) -> Result<(), RogError> {
|
|
if self.get_backlight(&BacklightType::Screenpad).is_none() {
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(sync) = self.config.lock().await.screenpad_sync_primary {
|
|
if !sync {
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
if let Some(backlight) = self.get_backlight(&BacklightType::Primary) {
|
|
let watch = backlight.monitor_brightness()?;
|
|
|
|
let backlights = self.clone();
|
|
tokio::spawn(async move {
|
|
let mut last_level = 0;
|
|
let mut buffer = [0; 32];
|
|
use futures_lite::StreamExt;
|
|
if let Ok(mut stream) = watch.into_event_stream(&mut buffer) {
|
|
loop {
|
|
let _ = stream.next().await;
|
|
|
|
let sync = backlights.config.lock().await.screenpad_sync_primary;
|
|
if let Some(sync) = sync {
|
|
if !sync {
|
|
continue;
|
|
}
|
|
} else if backlights
|
|
.config
|
|
.lock()
|
|
.await
|
|
.screenpad_sync_primary
|
|
.is_none()
|
|
{
|
|
continue;
|
|
}
|
|
|
|
let level = backlights
|
|
.get_brightness_percent(&BacklightType::Primary)
|
|
.await
|
|
.unwrap_or(60);
|
|
if last_level != level {
|
|
last_level = level;
|
|
backlights
|
|
.set_brightness_with_sync(&BacklightType::Screenpad, level)
|
|
.await
|
|
.ok();
|
|
}
|
|
|
|
// other processes cause "MODIFY" event and make this spin 100%, so sleep
|
|
tokio::time::sleep(Duration::from_millis(300)).await;
|
|
}
|
|
// watch
|
|
// .into_event_stream(&mut buffer)
|
|
// .unwrap()
|
|
// .for_each(|_| async {})
|
|
// .await;
|
|
}
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[interface(name = "xyz.ljones.Backlight")]
|
|
impl CtrlBacklight {
|
|
#[zbus(property)]
|
|
async fn screenpad_sync_with_primary(&self) -> bool {
|
|
self.config
|
|
.lock()
|
|
.await
|
|
.screenpad_sync_primary
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn set_screenpad_sync_with_primary(&self, sync: bool) -> Result<(), zbus::Error> {
|
|
self.config.lock().await.screenpad_sync_primary = Some(sync);
|
|
self.config.lock().await.write();
|
|
Ok(())
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn screenpad_gamma(&self) -> String {
|
|
(self.config.lock().await.screenpad_gamma.unwrap_or(1.0)).to_string()
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn set_screenpad_gamma(&self, value: &str) -> Result<(), zbus::Error> {
|
|
let gamma: f32 = value
|
|
.parse()
|
|
.map_err(|_| FdoErr::Failed("Invalid gamma value, must be a valid number".into()))?;
|
|
|
|
if gamma < 0.1 {
|
|
return Err(FdoErr::Failed("Gamma value must be greater than 0".into()).into());
|
|
}
|
|
if gamma > 2.0 {
|
|
return Err(FdoErr::Failed("Gamma value must be 2.0 or less".into()).into());
|
|
}
|
|
self.config.lock().await.screenpad_gamma = Some(gamma);
|
|
self.config.lock().await.write();
|
|
Ok(())
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn primary_brightness(&self) -> Result<i32, FdoErr> {
|
|
self.get_brightness_percent(&BacklightType::Primary).await
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn set_primary_brightness(
|
|
&self,
|
|
#[zbus(signal_context)] ctxt: SignalEmitter<'_>,
|
|
level: i32,
|
|
) -> Result<(), zbus::Error> {
|
|
if level > 100 {
|
|
return Err(FdoErr::Failed("Brightness level must be 0-100".into()).into());
|
|
}
|
|
|
|
self.set_brightness_with_sync(&BacklightType::Primary, level)
|
|
.await?;
|
|
self.primary_brightness_changed(&ctxt).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn screenpad_brightness(&self) -> Result<i32, FdoErr> {
|
|
self.get_brightness_percent(&BacklightType::Screenpad).await
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn set_screenpad_brightness(
|
|
&self,
|
|
// #[zbus(signal_context)] ctxt: SignalEmitter<'_>,
|
|
level: i32,
|
|
) -> Result<(), zbus::Error> {
|
|
if level > 100 {
|
|
return Err(FdoErr::Failed("Brightness level must be 0-100".into()).into());
|
|
}
|
|
|
|
self.set_brightness_with_sync(&BacklightType::Screenpad, level)
|
|
.await?;
|
|
// self.screenpad_brightness_changed(&ctxt).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn screenpad_power(&self) -> Result<bool, FdoErr> {
|
|
if let Some(backlight) = self.get_backlight(&BacklightType::Screenpad) {
|
|
let power = backlight.get_bl_power().map_err(|e| {
|
|
warn!("Failed to get backlight power: {}", e);
|
|
FdoErr::Failed(format!("Failed to get backlight power: {}", e))
|
|
})?;
|
|
Ok(power == 0)
|
|
} else {
|
|
Err(FdoErr::NotSupported("Screenpad backlight not found".into()))
|
|
}
|
|
}
|
|
|
|
#[zbus(property)]
|
|
async fn set_screenpad_power(
|
|
&self,
|
|
#[zbus(signal_context)] ctxt: SignalEmitter<'_>,
|
|
power: bool,
|
|
) -> Result<(), zbus::Error> {
|
|
if let Some(backlight) = self.get_backlight(&BacklightType::Screenpad) {
|
|
backlight
|
|
.set_bl_power(if power { 0 } else { 1 })
|
|
.map_err(|e| {
|
|
warn!("Failed to set backlight power: {}", e);
|
|
FdoErr::Failed(format!("Failed to set backlight power: {}", e))
|
|
})?;
|
|
self.screenpad_power_changed(&ctxt).await?;
|
|
Ok(())
|
|
} else {
|
|
Err(FdoErr::NotSupported("Screenpad backlight not found".into()).into())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl crate::ZbusRun for CtrlBacklight {
|
|
async fn add_to_server(self, server: &mut Connection) {
|
|
Self::add_to_server_helper(self, ASUS_ZBUS_PATH, server).await;
|
|
}
|
|
}
|
|
|
|
impl crate::Reloadable for CtrlBacklight {
|
|
async fn reload(&mut self) -> Result<(), RogError> {
|
|
info!("Reloading backlight settings");
|
|
Ok(())
|
|
}
|
|
}
|