atagen

monolith

public
Star0
Watch0

Commit 5d5ec436eb31

tarball

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

6 files changed+131-4
power: take the lid switch from logind and act through it

logind also handled the lid, so "ignore" still suspended and "suspend" ran twice. A handle-lid-switch block inhibitor held for the compositor's lifetime leaves the switch to us, and Suspend/PowerOff go over the same connection with a systemctl fallback. The calls run on a short-lived thread so a slow bus cannot stall the event loop.
Modified file: Cargo.lock+21 additions
@@ -201,6 +201,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
201201 checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
202202
203203 [[package]]
204+name = "dbus"
205+version = "0.9.12"
206+source = "registry+https://github.com/rust-lang/crates.io-index"
207+checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e"
208+dependencies = [
209+ "libc",
210+ "libdbus-sys",
211+ "windows-sys 0.61.2",
212+]
213+
214+[[package]]
204215 name = "downcast-rs"
205216 version = "1.2.1"
206217 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -452,6 +463,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
452463 checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
453464
454465 [[package]]
466+name = "libdbus-sys"
467+version = "0.2.7"
468+source = "registry+https://github.com/rust-lang/crates.io-index"
469+checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
470+dependencies = [
471+ "pkg-config",
472+]
473+
474+[[package]]
455475 name = "libloading"
456476 version = "0.8.9"
457477 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -544,6 +564,7 @@ name = "monolith"
544564 version = "0.0.0"
545565 dependencies = [
546566 "calloop-wayland-source",
567+ "dbus",
547568 "input",
548569 "libc",
549570 "monolith-config",
Modified file: crates/monolith/Cargo.toml+1 additions
@@ -22,6 +22,7 @@ monolith-planner.workspace = true
2222 monolith-protocols.workspace = true
2323 monolith-render-vk.workspace = true
2424 libc = "0.2"
25+dbus = "0.9"
2526 # Raises the libinput bindings smithay uses. 1.23 brings custom acceleration,
2627 # and below 1.27 reading a sticky drag lock default panics.
2728 input = { version = "0.10", default-features = false, features = ["libinput_1_27"] }
Added file: crates/monolith/src/logind.rs+92 additions
@@ -0,0 +1,92 @@
1+//! The system bus connection to logind, for taking over the lid switch and
2+//! for suspending or powering off through it.
3+
4+use dbus::arg::OwnedFd;
5+use dbus::blocking::SyncConnection;
6+use std::process::Command;
7+use std::sync::Arc;
8+use std::time::Duration;
9+
10+const DEST: &str = "org.freedesktop.login1";
11+const PATH: &str = "/org/freedesktop/login1";
12+const MANAGER: &str = "org.freedesktop.login1.Manager";
13+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+}
21+
22+#[derive(Clone, Copy)]
23+pub enum Action {
24+ Suspend,
25+ Poweroff,
26+}
27+
28+impl Action {
29+ fn method(self) -> &'static str {
30+ match self {
31+ Action::Suspend => "Suspend",
32+ Action::Poweroff => "PowerOff",
33+ }
34+ }
35+
36+ pub fn verb(self) -> &'static str {
37+ match self {
38+ Action::Suspend => "suspend",
39+ Action::Poweroff => "poweroff",
40+ }
41+ }
42+}
43+
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+ })
62+ }
63+
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}");
82+ }
83+ }
84+}
85+
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()),
91+ }
92+}
Modified file: crates/monolith/src/main.rs+7 additions
@@ -17,6 +17,7 @@ mod ipc;
1717 mod launch;
1818 mod layers;
1919 mod lock;
20+mod logind;
2021 mod nested;
2122 mod pacing;
2223 mod popups;
@@ -275,6 +276,12 @@ fn run(
275276 signals::init(&event_loop)?;
276277 xwayland::init(&mut state);
277278 state.configure_idle();
279+ if gpu.is_some() {
280+ match logind::Logind::connect() {
281+ Ok(logind) => state.services.power.logind = Some(logind),
282+ Err(e) => tracing::warn!("cannot take the lid switch from logind: {e}"),
283+ }
284+ }
278285 tracing::info!(socket = ?state.core.socket_name, device = state.renderer.device_name(), "monolith running");
279286
280287 if gpu.is_some() {
Modified file: crates/monolith/src/power.rs+9 additions−4 deletions
@@ -2,6 +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};
56 use crate::state::{State, now_ns};
67 use monolith_config::LidAction;
78 use monolith_protocols::output_power::{OutputPowerHandler, OutputPowerState};
@@ -13,6 +14,8 @@ use std::time::Duration;
1314
1415 #[derive(Default)]
1516 pub struct Power {
17+ /// Absent on the nested backend, or when the system bus is unreachable.
18+ pub logind: Option<Logind>,
1619 /// Every output is dark until the next input.
1720 idle_blanked: bool,
1821 /// Outputs switched off by name, which input does not wake.
@@ -141,12 +144,14 @@ impl State {
141144 return;
142145 }
143146 power.lid_acted = true;
144- let verb = match self.settings.config.lid.close {
145- LidAction::Suspend => "suspend",
146- LidAction::Poweroff => "poweroff",
147+ let action = match self.settings.config.lid.close {
148+ LidAction::Suspend => Action::Suspend,
149+ LidAction::Poweroff => Action::Poweroff,
147150 LidAction::Ignore => return,
148151 };
149- if let Err(e) = self.spawn(&["systemctl".into(), verb.into()]) {
152+ if let Some(logind) = &power.logind {
153+ logind.act(action);
154+ } else if let Err(e) = self.spawn(&["systemctl".into(), action.verb().into()]) {
150155 tracing::warn!("lid close action failed: {e}");
151156 }
152157 }
Modified file: flake.nix+1 additions
@@ -47,6 +47,7 @@
4747 libgbm
4848 libdrm
4949 udev
50+ dbus
5051 ];
5152 };
5253 cargoArtifacts = craneLib.buildDepsOnly commonArgs;