Commit 2e7a7840d045
tarball4 files changed+258-237
@@ -1,237 +0,0 @@
1
-//! Whether each output is lit. Idle blanking, the ipc `power` command and2
-//! wlr-output-power-management all end up here, and the backend is told what3
-//! came out.4
-5
-use crate::logind::Action;6
-use crate::state::{State, now_ns};7
-use monolith_config::LidAction;8
-use monolith_protocols::output_power::{OutputPowerHandler, OutputPowerState};9
-use smithay::output::Output;10
-use smithay::reexports::calloop::RegistrationToken;11
-use smithay::reexports::calloop::timer::{TimeoutAction, Timer};12
-use std::collections::HashSet;13
-use std::time::Duration;14
-15
-/// How long input with the lid shut may go on before the close action runs16
-/// again, so waking a closed laptop does not suspend it mid-keystroke.17
-const LID_GRACE: Duration = Duration::from_secs(5);18
-19
-#[derive(Default)]20
-pub struct Power {21
- /// Every output is dark until the next input.22
- idle_blanked: bool,23
- /// Outputs switched off by name, which input does not wake.24
- off: HashSet<String>,25
- lid_closed: bool,26
- /// The lid close action already ran for this closing.27
- lid_acted: bool,28
- /// The internal panel is dark and out of the layout because the lid is29
- /// shut and something else is connected.30
- panel_off: bool,31
- last_activity: u64,32
- timer: Option<RegistrationToken>,33
-}34
-35
-impl Power {36
- pub fn dark(&self, name: &str) -> bool {37
- self.idle_blanked || self.off.contains(name) || (self.panel_off && internal(name))38
- }39
-40
- /// An inhibitor going away counts as activity, so the screen does not41
- /// blank the moment a video stops.42
- pub fn touch(&mut self) {43
- self.last_activity = now_ns();44
- }45
-46
- pub fn forget(&mut self, name: &str) {47
- self.off.remove(name);48
- }49
-}50
-51
-pub fn internal(name: &str) -> bool {52
- ["eDP-", "LVDS-", "DSI-"]53
- .iter()54
- .any(|p| name.starts_with(p))55
-}56
-57
-impl OutputPowerHandler for State {58
- fn output_power_state(&mut self) -> &mut OutputPowerState {59
- &mut self.protocols.output_power60
- }61
-62
- fn output_power_request(&mut self, output: &Output, on: bool) {63
- let _ = self.set_output_power(Some(&output.name()), on);64
- }65
-66
- fn output_is_powered(&self, output: &Output) -> bool {67
- self.output_powered(&output.name())68
- }69
-}70
-71
-impl State {72
- pub fn output_powered(&self, name: &str) -> bool {73
- !self.services.power.dark(name)74
- }75
-76
- /// Switches outputs on or off by name, every output when `name` is None.77
- /// Turning on also ends an idle blank.78
- pub fn set_output_power(&mut self, name: Option<&str>, on: bool) -> Result<(), String> {79
- let known: Vec<String> = self.wm.space.outputs().map(|o| o.name()).collect();80
- if let Some(n) = name81
- && !known.iter().any(|k| k == n)82
- {83
- return Err(format!("no output named {n}"));84
- }85
- let power = &mut self.services.power;86
- for k in known.into_iter().filter(|k| name.is_none_or(|n| n == k)) {87
- if on {88
- power.off.remove(&k);89
- } else {90
- power.off.insert(k);91
- }92
- }93
- if on {94
- self.services.power.touch();95
- self.wake_idle();96
- }97
- self.apply_power();98
- Ok(())99
- }100
-101
- /// Pushes the wanted power state of every output to the backend.102
- pub fn apply_power(&mut self) {103
- let names: Vec<String> = match self.backend.drm() {104
- Some(drm) => drm.names(),105
- None => self.wm.space.outputs().map(|o| o.name()).collect(),106
- };107
- let power = &mut self.services.power;108
- power.panel_off = power.lid_closed && names.iter().any(|n| !internal(n));109
- let parked = power.panel_off;110
- for name in names {111
- let on = self.output_powered(&name);112
- if let Some(drm) = self.backend.drm_mut() {113
- drm.set_powered(&name, on);114
- }115
- if internal(&name) {116
- crate::drm::set_parked(self, &name, parked);117
- }118
- }119
- let power = &self.services.power;120
- self.protocols.output_power.sync(|o| !power.dark(&o.name()));121
- }122
-123
- /// Notes input, and lights the outputs again if idleness had blanked them.124
- pub fn activity(&mut self) {125
- let seat = self.input.seat.clone();126
- self.services.idle.notifier.notify_activity(&seat);127
- self.services.power.touch();128
- if self.services.power.idle_blanked {129
- self.wake_idle();130
- self.apply_power();131
- }132
- let acts = !matches!(self.settings.config.lid.close, LidAction::Ignore);133
- if acts134
- && self.services.power.lid_closed135
- && std::mem::take(&mut self.services.power.lid_acted)136
- {137
- let again = Timer::from_duration(LID_GRACE);138
- let armed = self.core.handle.insert_source(again, |_, _, state| {139
- state.lid_act();140
- TimeoutAction::Drop141
- });142
- if let Err(e) = armed {143
- tracing::warn!("could not arm the lid action: {e}");144
- }145
- }146
- }147
-148
- /// The lid switch moved. Closing it darkens the internal panel when149
- /// something else shows the desktop, and otherwise runs the configured150
- /// action. Opening it counts as input.151
- pub fn lid_changed(&mut self, closed: bool) {152
- tracing::info!(closed, "lid");153
- self.services.power.lid_closed = closed;154
- self.services.power.lid_acted = false;155
- if !closed {156
- self.activity();157
- }158
- self.apply_power();159
- self.lid_act();160
- }161
-162
- /// Runs the lid close action if the lid is shut with no other output.163
- /// Also called when outputs come and go, since unplugging the last164
- /// external display of a closed laptop is when it should sleep.165
- pub fn lid_act(&mut self) {166
- let lit = self.wm.space.outputs().next().is_some();167
- let power = &mut self.services.power;168
- if !lit || !power.lid_closed || power.lid_acted || power.panel_off {169
- return;170
- }171
- power.lid_acted = true;172
- let action = match self.settings.config.lid.close {173
- LidAction::Suspend => Action::Suspend,174
- LidAction::Poweroff => Action::Poweroff,175
- LidAction::Ignore => return,176
- };177
- crate::logind::act(action);178
- }179
-180
- fn wake_idle(&mut self) {181
- if std::mem::take(&mut self.services.power.idle_blanked) {182
- tracing::info!("input after idle, outputs back on");183
- self.configure_idle();184
- }185
- }186
-187
- /// Arms the idle timer from the config, or disarms it.188
- pub fn configure_idle(&mut self) {189
- if let Some(token) = self.services.power.timer.take() {190
- self.core.handle.remove(token);191
- }192
- let secs = self.settings.config.idle.blank_after;193
- if secs == 0 || self.services.power.idle_blanked || self.backend.drm().is_none() {194
- return;195
- }196
- let power = &mut self.services.power;197
- if power.last_activity == 0 {198
- power.touch();199
- }200
- let timeout = secs.saturating_mul(1_000_000_000);201
- let left = timeout.saturating_sub(now_ns().saturating_sub(power.last_activity));202
- let after = Timer::from_duration(Duration::from_nanos(left.max(1_000_000)));203
- let token = self204
- .core205
- .handle206
- .insert_source(after, |_, _, state| state.idle_elapsed());207
- match token {208
- Ok(token) => self.services.power.timer = Some(token),209
- Err(e) => tracing::warn!("could not arm the idle timer: {e}"),210
- }211
- }212
-213
- fn idle_elapsed(&mut self) -> TimeoutAction {214
- let timeout = self215
- .settings216
- .config217
- .idle218
- .blank_after219
- .saturating_mul(1_000_000_000);220
- if timeout == 0 {221
- self.services.power.timer = None;222
- return TimeoutAction::Drop;223
- }224
- let idle_for = now_ns().saturating_sub(self.services.power.last_activity);225
- if idle_for < timeout {226
- return TimeoutAction::ToDuration(Duration::from_nanos(timeout - idle_for));227
- }228
- if self.services.idle.inhibited {229
- return TimeoutAction::ToDuration(Duration::from_nanos(timeout));230
- }231
- tracing::info!("idle, switching outputs off");232
- self.services.power.timer = None;233
- self.services.power.idle_blanked = true;234
- self.apply_power();235
- TimeoutAction::Drop236
- }237
-}@@ -0,0 +1,77 @@
1
+//! Idle blanking, and the input that ends it.2
+3
+use crate::state::{State, now_ns};4
+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};5
+use std::time::Duration;6
+7
+impl State {8
+ /// Notes input, and lights the outputs again if idleness had blanked them.9
+ pub fn activity(&mut self) {10
+ let seat = self.input.seat.clone();11
+ self.services.idle.notifier.notify_activity(&seat);12
+ self.services.power.touch();13
+ if self.services.power.idle_blanked {14
+ self.wake_idle();15
+ self.apply_power();16
+ }17
+ self.lid_woken();18
+ }19
+20
+ pub(super) fn wake_idle(&mut self) {21
+ if std::mem::take(&mut self.services.power.idle_blanked) {22
+ tracing::info!("input after idle, outputs back on");23
+ self.configure_idle();24
+ }25
+ }26
+27
+ /// Arms the idle timer from the config, or disarms it.28
+ pub fn configure_idle(&mut self) {29
+ if let Some(token) = self.services.power.timer.take() {30
+ self.core.handle.remove(token);31
+ }32
+ let secs = self.settings.config.idle.blank_after;33
+ if secs == 0 || self.services.power.idle_blanked || self.backend.drm().is_none() {34
+ return;35
+ }36
+ let power = &mut self.services.power;37
+ if power.last_activity == 0 {38
+ power.touch();39
+ }40
+ let timeout = secs.saturating_mul(1_000_000_000);41
+ let left = timeout.saturating_sub(now_ns().saturating_sub(power.last_activity));42
+ let after = Timer::from_duration(Duration::from_nanos(left.max(1_000_000)));43
+ let token = self44
+ .core45
+ .handle46
+ .insert_source(after, |_, _, state| state.idle_elapsed());47
+ match token {48
+ Ok(token) => self.services.power.timer = Some(token),49
+ Err(e) => tracing::warn!("could not arm the idle timer: {e}"),50
+ }51
+ }52
+53
+ fn idle_elapsed(&mut self) -> TimeoutAction {54
+ let timeout = self55
+ .settings56
+ .config57
+ .idle58
+ .blank_after59
+ .saturating_mul(1_000_000_000);60
+ if timeout == 0 {61
+ self.services.power.timer = None;62
+ return TimeoutAction::Drop;63
+ }64
+ let idle_for = now_ns().saturating_sub(self.services.power.last_activity);65
+ if idle_for < timeout {66
+ return TimeoutAction::ToDuration(Duration::from_nanos(timeout - idle_for));67
+ }68
+ if self.services.idle.inhibited {69
+ return TimeoutAction::ToDuration(Duration::from_nanos(timeout));70
+ }71
+ tracing::info!("idle, switching outputs off");72
+ self.services.power.timer = None;73
+ self.services.power.idle_blanked = true;74
+ self.apply_power();75
+ TimeoutAction::Drop76
+ }77
+}@@ -0,0 +1,64 @@
1
+//! The lid switch, and what closing it does.2
+3
+use crate::logind::Action;4
+use crate::state::State;5
+use monolith_config::LidAction;6
+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};7
+use std::time::Duration;8
+9
+/// How long input with the lid shut may go on before the close action runs10
+/// again, so waking a closed laptop does not suspend it mid-keystroke.11
+const LID_GRACE: Duration = Duration::from_secs(5);12
+13
+impl State {14
+ /// The lid switch moved. Closing it darkens the internal panel when15
+ /// something else shows the desktop, and otherwise runs the configured16
+ /// action. Opening it counts as input.17
+ pub fn lid_changed(&mut self, closed: bool) {18
+ tracing::info!(closed, "lid");19
+ self.services.power.lid_closed = closed;20
+ self.services.power.lid_acted = false;21
+ if !closed {22
+ self.activity();23
+ }24
+ self.apply_power();25
+ self.lid_act();26
+ }27
+28
+ /// Runs the lid close action if the lid is shut with no other output.29
+ /// Also called when outputs come and go, since unplugging the last30
+ /// external display of a closed laptop is when it should sleep.31
+ pub fn lid_act(&mut self) {32
+ let lit = self.wm.space.outputs().next().is_some();33
+ let power = &mut self.services.power;34
+ if !lit || !power.lid_closed || power.lid_acted || power.panel_off {35
+ return;36
+ }37
+ power.lid_acted = true;38
+ let action = match self.settings.config.lid.close {39
+ LidAction::Suspend => Action::Suspend,40
+ LidAction::Poweroff => Action::Poweroff,41
+ LidAction::Ignore => return,42
+ };43
+ crate::logind::act(action);44
+ }45
+46
+ /// Runs the lid close action again a while after input woke a laptop whose47
+ /// lid is still shut, since it only ran once when the lid closed.48
+ pub(super) fn lid_woken(&mut self) {49
+ let acts = !matches!(self.settings.config.lid.close, LidAction::Ignore);50
+ if acts51
+ && self.services.power.lid_closed52
+ && std::mem::take(&mut self.services.power.lid_acted)53
+ {54
+ let again = Timer::from_duration(LID_GRACE);55
+ let armed = self.core.handle.insert_source(again, |_, _, state| {56
+ state.lid_act();57
+ TimeoutAction::Drop58
+ });59
+ if let Err(e) = armed {60
+ tracing::warn!("could not arm the lid action: {e}");61
+ }62
+ }63
+ }64
+}@@ -0,0 +1,117 @@
1
+//! Whether each output is lit. Idle blanking, the ipc `power` command and2
+//! wlr-output-power-management all end up here, and the backend is told what3
+//! came out.4
+5
+mod blank;6
+mod lid;7
+8
+use crate::state::{State, now_ns};9
+use monolith_protocols::output_power::{OutputPowerHandler, OutputPowerState};10
+use smithay::output::Output;11
+use smithay::reexports::calloop::RegistrationToken;12
+use std::collections::HashSet;13
+14
+#[derive(Default)]15
+pub struct Power {16
+ /// Every output is dark until the next input.17
+ idle_blanked: bool,18
+ /// Outputs switched off by name, which input does not wake.19
+ 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 and out of the layout because the lid is24
+ /// shut and something else is connected.25
+ panel_off: bool,26
+ last_activity: u64,27
+ timer: Option<RegistrationToken>,28
+}29
+30
+impl Power {31
+ pub fn dark(&self, name: &str) -> bool {32
+ self.idle_blanked || self.off.contains(name) || (self.panel_off && internal(name))33
+ }34
+35
+ /// An inhibitor going away counts as activity, so the screen does not36
+ /// blank the moment a video stops.37
+ pub fn touch(&mut self) {38
+ self.last_activity = now_ns();39
+ }40
+41
+ pub fn forget(&mut self, name: &str) {42
+ self.off.remove(name);43
+ }44
+}45
+46
+pub fn internal(name: &str) -> bool {47
+ ["eDP-", "LVDS-", "DSI-"]48
+ .iter()49
+ .any(|p| name.starts_with(p))50
+}51
+52
+impl OutputPowerHandler for State {53
+ fn output_power_state(&mut self) -> &mut OutputPowerState {54
+ &mut self.protocols.output_power55
+ }56
+57
+ fn output_power_request(&mut self, output: &Output, on: bool) {58
+ let _ = self.set_output_power(Some(&output.name()), on);59
+ }60
+61
+ fn output_is_powered(&self, output: &Output) -> bool {62
+ self.output_powered(&output.name())63
+ }64
+}65
+66
+impl State {67
+ pub fn output_powered(&self, name: &str) -> bool {68
+ !self.services.power.dark(name)69
+ }70
+71
+ /// Switches outputs on or off by name, every output when `name` is None.72
+ /// Turning on also ends an idle blank.73
+ pub fn set_output_power(&mut self, name: Option<&str>, on: bool) -> Result<(), String> {74
+ let known: Vec<String> = self.wm.space.outputs().map(|o| o.name()).collect();75
+ if let Some(n) = name76
+ && !known.iter().any(|k| k == n)77
+ {78
+ return Err(format!("no output named {n}"));79
+ }80
+ let power = &mut self.services.power;81
+ for k in known.into_iter().filter(|k| name.is_none_or(|n| n == k)) {82
+ if on {83
+ power.off.remove(&k);84
+ } else {85
+ power.off.insert(k);86
+ }87
+ }88
+ if on {89
+ self.services.power.touch();90
+ self.wake_idle();91
+ }92
+ self.apply_power();93
+ Ok(())94
+ }95
+96
+ /// Pushes the wanted power state of every output to the backend.97
+ pub fn apply_power(&mut self) {98
+ let names: Vec<String> = match self.backend.drm() {99
+ Some(drm) => drm.names(),100
+ None => self.wm.space.outputs().map(|o| o.name()).collect(),101
+ };102
+ let power = &mut self.services.power;103
+ power.panel_off = power.lid_closed && names.iter().any(|n| !internal(n));104
+ let parked = power.panel_off;105
+ for name in names {106
+ let on = self.output_powered(&name);107
+ if let Some(drm) = self.backend.drm_mut() {108
+ drm.set_powered(&name, on);109
+ }110
+ if internal(&name) {111
+ crate::drm::set_parked(self, &name, parked);112
+ }113
+ }114
+ let power = &self.services.power;115
+ self.protocols.output_power.sync(|o| !power.dark(&o.name()));116
+ }117
+}