From 0f156ef49fdeaa44e9f7e14975c4d950a6fd99bd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 8 May 2025 17:23:18 -0700 Subject: [PATCH] manager/user: Emit signals when we change properties --- README.md | 6 ++ src/daemon/user.rs | 2 +- src/manager/user.rs | 137 +++++++++++++++++++++++++++++++------------- src/power.rs | 30 +++++++--- 4 files changed, 127 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index f33b799..3677b61 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ an interface will be used if available. As a rule of thumb, the client will always provide full support for the SteamOS Manager interface version available in the Stable release of SteamOS. +Another pitfall is that while most of the properties do signal when they are +changed by SteamOS Manager itself, several of these properties can also change +out from under SteamOS Manager if something on the system bypasses it. While +this should never be the case if the user doesn't prod at the underlying system +manually, it's something that interface users should be aware of. + ## Implementation details SteamOS Manager is compromised of two daemons: one runs as the logged in user diff --git a/src/daemon/user.rs b/src/daemon/user.rs index cd30ef0..d7e25fb 100644 --- a/src/daemon/user.rs +++ b/src/daemon/user.rs @@ -116,7 +116,7 @@ async fn create_connections( let jm_service = JobManagerService::new(job_manager, rx, system.clone()); let (tdp_tx, rx) = unbounded_channel(); - let tdp_service = TdpManagerService::new(rx, &system).await?; + let tdp_service = TdpManagerService::new(rx, &system, &connection).await?; create_interfaces(connection.clone(), system.clone(), channel, jm_tx, tdp_tx).await?; diff --git a/src/manager/user.rs b/src/manager/user.rs index a8e40d3..fdfa9b9 100644 --- a/src/manager/user.rs +++ b/src/manager/user.rs @@ -36,7 +36,7 @@ use crate::wifi::{ }; use crate::API_VERSION; -const MANAGER_PATH: &str = "/com/steampowered/SteamOSManager1"; +pub(crate) const MANAGER_PATH: &str = "/com/steampowered/SteamOSManager1"; macro_rules! method { ($self:expr, $method:expr, $($args:expr),+) => { @@ -131,7 +131,7 @@ struct GpuPowerProfile1 { proxy: Proxy<'static>, } -struct TdpLimit1 { +pub(crate) struct TdpLimit1 { manager: UnboundedSender, } @@ -226,7 +226,7 @@ impl SteamOSManager { Ok(()) } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn wifi_backend(&self) -> fdo::Result { match get_wifi_backend().await { Ok(backend) => Ok(backend as u32), @@ -235,8 +235,13 @@ impl SteamOSManager { } #[zbus(property)] - async fn set_wifi_backend(&self, backend: u32) -> zbus::Result<()> { - self.proxy.call("SetWifiBackend", &(backend)).await + async fn set_wifi_backend( + &self, + backend: u32, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { + let _: () = self.proxy.call("SetWifiBackend", &(backend)).await?; + self.wifi_backend_changed(&ctx).await } } @@ -254,7 +259,7 @@ impl BatteryChargeLimit1 { #[interface(name = "com.steampowered.SteamOSManager1.BatteryChargeLimit1")] impl BatteryChargeLimit1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn max_charge_level(&self) -> fdo::Result { let level = get_max_charge_level().await.map_err(to_zbus_fdo_error)?; if level <= 0 { @@ -265,8 +270,13 @@ impl BatteryChargeLimit1 { } #[zbus(property)] - async fn set_max_charge_level(&self, limit: i32) -> zbus::Result<()> { - self.proxy.call("SetMaxChargeLevel", &(limit)).await + async fn set_max_charge_level( + &self, + limit: i32, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { + let _: () = self.proxy.call("SetMaxChargeLevel", &(limit)).await?; + self.max_charge_level_changed(&ctx).await } #[zbus(property(emits_changed_signal = "const"))] @@ -285,7 +295,7 @@ impl BatteryChargeLimit1 { #[interface(name = "com.steampowered.SteamOSManager1.CpuScaling1")] impl CpuScaling1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property(emits_changed_signal = "const"))] async fn available_cpu_scaling_governors(&self) -> fdo::Result> { let governors = get_available_cpu_scaling_governors() .await @@ -297,7 +307,7 @@ impl CpuScaling1 { Ok(result) } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn cpu_scaling_governor(&self) -> fdo::Result { let governor = get_cpu_scaling_governor() .await @@ -306,8 +316,16 @@ impl CpuScaling1 { } #[zbus(property)] - async fn set_cpu_scaling_governor(&self, governor: String) -> zbus::Result<()> { - self.proxy.call("SetCpuScalingGovernor", &(governor)).await + async fn set_cpu_scaling_governor( + &self, + governor: String, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { + let _: () = self + .proxy + .call("SetCpuScalingGovernor", &(governor)) + .await?; + self.cpu_scaling_governor_changed(&ctx).await } } @@ -320,20 +338,25 @@ impl FactoryReset1 { #[interface(name = "com.steampowered.SteamOSManager1.FanControl1")] impl FanControl1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn fan_control_state(&self) -> fdo::Result { getter!(self, "FanControlState") } #[zbus(property)] - async fn set_fan_control_state(&self, state: u32) -> zbus::Result<()> { - setter!(self, "FanControlState", state) + async fn set_fan_control_state( + &self, + state: u32, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { + let _: () = setter!(self, "FanControlState", state)?; + self.fan_control_state_changed(&ctx).await } } #[interface(name = "com.steampowered.SteamOSManager1.GpuPerformanceLevel1")] impl GpuPerformanceLevel1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property(emits_changed_signal = "const"))] async fn available_gpu_performance_levels(&self) -> fdo::Result> { get_available_gpu_performance_levels() .await @@ -342,7 +365,7 @@ impl GpuPerformanceLevel1 { .map_err(to_zbus_fdo_error) } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn gpu_performance_level(&self) -> fdo::Result { match get_gpu_performance_level().await { Ok(level) => Ok(level.to_string()), @@ -354,11 +377,16 @@ impl GpuPerformanceLevel1 { } #[zbus(property)] - async fn set_gpu_performance_level(&self, level: &str) -> zbus::Result<()> { - self.proxy.call("SetGpuPerformanceLevel", &(level)).await + async fn set_gpu_performance_level( + &self, + level: &str, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { + let _: () = self.proxy.call("SetGpuPerformanceLevel", &(level)).await?; + self.gpu_performance_level_changed(&ctx).await } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn manual_gpu_clock(&self) -> fdo::Result { get_gpu_clocks() .await @@ -367,8 +395,13 @@ impl GpuPerformanceLevel1 { } #[zbus(property)] - async fn set_manual_gpu_clock(&self, clocks: u32) -> zbus::Result<()> { - self.proxy.call("SetManualGpuClock", &(clocks)).await + async fn set_manual_gpu_clock( + &self, + clocks: u32, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { + let _: () = self.proxy.call("SetManualGpuClock", &(clocks)).await?; + self.manual_gpu_clock_changed(&ctx).await } #[zbus(property(emits_changed_signal = "const"))] @@ -390,7 +423,7 @@ impl GpuPerformanceLevel1 { #[interface(name = "com.steampowered.SteamOSManager1.GpuPowerProfile1")] impl GpuPowerProfile1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property(emits_changed_signal = "const"))] async fn available_gpu_power_profiles(&self) -> fdo::Result> { let (_, names): (Vec, Vec) = get_available_gpu_power_profiles() .await @@ -400,7 +433,7 @@ impl GpuPowerProfile1 { Ok(names) } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn gpu_power_profile(&self) -> fdo::Result { match get_gpu_power_profile().await { Ok(profile) => Ok(profile.to_string()), @@ -412,8 +445,13 @@ impl GpuPowerProfile1 { } #[zbus(property)] - async fn set_gpu_power_profile(&self, profile: &str) -> zbus::Result<()> { - self.proxy.call("SetGpuPowerProfile", &(profile)).await + async fn set_gpu_power_profile( + &self, + profile: &str, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { + let _: () = self.proxy.call("SetGpuPowerProfile", &(profile)).await?; + self.gpu_power_profile_changed(&ctx).await } } @@ -426,7 +464,7 @@ impl HdmiCec1 { #[interface(name = "com.steampowered.SteamOSManager1.HdmiCec1")] impl HdmiCec1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn hdmi_cec_state(&self) -> fdo::Result { match self.hdmi_cec.get_enabled_state().await { Ok(state) => Ok(state as u32), @@ -435,16 +473,22 @@ impl HdmiCec1 { } #[zbus(property)] - async fn set_hdmi_cec_state(&self, state: u32) -> zbus::Result<()> { + async fn set_hdmi_cec_state( + &self, + state: u32, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { let state = match HdmiCecState::try_from(state) { Ok(state) => state, Err(err) => return Err(fdo::Error::InvalidArgs(err.to_string()).into()), }; - self.hdmi_cec + let (): _ = self + .hdmi_cec .set_enabled_state(state) .await .inspect_err(|message| error!("Error setting CEC state: {message}")) - .map_err(to_zbus_error) + .map_err(to_zbus_error)?; + self.hdmi_cec_state_changed(&ctx).await } } @@ -501,7 +545,7 @@ impl Manager2 { #[interface(name = "com.steampowered.SteamOSManager1.PerformanceProfile1")] impl PerformanceProfile1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property(emits_changed_signal = "const"))] async fn available_performance_profiles(&self) -> fdo::Result> { let config = platform_config().await.map_err(to_zbus_fdo_error)?; let config = config @@ -515,7 +559,7 @@ impl PerformanceProfile1 { .map_err(to_zbus_fdo_error) } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn performance_profile(&self) -> fdo::Result { let config = platform_config().await.map_err(to_zbus_fdo_error)?; let config = config @@ -534,8 +578,10 @@ impl PerformanceProfile1 { &self, profile: &str, #[zbus(connection)] connection: &Connection, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, ) -> zbus::Result<()> { let _: () = self.proxy.call("SetPerformanceProfile", &(profile)).await?; + self.performance_profile_changed(&ctx).await?; let connection = connection.clone(); let manager = self.tdp_limit_manager.clone(); let _ = manager.send(TdpManagerCommand::UpdateDownloadMode); @@ -589,7 +635,7 @@ impl Storage1 { #[interface(name = "com.steampowered.SteamOSManager1.TdpLimit1")] impl TdpLimit1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn tdp_limit(&self) -> u32 { let (tx, rx) = oneshot::channel(); if self @@ -678,7 +724,7 @@ impl WifiDebug1 { Ok(()) } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn wifi_backend(&self) -> fdo::Result { match get_wifi_backend().await { Ok(backend) => Ok(backend.to_string()), @@ -687,12 +733,17 @@ impl WifiDebug1 { } #[zbus(property)] - async fn set_wifi_backend(&self, backend: &str) -> zbus::Result<()> { + async fn set_wifi_backend( + &self, + backend: &str, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { let backend = match WifiBackend::try_from(backend) { Ok(backend) => backend, Err(e) => return Err(fdo::Error::InvalidArgs(e.to_string()).into()), }; - self.proxy.call("SetWifiBackend", &(backend as u32)).await + let _: () = self.proxy.call("SetWifiBackend", &(backend as u32)).await?; + self.wifi_backend_changed(&ctx).await } async fn capture_debug_trace_output(&self) -> fdo::Result { @@ -709,7 +760,7 @@ impl WifiDebugDump1 { #[interface(name = "com.steampowered.SteamOSManager1.WifiPowerManagement1")] impl WifiPowerManagement1 { - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property)] async fn wifi_power_management_state(&self) -> fdo::Result { match get_wifi_power_management_state().await { Ok(state) => Ok(state as u32), @@ -718,10 +769,16 @@ impl WifiPowerManagement1 { } #[zbus(property)] - async fn set_wifi_power_management_state(&self, state: u32) -> zbus::Result<()> { - self.proxy + async fn set_wifi_power_management_state( + &self, + state: u32, + #[zbus(signal_emitter)] ctx: SignalEmitter<'_>, + ) -> zbus::Result<()> { + let _: () = self + .proxy .call("SetWifiPowerManagementState", &(state)) - .await + .await?; + self.wifi_power_management_state_changed(&ctx).await } } diff --git a/src/power.rs b/src/power.rs index 769ee47..a1cd640 100644 --- a/src/power.rs +++ b/src/power.rs @@ -29,6 +29,7 @@ use zbus::Connection; use crate::hardware::{device_type, DeviceType}; use crate::manager::root::RootManagerProxy; +use crate::manager::user::{TdpLimit1, MANAGER_PATH}; use crate::platform::platform_config; use crate::Service; use crate::{path, write_synced}; @@ -138,6 +139,7 @@ pub(crate) async fn tdp_limit_manager() -> Result> { pub(crate) struct TdpManagerService { proxy: RootManagerProxy<'static>, + session: Connection, channel: UnboundedReceiver, download_set: JoinSet, download_handles: HashMap, @@ -640,7 +642,8 @@ pub(crate) async fn set_platform_profile(name: &str, profile: &str) -> Result<() impl TdpManagerService { pub async fn new( channel: UnboundedReceiver, - connection: &Connection, + system: &Connection, + session: &Connection, ) -> Result { let config = platform_config().await?; let config = config @@ -649,10 +652,11 @@ impl TdpManagerService { .ok_or(anyhow!("No TDP limit configured"))?; let manager = tdp_limit_manager().await?; - let proxy = RootManagerProxy::new(connection).await?; + let proxy = RootManagerProxy::new(system).await?; Ok(TdpManagerService { proxy, + session: session.clone(), channel, download_set: JoinSet::new(), download_handles: HashMap::new(), @@ -737,11 +741,23 @@ impl TdpManagerService { } async fn set_tdp_limit(&self, limit: u32) -> Result<()> { - Ok(self - .proxy + self.proxy .set_tdp_limit(limit) .await - .inspect_err(|e| error!("Failed to set TDP limit: {e}"))?) + .inspect_err(|e| error!("Failed to set TDP limit: {e}"))?; + + if let Ok(interface) = self + .session + .object_server() + .interface::<_, TdpLimit1>(MANAGER_PATH) + .await + { + tokio::spawn(async move { + let ctx = interface.signal_emitter(); + interface.get().await.tdp_limit_changed(&ctx).await + }); + } + Ok(()) } async fn handle_command(&mut self, command: TdpManagerCommand) -> Result<()> { @@ -1639,7 +1655,7 @@ CCLK_RANGE in Core0: .await .expect("at"); - let mut service = TdpManagerService::new(rx, &connection) + let mut service = TdpManagerService::new(rx, &connection, &connection) .await .expect("service"); let task = tokio::spawn(async move { @@ -1734,7 +1750,7 @@ CCLK_RANGE in Core0: .await .expect("at"); - let mut service = TdpManagerService::new(rx, &connection) + let mut service = TdpManagerService::new(rx, &connection, &connection) .await .expect("service"); let task = tokio::spawn(async move {