mirror of
https://gitlab.steamos.cloud/holo/steamos-manager.git
synced 2025-07-15 18:56:49 -04:00
Merge branch 'work/sitter/sessionmgmt' into 'master'
implement SessionManagement1 See merge request holo/steamos-manager!9
This commit is contained in:
commit
f85317b43b
10 changed files with 445 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1064,6 +1064,7 @@ dependencies = [
|
||||||
"nix 0.30.1",
|
"nix 0.30.1",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"regex",
|
"regex",
|
||||||
|
"rust-ini",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
|
|
|
@ -21,6 +21,7 @@ libc = "0.2"
|
||||||
nix = { version = "0.30", default-features = false, features = ["fs", "poll", "signal", "time"] }
|
nix = { version = "0.30", default-features = false, features = ["fs", "poll", "signal", "time"] }
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
rust-ini = "0.21"
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
strum = { version = "0.27", features = ["derive"] }
|
strum = { version = "0.27", features = ["derive"] }
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
Copyright © 2023 Collabora Ltd.
|
Copyright © 2023 Collabora Ltd.
|
||||||
Copyright © 2024 Igalia S.L.
|
Copyright © 2024 Igalia S.L.
|
||||||
Copyright © 2024 Valve Corporation.
|
Copyright © 2024 Valve Corporation.
|
||||||
|
Copyright © 2025 Harald Sitter <sitter@kde.org>
|
||||||
SPDX-License-Identifier: MIT
|
SPDX-License-Identifier: MIT
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
@ -681,4 +682,63 @@
|
||||||
|
|
||||||
</interface>
|
</interface>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
com.steampowered.SteamOSManager1.SessionManagement1
|
||||||
|
@short_description: Interface to manage session types.
|
||||||
|
|
||||||
|
Valid values include all sessions available in /usr/share/xsessions and /usr/share/wayland-sessions.
|
||||||
|
-->
|
||||||
|
<interface name="com.steampowered.SteamOSManager1.SessionManagement1">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
DefaultSessionType:
|
||||||
|
|
||||||
|
The default session type to use when booting the system.
|
||||||
|
|
||||||
|
Valid values include all sessions available in /usr/share/xsessions and /usr/share/wayland-sessions.
|
||||||
|
-->
|
||||||
|
<property name="DefaultSessionType" type="s" access="readwrite"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
DefaultDesktopSessionType:
|
||||||
|
|
||||||
|
The default session type to use when starting desktop mode.
|
||||||
|
|
||||||
|
Valid values include all sessions available in /usr/share/xsessions and /usr/share/wayland-sessions.
|
||||||
|
-->
|
||||||
|
<property name="DefaultDesktopSessionType" type="s" access="readwrite"/>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SwitchToSession:
|
||||||
|
|
||||||
|
Switch to the provided session type. This will change the session type temporarily and stop the running
|
||||||
|
session. The login manager will then log into the new session type.
|
||||||
|
|
||||||
|
Note that resetting this temporary type needs to happen as part of the login. steamos-manager will not
|
||||||
|
automatically reset the session type on its own!
|
||||||
|
|
||||||
|
Valid values include all sessions available in /usr/share/xsessions and /usr/share/wayland-sessions.
|
||||||
|
-->
|
||||||
|
<method name="SwitchToSession">
|
||||||
|
<arg type="s" name="type" direction="in"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SwitchToGameMode:
|
||||||
|
|
||||||
|
Convenience method to switch to the game mode session type.
|
||||||
|
-->
|
||||||
|
<method name="SwitchToGameMode">
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
SwitchToDesktopMode:
|
||||||
|
|
||||||
|
Convenience method to switch to the desktop mode session type (adheres to DefaultDesktopSessionType)
|
||||||
|
-->
|
||||||
|
<method name="SwitchToDesktopMode">
|
||||||
|
</method>
|
||||||
|
|
||||||
|
</interface>
|
||||||
|
|
||||||
</node>
|
</node>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright © 2023 Collabora Ltd.
|
* Copyright © 2023 Collabora Ltd.
|
||||||
* Copyright © 2024 Valve Software
|
* Copyright © 2024 Valve Software
|
||||||
|
* Copyright © 2025 Harald Sitter <sitter@kde.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
@ -17,9 +18,9 @@ use steamos_manager::power::{CPUScalingGovernor, GPUPerformanceLevel, GPUPowerPr
|
||||||
use steamos_manager::proxy::{
|
use steamos_manager::proxy::{
|
||||||
AmbientLightSensor1Proxy, BatteryChargeLimit1Proxy, CpuScaling1Proxy, FactoryReset1Proxy,
|
AmbientLightSensor1Proxy, BatteryChargeLimit1Proxy, CpuScaling1Proxy, FactoryReset1Proxy,
|
||||||
FanControl1Proxy, GpuPerformanceLevel1Proxy, GpuPowerProfile1Proxy, HdmiCec1Proxy,
|
FanControl1Proxy, GpuPerformanceLevel1Proxy, GpuPowerProfile1Proxy, HdmiCec1Proxy,
|
||||||
LowPowerMode1Proxy, Manager2Proxy, PerformanceProfile1Proxy, ScreenReader0Proxy, Storage1Proxy,
|
LowPowerMode1Proxy, Manager2Proxy, PerformanceProfile1Proxy, ScreenReader0Proxy,
|
||||||
TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy, WifiDebug1Proxy, WifiDebugDump1Proxy,
|
SessionManagement1Proxy, Storage1Proxy, TdpLimit1Proxy, UpdateBios1Proxy, UpdateDock1Proxy,
|
||||||
WifiPowerManagement1Proxy,
|
WifiDebug1Proxy, WifiDebugDump1Proxy, WifiPowerManagement1Proxy,
|
||||||
};
|
};
|
||||||
use steamos_manager::screenreader::{ScreenReaderAction, ScreenReaderMode};
|
use steamos_manager::screenreader::{ScreenReaderAction, ScreenReaderMode};
|
||||||
use steamos_manager::wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement};
|
use steamos_manager::wifi::{WifiBackend, WifiDebugMode, WifiPowerManagement};
|
||||||
|
@ -269,6 +270,36 @@ enum Commands {
|
||||||
/// `toggle_mode`
|
/// `toggle_mode`
|
||||||
action: ScreenReaderAction,
|
action: ScreenReaderAction,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Switch to desktop mode
|
||||||
|
SwitchToDesktopMode,
|
||||||
|
|
||||||
|
/// Switch to game mode
|
||||||
|
SwitchToGameMode,
|
||||||
|
|
||||||
|
/// Switch to a specific session type
|
||||||
|
SwitchToSession {
|
||||||
|
/// The session type to switch to, e.g. `plasma`, `gamescope`.
|
||||||
|
ty: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Default desktop session type
|
||||||
|
GetDefaultDesktopSessionType,
|
||||||
|
|
||||||
|
/// Set default desktop session type
|
||||||
|
SetDefaultDesktopSessionType {
|
||||||
|
/// The session type to set as default, e.g. `plasma`, `plasmax11`.
|
||||||
|
ty: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Default session type
|
||||||
|
GetDefaultSessionType,
|
||||||
|
|
||||||
|
/// Set default session type
|
||||||
|
SetDefaultSessionType {
|
||||||
|
/// The session type to set as default, e.g. `plasma`, `gamescope`.
|
||||||
|
ty: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_all_properties(conn: &Connection) -> Result<()> {
|
async fn get_all_properties(conn: &Connection) -> Result<()> {
|
||||||
|
@ -638,6 +669,36 @@ async fn main() -> Result<()> {
|
||||||
.trigger_action(*action as u32, now.try_into()?)
|
.trigger_action(*action as u32, now.try_into()?)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
Commands::SwitchToDesktopMode => {
|
||||||
|
let proxy = SessionManagement1Proxy::new(&conn).await?;
|
||||||
|
proxy.switch_to_desktop_mode().await?;
|
||||||
|
}
|
||||||
|
Commands::SwitchToGameMode => {
|
||||||
|
let proxy = SessionManagement1Proxy::new(&conn).await?;
|
||||||
|
proxy.switch_to_game_mode().await?;
|
||||||
|
}
|
||||||
|
Commands::SwitchToSession { ty } => {
|
||||||
|
let proxy = SessionManagement1Proxy::new(&conn).await?;
|
||||||
|
proxy.switch_to_session(ty.as_str()).await?;
|
||||||
|
}
|
||||||
|
Commands::GetDefaultDesktopSessionType => {
|
||||||
|
let proxy = SessionManagement1Proxy::new(&conn).await?;
|
||||||
|
let ty = proxy.default_desktop_session_type().await?;
|
||||||
|
println!("Default desktop session type: {ty}");
|
||||||
|
}
|
||||||
|
Commands::SetDefaultDesktopSessionType { ty } => {
|
||||||
|
let proxy = SessionManagement1Proxy::new(&conn).await?;
|
||||||
|
proxy.set_default_desktop_session_type(ty.as_str()).await?;
|
||||||
|
}
|
||||||
|
Commands::GetDefaultSessionType => {
|
||||||
|
let proxy = SessionManagement1Proxy::new(&conn).await?;
|
||||||
|
let ty = proxy.default_session_type().await?;
|
||||||
|
println!("Default session type: {ty}");
|
||||||
|
}
|
||||||
|
Commands::SetDefaultSessionType { ty } => {
|
||||||
|
let proxy = SessionManagement1Proxy::new(&conn).await?;
|
||||||
|
proxy.set_default_session_type(ty.as_str()).await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -36,6 +36,7 @@ pub mod hardware;
|
||||||
pub mod power;
|
pub mod power;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub mod screenreader;
|
pub mod screenreader;
|
||||||
|
pub mod session_management;
|
||||||
pub mod wifi;
|
pub mod wifi;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* Copyright © 2023 Collabora Ltd.
|
* Copyright © 2023 Collabora Ltd.
|
||||||
* Copyright © 2024 Valve Software
|
* Copyright © 2024 Valve Software
|
||||||
* Copyright © 2024 Igalia S.L.
|
* Copyright © 2024 Igalia S.L.
|
||||||
|
* Copyright © 2025 Harald Sitter <sitter@kde.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
@ -33,6 +34,9 @@ use crate::power::{
|
||||||
GPUPerformanceLevel, GPUPowerProfile, SysfsWritten, TdpLimitManager,
|
GPUPerformanceLevel, GPUPowerProfile, SysfsWritten, TdpLimitManager,
|
||||||
};
|
};
|
||||||
use crate::process::{run_script, script_output};
|
use crate::process::{run_script, script_output};
|
||||||
|
use crate::session_management::{
|
||||||
|
clear_ephemeral_session, set_session_to_switch_to, write_default_desktop_session_type, write_default_session_type
|
||||||
|
};
|
||||||
use crate::wifi::{
|
use crate::wifi::{
|
||||||
extract_wifi_trace, generate_wifi_dump, set_wifi_backend, set_wifi_debug_mode,
|
extract_wifi_trace, generate_wifi_dump, set_wifi_backend, set_wifi_debug_mode,
|
||||||
set_wifi_power_management_state, WifiBackend, WifiDebugMode, WifiPowerManagement,
|
set_wifi_power_management_state, WifiBackend, WifiDebugMode, WifiPowerManagement,
|
||||||
|
@ -488,6 +492,30 @@ impl SteamOSManager {
|
||||||
.map_err(to_zbus_fdo_error)
|
.map_err(to_zbus_fdo_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn set_session_to_switch_to(&self, ty: &str) -> fdo::Result<()> {
|
||||||
|
set_session_to_switch_to(ty)
|
||||||
|
.await
|
||||||
|
.inspect_err(|message| error!("Error switching to session: {message}"))
|
||||||
|
.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_default_desktop_session_type(&mut self, ty: &str) -> fdo::Result<()> {
|
||||||
|
write_default_desktop_session_type(ty)
|
||||||
|
.await
|
||||||
|
.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_default_session_type(&mut self, ty: &str) -> fdo::Result<()> {
|
||||||
|
write_default_session_type(ty)
|
||||||
|
.await
|
||||||
|
.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_login_successful(&mut self) -> fdo::Result<()> {
|
||||||
|
clear_ephemeral_session().await
|
||||||
|
.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
/// A version property.
|
/// A version property.
|
||||||
#[zbus(property(emits_changed_signal = "const"))]
|
#[zbus(property(emits_changed_signal = "const"))]
|
||||||
async fn version(&self) -> u32 {
|
async fn version(&self) -> u32 {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* Copyright © 2023 Collabora Ltd.
|
* Copyright © 2023 Collabora Ltd.
|
||||||
* Copyright © 2024 Valve Software
|
* Copyright © 2024 Valve Software
|
||||||
* Copyright © 2024 Igalia S.L.
|
* Copyright © 2024 Igalia S.L.
|
||||||
|
* Copyright © 2025 Harald Sitter <sitter@kde.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
@ -35,6 +36,9 @@ use crate::power::{
|
||||||
get_max_charge_level, get_platform_profile, TdpManagerCommand,
|
get_max_charge_level, get_platform_profile, TdpManagerCommand,
|
||||||
};
|
};
|
||||||
use crate::screenreader::{OrcaManager, ScreenReaderAction, ScreenReaderMode};
|
use crate::screenreader::{OrcaManager, ScreenReaderAction, ScreenReaderMode};
|
||||||
|
use crate::session_management::{
|
||||||
|
read_default_desktop_session_type, read_default_session_type, restart_session,
|
||||||
|
};
|
||||||
use crate::wifi::{
|
use crate::wifi::{
|
||||||
get_wifi_backend, get_wifi_power_management_state, list_wifi_interfaces, WifiBackend,
|
get_wifi_backend, get_wifi_power_management_state, list_wifi_interfaces, WifiBackend,
|
||||||
};
|
};
|
||||||
|
@ -157,6 +161,11 @@ struct PerformanceProfile1 {
|
||||||
tdp_limit_manager: Option<UnboundedSender<TdpManagerCommand>>,
|
tdp_limit_manager: Option<UnboundedSender<TdpManagerCommand>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SessionManagement1 {
|
||||||
|
proxy: Proxy<'static>,
|
||||||
|
connection: Connection,
|
||||||
|
}
|
||||||
|
|
||||||
struct ScreenReader0 {
|
struct ScreenReader0 {
|
||||||
screen_reader: OrcaManager<'static>,
|
screen_reader: OrcaManager<'static>,
|
||||||
}
|
}
|
||||||
|
@ -723,6 +732,65 @@ impl ScreenReader0 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[interface(name = "com.steampowered.SteamOSManager1.SessionManagement1")]
|
||||||
|
impl SessionManagement1 {
|
||||||
|
async fn switch_to_desktop_mode(&self) -> fdo::Result<()> {
|
||||||
|
self.switch_to_session(
|
||||||
|
read_default_desktop_session_type()
|
||||||
|
.await
|
||||||
|
.map_err(to_zbus_fdo_error)?
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn switch_to_game_mode(&self) -> fdo::Result<()> {
|
||||||
|
self.switch_to_session("gamescope-wayland").await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn switch_to_session(&self, ty: &str) -> fdo::Result<()> {
|
||||||
|
let _: () = method!(self, "SetSessionToSwitchTo", ty)?;
|
||||||
|
restart_session(&self.connection)
|
||||||
|
.await
|
||||||
|
.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn default_desktop_session_type(&self) -> fdo::Result<String> {
|
||||||
|
read_default_desktop_session_type()
|
||||||
|
.await
|
||||||
|
.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn set_default_desktop_session_type(
|
||||||
|
&self,
|
||||||
|
ty: &str,
|
||||||
|
#[zbus(signal_emitter)] ctx: SignalEmitter<'_>,
|
||||||
|
) -> zbus::Result<()> {
|
||||||
|
let _: () = self
|
||||||
|
.proxy
|
||||||
|
.call("SetDefaultDesktopSessionType", &(ty))
|
||||||
|
.await?;
|
||||||
|
self.default_desktop_session_type_changed(&ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn default_session_type(&self) -> fdo::Result<String> {
|
||||||
|
read_default_session_type().await.map_err(to_zbus_fdo_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property)]
|
||||||
|
async fn set_default_session_type(
|
||||||
|
&self,
|
||||||
|
ty: &str,
|
||||||
|
#[zbus(signal_emitter)] ctx: SignalEmitter<'_>,
|
||||||
|
) -> zbus::Result<()> {
|
||||||
|
let _: () = self.proxy.call("SetDefaultSessionType", &(ty)).await?;
|
||||||
|
self.default_session_type_changed(&ctx).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[interface(name = "com.steampowered.SteamOSManager1.Storage1")]
|
#[interface(name = "com.steampowered.SteamOSManager1.Storage1")]
|
||||||
impl Storage1 {
|
impl Storage1 {
|
||||||
async fn format_device(
|
async fn format_device(
|
||||||
|
@ -1087,6 +1155,10 @@ pub(crate) async fn create_interfaces(
|
||||||
channel: daemon,
|
channel: daemon,
|
||||||
};
|
};
|
||||||
let screen_reader = ScreenReader0::new(&session).await?;
|
let screen_reader = ScreenReader0::new(&session).await?;
|
||||||
|
let session_management = SessionManagement1 {
|
||||||
|
proxy: proxy.clone(),
|
||||||
|
connection: session.clone(),
|
||||||
|
};
|
||||||
let wifi_debug = WifiDebug1 {
|
let wifi_debug = WifiDebug1 {
|
||||||
proxy: proxy.clone(),
|
proxy: proxy.clone(),
|
||||||
};
|
};
|
||||||
|
@ -1144,6 +1216,8 @@ pub(crate) async fn create_interfaces(
|
||||||
object_server.at(MANAGER_PATH, screen_reader).await?;
|
object_server.at(MANAGER_PATH, screen_reader).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object_server.at(MANAGER_PATH, session_management).await?;
|
||||||
|
|
||||||
if steam_deck_variant().await.unwrap_or_default() == SteamDeckVariant::Galileo {
|
if steam_deck_variant().await.unwrap_or_default() == SteamDeckVariant::Galileo {
|
||||||
object_server.at(MANAGER_PATH, wifi_debug).await?;
|
object_server.at(MANAGER_PATH, wifi_debug).await?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ mod low_power_mode1;
|
||||||
mod manager2;
|
mod manager2;
|
||||||
mod performance_profile1;
|
mod performance_profile1;
|
||||||
mod screenreader0;
|
mod screenreader0;
|
||||||
|
mod session_management1;
|
||||||
mod storage1;
|
mod storage1;
|
||||||
mod tdp_limit1;
|
mod tdp_limit1;
|
||||||
mod update_bios1;
|
mod update_bios1;
|
||||||
|
@ -45,6 +46,7 @@ pub use crate::proxy::low_power_mode1::LowPowerMode1Proxy;
|
||||||
pub use crate::proxy::manager2::Manager2Proxy;
|
pub use crate::proxy::manager2::Manager2Proxy;
|
||||||
pub use crate::proxy::performance_profile1::PerformanceProfile1Proxy;
|
pub use crate::proxy::performance_profile1::PerformanceProfile1Proxy;
|
||||||
pub use crate::proxy::screenreader0::ScreenReader0Proxy;
|
pub use crate::proxy::screenreader0::ScreenReader0Proxy;
|
||||||
|
pub use crate::proxy::session_management1::SessionManagement1Proxy;
|
||||||
pub use crate::proxy::storage1::Storage1Proxy;
|
pub use crate::proxy::storage1::Storage1Proxy;
|
||||||
pub use crate::proxy::tdp_limit1::TdpLimit1Proxy;
|
pub use crate::proxy::tdp_limit1::TdpLimit1Proxy;
|
||||||
pub use crate::proxy::update_bios1::UpdateBios1Proxy;
|
pub use crate::proxy::update_bios1::UpdateBios1Proxy;
|
||||||
|
|
42
src/proxy/session_management1.rs
Normal file
42
src/proxy/session_management1.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
//! # D-Bus interface proxy for: `com.steampowered.SteamOSManager1.SessionManagement1`
|
||||||
|
//!
|
||||||
|
//! This code was generated by `zbus-xmlgen` `5.1.0` from D-Bus introspection data.
|
||||||
|
//! Source: `com.steampowered.SteamOSManager1.xml`.
|
||||||
|
//!
|
||||||
|
//! You may prefer to adapt it, instead of using it verbatim.
|
||||||
|
//!
|
||||||
|
//! More information can be found in the [Writing a client proxy] section of the zbus
|
||||||
|
//! documentation.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! [Writing a client proxy]: https://dbus2.github.io/zbus/client.html
|
||||||
|
//! [D-Bus standard interfaces]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces,
|
||||||
|
use zbus::proxy;
|
||||||
|
#[proxy(
|
||||||
|
interface = "com.steampowered.SteamOSManager1.SessionManagement1",
|
||||||
|
default_service = "com.steampowered.SteamOSManager1",
|
||||||
|
default_path = "/com/steampowered/SteamOSManager1",
|
||||||
|
assume_defaults = true
|
||||||
|
)]
|
||||||
|
pub trait SessionManagement1 {
|
||||||
|
/// SwitchToDesktopMode method
|
||||||
|
fn switch_to_desktop_mode(&self) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// SwitchToGameMode method
|
||||||
|
fn switch_to_game_mode(&self) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// SwitchToSession method
|
||||||
|
fn switch_to_session(&self, type_: &str) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// DefaultDesktopSessionType property
|
||||||
|
#[zbus(property)]
|
||||||
|
fn default_desktop_session_type(&self) -> zbus::Result<String>;
|
||||||
|
#[zbus(property)]
|
||||||
|
fn set_default_desktop_session_type(&self, value: &str) -> zbus::Result<()>;
|
||||||
|
|
||||||
|
/// DefaultSessionType property
|
||||||
|
#[zbus(property)]
|
||||||
|
fn default_session_type(&self) -> zbus::Result<String>;
|
||||||
|
#[zbus(property)]
|
||||||
|
fn set_default_session_type(&self, value: &str) -> zbus::Result<()>;
|
||||||
|
}
|
172
src/session_management.rs
Normal file
172
src/session_management.rs
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2025 Harald Sitter <sitter@kde.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
use anyhow::{anyhow, bail, Result};
|
||||||
|
use ini::Ini;
|
||||||
|
use tracing::debug;
|
||||||
|
use std::path::Path;
|
||||||
|
use tokio::fs;
|
||||||
|
use zbus::{self, Connection};
|
||||||
|
|
||||||
|
use crate::systemd::SystemdUnit;
|
||||||
|
|
||||||
|
// This is our persistent store. It applies unless an ephemeral session is set.
|
||||||
|
const PERSISTENT_CONFIG_FILE: &str = "/etc/sddm.conf.d/yy-steamos-session.conf";
|
||||||
|
// The ephemeral session configuration is ordered AFTER the persistent one so it can temporarily override it.
|
||||||
|
const EPHEMERAL_CONFIG_FILE: &str = "/etc/sddm.conf.d/zz-steamos-autologin.conf";
|
||||||
|
const CONFIG_SECTION_STEAM: &str = "X-SteamOS";
|
||||||
|
const CONFIG_SECTION_AUTOLOGIN: &str = "Autologin";
|
||||||
|
const DEFAULT_DESKTOP_SESSION: &str = "plasmax11";
|
||||||
|
const DEFAULT_SESSION: &str = "gamescope-wayland";
|
||||||
|
const CONFIG_KEY_DEFAULT_DESKTOP_SESSION: &str = "DefaultDesktopSession";
|
||||||
|
const CONFIG_KEY_SESSION: &str = "Session";
|
||||||
|
|
||||||
|
async fn find_type_in_dir(
|
||||||
|
prefix: impl AsRef<Path>,
|
||||||
|
ty: &str
|
||||||
|
) -> Result<String> {
|
||||||
|
let type_without_suffix = ty.trim_end_matches(".desktop");
|
||||||
|
let expected_session = format!("{type_without_suffix}.desktop");
|
||||||
|
|
||||||
|
let mut dir = fs::read_dir(prefix.as_ref()).await?;
|
||||||
|
while let Some(entry) = dir.next_entry().await? {
|
||||||
|
let file_name = entry.file_name();
|
||||||
|
let session: &str = &file_name.to_string_lossy();
|
||||||
|
if session == &expected_session {
|
||||||
|
return Ok(expected_session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!( "Session type {ty} not found in directory {}",
|
||||||
|
prefix.as_ref().display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ensure_session_exists(ty: &str) -> Result<String> {
|
||||||
|
// Guard against bad input strings. Notably we don't want relative paths here as they would allow inspecting
|
||||||
|
// all root owned files.
|
||||||
|
// While we are at it also figure out if the session type has a one-shot variant and prefer that.
|
||||||
|
|
||||||
|
for dir in ["/usr/share/wayland-sessions", "/usr/share/xsessions"] {
|
||||||
|
match find_type_in_dir(dir, ty).await {
|
||||||
|
Ok(session) => return Ok(session),
|
||||||
|
Err(e) => debug!("{e}"), // output and try next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("Session type {ty} not found in any of the known session directories")
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ini_load_async(path: &str) -> Result<Ini> {
|
||||||
|
let data = tokio::fs::read_to_string(path).await?;
|
||||||
|
Ini::load_from_str(data.as_str()).map_err(|e| anyhow!("Failed to load INI from {path}: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_type_from_config(
|
||||||
|
config_file: &str,
|
||||||
|
config_section: &str,
|
||||||
|
config_key: &str,
|
||||||
|
default_session: &str,
|
||||||
|
) -> Result<String> {
|
||||||
|
let config = match ini_load_async(config_file).await {
|
||||||
|
Ok(config) => config,
|
||||||
|
_ => return Ok(default_session.to_owned()),
|
||||||
|
};
|
||||||
|
match config.section(Some(config_section)) {
|
||||||
|
Some(section) => {
|
||||||
|
let session = section.get(config_key);
|
||||||
|
return Ok(session.unwrap_or(default_session).to_owned());
|
||||||
|
}
|
||||||
|
None => return Ok(default_session.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_type_to_config(
|
||||||
|
config_file: &str,
|
||||||
|
config_section: &str,
|
||||||
|
config_key: &str,
|
||||||
|
session_name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut config = match ini_load_async(config_file).await {
|
||||||
|
Ok(config) => config,
|
||||||
|
_ => Ini::new(),
|
||||||
|
};
|
||||||
|
config
|
||||||
|
.with_section(Some(config_section))
|
||||||
|
.set(config_key, session_name);
|
||||||
|
config.write_to_file(config_file)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn set_session_to_switch_to(ty: &str) -> Result<()> {
|
||||||
|
ensure_session_exists(ty).await?;
|
||||||
|
write_type_to_config(
|
||||||
|
EPHEMERAL_CONFIG_FILE,
|
||||||
|
CONFIG_SECTION_AUTOLOGIN,
|
||||||
|
CONFIG_KEY_SESSION,
|
||||||
|
&ty,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn read_default_desktop_session_type() -> Result<String> {
|
||||||
|
read_type_from_config(
|
||||||
|
PERSISTENT_CONFIG_FILE,
|
||||||
|
CONFIG_SECTION_STEAM,
|
||||||
|
CONFIG_KEY_DEFAULT_DESKTOP_SESSION,
|
||||||
|
DEFAULT_DESKTOP_SESSION,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn write_default_desktop_session_type(ty: &str) -> Result<()> {
|
||||||
|
ensure_session_exists(ty).await?;
|
||||||
|
write_type_to_config(
|
||||||
|
PERSISTENT_CONFIG_FILE,
|
||||||
|
CONFIG_SECTION_STEAM,
|
||||||
|
CONFIG_KEY_DEFAULT_DESKTOP_SESSION,
|
||||||
|
&ty,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn read_default_session_type() -> Result<String> {
|
||||||
|
read_type_from_config(
|
||||||
|
PERSISTENT_CONFIG_FILE,
|
||||||
|
CONFIG_SECTION_AUTOLOGIN,
|
||||||
|
CONFIG_KEY_SESSION,
|
||||||
|
DEFAULT_SESSION,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn write_default_session_type(ty: &str) -> Result<()> {
|
||||||
|
ensure_session_exists(ty).await?;
|
||||||
|
write_type_to_config(
|
||||||
|
PERSISTENT_CONFIG_FILE,
|
||||||
|
CONFIG_SECTION_AUTOLOGIN,
|
||||||
|
CONFIG_KEY_SESSION,
|
||||||
|
ty,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn restart_session(connection: &Connection) -> Result<()> {
|
||||||
|
for service in ["plasma-workspace.target", "gamescope-session.service"] {
|
||||||
|
let unit = SystemdUnit::new(connection.clone(), service).await?;
|
||||||
|
unit.stop()
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!("Failed to stop {service}: {e}"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn clear_ephemeral_session() -> Result<()> {
|
||||||
|
tokio::fs::remove_file(EPHEMERAL_CONFIG_FILE)
|
||||||
|
.await
|
||||||
|
.map_err(|e| anyhow!("Failed to clear ephemeral session: {e}"))
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue