manager/user: Emit signals when we change properties

This commit is contained in:
Vicki Pfau 2025-05-08 17:23:18 -07:00
parent 65a81cee47
commit 0f156ef49f
4 changed files with 127 additions and 48 deletions

View file

@ -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 always provide full support for the SteamOS Manager interface version available
in the Stable release of SteamOS. 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 ## Implementation details
SteamOS Manager is compromised of two daemons: one runs as the logged in user SteamOS Manager is compromised of two daemons: one runs as the logged in user

View file

@ -116,7 +116,7 @@ async fn create_connections(
let jm_service = JobManagerService::new(job_manager, rx, system.clone()); let jm_service = JobManagerService::new(job_manager, rx, system.clone());
let (tdp_tx, rx) = unbounded_channel(); 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?; create_interfaces(connection.clone(), system.clone(), channel, jm_tx, tdp_tx).await?;

View file

@ -36,7 +36,7 @@ use crate::wifi::{
}; };
use crate::API_VERSION; use crate::API_VERSION;
const MANAGER_PATH: &str = "/com/steampowered/SteamOSManager1"; pub(crate) const MANAGER_PATH: &str = "/com/steampowered/SteamOSManager1";
macro_rules! method { macro_rules! method {
($self:expr, $method:expr, $($args:expr),+) => { ($self:expr, $method:expr, $($args:expr),+) => {
@ -131,7 +131,7 @@ struct GpuPowerProfile1 {
proxy: Proxy<'static>, proxy: Proxy<'static>,
} }
struct TdpLimit1 { pub(crate) struct TdpLimit1 {
manager: UnboundedSender<TdpManagerCommand>, manager: UnboundedSender<TdpManagerCommand>,
} }
@ -226,7 +226,7 @@ impl SteamOSManager {
Ok(()) Ok(())
} }
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn wifi_backend(&self) -> fdo::Result<u32> { async fn wifi_backend(&self) -> fdo::Result<u32> {
match get_wifi_backend().await { match get_wifi_backend().await {
Ok(backend) => Ok(backend as u32), Ok(backend) => Ok(backend as u32),
@ -235,8 +235,13 @@ impl SteamOSManager {
} }
#[zbus(property)] #[zbus(property)]
async fn set_wifi_backend(&self, backend: u32) -> zbus::Result<()> { async fn set_wifi_backend(
self.proxy.call("SetWifiBackend", &(backend)).await &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")] #[interface(name = "com.steampowered.SteamOSManager1.BatteryChargeLimit1")]
impl BatteryChargeLimit1 { impl BatteryChargeLimit1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn max_charge_level(&self) -> fdo::Result<i32> { async fn max_charge_level(&self) -> fdo::Result<i32> {
let level = get_max_charge_level().await.map_err(to_zbus_fdo_error)?; let level = get_max_charge_level().await.map_err(to_zbus_fdo_error)?;
if level <= 0 { if level <= 0 {
@ -265,8 +270,13 @@ impl BatteryChargeLimit1 {
} }
#[zbus(property)] #[zbus(property)]
async fn set_max_charge_level(&self, limit: i32) -> zbus::Result<()> { async fn set_max_charge_level(
self.proxy.call("SetMaxChargeLevel", &(limit)).await &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"))] #[zbus(property(emits_changed_signal = "const"))]
@ -285,7 +295,7 @@ impl BatteryChargeLimit1 {
#[interface(name = "com.steampowered.SteamOSManager1.CpuScaling1")] #[interface(name = "com.steampowered.SteamOSManager1.CpuScaling1")]
impl CpuScaling1 { impl CpuScaling1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property(emits_changed_signal = "const"))]
async fn available_cpu_scaling_governors(&self) -> fdo::Result<Vec<String>> { async fn available_cpu_scaling_governors(&self) -> fdo::Result<Vec<String>> {
let governors = get_available_cpu_scaling_governors() let governors = get_available_cpu_scaling_governors()
.await .await
@ -297,7 +307,7 @@ impl CpuScaling1 {
Ok(result) Ok(result)
} }
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn cpu_scaling_governor(&self) -> fdo::Result<String> { async fn cpu_scaling_governor(&self) -> fdo::Result<String> {
let governor = get_cpu_scaling_governor() let governor = get_cpu_scaling_governor()
.await .await
@ -306,8 +316,16 @@ impl CpuScaling1 {
} }
#[zbus(property)] #[zbus(property)]
async fn set_cpu_scaling_governor(&self, governor: String) -> zbus::Result<()> { async fn set_cpu_scaling_governor(
self.proxy.call("SetCpuScalingGovernor", &(governor)).await &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")] #[interface(name = "com.steampowered.SteamOSManager1.FanControl1")]
impl FanControl1 { impl FanControl1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn fan_control_state(&self) -> fdo::Result<u32> { async fn fan_control_state(&self) -> fdo::Result<u32> {
getter!(self, "FanControlState") getter!(self, "FanControlState")
} }
#[zbus(property)] #[zbus(property)]
async fn set_fan_control_state(&self, state: u32) -> zbus::Result<()> { async fn set_fan_control_state(
setter!(self, "FanControlState", 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")] #[interface(name = "com.steampowered.SteamOSManager1.GpuPerformanceLevel1")]
impl GpuPerformanceLevel1 { impl GpuPerformanceLevel1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property(emits_changed_signal = "const"))]
async fn available_gpu_performance_levels(&self) -> fdo::Result<Vec<String>> { async fn available_gpu_performance_levels(&self) -> fdo::Result<Vec<String>> {
get_available_gpu_performance_levels() get_available_gpu_performance_levels()
.await .await
@ -342,7 +365,7 @@ impl GpuPerformanceLevel1 {
.map_err(to_zbus_fdo_error) .map_err(to_zbus_fdo_error)
} }
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn gpu_performance_level(&self) -> fdo::Result<String> { async fn gpu_performance_level(&self) -> fdo::Result<String> {
match get_gpu_performance_level().await { match get_gpu_performance_level().await {
Ok(level) => Ok(level.to_string()), Ok(level) => Ok(level.to_string()),
@ -354,11 +377,16 @@ impl GpuPerformanceLevel1 {
} }
#[zbus(property)] #[zbus(property)]
async fn set_gpu_performance_level(&self, level: &str) -> zbus::Result<()> { async fn set_gpu_performance_level(
self.proxy.call("SetGpuPerformanceLevel", &(level)).await &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<u32> { async fn manual_gpu_clock(&self) -> fdo::Result<u32> {
get_gpu_clocks() get_gpu_clocks()
.await .await
@ -367,8 +395,13 @@ impl GpuPerformanceLevel1 {
} }
#[zbus(property)] #[zbus(property)]
async fn set_manual_gpu_clock(&self, clocks: u32) -> zbus::Result<()> { async fn set_manual_gpu_clock(
self.proxy.call("SetManualGpuClock", &(clocks)).await &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"))] #[zbus(property(emits_changed_signal = "const"))]
@ -390,7 +423,7 @@ impl GpuPerformanceLevel1 {
#[interface(name = "com.steampowered.SteamOSManager1.GpuPowerProfile1")] #[interface(name = "com.steampowered.SteamOSManager1.GpuPowerProfile1")]
impl GpuPowerProfile1 { impl GpuPowerProfile1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property(emits_changed_signal = "const"))]
async fn available_gpu_power_profiles(&self) -> fdo::Result<Vec<String>> { async fn available_gpu_power_profiles(&self) -> fdo::Result<Vec<String>> {
let (_, names): (Vec<u32>, Vec<String>) = get_available_gpu_power_profiles() let (_, names): (Vec<u32>, Vec<String>) = get_available_gpu_power_profiles()
.await .await
@ -400,7 +433,7 @@ impl GpuPowerProfile1 {
Ok(names) Ok(names)
} }
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn gpu_power_profile(&self) -> fdo::Result<String> { async fn gpu_power_profile(&self) -> fdo::Result<String> {
match get_gpu_power_profile().await { match get_gpu_power_profile().await {
Ok(profile) => Ok(profile.to_string()), Ok(profile) => Ok(profile.to_string()),
@ -412,8 +445,13 @@ impl GpuPowerProfile1 {
} }
#[zbus(property)] #[zbus(property)]
async fn set_gpu_power_profile(&self, profile: &str) -> zbus::Result<()> { async fn set_gpu_power_profile(
self.proxy.call("SetGpuPowerProfile", &(profile)).await &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")] #[interface(name = "com.steampowered.SteamOSManager1.HdmiCec1")]
impl HdmiCec1 { impl HdmiCec1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn hdmi_cec_state(&self) -> fdo::Result<u32> { async fn hdmi_cec_state(&self) -> fdo::Result<u32> {
match self.hdmi_cec.get_enabled_state().await { match self.hdmi_cec.get_enabled_state().await {
Ok(state) => Ok(state as u32), Ok(state) => Ok(state as u32),
@ -435,16 +473,22 @@ impl HdmiCec1 {
} }
#[zbus(property)] #[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) { let state = match HdmiCecState::try_from(state) {
Ok(state) => state, Ok(state) => state,
Err(err) => return Err(fdo::Error::InvalidArgs(err.to_string()).into()), Err(err) => return Err(fdo::Error::InvalidArgs(err.to_string()).into()),
}; };
self.hdmi_cec let (): _ = self
.hdmi_cec
.set_enabled_state(state) .set_enabled_state(state)
.await .await
.inspect_err(|message| error!("Error setting CEC state: {message}")) .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")] #[interface(name = "com.steampowered.SteamOSManager1.PerformanceProfile1")]
impl PerformanceProfile1 { impl PerformanceProfile1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property(emits_changed_signal = "const"))]
async fn available_performance_profiles(&self) -> fdo::Result<Vec<String>> { async fn available_performance_profiles(&self) -> fdo::Result<Vec<String>> {
let config = platform_config().await.map_err(to_zbus_fdo_error)?; let config = platform_config().await.map_err(to_zbus_fdo_error)?;
let config = config let config = config
@ -515,7 +559,7 @@ impl PerformanceProfile1 {
.map_err(to_zbus_fdo_error) .map_err(to_zbus_fdo_error)
} }
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn performance_profile(&self) -> fdo::Result<String> { async fn performance_profile(&self) -> fdo::Result<String> {
let config = platform_config().await.map_err(to_zbus_fdo_error)?; let config = platform_config().await.map_err(to_zbus_fdo_error)?;
let config = config let config = config
@ -534,8 +578,10 @@ impl PerformanceProfile1 {
&self, &self,
profile: &str, profile: &str,
#[zbus(connection)] connection: &Connection, #[zbus(connection)] connection: &Connection,
#[zbus(signal_emitter)] ctx: SignalEmitter<'_>,
) -> zbus::Result<()> { ) -> zbus::Result<()> {
let _: () = self.proxy.call("SetPerformanceProfile", &(profile)).await?; let _: () = self.proxy.call("SetPerformanceProfile", &(profile)).await?;
self.performance_profile_changed(&ctx).await?;
let connection = connection.clone(); let connection = connection.clone();
let manager = self.tdp_limit_manager.clone(); let manager = self.tdp_limit_manager.clone();
let _ = manager.send(TdpManagerCommand::UpdateDownloadMode); let _ = manager.send(TdpManagerCommand::UpdateDownloadMode);
@ -589,7 +635,7 @@ impl Storage1 {
#[interface(name = "com.steampowered.SteamOSManager1.TdpLimit1")] #[interface(name = "com.steampowered.SteamOSManager1.TdpLimit1")]
impl TdpLimit1 { impl TdpLimit1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn tdp_limit(&self) -> u32 { async fn tdp_limit(&self) -> u32 {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
if self if self
@ -678,7 +724,7 @@ impl WifiDebug1 {
Ok(()) Ok(())
} }
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn wifi_backend(&self) -> fdo::Result<String> { async fn wifi_backend(&self) -> fdo::Result<String> {
match get_wifi_backend().await { match get_wifi_backend().await {
Ok(backend) => Ok(backend.to_string()), Ok(backend) => Ok(backend.to_string()),
@ -687,12 +733,17 @@ impl WifiDebug1 {
} }
#[zbus(property)] #[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) { let backend = match WifiBackend::try_from(backend) {
Ok(backend) => backend, Ok(backend) => backend,
Err(e) => return Err(fdo::Error::InvalidArgs(e.to_string()).into()), 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<String> { async fn capture_debug_trace_output(&self) -> fdo::Result<String> {
@ -709,7 +760,7 @@ impl WifiDebugDump1 {
#[interface(name = "com.steampowered.SteamOSManager1.WifiPowerManagement1")] #[interface(name = "com.steampowered.SteamOSManager1.WifiPowerManagement1")]
impl WifiPowerManagement1 { impl WifiPowerManagement1 {
#[zbus(property(emits_changed_signal = "false"))] #[zbus(property)]
async fn wifi_power_management_state(&self) -> fdo::Result<u32> { async fn wifi_power_management_state(&self) -> fdo::Result<u32> {
match get_wifi_power_management_state().await { match get_wifi_power_management_state().await {
Ok(state) => Ok(state as u32), Ok(state) => Ok(state as u32),
@ -718,10 +769,16 @@ impl WifiPowerManagement1 {
} }
#[zbus(property)] #[zbus(property)]
async fn set_wifi_power_management_state(&self, state: u32) -> zbus::Result<()> { async fn set_wifi_power_management_state(
self.proxy &self,
state: u32,
#[zbus(signal_emitter)] ctx: SignalEmitter<'_>,
) -> zbus::Result<()> {
let _: () = self
.proxy
.call("SetWifiPowerManagementState", &(state)) .call("SetWifiPowerManagementState", &(state))
.await .await?;
self.wifi_power_management_state_changed(&ctx).await
} }
} }

View file

@ -29,6 +29,7 @@ use zbus::Connection;
use crate::hardware::{device_type, DeviceType}; use crate::hardware::{device_type, DeviceType};
use crate::manager::root::RootManagerProxy; use crate::manager::root::RootManagerProxy;
use crate::manager::user::{TdpLimit1, MANAGER_PATH};
use crate::platform::platform_config; use crate::platform::platform_config;
use crate::Service; use crate::Service;
use crate::{path, write_synced}; use crate::{path, write_synced};
@ -138,6 +139,7 @@ pub(crate) async fn tdp_limit_manager() -> Result<Box<dyn TdpLimitManager>> {
pub(crate) struct TdpManagerService { pub(crate) struct TdpManagerService {
proxy: RootManagerProxy<'static>, proxy: RootManagerProxy<'static>,
session: Connection,
channel: UnboundedReceiver<TdpManagerCommand>, channel: UnboundedReceiver<TdpManagerCommand>,
download_set: JoinSet<String>, download_set: JoinSet<String>,
download_handles: HashMap<String, u32>, download_handles: HashMap<String, u32>,
@ -640,7 +642,8 @@ pub(crate) async fn set_platform_profile(name: &str, profile: &str) -> Result<()
impl TdpManagerService { impl TdpManagerService {
pub async fn new( pub async fn new(
channel: UnboundedReceiver<TdpManagerCommand>, channel: UnboundedReceiver<TdpManagerCommand>,
connection: &Connection, system: &Connection,
session: &Connection,
) -> Result<TdpManagerService> { ) -> Result<TdpManagerService> {
let config = platform_config().await?; let config = platform_config().await?;
let config = config let config = config
@ -649,10 +652,11 @@ impl TdpManagerService {
.ok_or(anyhow!("No TDP limit configured"))?; .ok_or(anyhow!("No TDP limit configured"))?;
let manager = tdp_limit_manager().await?; let manager = tdp_limit_manager().await?;
let proxy = RootManagerProxy::new(connection).await?; let proxy = RootManagerProxy::new(system).await?;
Ok(TdpManagerService { Ok(TdpManagerService {
proxy, proxy,
session: session.clone(),
channel, channel,
download_set: JoinSet::new(), download_set: JoinSet::new(),
download_handles: HashMap::new(), download_handles: HashMap::new(),
@ -737,11 +741,23 @@ impl TdpManagerService {
} }
async fn set_tdp_limit(&self, limit: u32) -> Result<()> { async fn set_tdp_limit(&self, limit: u32) -> Result<()> {
Ok(self self.proxy
.proxy
.set_tdp_limit(limit) .set_tdp_limit(limit)
.await .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<()> { async fn handle_command(&mut self, command: TdpManagerCommand) -> Result<()> {
@ -1639,7 +1655,7 @@ CCLK_RANGE in Core0:
.await .await
.expect("at"); .expect("at");
let mut service = TdpManagerService::new(rx, &connection) let mut service = TdpManagerService::new(rx, &connection, &connection)
.await .await
.expect("service"); .expect("service");
let task = tokio::spawn(async move { let task = tokio::spawn(async move {
@ -1734,7 +1750,7 @@ CCLK_RANGE in Core0:
.await .await
.expect("at"); .expect("at");
let mut service = TdpManagerService::new(rx, &connection) let mut service = TdpManagerService::new(rx, &connection, &connection)
.await .await
.expect("service"); .expect("service");
let task = tokio::spawn(async move { let task = tokio::spawn(async move {