atagen

monolith

public
Star0
Watch0

Commit afc75793f411

tarball

atagen <boss@atagen.co>2026-10-19 10:44 UTC

3 files changed+94-60
logind: take the lid switch on a thread and again after the bus drops

Logind::connect blocked startup for up to 2 s, and a system bus restart left the held connection dead with nothing to retake the inhibitor. A watcher thread now owns its own connection, retries, and re-inhibits on NameOwnerChanged for login1. Actions use a fresh connection with one systemctl fallback instead of a second copy in power.rs.
Modified file: crates/monolith/src/logind.rs+91 additions−48 deletions
@@ -3,21 +3,20 @@
33
44 use dbus::arg::OwnedFd;
55 use dbus::blocking::SyncConnection;
6+use dbus::message::MatchRule;
7+use std::convert::Infallible;
68 use std::process::Command;
79 use std::sync::Arc;
10+use std::sync::atomic::{AtomicBool, Ordering};
811 use std::time::Duration;
912
1013 const DEST: &str = "org.freedesktop.login1";
1114 const PATH: &str = "/org/freedesktop/login1";
1215 const MANAGER: &str = "org.freedesktop.login1.Manager";
16+const BUS: &str = "org.freedesktop.DBus";
1317 const TIMEOUT: Duration = Duration::from_secs(2);
14-
15-pub struct Logind {
16- conn: Arc<SyncConnection>,
17- /// Held so that logind leaves the lid switch to us. Closing it releases
18- /// the inhibitor.
19- _lid_lock: OwnedFd,
20-}
18+const RETRY: Duration = Duration::from_secs(10);
19+const IDLE: Duration = Duration::from_secs(3600);
2120
2221 #[derive(Clone, Copy)]
2322 pub enum Action {
@@ -41,52 +40,96 @@ impl Action {
4140 }
4241 }
4342
44-impl Logind {
45- pub fn connect() -> Result<Self, dbus::Error> {
46- let conn = Arc::new(SyncConnection::new_system()?);
47- let proxy = conn.with_proxy(DEST, PATH, TIMEOUT);
48- let (fd,): (OwnedFd,) = proxy.method_call(
49- MANAGER,
50- "Inhibit",
51- (
52- "handle-lid-switch",
53- "monolith",
54- "monolith handles the lid",
55- "block",
56- ),
57- )?;
58- Ok(Self {
59- conn,
60- _lid_lock: fd,
61- })
43+/// Keeps logind away from the lid switch for as long as the process lives. The
44+/// connection and the inhibitor are set up on a thread of their own, so a slow
45+/// or absent system bus never holds up startup, and both are taken again when
46+/// the bus drops or logind restarts.
47+pub fn hold_lid() {
48+ let spawned = std::thread::Builder::new()
49+ .name("logind-lid".into())
50+ .spawn(|| {
51+ let mut warned = false;
52+ loop {
53+ let Err(e) = hold(&mut warned);
54+ if !warned {
55+ tracing::warn!("cannot hold the lid switch from logind: {e}");
56+ warned = true;
57+ }
58+ std::thread::sleep(RETRY);
59+ }
60+ });
61+ if let Err(e) = spawned {
62+ tracing::warn!("could not start the logind lid thread: {e}");
6263 }
64+}
6365
64- /// Asks logind to run the action without waiting for the reply, and falls
65- /// back to systemctl if the call fails. The call can take as long as the
66- /// timeout, so it stays off the event loop.
67- pub fn act(&self, action: Action) {
68- let conn = self.conn.clone();
69- let spawned = std::thread::Builder::new()
70- .name("logind".into())
71- .spawn(move || {
72- let proxy = conn.with_proxy(DEST, PATH, TIMEOUT);
73- let called: Result<(), dbus::Error> =
74- proxy.method_call(MANAGER, action.method(), (false,));
75- if let Err(e) = called {
76- tracing::warn!("logind {} failed, using systemctl: {e}", action.verb());
77- systemctl(action);
78- }
79- });
80- if let Err(e) = spawned {
81- tracing::warn!("could not start the logind thread: {e}");
66+fn hold(warned: &mut bool) -> Result<Infallible, dbus::Error> {
67+ let conn = SyncConnection::new_system()?;
68+ let mut _lid_lock = inhibit(&conn)?;
69+ let restarted = Arc::new(AtomicBool::new(false));
70+ let seen = restarted.clone();
71+ let rule = MatchRule::new_signal(BUS, "NameOwnerChanged").with_sender(BUS);
72+ conn.add_match(
73+ rule,
74+ move |(name, _, owner): (String, String, String), _, _| {
75+ if name == DEST && !owner.is_empty() {
76+ seen.store(true, Ordering::Relaxed);
77+ }
78+ true
79+ },
80+ )?;
81+ *warned = false;
82+ tracing::info!("holding the lid switch");
83+ loop {
84+ conn.process(IDLE)?;
85+ if restarted.swap(false, Ordering::Relaxed) {
86+ _lid_lock = inhibit(&conn)?;
87+ tracing::info!("logind restarted, took the lid switch again");
8288 }
8389 }
8490 }
8591
86-fn systemctl(action: Action) {
87- match Command::new("systemctl").arg(action.verb()).status() {
88- Ok(s) if !s.success() => tracing::warn!("systemctl {} exited with {s}", action.verb()),
89- Ok(_) => {}
90- Err(e) => tracing::warn!("systemctl {} failed: {e}", action.verb()),
92+/// The returned descriptor keeps the inhibitor alive, and closing it releases
93+/// it.
94+fn inhibit(conn: &SyncConnection) -> Result<OwnedFd, dbus::Error> {
95+ let proxy = conn.with_proxy(DEST, PATH, TIMEOUT);
96+ let (fd,): (OwnedFd,) = proxy.method_call(
97+ MANAGER,
98+ "Inhibit",
99+ (
100+ "handle-lid-switch",
101+ "monolith",
102+ "monolith handles the lid",
103+ "block",
104+ ),
105+ )?;
106+ Ok(fd)
107+}
108+
109+/// Asks logind to run the action and falls back to systemctl if the call
110+/// fails. It happens on a thread, since the call can take as long as the
111+/// timeout, and on a fresh connection, since a long-lived one may have died
112+/// with the bus.
113+pub fn act(action: Action) {
114+ let spawned = std::thread::Builder::new()
115+ .name("logind".into())
116+ .spawn(move || {
117+ let called = SyncConnection::new_system().and_then(|conn| {
118+ conn.with_proxy(DEST, PATH, TIMEOUT)
119+ .method_call::<(), _, _, _>(MANAGER, action.method(), (false,))
120+ });
121+ if let Err(e) = called {
122+ tracing::warn!("logind {} failed, using systemctl: {e}", action.verb());
123+ match Command::new("systemctl").arg(action.verb()).status() {
124+ Ok(s) if !s.success() => {
125+ tracing::warn!("systemctl {} exited with {s}", action.verb());
126+ }
127+ Ok(_) => {}
128+ Err(e) => tracing::warn!("systemctl {} failed: {e}", action.verb()),
129+ }
130+ }
131+ });
132+ if let Err(e) = spawned {
133+ tracing::warn!("could not start the logind thread: {e}");
91134 }
92135 }
Modified file: crates/monolith/src/main.rs+1 additions−4 deletions
@@ -282,10 +282,7 @@ fn run(
282282 xwayland::init(&mut state);
283283 state.configure_idle();
284284 if gpu.is_some() {
285- match logind::Logind::connect() {
286- Ok(logind) => state.services.power.logind = Some(logind),
287- Err(e) => tracing::warn!("cannot take the lid switch from logind: {e}"),
288- }
285+ logind::hold_lid();
289286 }
290287 tracing::info!(socket = ?state.core.socket_name, device = state.renderer.device_name(), "monolith running");
291288
Modified file: crates/monolith/src/power.rs+2 additions−8 deletions
@@ -2,7 +2,7 @@
22 //! wlr-output-power-management all end up here, and the backend is told what
33 //! came out.
44
5-use crate::logind::{Action, Logind};
5+use crate::logind::Action;
66 use crate::state::{State, now_ns};
77 use monolith_config::LidAction;
88 use monolith_protocols::output_power::{OutputPowerHandler, OutputPowerState};
@@ -18,8 +18,6 @@ const LID_GRACE: Duration = Duration::from_secs(5);
1818
1919 #[derive(Default)]
2020 pub struct Power {
21- /// Absent on the nested backend, or when the system bus is unreachable.
22- pub logind: Option<Logind>,
2321 /// Every output is dark until the next input.
2422 idle_blanked: bool,
2523 /// Outputs switched off by name, which input does not wake.
@@ -169,11 +167,7 @@ impl State {
169167 LidAction::Poweroff => Action::Poweroff,
170168 LidAction::Ignore => return,
171169 };
172- if let Some(logind) = &power.logind {
173- logind.act(action);
174- } else if let Err(e) = self.spawn(&["systemctl".into(), action.verb().into()]) {
175- tracing::warn!("lid close action failed: {e}");
176- }
170+ crate::logind::act(action);
177171 }
178172
179173 fn wake_idle(&mut self) {