From 4e823fa04441e08ee2f23de8b7f82bb61329149c Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 16 Aug 2024 20:49:59 -0700 Subject: [PATCH] WIP DPMS with `wlr-output-power-management-unstable-v1` protocol --- Cargo.lock | 23 ++- Cargo.toml | 4 +- src/backend/kms/mod.rs | 3 +- src/backend/kms/surface/mod.rs | 51 ++++++- src/state.rs | 4 + src/wayland/handlers/mod.rs | 1 + src/wayland/handlers/output_power.rs | 46 ++++++ src/wayland/protocols/mod.rs | 1 + src/wayland/protocols/output_power.rs | 200 ++++++++++++++++++++++++++ 9 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 src/wayland/handlers/output_power.rs create mode 100644 src/wayland/protocols/output_power.rs diff --git a/Cargo.lock b/Cargo.lock index 9cd1c16c..d4465081 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -811,6 +811,7 @@ dependencies = [ "cosmic-config", "cosmic-protocols", "cosmic-settings-config", + "drm-ffi 0.8.0", "edid-rs", "egui", "egui_plot", @@ -1269,6 +1270,16 @@ dependencies = [ "rustix", ] +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys 0.7.0", + "rustix", +] + [[package]] name = "drm-ffi" version = "0.9.0" @@ -1295,6 +1306,16 @@ dependencies = [ "linux-raw-sys 0.6.4", ] +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.4", +] + [[package]] name = "drm-sys" version = "0.8.0" @@ -4635,7 +4656,7 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay" version = "0.3.0" -source = "git+https://github.com/smithay//smithay?rev=df79eeb#df79eeba63a8e9c2d33b9be2418aee6a940135e7" +source = "git+https://github.com/ids1024/smithay?branch=dpms#abf158d3b96b712b0fee02e9eb80a74be310396b" dependencies = [ "appendlist", "ash 0.38.0+1.3.281", diff --git a/Cargo.toml b/Cargo.toml index 57adee5b..3fc87b5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ profiling = { version = "1.0" } rustix = { version = "0.38.32", features = ["process"] } smallvec = "1.13.2" rand = "0.8.5" +drm-ffi = "0.8.0" [dependencies.id_tree] branch = "feature/copy_clone" @@ -117,4 +118,5 @@ inherits = "release" lto = "fat" [patch."https://github.com/Smithay/smithay.git"] -smithay = { git = "https://github.com/smithay//smithay", rev = "df79eeb" } +#smithay = { git = "https://github.com/smithay//smithay", rev = "df79eeb" } +smithay = { git = "https://github.com/ids1024/smithay", branch = "dpms" } diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index f1e19c28..fee4ca85 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -39,10 +39,11 @@ use std::{ }; mod device; -mod drm_helpers; +pub(crate) mod drm_helpers; pub mod render; mod socket; mod surface; +pub(crate) use surface::Surface; use device::*; pub use surface::Timings; diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index a10ab9e2..1326acc6 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -102,9 +102,9 @@ static NVIDIA_LOGO: &'static [u8] = include_bytes!("../../../../resources/icons/ #[derive(Debug)] pub struct Surface { - pub(super) connector: connector::Handle, + pub(crate) connector: connector::Handle, pub(super) crtc: crtc::Handle, - pub(super) output: Output, + pub(crate) output: Output, known_nodes: HashSet, active: Arc, @@ -114,6 +114,8 @@ pub struct Surface { loop_handle: LoopHandle<'static, State>, thread_command: Sender, thread_token: RegistrationToken, + + dpms: bool, } pub struct SurfaceThreadState { @@ -238,6 +240,7 @@ pub enum ThreadCommand { ScheduleRender, SetMode(Mode, SyncSender>), End, + DpmsOff, } #[derive(Debug)] @@ -350,6 +353,7 @@ impl Surface { loop_handle: evlh.clone(), thread_command: tx, thread_token, + dpms: true, }) } @@ -380,7 +384,9 @@ impl Surface { } pub fn schedule_render(&self) { - let _ = self.thread_command.send(ThreadCommand::ScheduleRender); + if self.dpms { + let _ = self.thread_command.send(ThreadCommand::ScheduleRender); + } } pub fn set_mirroring(&mut self, output: Option) { @@ -431,6 +437,21 @@ impl Surface { rx.recv().context("Surface thread died")? } + + pub fn get_dpms(&mut self) -> bool { + self.dpms + } + + pub fn set_dpms(&mut self, on: bool) { + if self.dpms != on { + self.dpms = on; + if on { + self.schedule_render(); + } else { + let _ = self.thread_command.send(ThreadCommand::DpmsOff); + } + } + } } impl Drop for Surface { @@ -540,6 +561,30 @@ fn surface_thread( let _ = result.send(Err(anyhow::anyhow!("Set mode with inactive surface"))); } } + Event::Msg(ThreadCommand::DpmsOff) => { + if let Some(compositor) = state.compositor.as_mut() { + if let Err(err) = compositor.disable() { + error!("Failed to set DPMS off: {:?}", err); + } + match std::mem::replace(&mut state.state, QueueState::Idle) { + QueueState::Idle => {} + QueueState::Queued(token) + | QueueState::WaitingForEstimatedVBlank(token) => { + state.loop_handle.remove(token); + } + QueueState::WaitingForVBlank { .. } => { + state.timings.discard_current_frame() + } + QueueState::WaitingForEstimatedVBlankAndQueued { + estimated_vblank, + queued_render, + } => { + state.loop_handle.remove(estimated_vblank); + state.loop_handle.remove(queued_render); + } + }; + } + } Event::Closed | Event::Msg(ThreadCommand::End) => { signal.stop(); signal.wakeup(); diff --git a/src/state.rs b/src/state.rs index 7a513a32..835a015c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -15,6 +15,7 @@ use crate::{ drm::WlDrmState, image_source::ImageSourceState, output_configuration::OutputConfigurationState, + output_power::OutputPowerState, screencopy::ScreencopyState, toplevel_info::ToplevelInfoState, toplevel_management::{ManagementCapabilities, ToplevelManagementState}, @@ -198,6 +199,7 @@ pub struct Common { pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, pub output_state: OutputManagerState, pub output_configuration_state: OutputConfigurationState, + pub output_power_state: OutputPowerState, pub presentation_state: PresentationState, pub primary_selection_state: PrimarySelectionState, pub data_control_state: Option, @@ -494,6 +496,7 @@ impl State { let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(dh); let output_state = OutputManagerState::new_with_xdg_output::(dh); let output_configuration_state = OutputConfigurationState::new(dh, client_is_privileged); + let output_power_state = OutputPowerState::new::(dh, client_is_privileged); let presentation_state = PresentationState::new::(dh, clock.id() as u32); let primary_selection_state = PrimarySelectionState::new::(dh); let image_source_state = ImageSourceState::new::(dh, client_is_privileged); @@ -600,6 +603,7 @@ impl State { keyboard_shortcuts_inhibit_state, output_state, output_configuration_state, + output_power_state, presentation_state, primary_selection_state, data_control_state, diff --git a/src/wayland/handlers/mod.rs b/src/wayland/handlers/mod.rs index 82415aa4..78604722 100644 --- a/src/wayland/handlers/mod.rs +++ b/src/wayland/handlers/mod.rs @@ -19,6 +19,7 @@ pub mod keyboard_shortcuts_inhibit; pub mod layer_shell; pub mod output; pub mod output_configuration; +pub mod output_power; pub mod pointer_constraints; pub mod pointer_gestures; pub mod presentation; diff --git a/src/wayland/handlers/output_power.rs b/src/wayland/handlers/output_power.rs new file mode 100644 index 00000000..e01bab9c --- /dev/null +++ b/src/wayland/handlers/output_power.rs @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::{output::Output, reexports::wayland_server::protocol::wl_output::WlOutput}; + +use crate::{ + backend::kms::Surface, + state::{BackendData, State}, + wayland::protocols::output_power::{ + delegate_output_power, OutputPowerHandler, OutputPowerState, + }, +}; + +fn kms_surface_for_wl_output<'a>( + state: &'a mut State, + wl_output: &WlOutput, +) -> Option<&'a mut Surface> { + let output = Output::from_resource(wl_output)?; + if let BackendData::Kms(ref mut kms_state) = &mut state.backend { + kms_state + .drm_devices + .values_mut() + .flat_map(|device| device.surfaces.values_mut()) + .find(|surface| surface.output == output) + } else { + None + } +} + +impl OutputPowerHandler for State { + fn output_power_state(&mut self) -> &mut OutputPowerState { + &mut self.common.output_power_state + } + + fn get_dpms(&mut self, wl_output: &WlOutput) -> Option { + let surface = kms_surface_for_wl_output(self, wl_output)?; + Some(surface.get_dpms()) + } + + fn set_dpms(&mut self, wl_output: &WlOutput, on: bool) { + if let Some(surface) = kms_surface_for_wl_output(self, wl_output) { + surface.set_dpms(on); + } + } +} + +delegate_output_power!(State); diff --git a/src/wayland/protocols/mod.rs b/src/wayland/protocols/mod.rs index 6010d69e..7d18b003 100644 --- a/src/wayland/protocols/mod.rs +++ b/src/wayland/protocols/mod.rs @@ -3,6 +3,7 @@ pub mod drm; pub mod image_source; pub mod output_configuration; +pub mod output_power; pub mod overlap_notify; pub mod screencopy; pub mod toplevel_info; diff --git a/src/wayland/protocols/output_power.rs b/src/wayland/protocols/output_power.rs new file mode 100644 index 00000000..4ac0e137 --- /dev/null +++ b/src/wayland/protocols/output_power.rs @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use smithay::reexports::{ + wayland_protocols_wlr::output_power_management::v1::server::{ + zwlr_output_power_manager_v1::{self, ZwlrOutputPowerManagerV1}, + zwlr_output_power_v1::{self, ZwlrOutputPowerV1}, + }, + wayland_server::{ + backend::GlobalId, protocol::wl_output::WlOutput, Client, DataInit, Dispatch, + DisplayHandle, GlobalDispatch, New, Resource, Weak, + }, +}; +use std::collections::HashSet; +use wayland_backend::{protocol::WEnum, server::ClientId}; + +pub trait OutputPowerHandler { + fn output_power_state(&mut self) -> &mut OutputPowerState; + fn get_dpms(&mut self, output: &WlOutput) -> Option; + fn set_dpms(&mut self, output: &WlOutput, on: bool); +} + +#[derive(Debug)] +pub struct OutputPowerState { + global: GlobalId, + output_powers: HashSet, +} + +impl OutputPowerState { + pub fn new(dh: &DisplayHandle, client_filter: F) -> OutputPowerState + where + D: GlobalDispatch + 'static, + F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static, + { + let global = dh.create_global::( + 1, + OutputPowerManagerGlobalData { + filter: Box::new(client_filter.clone()), + }, + ); + + OutputPowerState { + global, + output_powers: HashSet::new(), + } + } + + pub fn global_id(&self) -> GlobalId { + self.global.clone() + } +} + +pub struct OutputPowerManagerGlobalData { + filter: Box Fn(&'a Client) -> bool + Send + Sync>, +} + +pub struct OutputPowerData { + output: Weak, +} + +impl GlobalDispatch + for OutputPowerState +where + D: GlobalDispatch + + Dispatch + + 'static, +{ + fn bind( + _state: &mut D, + _dh: &DisplayHandle, + _client: &Client, + resource: New, + _global_data: &OutputPowerManagerGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, global_data: &OutputPowerManagerGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for OutputPowerState +where + D: GlobalDispatch + + Dispatch + + Dispatch + + OutputPowerHandler + + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + _obj: &ZwlrOutputPowerManagerV1, + request: zwlr_output_power_manager_v1::Request, + _data: &(), + _dh: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_power_manager_v1::Request::GetOutputPower { id, output } => { + let output_power = data_init.init( + id, + OutputPowerData { + output: output.downgrade(), + }, + ); + if let Some(on) = state.get_dpms(&output) { + output_power.mode(output_power_mode(on)); + state + .output_power_state() + .output_powers + .insert(output_power); + } else { + output_power.failed(); + } + } + zwlr_output_power_manager_v1::Request::Destroy => {} + _ => unreachable!(), + } + } +} + +impl Dispatch for OutputPowerState +where + D: Dispatch + OutputPowerHandler + 'static, +{ + fn request( + state: &mut D, + _client: &Client, + obj: &ZwlrOutputPowerV1, + request: zwlr_output_power_v1::Request, + data: &OutputPowerData, + _dh: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_power_v1::Request::SetMode { mode } => { + if let Ok(output) = data.output.upgrade() { + let on = match mode { + WEnum::Value(zwlr_output_power_v1::Mode::On) => true, + WEnum::Value(zwlr_output_power_v1::Mode::Off) => false, + _ => { + return; + } + }; + state.set_dpms(&output, on); + if let Some(on) = state.get_dpms(&output) { + for output_power in &state.output_power_state().output_powers { + let data = output_power.data::().unwrap(); + if data.output.id() == output.id() { + output_power.mode(output_power_mode(on)); + } + } + } else { + obj.failed(); + state.output_power_state().output_powers.remove(obj); + } + } else { + obj.failed(); + state.output_power_state().output_powers.remove(obj); + } + } + zwlr_output_power_v1::Request::Destroy => {} + _ => unreachable!(), + } + } + + fn destroyed( + state: &mut D, + _client: ClientId, + obj: &ZwlrOutputPowerV1, + _data: &OutputPowerData, + ) { + state.output_power_state().output_powers.remove(obj); + } +} + +fn output_power_mode(on: bool) -> zwlr_output_power_v1::Mode { + if on { + zwlr_output_power_v1::Mode::On + } else { + zwlr_output_power_v1::Mode::Off + } +} + +macro_rules! delegate_output_power { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1: $crate::wayland::protocols::output_power::OutputPowerManagerGlobalData + ] => $crate::wayland::protocols::output_power::OutputPowerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1: () + ] => $crate::wayland::protocols::output_power::OutputPowerState); + smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_v1::ZwlrOutputPowerV1: $crate::wayland::protocols::output_power::OutputPowerData + ] => $crate::wayland::protocols::output_power::OutputPowerState); + }; +} +pub(crate) use delegate_output_power;