diff --git a/asusd/src/asus_armoury.rs b/asusd/src/asus_armoury.rs index bb78130e..776bcd42 100644 --- a/asusd/src/asus_armoury.rs +++ b/asusd/src/asus_armoury.rs @@ -55,6 +55,25 @@ impl AsusArmouryAttribute { String::from(self.attr.name()) } + fn resolve_i32_value(refreshed: Option, cached: &AttrValue) -> i32 { + refreshed + .or(match cached { + AttrValue::Integer(i) => Some(*i), + _ => None, + }) + .unwrap_or(-1) + } + + pub async fn emit_limits(&self, connection: &Connection) -> Result<(), RogError> { + let path = dbus_path_for_attr(self.attr.name()); + let signal = SignalEmitter::new(connection, path)?; + self.min_value_changed(&signal).await?; + self.max_value_changed(&signal).await?; + self.scalar_increment_changed(&signal).await?; + self.current_value_changed(&signal).await?; + Ok(()) + } + pub async fn move_to_zbus(self, connection: &Connection) -> Result<(), RogError> { let path = dbus_path_for_attr(self.attr.name()); connection @@ -117,6 +136,35 @@ impl AsusArmouryAttribute { } } +#[derive(Clone, Default)] +pub struct ArmouryAttributeRegistry { + attrs: Vec, +} + +impl ArmouryAttributeRegistry { + pub fn push(&mut self, attr: AsusArmouryAttribute) { + self.attrs.push(attr); + } + + pub async fn emit_limits(&self, connection: &Connection) -> Result<(), RogError> { + let mut last_err: Option = None; + for attr in &self.attrs { + if let Err(e) = attr.emit_limits(connection).await { + error!( + "Failed to emit updated limits for attribute '{}': {e:?}", + attr.attribute_name() + ); + last_err = Some(e); + } + } + if let Some(err) = last_err { + Err(err) + } else { + Ok(()) + } + } +} + impl crate::Reloadable for AsusArmouryAttribute { async fn reload(&mut self) -> Result<(), RogError> { info!("Reloading {}", self.attr.name()); @@ -131,25 +179,31 @@ impl crate::Reloadable for AsusArmouryAttribute { error!("Could not get power status: {e:?}"); e }) - .unwrap_or_default(); - let config = if power_plugged == 1 { - &self.config.lock().await.ac_profile_tunings - } else { - &self.config.lock().await.dc_profile_tunings + .unwrap_or_default() + == 1; + + let apply_value = { + let config = self.config.lock().await; + config + .select_tunings_ref(power_plugged, profile) + .and_then(|tuning| { + if tuning.enabled { + tuning.group.get(&self.name()).copied() + } else { + None + } + }) }; - if let Some(tuning) = config.get(&profile) { - if tuning.enabled { - if let Some(tune) = tuning.group.get(&self.name()) { - self.attr - .set_current_value(&AttrValue::Integer(*tune)) - .map_err(|e| { - error!("Could not set {} value: {e:?}", self.attr.name()); - self.attr.base_path_exists(); - e - })?; - info!("Set {} to {:?}", self.attr.name(), tune); - } - } + + if let Some(tune) = apply_value { + self.attr + .set_current_value(&AttrValue::Integer(tune)) + .map_err(|e| { + error!("Could not set {} value: {e:?}", self.attr.name()); + self.attr.base_path_exists(); + e + })?; + info!("Set {} to {:?}", self.attr.name(), tune); } } else { // Handle non-PPT attributes (boolean and other settings) @@ -256,26 +310,20 @@ impl AsusArmouryAttribute { #[zbus(property)] async fn min_value(&self) -> i32 { - match self.attr.min_value() { - AttrValue::Integer(i) => *i, - _ => -1, - } + Self::resolve_i32_value(self.attr.refresh_min_value(), self.attr.min_value()) } #[zbus(property)] async fn max_value(&self) -> i32 { - match self.attr.max_value() { - AttrValue::Integer(i) => *i, - _ => -1, - } + Self::resolve_i32_value(self.attr.refresh_max_value(), self.attr.max_value()) } #[zbus(property)] async fn scalar_increment(&self) -> i32 { - match self.attr.scalar_increment() { - AttrValue::Integer(i) => *i, - _ => -1, - } + Self::resolve_i32_value( + self.attr.refresh_scalar_increment(), + self.attr.scalar_increment(), + ) } #[zbus(property)] @@ -297,12 +345,15 @@ impl AsusArmouryAttribute { error!("Could not get power status: {e:?}"); e }) - .unwrap_or_default(); - let mut config = self.config.lock().await; - let tuning = config.select_tunings(power_plugged == 1, profile); - if let Some(tune) = tuning.group.get(&self.name()) { - return Ok(*tune); - } else if let AttrValue::Integer(i) = self.attr.default_value() { + .unwrap_or_default() + == 1; + let config = self.config.lock().await; + if let Some(tuning) = config.select_tunings_ref(power_plugged, profile) { + if let Some(tune) = tuning.group.get(&self.name()) { + return Ok(*tune); + } + } + if let AttrValue::Integer(i) = self.attr.default_value() { return Ok(*i); } return Err(fdo::Error::Failed( @@ -318,6 +369,83 @@ impl AsusArmouryAttribute { )) } + async fn stored_value_for_power(&self, on_ac: bool) -> fdo::Result { + if !self.name().is_ppt() { + return Err(fdo::Error::NotSupported( + "Stored values are only available for PPT attributes".to_string(), + )); + } + + let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); + let config = self.config.lock().await; + if let Some(tuning) = config.select_tunings_ref(on_ac, profile) { + if let Some(tune) = tuning.group.get(&self.name()) { + return Ok(*tune); + } + } + + if let AttrValue::Integer(i) = self.attr.default_value() { + return Ok(*i); + } + Err(fdo::Error::Failed( + "Could not read stored value".to_string(), + )) + } + + async fn set_value_for_power(&mut self, on_ac: bool, value: i32) -> fdo::Result<()> { + if !self.name().is_ppt() { + return Err(fdo::Error::NotSupported( + "Setting stored values is only supported for PPT attributes".to_string(), + )); + } + + let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); + let apply_now; + + { + let mut config = self.config.lock().await; + let tuning = config.select_tunings(on_ac, profile); + + if let Some(tune) = tuning.group.get_mut(&self.name()) { + *tune = value; + } else { + tuning.group.insert(self.name(), value); + debug!( + "Store {} value for {} power = {}", + self.attr.name(), + if on_ac { "AC" } else { "DC" }, + value + ); + } + + apply_now = tuning.enabled; + config.write(); + } + + if apply_now { + let power_plugged = self + .power + .get_online() + .map_err(|e| { + error!("Could not get power status: {e:?}"); + e + }) + .unwrap_or_default() + != 0; + + if power_plugged == on_ac { + self.attr + .set_current_value(&AttrValue::Integer(value)) + .map_err(|e| { + error!("Could not set value: {e:?}"); + e + })?; + } + } + + Ok(()) + } + #[zbus(property)] async fn set_current_value(&mut self, value: i32) -> fdo::Result<()> { if self.name().is_ppt() { @@ -393,7 +521,8 @@ pub async fn start_attributes_zbus( power: AsusPower, attributes: FirmwareAttributes, config: Arc>, -) -> Result<(), RogError> { +) -> Result { + let mut registry = ArmouryAttributeRegistry::default(); for attr in attributes.attributes() { let mut attr = AsusArmouryAttribute::new( attr.clone(), @@ -402,6 +531,8 @@ pub async fn start_attributes_zbus( config.clone(), ); + let registry_attr = attr.clone(); + if let Err(e) = attr.reload().await { error!( "Skipping attribute '{}' due to reload error: {e:?}", @@ -430,9 +561,12 @@ pub async fn start_attributes_zbus( if let Err(e) = attr.move_to_zbus(conn).await { error!("Failed to register attribute '{attr_name}' on zbus: {e:?}"); + continue; } + + registry.push(registry_attr); } - Ok(()) + Ok(registry) } pub async fn set_config_or_default( diff --git a/asusd/src/config.rs b/asusd/src/config.rs index db7f9cf2..9515cfc4 100644 --- a/asusd/src/config.rs +++ b/asusd/src/config.rs @@ -67,6 +67,19 @@ impl Config { }; config.entry(profile).or_insert_with(Tuning::default) } + + pub fn select_tunings_ref( + &self, + power_plugged: bool, + profile: PlatformProfile, + ) -> Option<&Tuning> { + let config = if power_plugged { + &self.ac_profile_tunings + } else { + &self.dc_profile_tunings + }; + config.get(&profile) + } } impl Default for Config { @@ -146,7 +159,7 @@ pub struct Config611 { impl From for Config { fn from(c: Config611) -> Self { - Self { + let mut config = Self { // Restore the base charge limit charge_control_end_threshold: c.charge_control_end_threshold, base_charge_control_end_threshold: c.charge_control_end_threshold, @@ -168,7 +181,12 @@ impl From for Config { armoury_settings: HashMap::default(), screenpad_gamma: None, screenpad_sync_primary: Default::default(), - } + }; + + config.ac_profile_tunings = c.ac_profile_tunings; + config.dc_profile_tunings = c.dc_profile_tunings; + config.armoury_settings = c.armoury_settings; + config } } diff --git a/asusd/src/ctrl_platform.rs b/asusd/src/ctrl_platform.rs index f3dc369c..65e57c5f 100644 --- a/asusd/src/ctrl_platform.rs +++ b/asusd/src/ctrl_platform.rs @@ -13,7 +13,7 @@ use zbus::fdo::Error as FdoErr; use zbus::object_server::SignalEmitter; use zbus::{interface, Connection}; -use crate::asus_armoury::set_config_or_default; +use crate::asus_armoury::{set_config_or_default, ArmouryAttributeRegistry}; use crate::config::Config; use crate::error::RogError; use crate::{task_watch_item, CtrlTask, ReloadAndNotify}; @@ -46,6 +46,8 @@ pub struct CtrlPlatform { attributes: FirmwareAttributes, cpu_control: Option, config: Arc>, + connection: Connection, + armoury_registry: ArmouryAttributeRegistry, } impl CtrlPlatform { @@ -56,6 +58,8 @@ impl CtrlPlatform { config: Arc>, config_path: &Path, signal_context: SignalEmitter<'static>, + connection: Connection, + armoury_registry: ArmouryAttributeRegistry, ) -> Result { let config1 = config.clone(); let config_path = config_path.to_owned(); @@ -68,6 +72,8 @@ impl CtrlPlatform { cpu_control: CPUControl::new() .map_err(|e| error!("Couldn't get CPU control sysfs: {e}")) .ok(), + connection, + armoury_registry, }; let mut inotify_self = ret_self.clone(); @@ -729,6 +735,31 @@ impl CtrlTask for CtrlPlatform { } if !sleeping { platform1.run_ac_or_bat_cmd(power_plugged > 0).await; + if let Ok(profile) = + platform1.platform.get_platform_profile().map(|p| p.into()) + { + let attrs = FirmwareAttributes::new(); + { + let mut cfg = platform1.config.lock().await; + set_config_or_default( + &attrs, + &mut cfg, + power_plugged > 0, + profile, + ) + .await; + } + if let Err(e) = platform1 + .armoury_registry + .emit_limits(&platform1.connection) + .await + { + error!( + "Failed to emit armoury updates after power change: \ + {e:?}" + ); + } + } } platform1.config.lock().await.last_power_plugged = power_plugged; } @@ -789,13 +820,17 @@ impl CtrlTask for CtrlPlatform { { // TODO: manage this better, shouldn't need to create every time let attrs = FirmwareAttributes::new(); - set_config_or_default( - &attrs, - &mut *platform3.config.lock().await, - power_plugged, - profile, - ) - .await; + { + let mut cfg = platform3.config.lock().await; + set_config_or_default(&attrs, &mut cfg, power_plugged, profile).await; + } + if let Err(e) = platform3 + .armoury_registry + .emit_limits(&platform3.connection) + .await + { + error!("Failed to emit armoury updates after AC/DC toggle: {e:?}"); + } platform3 .enable_ppt_group_changed(&signal_ctxt_copy) .await @@ -852,6 +887,9 @@ impl CtrlTask for CtrlPlatform { profile, ) .await; + if let Err(e) = ctrl.armoury_registry.emit_limits(&ctrl.connection).await { + error!("Failed to emit armoury updates after profile change: {e:?}"); + } } } } diff --git a/asusd/src/daemon.rs b/asusd/src/daemon.rs index 10b8f371..455b7c89 100644 --- a/asusd/src/daemon.rs +++ b/asusd/src/daemon.rs @@ -3,7 +3,7 @@ use std::error::Error; use std::sync::Arc; use ::zbus::Connection; -use asusd::asus_armoury::start_attributes_zbus; +use asusd::asus_armoury::{start_attributes_zbus, ArmouryAttributeRegistry}; use asusd::aura_manager::DeviceManager; use asusd::config::Config; use asusd::ctrl_backlight::CtrlBacklight; @@ -74,7 +74,7 @@ async fn start_daemon() -> Result<(), Box> { let platform = RogPlatform::new()?; // TODO: maybe needs async mutex? let power = AsusPower::new()?; // TODO: maybe needs async mutex? let attributes = FirmwareAttributes::new(); - if let Err(e) = start_attributes_zbus( + let armoury_registry = match start_attributes_zbus( &server, platform.clone(), power.clone(), @@ -83,8 +83,12 @@ async fn start_daemon() -> Result<(), Box> { ) .await { - error!("Failed to initialize firmware attributes over zbus: {e:?}"); - } + Ok(registry) => registry, + Err(e) => { + error!("Failed to initialize firmware attributes over zbus: {e:?}"); + ArmouryAttributeRegistry::default() + } + }; match CtrlFanCurveZbus::new() { Ok(ctrl) => { @@ -113,6 +117,8 @@ async fn start_daemon() -> Result<(), Box> { config.clone(), &cfg_path, CtrlPlatform::signal_context(&server)?, + server.clone(), + armoury_registry, ) { Ok(ctrl) => { let sig_ctx = CtrlPlatform::signal_context(&server)?; diff --git a/rog-anime/src/image.rs b/rog-anime/src/image.rs index 197d2378..fee51da7 100644 --- a/rog-anime/src/image.rs +++ b/rog-anime/src/image.rs @@ -169,7 +169,7 @@ impl AnimeImage { // first 5 rows for GA401 are always at X = 0 return 0; } - (y + 1) / 2 - 3 + y.div_ceil(2) - 3 } AnimeType::GU604 => { // first 9 rows start at zero @@ -185,7 +185,7 @@ impl AnimeImage { return 0; } // and then their offset grows by one every two rows - (y + 1) / 2 - 5 + y.div_ceil(2) - 5 } } } @@ -213,7 +213,7 @@ impl AnimeImage { // First 5 rows for GA401 are always 33 physical LEDs long return 33; } - 36 - (y + 1) / 2 + 36 - y.div_ceil(2) } AnimeType::GU604 => { if y <= 9 { diff --git a/rog-control-center/ui/pages/system.slint b/rog-control-center/ui/pages/system.slint index df913430..328c2735 100644 --- a/rog-control-center/ui/pages/system.slint +++ b/rog-control-center/ui/pages/system.slint @@ -302,7 +302,7 @@ export component PageSystem inherits Rectangle { } } - if SystemPageData.ppt_pl1_spl.current != -1 || SystemPageData.ppt_pl2_sppt.current != -1 || SystemPageData.ppt_pl3_fppt.current != -1 || SystemPageData.ppt_fppt.current != -1 || SystemPageData.ppt_apu_sppt.current != -1 || SystemPageData.nv_temp_target.current != -1 || SystemPageData.nv_dynamic_boost.current != -1: HorizontalLayout { + if (SystemPageData.ppt_pl1_spl.max > 0 && SystemPageData.ppt_pl1_spl.current != -1) || (SystemPageData.ppt_pl2_sppt.max > 0 && SystemPageData.ppt_pl2_sppt.current != -1) || (SystemPageData.ppt_pl3_fppt.max > 0 && SystemPageData.ppt_pl3_fppt.current != -1) || (SystemPageData.ppt_fppt.max > 0 && SystemPageData.ppt_fppt.current != -1) || (SystemPageData.ppt_apu_sppt.max > 0 && SystemPageData.ppt_apu_sppt.current != -1) || (SystemPageData.nv_temp_target.max > 0 && SystemPageData.nv_temp_target.current != -1) || (SystemPageData.nv_dynamic_boost.max > 0 && SystemPageData.nv_dynamic_boost.current != -1): HorizontalLayout { padding-right: 10px; padding-left: 10px; alignment: LayoutAlignment.space-between; @@ -331,7 +331,7 @@ export component PageSystem inherits Rectangle { } } - if SystemPageData.ppt_pl1_spl.current != -1: SystemSlider { + if SystemPageData.ppt_pl1_spl.max > 0 && SystemPageData.ppt_pl1_spl.current != -1: SystemSlider { text: @tr("ppt_pl1_spl" => "CPU Sustained Power Limit"); title: @tr("ppt_pl1_spl" => "CPU Sustained Power Limit"); help_text: @tr("ppt_pl1_spl_help" => "Long-term CPU power limit that affects sustained workload performance. Higher values may increase heat and power consumption."); @@ -349,7 +349,7 @@ export component PageSystem inherits Rectangle { } } - if SystemPageData.ppt_pl2_sppt.current != -1: SystemSlider { + if SystemPageData.ppt_pl2_sppt.max > 0 && SystemPageData.ppt_pl2_sppt.current != -1: SystemSlider { text: @tr("ppt_pl2_sppt" => "CPU Turbo Power Limit"); title: @tr("ppt_pl2_sppt" => "CPU Turbo Power Limit"); help_text: @tr("ppt_pl2_sppt_help" => "Short-term CPU power limit for boost periods. Controls maximum power during brief high-performance bursts."); @@ -367,7 +367,7 @@ export component PageSystem inherits Rectangle { } } - if SystemPageData.ppt_pl3_fppt.current != -1: SystemSlider { + if SystemPageData.ppt_pl3_fppt.max > 0 && SystemPageData.ppt_pl3_fppt.current != -1: SystemSlider { text: @tr("ppt_pl3_fppt" => "CPU Fast Burst Power Limit"); title: @tr("ppt_pl3_fppt" => "CPU Fast Burst Power Limit"); help_text: @tr("ppt_pl3_fppt_help" => "Ultra-short duration power limit for instantaneous CPU bursts. Affects responsiveness during sudden workload spikes."); @@ -384,7 +384,7 @@ export component PageSystem inherits Rectangle { SystemPageData.cb_ppt_pl3_fppt(Math.round(value)); } } - if SystemPageData.ppt_fppt.current != -1: SystemSlider { + if SystemPageData.ppt_fppt.max > 0 && SystemPageData.ppt_fppt.current != -1: SystemSlider { text: @tr("ppt_fppt" => "Fast Package Power Limit"); title: @tr("ppt_fppt" => "Fast Package Power Limit"); help_text: @tr("ppt_fppt_help" => "Ultra-short duration power limit for system package. Controls maximum power during millisecond-scale load spikes."); @@ -402,7 +402,7 @@ export component PageSystem inherits Rectangle { } } - if SystemPageData.ppt_apu_sppt.current != -1: SystemSlider { + if SystemPageData.ppt_apu_sppt.max > 0 && SystemPageData.ppt_apu_sppt.current != -1: SystemSlider { text: @tr("ppt_apu_sppt" => "APU Sustained Power Limit"); title: @tr("ppt_apu_sppt" => "APU Sustained Power Limit"); help_text: @tr("ppt_apu_sppt_help" => "Long-term power limit for integrated graphics and CPU combined. Affects sustained performance of APU-based workloads."); @@ -420,7 +420,7 @@ export component PageSystem inherits Rectangle { } } - if SystemPageData.ppt_platform_sppt.current != -1: SystemSlider { + if SystemPageData.ppt_platform_sppt.max > 0 && SystemPageData.ppt_platform_sppt.current != -1: SystemSlider { text: @tr("ppt_platform_sppt" => "Platform Sustained Power Limit"); title: @tr("ppt_platform_sppt" => "Platform Sustained Power Limit"); help_text: @tr("ppt_platform_sppt_help" => "Overall system power limit for sustained operations. Controls total platform power consumption over extended periods."); @@ -438,7 +438,7 @@ export component PageSystem inherits Rectangle { } } - if SystemPageData.nv_dynamic_boost.current != -1: SystemSlider { + if SystemPageData.nv_dynamic_boost.max > 0 && SystemPageData.nv_dynamic_boost.current != -1: SystemSlider { text: @tr("nv_dynamic_boost" => "GPU Power Boost"); title: @tr("nv_dynamic_boost" => "GPU Power Boost"); help_text: @tr("nv_dynamic_boost_help" => "Additional power allocation for GPU dynamic boost. Higher values increase GPU performance but generate more heat."); @@ -456,7 +456,7 @@ export component PageSystem inherits Rectangle { } } - if SystemPageData.nv_temp_target.current != -1: SystemSlider { + if SystemPageData.nv_temp_target.max > 0 && SystemPageData.nv_temp_target.current != -1: SystemSlider { text: @tr("nv_temp_target" => "GPU Temperature Limit"); title: @tr("nv_temp_target" => "GPU Temperature Limit"); help_text: @tr("nv_temp_target_help" => "Maximum GPU temperature threshold in Celsius. GPU will throttle to maintain temperature below this limit."); diff --git a/rog-platform/src/asus_armoury.rs b/rog-platform/src/asus_armoury.rs index 455f3158..b0dd2834 100644 --- a/rog-platform/src/asus_armoury.rs +++ b/rog-platform/src/asus_armoury.rs @@ -124,6 +124,22 @@ impl Attribute { &self.scalar_increment } + fn read_attr_i32(&self, name: &str) -> Option { + read_i32(&self.base_path.join(name)).ok() + } + + pub fn refresh_min_value(&self) -> Option { + self.read_attr_i32("min_value") + } + + pub fn refresh_max_value(&self) -> Option { + self.read_attr_i32("max_value") + } + + pub fn refresh_scalar_increment(&self) -> Option { + self.read_attr_i32("scalar_increment") + } + /// Read all the immutable values to struct data. These should *never* /// change, if they do then it is possibly a driver issue - although this is /// subject to `firmware_attributes` class changes in kernel. diff --git a/rog-profiles/src/lib.rs b/rog-profiles/src/lib.rs index 1000923c..51e91d88 100644 --- a/rog-profiles/src/lib.rs +++ b/rog-profiles/src/lib.rs @@ -37,8 +37,9 @@ pub fn find_fan_curve_node() -> Result { derive(Type, Value, OwnedValue), zvariant(signature = "s") )] -#[derive(Deserialize, Serialize, Debug, Hash, PartialEq, Eq, Clone, Copy)] +#[derive(Default, Deserialize, Serialize, Debug, Hash, PartialEq, Eq, Clone, Copy)] pub enum FanCurvePU { + #[default] CPU = 0, GPU = 1, MID = 2, @@ -100,12 +101,6 @@ impl std::str::FromStr for FanCurvePU { } } -impl Default for FanCurvePU { - fn default() -> Self { - Self::CPU - } -} - /// Main purpose of `FanCurves` is to enable restoring state on system boot #[cfg_attr(feature = "dbus", derive(Type))] #[derive(Deserialize, Serialize, Debug, Default)] diff --git a/simulators/src/simulator.rs b/simulators/src/simulator.rs index c719ffd9..cbeacf09 100644 --- a/simulators/src/simulator.rs +++ b/simulators/src/simulator.rs @@ -158,7 +158,7 @@ fn main() -> Result<(), Box> { canvas.set_draw_color(Color::RGB(*b, *b, *b)); let x: i32 = w + x_count as i32 * w - - if (y_count + y_offset as usize) % 2 != 0 { + - if !(y_count + y_offset as usize).is_multiple_of(2) { 0 } else { w / 2