desk_logind/
lib.rs

1//! `systemd-logind` client library
2#![feature(backtrace)]
3use std::env;
4use std::time::Duration;
5
6use dbus::blocking::{Connection, Proxy};
7use dbus::Message;
8
9use crate::api::manager::{
10    OrgFreedesktopLogin1Manager, OrgFreedesktopLogin1ManagerPrepareForSleep,
11};
12pub use crate::error::LogindError;
13use crate::inhibitor::{InhibitEventSet, InhibitMode, InhibitorLock};
14pub use crate::session::{Session, SessionId};
15
16mod api;
17mod error;
18pub mod inhibitor;
19mod session;
20
21pub fn session_id() -> Result<SessionId, LogindError> {
22    match env::var("XDG_SESSION_ID") {
23        Ok(id) => Ok(SessionId::new(id)),
24        Err(_) => Err(LogindError::no_session_id()),
25    }
26}
27
28/// A logind client connection. This is a relatively thin wrapper over the
29/// [D-Bus API](https://www.freedesktop.org/wiki/Software/systemd/logind/).
30pub struct Logind<'a> {
31    conn: &'a Connection,
32    timeout: Duration,
33}
34
35impl<'a> Logind<'a> {
36    pub fn new(conn: &'a Connection) -> Logind {
37        Logind {
38            conn,
39            timeout: Duration::from_millis(500),
40        }
41    }
42
43    /// Get a handle to a logind session by ID.
44    pub fn session(&self, id: &SessionId) -> Result<Session<'a>, LogindError> {
45        let manager = self.manager();
46        let path = manager.get_session(id.as_str())?;
47        let proxy = Proxy::new(
48            "org.freedesktop.login1",
49            path,
50            self.timeout,
51            self.conn,
52        );
53        Ok(Session::new(proxy))
54    }
55
56    /// Get a handle to the current logind session.
57    pub fn current_session(&self) -> Result<Session<'a>, LogindError> {
58        let id = session_id()?;
59        self.session(&id)
60    }
61
62    /// Attempt to suspend the system. If `interactive`, PolicyKit may prompt the current user
63    /// for authentication if needed.
64    pub fn suspend(&self, interactive: bool) -> Result<(), LogindError> {
65        let manager = self.manager();
66        manager.suspend(interactive)?;
67        Ok(())
68    }
69
70    /// Attempt to reboot the system. If `interactive`, PolicyKit may prompt the current user for
71    /// authentication.
72    pub fn reboot(&self, interactive: bool) -> Result<(), LogindError> {
73        let manager = self.manager();
74        manager.reboot(interactive)?;
75        Ok(())
76    }
77
78    /// Attempt to power off the system. If `interactive`, PolicyKit may prompt the current user for
79    /// authentication.
80    pub fn power_off(&self, interactive: bool) -> Result<(), LogindError> {
81        let manager = self.manager();
82        manager.power_off(interactive)?;
83        Ok(())
84    }
85
86    /// Attempt to hibernate the system. If `interactive`, PolicyKit may prompt the current user for
87    /// authentication.
88    pub fn hibernate(&self, interactive: bool) -> Result<(), LogindError> {
89        let manager = self.manager();
90        manager.hibernate(interactive)?;
91        Ok(())
92    }
93
94    pub fn inhibit(
95        &self,
96        who: &str,
97        why: &str,
98        events: &InhibitEventSet,
99        mode: InhibitMode,
100    ) -> Result<InhibitorLock, LogindError> {
101        let manager = self.manager();
102        let fd = manager.inhibit(events.as_str(), who, why, mode.as_str())?;
103        Ok(InhibitorLock::new(fd))
104    }
105
106    pub fn on_sleep<F: Fn(Logind) + Send + 'static, G: Fn(Logind) + Send + 'static>(
107        &self,
108        pre_sleep: F,
109        post_sleep: G,
110    ) -> Result<(), LogindError> {
111        let manager = self.manager();
112        match manager.match_signal(
113            move |signal: OrgFreedesktopLogin1ManagerPrepareForSleep,
114                  conn: &Connection,
115                  _: &Message| {
116                if signal.arg0 {
117                    // TODO: would be nice to make `Logind` less tied to dbus' threading model where we have to keep making new instances
118                    pre_sleep(Logind::new(conn));
119                } else {
120                    post_sleep(Logind::new(conn));
121                }
122                true
123            },
124        ) {
125            Ok(_) => Ok(()),
126            Err(e) => Err(LogindError::match_failed("PrepareForSleep", e)),
127        }
128    }
129
130    fn manager(&self) -> Proxy<'_, &'a Connection> {
131        Proxy::new(
132            "org.freedesktop.login1",
133            "/org/freedesktop/login1",
134            self.timeout,
135            self.conn,
136        )
137    }
138}