DPMS with wlr-output-power-management-unstable-v1 protocol

This commit is contained in:
Ian Douglas Scott 2024-08-16 20:49:59 -07:00 committed by Victoria Brekenfeld
parent 65a54706f5
commit ea27ec5e28
10 changed files with 390 additions and 5 deletions

23
Cargo.lock generated
View file

@ -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/smithay//smithay?rev=05c49f7#05c49f7a193bc89fba12a6484dbac895d5c9f853"
dependencies = [
"appendlist",
"ash 0.38.0+1.3.281",

View file

@ -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,4 @@ 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 = "05c49f7" }

View file

@ -43,6 +43,7 @@ mod drm_helpers;
pub mod render;
mod socket;
mod surface;
pub(crate) use surface::Surface;
use device::*;
pub use surface::Timings;

View file

@ -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<DrmNode>,
active: Arc<AtomicBool>,
@ -114,6 +114,8 @@ pub struct Surface {
loop_handle: LoopHandle<'static, State>,
thread_command: Sender<ThreadCommand>,
thread_token: RegistrationToken,
dpms: bool,
}
pub struct SurfaceThreadState {
@ -238,6 +240,7 @@ pub enum ThreadCommand {
ScheduleRender,
SetMode(Mode, SyncSender<Result<()>>),
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<Output>) {
@ -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.clear() {
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();

View file

@ -164,6 +164,8 @@ impl State {
where
<B as InputBackend>::Device: 'static,
{
crate::wayland::handlers::output_power::set_all_surfaces_dpms_on(self);
use smithay::backend::input::Event;
match event {
InputEvent::DeviceAdded { device } => {

View file

@ -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<State>,
pub output_power_state: OutputPowerState,
pub presentation_state: PresentationState,
pub primary_selection_state: PrimarySelectionState,
pub data_control_state: Option<DataControlState>,
@ -487,6 +489,7 @@ impl State {
let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::<Self>(dh);
let output_state = OutputManagerState::new_with_xdg_output::<Self>(dh);
let output_configuration_state = OutputConfigurationState::new(dh, client_is_privileged);
let output_power_state = OutputPowerState::new::<Self, _>(dh, client_is_privileged);
let presentation_state = PresentationState::new::<Self>(dh, clock.id() as u32);
let primary_selection_state = PrimarySelectionState::new::<Self>(dh);
let image_source_state = ImageSourceState::new::<Self, _>(dh, client_is_privileged);
@ -593,6 +596,7 @@ impl State {
keyboard_shortcuts_inhibit_state,
output_state,
output_configuration_state,
output_power_state,
presentation_state,
primary_selection_state,
data_control_state,

View file

@ -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;

View file

@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::output::Output;
use crate::{
backend::kms::Surface,
state::{BackendData, State},
utils::prelude::OutputExt,
wayland::protocols::output_power::{
delegate_output_power, OutputPowerHandler, OutputPowerState,
},
};
pub fn set_all_surfaces_dpms_on(state: &mut State) {
let mut changed = false;
for surface in kms_surfaces(state) {
if !surface.get_dpms() {
surface.set_dpms(true);
changed = true;
}
}
if changed {
OutputPowerState::refresh(state);
}
}
fn kms_surfaces(state: &mut State) -> impl Iterator<Item = &mut Surface> {
if let BackendData::Kms(ref mut kms_state) = &mut state.backend {
Some(
kms_state
.drm_devices
.values_mut()
.flat_map(|device| device.surfaces.values_mut()),
)
} else {
None
}
.into_iter()
.flatten()
}
// Get KMS `Surface` for output, and for all outputs mirroring it
fn kms_surfaces_for_output<'a>(
state: &'a mut State,
output: &'a Output,
) -> impl Iterator<Item = &'a mut Surface> + 'a {
kms_surfaces(state).filter(move |surface| {
surface.output == *output || surface.output.mirroring().as_ref() == Some(&output)
})
}
// Get KMS `Surface` for output
fn primary_kms_surface_for_output<'a>(
state: &'a mut State,
output: &Output,
) -> Option<&'a mut Surface> {
kms_surfaces(state).find(|surface| surface.output == *output)
}
impl OutputPowerHandler for State {
fn output_power_state(&mut self) -> &mut OutputPowerState {
&mut self.common.output_power_state
}
fn get_dpms(&mut self, output: &Output) -> Option<bool> {
let surface = primary_kms_surface_for_output(self, output)?;
Some(surface.get_dpms())
}
fn set_dpms(&mut self, output: &Output, on: bool) {
for surface in kms_surfaces_for_output(self, output) {
surface.set_dpms(on);
}
}
}
delegate_output_power!(State);

View file

@ -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;

View file

@ -0,0 +1,231 @@
// SPDX-License-Identifier: GPL-3.0-only
use smithay::{
output::{Output, WeakOutput},
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, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New,
Resource,
},
},
};
use std::{collections::HashMap, mem};
use wayland_backend::{protocol::WEnum, server::ClientId};
pub trait OutputPowerHandler {
fn output_power_state(&mut self) -> &mut OutputPowerState;
fn get_dpms(&mut self, output: &Output) -> Option<bool>;
fn set_dpms(&mut self, output: &Output, on: bool);
}
#[derive(Debug)]
pub struct OutputPowerState {
global: GlobalId,
output_powers: HashMap<ZwlrOutputPowerV1, zwlr_output_power_v1::Mode>,
}
impl OutputPowerState {
pub fn new<D, F>(dh: &DisplayHandle, client_filter: F) -> OutputPowerState
where
D: GlobalDispatch<ZwlrOutputPowerManagerV1, OutputPowerManagerGlobalData> + 'static,
F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static,
{
let global = dh.create_global::<D, ZwlrOutputPowerManagerV1, _>(
1,
OutputPowerManagerGlobalData {
filter: Box::new(client_filter.clone()),
},
);
OutputPowerState {
global,
output_powers: HashMap::new(),
}
}
pub fn global_id(&self) -> GlobalId {
self.global.clone()
}
/// Send `mode` events for any output powers where dpms state has changed.
///
/// This is handled automatically for changes made through the protocol.
pub fn refresh<D: OutputPowerHandler>(state: &mut D) {
let mut output_powers = mem::take(&mut state.output_power_state().output_powers);
for (output_power, old_mode) in output_powers.iter_mut() {
let data = output_power.data::<OutputPowerData>().unwrap();
if let Some(output) = data.output.as_ref().and_then(|o| o.upgrade()) {
if let Some(on) = state.get_dpms(&output) {
let mode = output_power_mode(on);
if mode != *old_mode {
output_power.mode(mode);
*old_mode = mode;
}
}
}
}
state.output_power_state().output_powers = output_powers;
}
}
pub struct OutputPowerManagerGlobalData {
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
}
pub struct OutputPowerData {
output: Option<WeakOutput>,
}
impl<D> GlobalDispatch<ZwlrOutputPowerManagerV1, OutputPowerManagerGlobalData, D>
for OutputPowerState
where
D: GlobalDispatch<ZwlrOutputPowerManagerV1, OutputPowerManagerGlobalData>
+ Dispatch<ZwlrOutputPowerManagerV1, ()>
+ 'static,
{
fn bind(
_state: &mut D,
_dh: &DisplayHandle,
_client: &Client,
resource: New<ZwlrOutputPowerManagerV1>,
_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<D> Dispatch<ZwlrOutputPowerManagerV1, (), D> for OutputPowerState
where
D: GlobalDispatch<ZwlrOutputPowerManagerV1, OutputPowerManagerGlobalData>
+ Dispatch<ZwlrOutputPowerManagerV1, ()>
+ Dispatch<ZwlrOutputPowerV1, OutputPowerData>
+ 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 = Output::from_resource(&output);
let output_power = data_init.init(
id,
OutputPowerData {
output: output.as_ref().map(|o| o.downgrade()),
},
);
if let Some(on) = output.as_ref().and_then(|o| state.get_dpms(o)) {
let mode = output_power_mode(on);
output_power.mode(mode);
state
.output_power_state()
.output_powers
.insert(output_power, mode);
} else {
output_power.failed();
}
}
zwlr_output_power_manager_v1::Request::Destroy => {}
_ => unreachable!(),
}
}
}
impl<D> Dispatch<ZwlrOutputPowerV1, OutputPowerData, D> for OutputPowerState
where
D: Dispatch<ZwlrOutputPowerV1, OutputPowerData> + 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 Some(output) = data.output.as_ref().and_then(|o| o.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) {
let mode = output_power_mode(on);
for (output_power, old_mode) in
state.output_power_state().output_powers.iter_mut()
{
let data = output_power.data::<OutputPowerData>().unwrap();
if let Some(o) = data.output.as_ref() {
if o == &output && mode != *old_mode {
output_power.mode(mode);
*old_mode = mode;
}
}
}
} 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;