diff --git a/com.steampowered.SteamOSManager1.xml b/com.steampowered.SteamOSManager1.xml index cb77bb2..3748618 100644 --- a/com.steampowered.SteamOSManager1.xml +++ b/com.steampowered.SteamOSManager1.xml @@ -252,16 +252,6 @@ --> - - - - - - - - diff --git a/src/manager.rs b/src/manager.rs index 6e0547a..84eb5fc 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -515,9 +515,7 @@ mod test { assert_eq!(remote_interface.len(), 1); let remote_interface = remote_interface[0]; let remote_methods = collect_methods(remote_interface.methods()); - let remote_method_names: HashSet<&String> = remote_methods.keys().collect(); let remote_properties = collect_properties(remote_interface.properties()); - let remote_property_names: HashSet<&String> = remote_properties.keys().collect(); let local_interface_string = read("com.steampowered.SteamOSManager1.xml") .await @@ -532,13 +530,11 @@ mod test { assert_eq!(local_interface.len(), 1); let local_interface = local_interface[0]; let local_methods = collect_methods(local_interface.methods()); - let local_method_names: HashSet<&String> = local_methods.keys().collect(); let local_properties = collect_properties(local_interface.properties()); - let local_property_names: HashSet<&String> = local_properties.keys().collect(); - for key in local_method_names.union(&remote_method_names) { - let local_method = local_methods.get(*key).expect(key); - let remote_method = remote_methods.get(*key).expect(key); + for key in remote_methods.keys() { + let local_method = local_methods.get(key).expect(key); + let remote_method = remote_methods.get(key).expect(key); assert_eq!(local_method.name(), remote_method.name()); assert_eq!(local_method.args().len(), remote_method.args().len()); @@ -550,9 +546,9 @@ mod test { } } - for key in local_property_names.union(&remote_property_names) { - let local_property = local_properties.get(*key).expect(key); - let remote_property = remote_properties.get(*key).expect(key); + for key in remote_properties.keys() { + let local_property = local_properties.get(key).expect(key); + let remote_property = remote_properties.get(key).expect(key); assert_eq!(local_property.name(), remote_property.name()); assert_eq!(local_property.ty(), remote_property.ty()); diff --git a/src/user.rs b/src/user.rs index 29cc8f9..f90a3c0 100644 --- a/src/user.rs +++ b/src/user.rs @@ -15,15 +15,15 @@ use zbus::ConnectionBuilder; use crate::daemon::Daemon; use crate::user_manager::SteamOSManagerUser; -async fn create_connection() -> Result { +async fn create_connection(system_conn: &Connection) -> Result { let connection = ConnectionBuilder::session()? .name("com.steampowered.SteamOSManager1")? .build() .await?; - let manager = SteamOSManagerUser::new(connection.clone()).await?; + let manager = SteamOSManagerUser::new(connection.clone(), system_conn).await?; connection .object_server() - .at("/com/steampowered/SteamOSManager1/User", manager) + .at("/com/steampowered/SteamOSManager1", manager) .await?; Ok(connection) } @@ -43,7 +43,7 @@ pub async fn daemon() -> Result<()> { bail!(e); } }; - let _session = match create_connection().await { + let _session = match create_connection(&system).await { Ok(c) => c, Err(e) => { let _guard = tracing::subscriber::set_default(subscriber); @@ -52,7 +52,7 @@ pub async fn daemon() -> Result<()> { } }; - let mut daemon = Daemon::new(subscriber, system.clone()).await?; + let mut daemon = Daemon::new(subscriber, system).await?; daemon.run().await } diff --git a/src/user_manager.rs b/src/user_manager.rs index ce93ddf..a57c470 100644 --- a/src/user_manager.rs +++ b/src/user_manager.rs @@ -8,27 +8,76 @@ use anyhow::Result; use tracing::error; -use zbus::{interface, Connection}; +use zbus::zvariant::Fd; +use zbus::{interface, Connection, Proxy, SignalContext}; -use crate::{to_zbus_error, to_zbus_fdo_error, API_VERSION}; use crate::cec::{HdmiCecControl, HdmiCecState}; +use crate::{to_zbus_error, to_zbus_fdo_error, zbus_to_zbus_fdo, API_VERSION}; + +macro_rules! method { + ($self:expr, $method:expr, $($args:expr),+) => { + $self.proxy + .call($method, &($($args,)*)) + .await + .map_err(zbus_to_zbus_fdo) + }; + ($self:expr, $method:expr) => { + $self.proxy + .call($method, &()) + .await + .map_err(zbus_to_zbus_fdo) + }; +} + +macro_rules! getter { + ($self:expr, $prop:expr) => { + $self + .proxy + .get_property($prop) + .await + .map_err(zbus_to_zbus_fdo) + }; +} + +macro_rules! setter { + ($self:expr, $prop:expr, $value:expr) => { + $self + .proxy + .set_property($prop, $value) + .await + .map_err(|e| zbus::Error::FDO(Box::new(e))) + }; +} pub struct SteamOSManagerUser { connection: Connection, + proxy: Proxy<'static>, hdmi_cec: HdmiCecControl<'static>, } impl SteamOSManagerUser { - pub async fn new(connection: Connection) -> Result { + pub async fn new(connection: Connection, system_conn: &Connection) -> Result { Ok(SteamOSManagerUser { hdmi_cec: HdmiCecControl::new(&connection).await?, connection, + proxy: Proxy::new( + system_conn, + "com.steampowered.SteamOSManager1", + "/com/steampowered/SteamOSManager1", + "com.steampowered.SteamOSManager1.Manager", + ) + .await?, }) } } -#[interface(name = "com.steampowered.SteamOSManager1.UserManager")] +#[interface(name = "com.steampowered.SteamOSManager1.Manager")] impl SteamOSManagerUser { + #[zbus(property(emits_changed_signal = "const"))] + async fn version(&self) -> u32 { + API_VERSION + } + #[zbus(property(emits_changed_signal = "false"))] async fn hdmi_cec_state(&self) -> zbus::fdo::Result { match self.hdmi_cec.get_enabled_state().await { @@ -50,8 +99,275 @@ impl SteamOSManagerUser { .map_err(to_zbus_error) } + async fn prepare_factory_reset(&self) -> zbus::fdo::Result { + method!(self, "PrepareFactoryReset") + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn wifi_power_management_state(&self) -> zbus::fdo::Result { + getter!(self, "WifiPowerManagementState") + } + + #[zbus(property)] + async fn set_wifi_power_management_state(&self, state: u32) -> zbus::Result<()> { + setter!(self, "WifiPowerManagementState", state) + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn fan_control_state(&self) -> zbus::fdo::Result { + getter!(self, "FanControlState") + } + + #[zbus(property)] + async fn set_fan_control_state(&self, state: u32) -> zbus::Result<()> { + setter!(self, "SetFanControlState", state) + } + #[zbus(property(emits_changed_signal = "const"))] - async fn version(&self) -> u32 { - API_VERSION + async fn hardware_currently_supported(&self) -> zbus::fdo::Result { + getter!(self, "HardwareCurrentlySupported") + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn als_calibration_gain(&self) -> zbus::fdo::Result { + getter!(self, "AlsCalibrationGain") + } + + async fn get_als_integration_time_file_descriptor(&self) -> zbus::fdo::Result { + let m = self + .proxy + .call_method::<&str, ()>("GetAlsIntegrationTimeFileDescriptor", &()) + .await + .map_err(zbus_to_zbus_fdo)?; + match m.body().deserialize::() { + Ok(fd) => fd.try_to_owned().map_err(to_zbus_fdo_error), + Err(e) => Err(zbus_to_zbus_fdo(e)), + } + } + + async fn update_bios(&self) -> zbus::fdo::Result<()> { + method!(self, "UpdateBios") + } + + async fn update_dock(&self) -> zbus::fdo::Result<()> { + method!(self, "UpdateDock") + } + + async fn trim_devices(&self) -> zbus::fdo::Result<()> { + method!(self, "TrimDevices") + } + + async fn format_device( + &self, + device: &str, + label: &str, + validate: bool, + ) -> zbus::fdo::Result<()> { + method!(self, "FormatDevice", device, label, validate) + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn gpu_performance_level(&self) -> zbus::fdo::Result { + getter!(self, "GpuPerformanceLevel") + } + + #[zbus(property)] + async fn set_gpu_performance_level(&self, level: u32) -> zbus::Result<()> { + setter!(self, "GpuPerformanceLevel", level) + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn manual_gpu_clock(&self) -> zbus::fdo::Result { + getter!(self, "ManualGpuClock") + } + + #[zbus(property)] + async fn set_manual_gpu_clock(&self, clocks: u32) -> zbus::Result<()> { + setter!(self, "ManualGpuClock", clocks) + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn manual_gpu_clock_min(&self) -> zbus::fdo::Result { + getter!(self, "ManualGpuClockMin") + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn manual_gpu_clock_max(&self) -> zbus::fdo::Result { + getter!(self, "ManualGpuClockMax") + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn tdp_limit(&self) -> zbus::fdo::Result { + getter!(self, "TdpLimit") + } + + #[zbus(property)] + async fn set_tdp_limit(&self, limit: u32) -> zbus::Result<()> { + setter!(self, "TdpLimit", limit) + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn tdp_limit_min(&self) -> zbus::fdo::Result { + getter!(self, "TdpLimitMin") + } + + #[zbus(property(emits_changed_signal = "const"))] + async fn tdp_limit_max(&self) -> zbus::fdo::Result { + getter!(self, "TdpLimitMax") + } + + #[zbus(property)] + async fn wifi_debug_mode_state(&self) -> zbus::fdo::Result { + getter!(self, "WifiDebugModeState") + } + + async fn set_wifi_debug_mode( + &self, + mode: u32, + buffer_size: u32, + #[zbus(signal_context)] ctx: SignalContext<'_>, + ) -> zbus::fdo::Result<()> { + method!(self, "SetWifiDebugMode", mode, buffer_size)?; + self.wifi_debug_mode_state_changed(&ctx) + .await + .map_err(zbus_to_zbus_fdo)?; + Ok(()) + } + + #[zbus(property(emits_changed_signal = "false"))] + async fn wifi_backend(&self) -> zbus::fdo::Result { + getter!(self, "WifiBackend") + } + + #[zbus(property)] + async fn set_wifi_backend(&self, backend: u32) -> zbus::Result<()> { + setter!(self, "WifiBackend", backend) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{power, testing}; + use std::collections::{HashMap, HashSet}; + use std::iter::zip; + use tokio::fs::{create_dir_all, read, write}; + use zbus::{Connection, ConnectionBuilder, Interface}; + use zbus_xml::{Method, Node, Property}; + + struct TestHandle { + _handle: testing::TestHandle, + connection: Connection, + } + + async fn start(name: &str) -> TestHandle { + let handle = testing::start(); + let connection = ConnectionBuilder::session() + .unwrap() + .name(format!("com.steampowered.SteamOSManager1.UserTest.{name}")) + .unwrap() + .build() + .await + .unwrap(); + let manager = SteamOSManagerUser::new(connection.clone(), &connection) + .await + .unwrap(); + connection + .object_server() + .at("/com/steampowered/SteamOSManager1", manager) + .await + .expect("object_server at"); + + TestHandle { + _handle: handle, + connection, + } + } + + fn collect_methods<'a>(methods: &'a [Method<'a>]) -> HashMap> { + let mut map = HashMap::new(); + for method in methods.iter() { + map.insert(method.name().to_string(), method); + } + map + } + + fn collect_properties<'a>(props: &'a [Property<'a>]) -> HashMap> { + let mut map = HashMap::new(); + for prop in props.iter() { + map.insert(prop.name().to_string(), prop); + } + map + } + + #[tokio::test] + async fn interface_matches() { + let test = start("Interface").await; + + let manager_ref = test + .connection + .object_server() + .interface::<_, SteamOSManagerUser>("/com/steampowered/SteamOSManager1") + .await + .expect("interface"); + let manager = manager_ref.get().await; + let mut remote_interface_string = String::from( + "", + ); + manager.introspect_to_writer(&mut remote_interface_string, 0); + remote_interface_string.push_str(""); + let remote_interfaces = + Node::from_reader::<&[u8]>(remote_interface_string.as_bytes()).expect("from_reader"); + let remote_interface: Vec<_> = remote_interfaces + .interfaces() + .iter() + .filter(|iface| iface.name() == "com.steampowered.SteamOSManager1.Manager") + .collect(); + assert_eq!(remote_interface.len(), 1); + let remote_interface = remote_interface[0]; + let remote_methods = collect_methods(remote_interface.methods()); + let remote_method_names: HashSet<&String> = remote_methods.keys().collect(); + let remote_properties = collect_properties(remote_interface.properties()); + let remote_property_names: HashSet<&String> = remote_properties.keys().collect(); + + let local_interface_string = read("com.steampowered.SteamOSManager1.xml") + .await + .expect("read"); + let local_interfaces = + Node::from_reader::<&[u8]>(local_interface_string.as_ref()).expect("from_reader"); + let local_interface: Vec<_> = local_interfaces + .interfaces() + .iter() + .filter(|iface| iface.name() == "com.steampowered.SteamOSManager1.Manager") + .collect(); + assert_eq!(local_interface.len(), 1); + let local_interface = local_interface[0]; + let local_methods = collect_methods(local_interface.methods()); + let local_method_names: HashSet<&String> = local_methods.keys().collect(); + let local_properties = collect_properties(local_interface.properties()); + let local_property_names: HashSet<&String> = local_properties.keys().collect(); + + for key in local_method_names.union(&remote_method_names) { + let local_method = local_methods.get(*key).expect(key); + let remote_method = remote_methods.get(*key).expect(key); + + assert_eq!(local_method.name(), remote_method.name()); + assert_eq!(local_method.args().len(), remote_method.args().len()); + for (local_arg, remote_arg) in + zip(local_method.args().iter(), remote_method.args().iter()) + { + assert_eq!(local_arg.direction(), remote_arg.direction()); + assert_eq!(local_arg.ty(), remote_arg.ty()); + } + } + + for key in local_property_names.union(&remote_property_names) { + let local_property = local_properties.get(*key).expect(key); + let remote_property = remote_properties.get(*key).expect(key); + + assert_eq!(local_property.name(), remote_property.name()); + assert_eq!(local_property.ty(), remote_property.ty()); + assert_eq!(local_property.access(), remote_property.access()); + } } }