From 1dcdfb2b238c7fb0ea4d9b32e3f33d245571962f Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 25 Apr 2024 18:28:52 -0700 Subject: [PATCH] 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