Commit 5b8422a225da
tarball4 files changed+86-4
@@ -55,6 +55,14 @@ curve = [0.45, 0.0, 0.2, 1.0]
5555
# keep the screen on while they are shown.5656
blank_after = 05757
58
+[lid]59
+# What closing the lid does, suspend, poweroff or ignore. The internal panel60
+# (eDP, LVDS) switches off whenever the lid is closed and another output is61
+# connected, and then neither suspend nor poweroff happens. Without a62
+# handle-lid-switch inhibitor, logind still acts on the lid itself, so set63
+# HandleLidSwitch=ignore there for ignore to mean anything.64
+close = "suspend"65
+5866
[keyboard]5967
layout = ""6068
repeat_delay = 200@@ -18,6 +18,7 @@ pub struct Config {
1818
pub keyboard: Keyboard,1919
pub input: Input,2020
pub idle: Idle,21
+ pub lid: Lid,2122
pub binds: Vec<Bind>,2223
pub rules: Vec<Rule>,2324
pub startup: Vec<Vec<String>>,@@ -244,6 +245,23 @@ pub struct Idle {
244245
pub blank_after: u64,245246
}246247
248
+#[derive(Deserialize, Debug, Clone, PartialEq, Default)]249
+#[serde(default, deny_unknown_fields)]250
+pub struct Lid {251
+ /// What closing the lid does while no other output is connected.252
+ pub close: LidAction,253
+}254
+255
+#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]256
+#[serde(rename_all = "lowercase")]257
+pub enum LidAction {258
+ #[default]259
+ Suspend,260
+ Poweroff,261
+ /// Leave it to logind or whatever else watches the switch.262
+ Ignore,263
+}264
+247265
#[derive(Deserialize, Debug, Clone, PartialEq)]248266
#[serde(default, deny_unknown_fields)]249267
pub struct Keyboard {@@ -522,6 +540,7 @@ impl Config {
522540
let keyboard = section("keyboard", table.remove("keyboard"), w);523541
let input = input(table.remove("input"), w);524542
let idle = section("idle", table.remove("idle"), w);543
+ let lid = section("lid", table.remove("lid"), w);525544
let animation = section("animation", table.remove("animation"), w);526545
let decoration = section("decoration", table.remove("decoration"), w);527546
let output: Outputs = section("output", table.remove("output"), w);@@ -582,6 +601,7 @@ impl Config {
582601
keyboard,583602
input,584603
idle,604
+ lid,585605
binds,586606
rules,587607
startup,@@ -771,6 +791,7 @@ mod tests {
771791
assert_eq!(c.keyboard, Keyboard::default());772792
assert_eq!(c.input, Input::default());773793
assert_eq!(c.idle, Idle::default());794
+ assert_eq!(c.lid, Lid::default());774795
assert!(c.binds.len() > 40);775796
}776797
@@ -34,6 +34,7 @@ use smithay::reexports::calloop::{EventLoop, LoopHandle, RegistrationToken};
3434
use smithay::reexports::drm::Device as _;3535
use smithay::reexports::drm::control::{self, ModeTypeFlags, connector, crtc};3636
use smithay::reexports::input::Libinput;37
+use smithay::reexports::input::event::switch::{Switch, SwitchState};3738
use smithay::reexports::rustix::fs::OFlags;3839
use smithay::reexports::wayland_server::backend::GlobalId;3940
use smithay::utils::{DeviceFd, Logical, Point, Transform};@@ -318,6 +319,7 @@ fn scan(state: &mut State) {
318319
retry_connector(state, handle);319320
}320321
state.apply_power();322
+ state.lid_act();321323
state.apply_layout();322324
}323325
@@ -1146,6 +1148,11 @@ fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
11461148
state.pointer_axis(frame);11471149
state.pointer_frame();11481150
}1151
+ InputEvent::SwitchToggle { event } => {1152
+ if event.switch() == Some(Switch::Lid) {1153
+ state.lid_changed(event.switch_state() == SwitchState::On);1154
+ }1155
+ }11491156
InputEvent::DeviceAdded { device } => {11501157
if let Some(drm) = state.backend.drm_mut() {11511158
drm.devices.add(device, &state.settings.config.input);@@ -3,6 +3,7 @@
33
//! came out.44
55
use crate::state::{State, now_ns};6
+use monolith_config::LidAction;67
use monolith_protocols::output_power::{OutputPowerHandler, OutputPowerState};78
use smithay::output::Output;89
use smithay::reexports::calloop::RegistrationToken;@@ -16,13 +17,19 @@ pub struct Power {
1617
idle_blanked: bool,1718
/// Outputs switched off by name, which input does not wake.1819
off: HashSet<String>,20
+ lid_closed: bool,21
+ /// The lid close action already ran for this closing.22
+ lid_acted: bool,23
+ /// The internal panel is dark because the lid is shut and something else24
+ /// is connected.25
+ panel_off: bool,1926
last_activity: u64,2027
timer: Option<RegistrationToken>,2128
}2229
2330
impl Power {2431
pub fn dark(&self, name: &str) -> bool {25
- self.idle_blanked || self.off.contains(name)32
+ self.idle_blanked || self.off.contains(name) || (self.panel_off && internal(name))2633
}2734
2835
/// An inhibitor going away counts as activity, so the screen does not@@ -36,6 +43,12 @@ impl Power {
3643
}3744
}3845
46
+fn internal(name: &str) -> bool {47
+ ["eDP-", "LVDS-", "DSI-"]48
+ .iter()49
+ .any(|p| name.starts_with(p))50
+}51
+3952
impl OutputPowerHandler for State {4053
fn output_power_state(&mut self) -> &mut OutputPowerState {4154
&mut self.protocols.output_power@@ -82,6 +95,8 @@ impl State {
8295
/// Pushes the wanted power state of every output to the backend.8396
pub fn apply_power(&mut self) {8497
let names: Vec<String> = self.wm.space.outputs().map(|o| o.name()).collect();98
+ let power = &mut self.services.power;99
+ power.panel_off = power.lid_closed && names.iter().any(|n| !internal(n));85100
for name in names {86101
let on = self.output_powered(&name);87102
if let Some(drm) = self.backend.drm_mut() {@@ -89,9 +104,7 @@ impl State {
89104
}90105
}91106
let power = &self.services.power;92
- self.protocols93
- .output_power94
- .sync(|o| !power.dark(&o.name()));107
+ self.protocols.output_power.sync(|o| !power.dark(&o.name()));95108
}96109
97110
/// Notes input, and lights the outputs again if idleness had blanked them.@@ -105,6 +118,39 @@ impl State {
105118
}106119
}107120
121
+ /// The lid switch moved. Closing it darkens the internal panel when122
+ /// something else shows the desktop, and otherwise runs the configured123
+ /// action. Opening it counts as input.124
+ pub fn lid_changed(&mut self, closed: bool) {125
+ tracing::info!(closed, "lid");126
+ self.services.power.lid_closed = closed;127
+ self.services.power.lid_acted = false;128
+ if !closed {129
+ self.activity();130
+ }131
+ self.apply_power();132
+ self.lid_act();133
+ }134
+135
+ /// Runs the lid close action if the lid is shut with no other output.136
+ /// Also called when outputs come and go, since unplugging the last137
+ /// external display of a closed laptop is when it should sleep.138
+ pub fn lid_act(&mut self) {139
+ let power = &mut self.services.power;140
+ if !power.lid_closed || power.lid_acted || power.panel_off {141
+ return;142
+ }143
+ power.lid_acted = true;144
+ let verb = match self.settings.config.lid.close {145
+ LidAction::Suspend => "suspend",146
+ LidAction::Poweroff => "poweroff",147
+ LidAction::Ignore => return,148
+ };149
+ if let Err(e) = self.spawn(&["systemctl".into(), verb.into()]) {150
+ tracing::warn!("lid close action failed: {e}");151
+ }152
+ }153
+108154
fn wake_idle(&mut self) {109155
if std::mem::take(&mut self.services.power.idle_blanked) {110156
tracing::info!("input after idle, outputs back on");