From 189d1bd97b7aa241b5ad651145ca4d3ef483f505 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Mon, 9 Oct 2023 18:37:43 +0400 Subject: Add power-off-monitors bind Implements https://github.com/YaLTeR/niri/issues/24 --- src/backend/mod.rs | 7 +++++++ src/backend/tty.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- src/config.rs | 1 + src/input.rs | 10 ++++++++++ src/niri.rs | 29 +++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 0c27abd6..64b1b5da 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -109,6 +109,13 @@ impl Backend { } } + pub fn set_monitors_active(&self, active: bool) { + match self { + Backend::Tty(tty) => tty.set_monitors_active(active), + Backend::Winit(_) => (), + } + } + pub fn tty(&mut self) -> &mut Tty { if let Self::Tty(v) = self { v diff --git a/src/backend/tty.rs b/src/backend/tty.rs index f6a40500..403f31ab 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -26,7 +26,7 @@ use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpix use smithay::reexports::calloop::timer::{Timer, TimeoutAction}; use smithay::reexports::calloop::{Dispatcher, LoopHandle, RegistrationToken}; use smithay::reexports::drm::control::{ - connector, crtc, Mode as DrmMode, ModeFlags, ModeTypeFlags, + connector, crtc, Mode as DrmMode, ModeFlags, ModeTypeFlags, Device, property, }; use smithay::reexports::input::Libinput; use smithay::reexports::nix::fcntl::OFlag; @@ -612,7 +612,12 @@ impl Tty { assert!(res.is_none(), "crtc must not have already existed"); niri.add_output(output.clone(), Some(refresh_interval(*mode))); - niri.queue_redraw(output); + + // Power on all monitors if necessary and queue a redraw on the new one. + niri.event_loop.insert_idle(move |state| { + state.niri.activate_monitors(&state.backend); + state.niri.queue_redraw(output); + }); Ok(()) } @@ -956,6 +961,45 @@ impl Tty { device.drm.is_active() } + + pub fn set_monitors_active(&self, active: bool) { + let Some(device) = &self.output_device else { + return; + }; + + for crtc in device.surfaces.keys() { + set_crtc_active(&device.drm, *crtc, active); + } + } +} + +fn find_drm_property(drm: &DrmDevice, crtc: crtc::Handle, name: &str) -> Option { + let props = match drm.get_properties(crtc) { + Ok(props) => props, + Err(err) => { + warn!("error getting CRTC properties: {err:?}"); + return None; + } + }; + + let (handles, _) = props.as_props_and_values(); + handles.iter().find_map(|handle| { + let info = drm.get_property(*handle).ok()?; + let n = info.name().to_str().ok()?; + + (n == name).then_some(*handle) + }) +} + +fn set_crtc_active(drm: &DrmDevice, crtc: crtc::Handle, active: bool) { + let Some(prop) = find_drm_property(drm, crtc, "ACTIVE") else { + return; + }; + + let value = property::Value::Boolean(active); + if let Err(err) = drm.set_property(crtc, prop, value.into()) { + warn!("error setting CRTC property: {err:?}"); + } } fn refresh_interval(mode: DrmMode) -> Duration { diff --git a/src/config.rs b/src/config.rs index 6f695c34..4ab9c36e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -235,6 +235,7 @@ pub enum Action { #[knuffel(skip)] ChangeVt(i32), Suspend, + PowerOffMonitors, ToggleDebugTint, Spawn(#[knuffel(arguments)] Vec), Screenshot, diff --git a/src/input.rs b/src/input.rs index 955c63f4..c1e335dd 100644 --- a/src/input.rs +++ b/src/input.rs @@ -103,6 +103,13 @@ impl State { // here. self.niri.layout.advance_animations(get_monotonic_time()); + // Power on monitors if they were off. + // HACK: ignore key releases so that the power-off-monitors bind can work. + if !matches!(&event, InputEvent::Keyboard { event } if event.state() == KeyState::Released) + { + self.niri.activate_monitors(&self.backend); + } + let comp_mod = self.backend.mod_key(); match event { @@ -139,6 +146,9 @@ impl State { Action::Suspend => { self.backend.suspend(); } + Action::PowerOffMonitors => { + self.niri.deactivate_monitors(&self.backend); + } Action::ToggleDebugTint => { self.backend.toggle_debug_tint(); } diff --git a/src/niri.rs b/src/niri.rs index 328f4aae..f47f97ae 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -107,6 +107,9 @@ pub struct Niri { pub output_state: HashMap, pub output_by_name: HashMap, + // When false, we're idling with monitors powered off. + pub monitors_active: bool, + // Smithay state. pub compositor_state: CompositorState, pub xdg_shell_state: XdgShellState, @@ -688,6 +691,7 @@ impl Niri { output_state: HashMap::new(), output_by_name: HashMap::new(), unmapped_windows: HashMap::new(), + monitors_active: true, compositor_state, xdg_shell_state, @@ -845,6 +849,26 @@ impl Niri { self.queue_redraw(output); } + pub fn deactivate_monitors(&mut self, backend: &Backend) { + if !self.monitors_active { + return; + } + + self.monitors_active = false; + backend.set_monitors_active(false); + } + + pub fn activate_monitors(&mut self, backend: &Backend) { + if self.monitors_active { + return; + } + + self.monitors_active = true; + backend.set_monitors_active(true); + + self.queue_redraw_all(); + } + pub fn output_under(&self, pos: Point) -> Option<(&Output, Point)> { let output = self.global_space.output_under(pos).next()?; let pos_within_output = pos @@ -1242,6 +1266,11 @@ impl Niri { RedrawState::Queued(_) | RedrawState::WaitingForEstimatedVBlankAndQueued(_) )); + if !self.monitors_active { + state.redraw_state = RedrawState::Idle; + return; + } + if !backend.is_active() { state.redraw_state = RedrawState::Idle; return; -- cgit