Rust · 2665 bytes Raw Blame History
1 //! Power management via systemd-logind
2 //!
3 //! Implements shutdown, reboot, and suspend via D-Bus calls to logind.
4
5 use anyhow::{Context, Result};
6 use zbus::blocking::Connection;
7
8 /// Power action to perform
9 #[derive(Debug, Clone, Copy)]
10 pub enum PowerAction {
11 Shutdown,
12 Reboot,
13 Suspend,
14 }
15
16 impl PowerAction {
17 /// Get the logind method name for this action
18 fn method_name(&self) -> &'static str {
19 match self {
20 PowerAction::Shutdown => "PowerOff",
21 PowerAction::Reboot => "Reboot",
22 PowerAction::Suspend => "Suspend",
23 }
24 }
25 }
26
27 /// Execute a power action via logind
28 pub fn execute(action: PowerAction) -> Result<()> {
29 let conn = Connection::system().context("Failed to connect to system D-Bus")?;
30
31 // Call org.freedesktop.login1.Manager.<Action>(interactive: bool)
32 // interactive=false means no polkit prompt
33 conn.call_method(
34 Some("org.freedesktop.login1"),
35 "/org/freedesktop/login1",
36 Some("org.freedesktop.login1.Manager"),
37 action.method_name(),
38 &(false,), // interactive = false
39 )
40 .context(format!("Failed to execute {:?}", action))?;
41
42 Ok(())
43 }
44
45 /// Async version of execute using zbus async API
46 pub async fn execute_async(action: PowerAction) -> Result<()> {
47 let conn = zbus::Connection::system()
48 .await
49 .context("Failed to connect to system D-Bus")?;
50
51 conn.call_method(
52 Some("org.freedesktop.login1"),
53 "/org/freedesktop/login1",
54 Some("org.freedesktop.login1.Manager"),
55 action.method_name(),
56 &(false,),
57 )
58 .await
59 .context(format!("Failed to execute {:?}", action))?;
60
61 Ok(())
62 }
63
64 /// Check if a power action is allowed (can be performed without authentication)
65 pub async fn can_execute(action: PowerAction) -> Result<bool> {
66 let conn = zbus::Connection::system()
67 .await
68 .context("Failed to connect to system D-Bus")?;
69
70 let can_method = match action {
71 PowerAction::Shutdown => "CanPowerOff",
72 PowerAction::Reboot => "CanReboot",
73 PowerAction::Suspend => "CanSuspend",
74 };
75
76 let reply: zbus::Message = conn
77 .call_method(
78 Some("org.freedesktop.login1"),
79 "/org/freedesktop/login1",
80 Some("org.freedesktop.login1.Manager"),
81 can_method,
82 &(),
83 )
84 .await
85 .context(format!("Failed to check {:?} capability", action))?;
86
87 let result: String = reply.body().deserialize().context("Failed to parse reply")?;
88
89 // Returns "yes", "no", "challenge" (needs auth), or "na" (not applicable)
90 Ok(result == "yes")
91 }
92