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, config: Arc>, } impl CtrlBacklight { pub fn new(config: Arc>) -> Result { 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 { 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 { 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 { 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 { 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(()) } }