From ff6aa760ca99e1aecd15843bfae761bb095fdfcb Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 23 Apr 2024 18:06:27 -0700 Subject: [PATCH 01/11] main: Start splitting out root daemon from main --- Cargo.lock | 51 ++++++++++ Cargo.toml | 1 + data/com.steampowered.SteamOSManager1.service | 2 +- data/steamos-manager.service | 2 +- src/main.rs | 99 ++++--------------- src/root.rs | 95 ++++++++++++++++++ 6 files changed, 166 insertions(+), 84 deletions(-) create mode 100644 src/root.rs diff --git a/Cargo.lock b/Cargo.lock index ccb2bfb..bc112d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + [[package]] name = "anyhow" version = "1.0.82" @@ -250,6 +256,44 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.59", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + [[package]] name = "concurrent-queue" version = "2.4.0" @@ -486,6 +530,12 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -886,6 +936,7 @@ name = "steamos-manager" version = "24.4.1" dependencies = [ "anyhow", + "clap", "inotify", "nix", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 8b892d1..f16357e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ strip="symbols" [dependencies] anyhow = "1" +clap = { version = "4.5", default-features = false, features = ["derive", "help", "std", "usage"] } inotify = { version = "0.10", default-features = false, features = ["stream"] } nix = { version = "0.28", default-features = false, features = ["fs"] } tokio = { version = "1", default-features = false, features = ["fs", "io-util", "macros", "rt-multi-thread", "signal", "sync"] } diff --git a/data/com.steampowered.SteamOSManager1.service b/data/com.steampowered.SteamOSManager1.service index c0683ca..792ea8d 100644 --- a/data/com.steampowered.SteamOSManager1.service +++ b/data/com.steampowered.SteamOSManager1.service @@ -1,5 +1,5 @@ [D-BUS Service] Name=com.steampowered.SteamOSManager1 -Exec=/usr/lib/steamos-manager +Exec=/usr/lib/steamos-manager -r User=root SystemdService=steamos-manager.service diff --git a/data/steamos-manager.service b/data/steamos-manager.service index 1675fc8..1e21630 100644 --- a/data/steamos-manager.service +++ b/data/steamos-manager.service @@ -7,7 +7,7 @@ After=steamos-log-submitter.service Type=dbus BusName=com.steampowered.SteamOSManager1 Environment=RUST_LOG='INFO' -ExecStart=/usr/lib/steamos-manager +ExecStart=/usr/lib/steamos-manager -r Restart=on-failure RestartSec=1 diff --git a/src/main.rs b/src/main.rs index 6b76e56..5e489f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,28 +5,21 @@ * SPDX-License-Identifier: MIT */ -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; +use clap::Parser; use std::path::{Path, PathBuf}; use tokio::fs::File; use tokio::io::AsyncWriteExt; use tokio::signal::unix::{signal, SignalKind}; -use tokio::task::JoinSet; use tokio_util::sync::CancellationToken; -use tracing::{error, info, warn}; -use tracing_subscriber::prelude::*; -use tracing_subscriber::{fmt, Registry}; -use zbus::connection::Connection; -use zbus::ConnectionBuilder; - -use crate::ds_inhibit::Inhibitor; -use crate::sls::ftrace::Ftrace; -use crate::sls::{LogLayer, LogReceiver}; +use tracing::{info, warn}; mod ds_inhibit; mod hardware; mod manager; mod power; mod process; +mod root; mod sls; mod systemd; mod wifi; @@ -65,6 +58,13 @@ where } } +#[derive(Parser)] +struct Args { + /// Run the root manager daemon + #[arg(short, long)] + root: bool +} + #[cfg(not(test))] pub fn path>(path: S) -> PathBuf { PathBuf::from(path.as_ref()) @@ -147,79 +147,14 @@ pub fn zbus_to_zbus_fdo(error: zbus::Error) -> zbus::fdo::Error { } } -async fn create_connection() -> Result { - let connection = ConnectionBuilder::system()? - .name("com.steampowered.SteamOSManager1")? - .build() - .await?; - let manager = manager::SteamOSManager::new(connection.clone()).await?; - connection - .object_server() - .at("/com/steampowered/SteamOSManager1", manager) - .await?; - Ok(connection) -} - #[tokio::main] -async fn main() -> Result<()> { - // This daemon is responsible for creating a dbus api that steam client can use to do various OS - // level things. It implements com.steampowered.SteamOSManager1.Manager interface - - let stdout_log = fmt::layer(); - let subscriber = Registry::default().with(stdout_log); - - let connection = match create_connection().await { - Ok(c) => c, - Err(e) => { - let _guard = tracing::subscriber::set_default(subscriber); - error!("Error connecting to DBus: {}", e); - bail!(e); - } - }; - - let mut services = JoinSet::new(); - let token = CancellationToken::new(); - - let mut log_receiver = LogReceiver::new(connection.clone()).await?; - let remote_logger = LogLayer::new(&log_receiver).await; - let subscriber = subscriber.with(remote_logger); - tracing::subscriber::set_global_default(subscriber)?; - - let mut sigterm = signal(SignalKind::terminate())?; - let mut sigquit = signal(SignalKind::quit())?; - - let ftrace = Ftrace::init(connection.clone()).await?; - services.spawn(ftrace.start(token.clone())); - - let inhibitor = Inhibitor::init().await?; - services.spawn(inhibitor.start(token.clone())); - - let mut res = tokio::select! { - e = log_receiver.run() => e, - e = services.join_next() => match e.unwrap() { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(e), - Err(e) => Err(e.into()) - }, - _ = tokio::signal::ctrl_c() => Ok(()), - e = sigterm.recv() => e.ok_or(anyhow!("SIGTERM machine broke")), - _ = sigquit.recv() => Err(anyhow!("Got SIGQUIT")), - e = reload() => e, +pub async fn main() -> Result<()> { + let args = Args::parse(); + if args.root { + root::daemon().await + } else { + todo!(); } - .inspect_err(|e| error!("Encountered error running: {e}")); - token.cancel(); - - info!("Shutting down"); - - while let Some(service_res) = services.join_next().await { - res = match service_res { - Ok(Err(e)) => Err(e), - Err(e) => Err(e.into()), - _ => continue, - }; - } - - res.inspect_err(|e| error!("Encountered error: {e}")) } #[cfg(test)] diff --git a/src/root.rs b/src/root.rs new file mode 100644 index 0000000..79bc06c --- /dev/null +++ b/src/root.rs @@ -0,0 +1,95 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::{anyhow, bail, Result}; +use tokio::signal::unix::{signal, SignalKind}; +use tokio::task::JoinSet; +use tokio_util::sync::CancellationToken; +use tracing::{error, info}; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{fmt, Registry}; +use zbus::connection::Connection; +use zbus::ConnectionBuilder; + +use crate::ds_inhibit::Inhibitor; +use crate::{manager, reload, Service}; +use crate::sls::ftrace::Ftrace; +use crate::sls::{LogLayer, LogReceiver}; + +async fn create_connection() -> Result { + let connection = ConnectionBuilder::system()? + .name("com.steampowered.SteamOSManager1")? + .build() + .await?; + let manager = manager::SteamOSManager::new(connection.clone()).await?; + connection + .object_server() + .at("/com/steampowered/SteamOSManager1", manager) + .await?; + Ok(connection) +} + +pub async fn daemon() -> Result<()> { + // This daemon is responsible for creating a dbus api that steam client can use to do various OS + // level things. It implements com.steampowered.SteamOSManager1.Manager interface + + let stdout_log = fmt::layer(); + let subscriber = Registry::default().with(stdout_log); + + let connection = match create_connection().await { + Ok(c) => c, + Err(e) => { + let _guard = tracing::subscriber::set_default(subscriber); + error!("Error connecting to DBus: {}", e); + bail!(e); + } + }; + + let mut services = JoinSet::new(); + let token = CancellationToken::new(); + + let mut log_receiver = LogReceiver::new(connection.clone()).await?; + let remote_logger = LogLayer::new(&log_receiver).await; + let subscriber = subscriber.with(remote_logger); + tracing::subscriber::set_global_default(subscriber)?; + + let mut sigterm = signal(SignalKind::terminate())?; + let mut sigquit = signal(SignalKind::quit())?; + + let ftrace = Ftrace::init(connection.clone()).await?; + services.spawn(ftrace.start(token.clone())); + + let inhibitor = Inhibitor::init().await?; + services.spawn(inhibitor.start(token.clone())); + + let mut res = tokio::select! { + e = log_receiver.run() => e, + e = services.join_next() => match e.unwrap() { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(e), + Err(e) => Err(e.into()) + }, + _ = tokio::signal::ctrl_c() => Ok(()), + e = sigterm.recv() => e.ok_or(anyhow!("SIGTERM machine broke")), + _ = sigquit.recv() => Err(anyhow!("Got SIGQUIT")), + e = reload() => e, + } + .inspect_err(|e| error!("Encountered error running: {e}")); + token.cancel(); + + info!("Shutting down"); + + while let Some(service_res) = services.join_next().await { + res = match service_res { + Ok(Err(e)) => Err(e), + Err(e) => Err(e.into()), + _ => continue, + }; + } + + res.inspect_err(|e| error!("Encountered error: {e}")) +} From ac823a845b60eb9f0cecb47e91fef6ab9a3ba3f5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 23 Apr 2024 19:22:35 -0700 Subject: [PATCH 02/11] daemon: Split parts of the root main into a reusable struct --- src/daemon.rs | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 44 ++++++++++++++------------ src/root.rs | 54 +++++--------------------------- 3 files changed, 119 insertions(+), 66 deletions(-) create mode 100644 src/daemon.rs diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 0000000..54a5e72 --- /dev/null +++ b/src/daemon.rs @@ -0,0 +1,87 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::{anyhow, Result}; +use tokio::signal::unix::{signal, Signal, SignalKind}; +use tokio::task::JoinSet; +use tokio_util::sync::CancellationToken; +use tracing::{error, info}; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::registry::LookupSpan; +use zbus::connection::Connection; + +use crate::sls::{LogLayer, LogReceiver}; +use crate::{reload, Service}; + +pub struct Daemon { + services: JoinSet>, + token: CancellationToken, + sigterm: Signal, + sigquit: Signal, +} + +impl Daemon { + pub async fn new LookupSpan<'a>>( + subscriber: S, + connection: Connection, + ) -> Result { + let services = JoinSet::new(); + let token = CancellationToken::new(); + + let log_receiver = LogReceiver::new(connection.clone()).await?; + let remote_logger = LogLayer::new(&log_receiver).await; + let subscriber = subscriber.with(remote_logger); + tracing::subscriber::set_global_default(subscriber)?; + + let sigterm = signal(SignalKind::terminate())?; + let sigquit = signal(SignalKind::quit())?; + + let mut daemon = Daemon { + services, + token, + sigterm, + sigquit, + }; + daemon.add_service(log_receiver); + + Ok(daemon) + } + + pub fn add_service(&mut self, service: S) { + let token = self.token.clone(); + self.services + .spawn(async move { service.start(token).await }); + } + + pub async fn run(&mut self) -> Result<()> { + let mut res = tokio::select! { + e = self.services.join_next() => match e.unwrap() { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(e), + Err(e) => Err(e.into()) + }, + _ = tokio::signal::ctrl_c() => Ok(()), + e = self.sigterm.recv() => e.ok_or(anyhow!("SIGTERM machine broke")), + _ = self.sigquit.recv() => Err(anyhow!("Got SIGQUIT")), + e = reload() => e, + } + .inspect_err(|e| error!("Encountered error running: {e}")); + self.token.cancel(); + + info!("Shutting down"); + + while let Some(service_res) = self.services.join_next().await { + res = match service_res { + Ok(Err(e)) => Err(e), + Err(e) => Err(e.into()), + _ => continue, + }; + } + + res.inspect_err(|e| error!("Encountered error: {e}")) + } +} diff --git a/src/main.rs b/src/main.rs index 5e489f3..06e2816 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use anyhow::{anyhow, Result}; use clap::Parser; +use std::future::Future; use std::path::{Path, PathBuf}; use tokio::fs::File; use tokio::io::AsyncWriteExt; @@ -14,6 +15,7 @@ use tokio::signal::unix::{signal, SignalKind}; use tokio_util::sync::CancellationToken; use tracing::{info, warn}; +mod daemon; mod ds_inhibit; mod hardware; mod manager; @@ -29,32 +31,34 @@ mod testing; trait Service where - Self: Sized, + Self: Sized + Send, { const NAME: &'static str; - async fn run(&mut self) -> Result<()>; + fn run(&mut self) -> impl Future> + Send; - async fn shutdown(&mut self) -> Result<()> { - Ok(()) + fn shutdown(&mut self) -> impl Future> + Send { + async { Ok(()) } } - async fn start(mut self, token: CancellationToken) -> Result<()> { - info!("Starting {}", Self::NAME); - let res = tokio::select! { - r = self.run() => r, - _ = token.cancelled() => Ok(()), - }; - if res.is_err() { - warn!( - "{} encountered an error: {}", - Self::NAME, - res.as_ref().unwrap_err() - ); - token.cancel(); + fn start(mut self, token: CancellationToken) -> impl Future> + Send { + async move { + info!("Starting {}", Self::NAME); + let res = tokio::select! { + r = self.run() => r, + _ = token.cancelled() => Ok(()), + }; + if res.is_err() { + warn!( + "{} encountered an error: {}", + Self::NAME, + res.as_ref().unwrap_err() + ); + token.cancel(); + } + info!("Shutting down {}", Self::NAME); + self.shutdown().await.and(res) } - info!("Shutting down {}", Self::NAME); - self.shutdown().await.and(res) } } @@ -62,7 +66,7 @@ where struct Args { /// Run the root manager daemon #[arg(short, long)] - root: bool + root: bool, } #[cfg(not(test))] diff --git a/src/root.rs b/src/root.rs index 79bc06c..525a8be 100644 --- a/src/root.rs +++ b/src/root.rs @@ -5,20 +5,17 @@ * SPDX-License-Identifier: MIT */ -use anyhow::{anyhow, bail, Result}; -use tokio::signal::unix::{signal, SignalKind}; -use tokio::task::JoinSet; -use tokio_util::sync::CancellationToken; -use tracing::{error, info}; +use anyhow::{bail, Result}; +use tracing::error; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, Registry}; use zbus::connection::Connection; use zbus::ConnectionBuilder; +use crate::daemon::Daemon; use crate::ds_inhibit::Inhibitor; -use crate::{manager, reload, Service}; +use crate::manager; use crate::sls::ftrace::Ftrace; -use crate::sls::{LogLayer, LogReceiver}; async fn create_connection() -> Result { let connection = ConnectionBuilder::system()? @@ -48,48 +45,13 @@ pub async fn daemon() -> Result<()> { bail!(e); } }; - - let mut services = JoinSet::new(); - let token = CancellationToken::new(); - - let mut log_receiver = LogReceiver::new(connection.clone()).await?; - let remote_logger = LogLayer::new(&log_receiver).await; - let subscriber = subscriber.with(remote_logger); - tracing::subscriber::set_global_default(subscriber)?; - - let mut sigterm = signal(SignalKind::terminate())?; - let mut sigquit = signal(SignalKind::quit())?; + let mut daemon = Daemon::new(subscriber, connection.clone()).await?; let ftrace = Ftrace::init(connection.clone()).await?; - services.spawn(ftrace.start(token.clone())); + daemon.add_service(ftrace); let inhibitor = Inhibitor::init().await?; - services.spawn(inhibitor.start(token.clone())); + daemon.add_service(inhibitor); - let mut res = tokio::select! { - e = log_receiver.run() => e, - e = services.join_next() => match e.unwrap() { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(e), - Err(e) => Err(e.into()) - }, - _ = tokio::signal::ctrl_c() => Ok(()), - e = sigterm.recv() => e.ok_or(anyhow!("SIGTERM machine broke")), - _ = sigquit.recv() => Err(anyhow!("Got SIGQUIT")), - e = reload() => e, - } - .inspect_err(|e| error!("Encountered error running: {e}")); - token.cancel(); - - info!("Shutting down"); - - while let Some(service_res) = services.join_next().await { - res = match service_res { - Ok(Err(e)) => Err(e), - Err(e) => Err(e.into()), - _ => continue, - }; - } - - res.inspect_err(|e| error!("Encountered error: {e}")) + daemon.run().await } From 24d740d178d568cfbc57858f971f1c2f720a6e53 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 23 Apr 2024 19:37:55 -0700 Subject: [PATCH 03/11] user: Add user-running manager prototype --- src/daemon.rs | 7 ++++++- src/main.rs | 3 ++- src/user.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/user.rs diff --git a/src/daemon.rs b/src/daemon.rs index 54a5e72..4fcccd5 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -5,7 +5,7 @@ * SPDX-License-Identifier: MIT */ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, ensure, Result}; use tokio::signal::unix::{signal, Signal, SignalKind}; use tokio::task::JoinSet; use tokio_util::sync::CancellationToken; @@ -58,6 +58,11 @@ impl Daemon { } pub async fn run(&mut self) -> Result<()> { + ensure!( + !self.services.is_empty(), + "Can't run a daemon with no services attached." + ); + let mut res = tokio::select! { e = self.services.join_next() => match e.unwrap() { Ok(Ok(())) => Ok(()), diff --git a/src/main.rs b/src/main.rs index 06e2816..731178f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,7 @@ mod process; mod root; mod sls; mod systemd; +mod user; mod wifi; #[cfg(test)] @@ -157,7 +158,7 @@ pub async fn main() -> Result<()> { if args.root { root::daemon().await } else { - todo!(); + user::daemon().await } } diff --git a/src/user.rs b/src/user.rs new file mode 100644 index 0000000..183691a --- /dev/null +++ b/src/user.rs @@ -0,0 +1,52 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::{bail, Result}; +use tracing::error; +use tracing_subscriber::prelude::*; +use tracing_subscriber::{fmt, Registry}; +use zbus::connection::Connection; +use zbus::ConnectionBuilder; + +use crate::daemon::Daemon; + +async fn create_connection() -> Result { + let connection = ConnectionBuilder::session()? + .name("com.steampowered.SteamOSManager1")? + .build() + .await?; + Ok(connection) +} + +pub async fn daemon() -> Result<()> { + // This daemon is responsible for creating a dbus api that steam client can use to do various OS + // level things. It implements com.steampowered.SteamOSManager1.Manager interface + + let stdout_log = fmt::layer(); + let subscriber = Registry::default().with(stdout_log); + + let system = match Connection::system().await { + Ok(c) => c, + Err(e) => { + let _guard = tracing::subscriber::set_default(subscriber); + error!("Error connecting to DBus: {}", e); + bail!(e); + } + }; + let session = match create_connection().await { + Ok(c) => c, + Err(e) => { + let _guard = tracing::subscriber::set_default(subscriber); + error!("Error connecting to DBus: {}", e); + bail!(e); + } + }; + + let mut daemon = Daemon::new(subscriber, system.clone()).await?; + + daemon.run().await +} From 65d8ff958ea95110d884df6c9976336e82640305 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 24 Apr 2024 16:50:10 -0700 Subject: [PATCH 04/11] manager: Move API_VERSION to crate root --- src/main.rs | 2 ++ src/manager.rs | 8 +++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 731178f..08f6dad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,8 @@ mod wifi; #[cfg(test)] mod testing; +const API_VERSION: u32 = 7; + trait Service where Self: Sized + Send, diff --git a/src/manager.rs b/src/manager.rs index 7cfc7f7..6e0547a 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -22,7 +22,7 @@ use crate::wifi::{ get_wifi_backend, get_wifi_power_management_state, set_wifi_backend, set_wifi_debug_mode, set_wifi_power_management_state, WifiBackend, WifiDebugMode, WifiPowerManagement, }; -use crate::{to_zbus_error, to_zbus_fdo_error}; +use crate::{to_zbus_error, to_zbus_fdo_error, API_VERSION}; #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] @@ -55,8 +55,6 @@ const ALS_INTEGRATION_PATH: &str = "/sys/devices/platform/AMDI0010:00/i2c-0/i2c- #[interface(name = "com.steampowered.SteamOSManager1.Manager")] impl SteamOSManager { - const API_VERSION: u32 = 7; - async fn prepare_factory_reset(&self) -> u32 { // Run steamos factory reset script and return true on success let res = run_script("/usr/bin/steamos-factory-reset-config", &[""]).await; @@ -327,7 +325,7 @@ impl SteamOSManager { /// A version property. #[zbus(property(emits_changed_signal = "const"))] async fn version(&self) -> u32 { - SteamOSManager::API_VERSION + API_VERSION } } @@ -472,7 +470,7 @@ mod test { async fn version() { let test = start("Version").await; let proxy = VersionProxy::new(&test.connection).await.unwrap(); - assert_eq!(proxy.version().await, Ok(SteamOSManager::API_VERSION)); + assert_eq!(proxy.version().await, Ok(API_VERSION)); } fn collect_methods<'a>(methods: &'a [Method<'a>]) -> HashMap> { From bf521a7bbb2b36783895d448aa3d0cb28fa4906c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 24 Apr 2024 17:04:53 -0700 Subject: [PATCH 05/11] user_manager: Start bringing up user manager --- com.steampowered.SteamOSManager1.xml | 21 +++++++++++++++++++ src/main.rs | 3 ++- src/user.rs | 6 ++++++ src/user_manager.rs | 30 ++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/user_manager.rs diff --git a/com.steampowered.SteamOSManager1.xml b/com.steampowered.SteamOSManager1.xml index 6121e85..e79f94c 100644 --- a/com.steampowered.SteamOSManager1.xml +++ b/com.steampowered.SteamOSManager1.xml @@ -254,4 +254,25 @@ + + + + + + + + diff --git a/src/main.rs b/src/main.rs index 08f6dad..f479d17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,12 +25,13 @@ mod root; mod sls; mod systemd; mod user; +mod user_manager; mod wifi; #[cfg(test)] mod testing; -const API_VERSION: u32 = 7; +const API_VERSION: u32 = 8; trait Service where diff --git a/src/user.rs b/src/user.rs index 183691a..b805030 100644 --- a/src/user.rs +++ b/src/user.rs @@ -13,12 +13,18 @@ use zbus::connection::Connection; use zbus::ConnectionBuilder; use crate::daemon::Daemon; +use crate::user_manager::SteamOSManagerUser; async fn create_connection() -> Result { let connection = ConnectionBuilder::session()? .name("com.steampowered.SteamOSManager1")? .build() .await?; + let manager = SteamOSManagerUser::new(connection.clone()).await?; + connection + .object_server() + .at("/com/steampowered/SteamOSManager1/User", manager) + .await?; Ok(connection) } diff --git a/src/user_manager.rs b/src/user_manager.rs new file mode 100644 index 0000000..462b161 --- /dev/null +++ b/src/user_manager.rs @@ -0,0 +1,30 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * Copyright © 2024 Igalia S.L. + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::Result; +use zbus::{interface, Connection}; + +use crate::API_VERSION; + +pub struct SteamOSManagerUser { + connection: Connection, +} + +impl SteamOSManagerUser { + pub async fn new(connection: Connection) -> Result { + Ok(SteamOSManagerUser { connection }) + } +} + +#[interface(name = "com.steampowered.SteamOSManager1.UserManager")] +impl SteamOSManagerUser { + #[zbus(property(emits_changed_signal = "const"))] + async fn version(&self) -> u32 { + API_VERSION + } +} From 1dcdfb2b238c7fb0ea4d9b32e3f33d245571962f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 25 Apr 2024 18:28:52 -0700 Subject: [PATCH 06/11] cec: Add module and interface for accessing/controlling state --- com.steampowered.SteamOSManager1.xml | 15 +++- src/cec.rs | 102 +++++++++++++++++++++++++++ src/main.rs | 1 + src/user.rs | 2 +- src/user_manager.rs | 31 +++++++- 5 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 src/cec.rs diff --git a/com.steampowered.SteamOSManager1.xml b/com.steampowered.SteamOSManager1.xml index e79f94c..cb77bb2 100644 --- a/com.steampowered.SteamOSManager1.xml +++ b/com.steampowered.SteamOSManager1.xml @@ -258,10 +258,21 @@ com.steampowered.SteamOSManager1.UserManager @short_description: Interface to control various aspects of SteamOS running as a user. - Version available: 8 + Version available: 8 --> + + + diff --git a/src/cec.rs b/src/cec.rs new file mode 100644 index 0000000..9372f35 --- /dev/null +++ b/src/cec.rs @@ -0,0 +1,102 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * Copyright © 2024 Igalia S.L. + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::Result; +use std::fmt; +use zbus::Connection; + +use crate::systemd::{daemon_reload, EnableState, SystemdUnit}; + +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum HdmiCecState { + Disabled = 0, + ControlOnly = 1, + ControlAndWake = 2, +} + +impl TryFrom for HdmiCecState { + type Error = &'static str; + fn try_from(v: u32) -> Result { + match v { + x if x == HdmiCecState::Disabled as u32 => Ok(HdmiCecState::Disabled), + x if x == HdmiCecState::ControlOnly as u32 => Ok(HdmiCecState::ControlOnly), + x if x == HdmiCecState::ControlAndWake as u32 => Ok(HdmiCecState::ControlAndWake), + _ => Err("No enum match for value {v}"), + } + } +} + +impl fmt::Display for HdmiCecState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + HdmiCecState::Disabled => write!(f, "Disabled"), + HdmiCecState::ControlOnly => write!(f, "ControlOnly"), + HdmiCecState::ControlAndWake => write!(f, "ControlAndWake"), + } + } +} + +pub struct HdmiCecControl<'dbus> { + plasma_rc_unit: SystemdUnit<'dbus>, + wakehook_unit: SystemdUnit<'dbus>, + connection: Connection, +} + +impl<'dbus> HdmiCecControl<'dbus> { + pub async fn new(connection: &Connection) -> Result> { + Ok(HdmiCecControl { + plasma_rc_unit: SystemdUnit::new( + connection.clone(), + "plasma-remotecontrollers.service", + ) + .await?, + wakehook_unit: SystemdUnit::new(connection.clone(), "wakehook.service").await?, + connection: connection.clone(), + }) + } + + pub async fn get_enabled_state(&self) -> Result { + Ok(match self.plasma_rc_unit.enabled().await? { + EnableState::Enabled | EnableState::Static => { + match self.wakehook_unit.enabled().await? { + EnableState::Enabled | EnableState::Static => HdmiCecState::ControlAndWake, + _ => HdmiCecState::ControlOnly, + } + } + _ => HdmiCecState::Disabled, + }) + } + + pub async fn set_enabled_state(&self, state: HdmiCecState) -> Result<()> { + match state { + HdmiCecState::Disabled => { + self.plasma_rc_unit.mask().await?; + self.plasma_rc_unit.stop().await?; + self.wakehook_unit.mask().await?; + self.wakehook_unit.stop().await?; + daemon_reload(&self.connection).await?; + } + HdmiCecState::ControlOnly => { + self.wakehook_unit.mask().await?; + self.wakehook_unit.stop().await?; + self.plasma_rc_unit.unmask().await?; + daemon_reload(&self.connection).await?; + self.plasma_rc_unit.start().await?; + } + HdmiCecState::ControlAndWake => { + self.plasma_rc_unit.unmask().await?; + self.wakehook_unit.unmask().await?; + daemon_reload(&self.connection).await?; + self.plasma_rc_unit.start().await?; + self.wakehook_unit.start().await?; + } + }; + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index f479d17..b7e444c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use tokio::signal::unix::{signal, SignalKind}; use tokio_util::sync::CancellationToken; use tracing::{info, warn}; +mod cec; mod daemon; mod ds_inhibit; mod hardware; diff --git a/src/user.rs b/src/user.rs index b805030..29cc8f9 100644 --- a/src/user.rs +++ b/src/user.rs @@ -43,7 +43,7 @@ pub async fn daemon() -> Result<()> { bail!(e); } }; - let session = match create_connection().await { + let _session = match create_connection().await { Ok(c) => c, Err(e) => { let _guard = tracing::subscriber::set_default(subscriber); diff --git a/src/user_manager.rs b/src/user_manager.rs index 462b161..ce93ddf 100644 --- a/src/user_manager.rs +++ b/src/user_manager.rs @@ -7,22 +7,49 @@ */ use anyhow::Result; +use tracing::error; use zbus::{interface, Connection}; -use crate::API_VERSION; +use crate::{to_zbus_error, to_zbus_fdo_error, API_VERSION}; +use crate::cec::{HdmiCecControl, HdmiCecState}; pub struct SteamOSManagerUser { connection: Connection, + hdmi_cec: HdmiCecControl<'static>, } impl SteamOSManagerUser { pub async fn new(connection: Connection) -> Result { - Ok(SteamOSManagerUser { connection }) + Ok(SteamOSManagerUser { + hdmi_cec: HdmiCecControl::new(&connection).await?, + connection, + }) } } #[interface(name = "com.steampowered.SteamOSManager1.UserManager")] impl SteamOSManagerUser { + #[zbus(property(emits_changed_signal = "false"))] + async fn hdmi_cec_state(&self) -> zbus::fdo::Result { + match self.hdmi_cec.get_enabled_state().await { + Ok(state) => Ok(state as u32), + Err(e) => Err(to_zbus_fdo_error(e)), + } + } + + #[zbus(property)] + async fn set_hdmi_cec_state(&self, state: u32) -> zbus::Result<()> { + let state = match HdmiCecState::try_from(state) { + Ok(state) => state, + Err(err) => return Err(zbus::fdo::Error::InvalidArgs(err.to_string()).into()), + }; + self.hdmi_cec + .set_enabled_state(state) + .await + .inspect_err(|message| error!("Error setting CEC state: {message}")) + .map_err(to_zbus_error) + } + #[zbus(property(emits_changed_signal = "const"))] async fn version(&self) -> u32 { API_VERSION From 42443b872f9332327cfa5eebc79ebd78d21f697a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 29 Apr 2024 20:18:28 -0700 Subject: [PATCH 07/11] user_manager: Act as transparent relay for messages that need the root daemon --- com.steampowered.SteamOSManager1.xml | 23 -- src/manager.rs | 16 +- src/user.rs | 10 +- src/user_manager.rs | 328 ++++++++++++++++++++++++++- 4 files changed, 333 insertions(+), 44 deletions(-) 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()); + } } } From 9339169d27178cbd32937526e41634ad95449fa4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 1 May 2024 20:11:00 -0700 Subject: [PATCH 08/11] data: Add user systemd service --- data/user-steamos-manager.service | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 data/user-steamos-manager.service diff --git a/data/user-steamos-manager.service b/data/user-steamos-manager.service new file mode 100644 index 0000000..c147e40 --- /dev/null +++ b/data/user-steamos-manager.service @@ -0,0 +1,11 @@ +[Unit] +Description=SteamOS Manager Daemon +After=gamescope.service + +[Service] +Type=dbus +BusName=com.steampowered.SteamOSManager1 +Environment=RUST_LOG='INFO' +ExecStart=/usr/lib/steamos-manager +Restart=on-failure +RestartSec=1 From 55b2acb533513daff79bf37a69ba2adcf47062fe Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 May 2024 17:27:12 -0700 Subject: [PATCH 09/11] Fix warnings --- src/manager.rs | 2 +- src/user_manager.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 84eb5fc..2a69fa8 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -333,7 +333,7 @@ impl SteamOSManager { mod test { use super::*; use crate::{power, testing}; - use std::collections::{HashMap, HashSet}; + use std::collections::HashMap; use std::iter::zip; use tokio::fs::{create_dir_all, read, write}; use zbus::{Connection, ConnectionBuilder, Interface}; diff --git a/src/user_manager.rs b/src/user_manager.rs index a57c470..f798e85 100644 --- a/src/user_manager.rs +++ b/src/user_manager.rs @@ -50,7 +50,6 @@ macro_rules! setter { } pub struct SteamOSManagerUser { - connection: Connection, proxy: Proxy<'static>, hdmi_cec: HdmiCecControl<'static>, } @@ -59,7 +58,6 @@ impl SteamOSManagerUser { 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", @@ -248,10 +246,10 @@ impl SteamOSManagerUser { #[cfg(test)] mod test { use super::*; - use crate::{power, testing}; + use crate::testing; use std::collections::{HashMap, HashSet}; use std::iter::zip; - use tokio::fs::{create_dir_all, read, write}; + use tokio::fs::read; use zbus::{Connection, ConnectionBuilder, Interface}; use zbus_xml::{Method, Node, Property}; From 752ebfc2970c3d2d1205e4da1abbcd3153a4c327 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 3 May 2024 18:53:26 -0700 Subject: [PATCH 10/11] user_manager: Don't cache properties --- src/user_manager.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/user_manager.rs b/src/user_manager.rs index f798e85..a3d1f59 100644 --- a/src/user_manager.rs +++ b/src/user_manager.rs @@ -8,6 +8,7 @@ use anyhow::Result; use tracing::error; +use zbus::proxy::Builder; use zbus::zvariant::Fd; use zbus::{interface, Connection, Proxy, SignalContext}; @@ -58,13 +59,13 @@ impl SteamOSManagerUser { pub async fn new(connection: Connection, system_conn: &Connection) -> Result { Ok(SteamOSManagerUser { hdmi_cec: HdmiCecControl::new(&connection).await?, - proxy: Proxy::new( - system_conn, - "com.steampowered.SteamOSManager1", - "/com/steampowered/SteamOSManager1", - "com.steampowered.SteamOSManager1.Manager", - ) - .await?, + proxy: Builder::new(system_conn) + .destination("com.steampowered.SteamOSManager1")? + .path("/com/steampowered/SteamOSManager1")? + .interface("com.steampowered.SteamOSManager1.Manager")? + .cache_properties(zbus::CacheProperties::No) + .build() + .await?, }) } } From 398445296d1593c5e63ce3626430741feb469ef8 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 6 May 2024 13:26:40 -0700 Subject: [PATCH 11/11] data: Split user and system data files into respective directories, add user D-Bus unit --- data/{ => system}/com.steampowered.SteamOSManager1.conf | 0 data/{ => system}/com.steampowered.SteamOSManager1.service | 0 data/{ => system}/steamos-manager.service | 0 data/user/com.steampowered.SteamOSManager1.service | 6 ++++++ .../steamos-manager.service} | 0 5 files changed, 6 insertions(+) rename data/{ => system}/com.steampowered.SteamOSManager1.conf (100%) rename data/{ => system}/com.steampowered.SteamOSManager1.service (100%) rename data/{ => system}/steamos-manager.service (100%) create mode 100644 data/user/com.steampowered.SteamOSManager1.service rename data/{user-steamos-manager.service => user/steamos-manager.service} (100%) diff --git a/data/com.steampowered.SteamOSManager1.conf b/data/system/com.steampowered.SteamOSManager1.conf similarity index 100% rename from data/com.steampowered.SteamOSManager1.conf rename to data/system/com.steampowered.SteamOSManager1.conf diff --git a/data/com.steampowered.SteamOSManager1.service b/data/system/com.steampowered.SteamOSManager1.service similarity index 100% rename from data/com.steampowered.SteamOSManager1.service rename to data/system/com.steampowered.SteamOSManager1.service diff --git a/data/steamos-manager.service b/data/system/steamos-manager.service similarity index 100% rename from data/steamos-manager.service rename to data/system/steamos-manager.service diff --git a/data/user/com.steampowered.SteamOSManager1.service b/data/user/com.steampowered.SteamOSManager1.service new file mode 100644 index 0000000..fccfb36 --- /dev/null +++ b/data/user/com.steampowered.SteamOSManager1.service @@ -0,0 +1,6 @@ +[D-BUS Service] +Name=com.steampowered.SteamOSManager1 +Exec=/usr/lib/steamos-manager +User=root +SystemdService=steamos-manager.service + diff --git a/data/user-steamos-manager.service b/data/user/steamos-manager.service similarity index 100% rename from data/user-steamos-manager.service rename to data/user/steamos-manager.service