From 07917a82ef0bad39cf141e45d4e79cb7348b2e10 Mon Sep 17 00:00:00 2001 From: Ottatop Date: Mon, 3 Jun 2024 20:59:23 -0500 Subject: [PATCH] Impl wlr-output-power-management --- src/api.rs | 1 + src/backend/udev.rs | 43 +---- src/config.rs | 1 + src/handlers.rs | 18 ++- src/output.rs | 10 +- src/protocol.rs | 2 + src/protocol/gamma_control.rs | 1 - src/protocol/output_management.rs | 5 - src/protocol/output_power_management.rs | 206 ++++++++++++++++++++++++ src/state.rs | 6 + 10 files changed, 242 insertions(+), 51 deletions(-) create mode 100644 src/protocol/output_power_management.rs diff --git a/src/api.rs b/src/api.rs index c7d60da..ce71925 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1070,6 +1070,7 @@ impl output_service_server::OutputService for OutputService { let Some(mode) = Some(request).and_then(|request| { Some(smithay::output::Mode { size: (request.pixel_width? as i32, request.pixel_height? as i32).into(), + // FIXME: this is nullable, pick a mode with highest refresh if None refresh: request.refresh_rate_millihz? as i32, }) }) else { diff --git a/src/backend/udev.rs b/src/backend/udev.rs index 9c3c6b0..f304fea 100644 --- a/src/backend/udev.rs +++ b/src/backend/udev.rs @@ -11,9 +11,7 @@ use std::{ use anyhow::{anyhow, ensure, Context}; use drm::{set_crtc_active, util::create_drm_mode}; -use pinnacle_api_defs::pinnacle::signal::v0alpha1::{ - OutputConnectResponse, OutputDisconnectResponse, -}; +use pinnacle_api_defs::pinnacle::signal::v0alpha1::OutputConnectResponse; use smithay::{ backend::{ allocator::{ @@ -51,10 +49,7 @@ use smithay::{ vulkan::{self, version::Version, PhysicalDevice}, SwapBuffersError, }, - desktop::{ - layer_map_for_output, - utils::{send_frames_surface_tree, OutputPresentationFeedback}, - }, + desktop::utils::{send_frames_surface_tree, OutputPresentationFeedback}, input::pointer::CursorImageStatus, output::{Output, PhysicalProperties, Subpixel}, reexports::{ @@ -1199,39 +1194,7 @@ impl Udev { .cloned(); if let Some(output) = output { - // Save this output's state. It will be restored if the monitor gets replugged. - pinnacle.config.connector_saved_states.insert( - OutputName(output.name()), - ConnectorSavedState { - loc: output.current_location(), - tags: output.with_state(|state| state.tags.clone()), - scale: Some(output.current_scale()), - }, - ); - - // TODO: extract into a `remove_output` function and unify with dummy backend - for layer in layer_map_for_output(&output).layers() { - layer.layer_surface().send_close(); - } - - pinnacle.space.unmap_output(&output); - pinnacle.gamma_control_manager_state.output_removed(&output); - - pinnacle.signal_state.output_disconnect.signal(|buffer| { - buffer.push_back(OutputDisconnectResponse { - output_name: Some(output.name()), - }) - }); - - pinnacle - .output_management_manager_state - .remove_head(&output); - pinnacle.output_management_manager_state.update::(); - - if let Some(global) = pinnacle.outputs.remove(&output) { - // TODO: disable ahead of time - pinnacle.display_handle.remove_global::(global); - } + pinnacle.remove_output(&output); } } diff --git a/src/config.rs b/src/config.rs index c65c51c..5d1f5a1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -347,6 +347,7 @@ pub struct ConnectorSavedState { pub tags: Vec, /// The output's previous scale pub scale: Option, + // TODO: transform } /// Parse a metaconfig file in `config_dir`, if any. diff --git a/src/handlers.rs b/src/handlers.rs index f29a2ab..8e53920 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -74,7 +74,7 @@ use tracing::{debug, error, trace, warn}; use crate::{ backend::Backend, delegate_foreign_toplevel, delegate_gamma_control, delegate_output_management, - delegate_screencopy, + delegate_output_power_management, delegate_screencopy, focus::{keyboard::KeyboardFocusTarget, pointer::PointerFocusTarget}, handlers::xdg_shell::snapshot_pre_commit_hook, protocol::{ @@ -83,6 +83,7 @@ use crate::{ output_management::{ OutputConfiguration, OutputManagementHandler, OutputManagementManagerState, }, + output_power_management::{OutputPowerManagementHandler, OutputPowerManagementState}, screencopy::{Screencopy, ScreencopyHandler}, }, render::util::snapshot::capture_snapshots_on_output, @@ -1011,6 +1012,21 @@ impl OutputManagementHandler for State { } delegate_output_management!(State); +impl OutputPowerManagementHandler for State { + fn output_power_management_state(&mut self) -> &mut OutputPowerManagementState { + &mut self.pinnacle.output_power_management_state + } + + fn set_mode(&mut self, output: &Output, powered: bool) { + self.backend.set_output_powered(output, powered); + + if powered { + self.schedule_render(output); + } + } +} +delegate_output_power_management!(State); + impl Pinnacle { fn position_popup(&self, popup: &PopupSurface) { trace!("State::position_popup"); diff --git a/src/output.rs b/src/output.rs index d9f7bb1..7c0afe5 100644 --- a/src/output.rs +++ b/src/output.rs @@ -258,10 +258,16 @@ impl Pinnacle { } self.unmapped_outputs.remove(output); + for layer in layer_map_for_output(output).layers() { + layer.layer_surface().send_close(); + } + self.space.unmap_output(output); self.gamma_control_manager_state.output_removed(output); + self.output_power_management_state.output_removed(output); + self.output_management_manager_state.remove_head(output); self.output_management_manager_state.update::(); @@ -279,9 +285,5 @@ impl Pinnacle { scale: Some(output.current_scale()), }, ); - - for layer in layer_map_for_output(output).layers() { - layer.layer_surface().send_close(); - } } } diff --git a/src/protocol.rs b/src/protocol.rs index a58b48b..4309c87 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,4 +1,6 @@ pub mod foreign_toplevel; pub mod gamma_control; pub mod output_management; +pub mod output_power_management; pub mod screencopy; + diff --git a/src/protocol/gamma_control.rs b/src/protocol/gamma_control.rs index 645dd9e..c65e0c6 100644 --- a/src/protocol/gamma_control.rs +++ b/src/protocol/gamma_control.rs @@ -150,7 +150,6 @@ pub trait GammaControlHandler { fn gamma_control_destroyed(&mut self, output: &Output); } -#[allow(missing_docs)] #[macro_export] macro_rules! delegate_gamma_control { ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { diff --git a/src/protocol/output_management.rs b/src/protocol/output_management.rs index c023a9c..e9a98ed 100644 --- a/src/protocol/output_management.rs +++ b/src/protocol/output_management.rs @@ -590,8 +590,6 @@ where let correct_serial = manager_data.serial == serial; if !correct_serial { - tracing::info!(mgr_serial = manager_data.serial, cfg_serial = serial); - tracing::info!("cancelled, incorrect serial 527"); config.cancelled(); config .data::() @@ -736,7 +734,6 @@ where manager_for_configuration(state, resource).map(|(_, data)| data.serial); if manager_serial != Some(pending_data.serial) { - tracing::info!("cancelled, incorrect serial 661"); resource.cancelled(); data.cancelled = true; return; @@ -765,7 +762,6 @@ where manager_for_configuration(state, resource).map(|(_, data)| data.serial); if manager_serial != Some(pending_data.serial) { - tracing::info!("cancelled, incorrect serial 689"); resource.cancelled(); data.cancelled = true; return; @@ -795,7 +791,6 @@ where manager_for_configuration(state, resource).map(|(_, data)| data.serial); if manager_serial != Some(pending_data.serial) { - tracing::info!("cancelled, incorrect serial 718"); resource.cancelled(); data.cancelled = true; return; diff --git a/src/protocol/output_power_management.rs b/src/protocol/output_power_management.rs new file mode 100644 index 0000000..4f2673f --- /dev/null +++ b/src/protocol/output_power_management.rs @@ -0,0 +1,206 @@ +use std::collections::HashMap; + +use smithay::{ + output::Output, + reexports::{ + wayland_protocols_wlr::output_power_management::v1::server::{ + zwlr_output_power_manager_v1::{self, ZwlrOutputPowerManagerV1}, + zwlr_output_power_v1::{self, ZwlrOutputPowerV1}, + }, + wayland_server::{ + self, backend::ClientId, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, + Resource, WEnum, + }, + }, +}; +use tracing::warn; + +use crate::state::WithState; + +const VERSION: u32 = 1; + +pub struct OutputPowerManagementState { + clients: HashMap, +} + +pub struct OutputPowerManagementGlobalData { + filter: Box bool + Send + Sync + 'static>, +} + +pub trait OutputPowerManagementHandler { + fn output_power_management_state(&mut self) -> &mut OutputPowerManagementState; + fn set_mode(&mut self, output: &Output, powered: bool); +} + +impl OutputPowerManagementState { + pub fn new(display: &DisplayHandle, filter: F) -> Self + where + D: GlobalDispatch + 'static, + F: Fn(&Client) -> bool + Send + Sync + 'static, + { + let data = OutputPowerManagementGlobalData { + filter: Box::new(filter), + }; + + display.create_global::(VERSION, data); + + Self { + clients: HashMap::new(), + } + } + + pub fn output_removed(&mut self, output: &Output) { + if let Some(power) = self.clients.remove(output) { + power.failed(); + } + } +} + +impl GlobalDispatch + for OutputPowerManagementState +where + D: Dispatch + OutputPowerManagementHandler, +{ + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &Client, + resource: wayland_server::New, + _global_data: &OutputPowerManagementGlobalData, + data_init: &mut DataInit<'_, D>, + ) { + data_init.init(resource, ()); + } + + fn can_view(client: Client, global_data: &OutputPowerManagementGlobalData) -> bool { + (global_data.filter)(&client) + } +} + +impl Dispatch for OutputPowerManagementState +where + D: Dispatch + OutputPowerManagementHandler, +{ + fn request( + state: &mut D, + _client: &Client, + _resource: &ZwlrOutputPowerManagerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_power_manager_v1::Request::GetOutputPower { id, output } => { + let Some(output) = Output::from_resource(&output) else { + warn!("wlr-output-power-management: no output for wl_output {output:?}"); + let power = data_init.init(id, ()); + power.failed(); + return; + }; + + if state + .output_power_management_state() + .clients + .contains_key(&output) + { + warn!( + "wlr-output-power-management: {} already has an active power manager", + output.name() + ); + let power = data_init.init(id, ()); + power.failed(); + return; + } + + let power = data_init.init(id, ()); + let is_powered = output.with_state(|state| state.powered); + power.mode(match is_powered { + true => zwlr_output_power_v1::Mode::On, + false => zwlr_output_power_v1::Mode::Off, + }); + + state + .output_power_management_state() + .clients + .insert(output, power); + } + zwlr_output_power_manager_v1::Request::Destroy => (), + _ => unreachable!(), + } + } +} + +impl Dispatch for OutputPowerManagementState +where + D: Dispatch + OutputPowerManagementHandler, +{ + fn request( + state: &mut D, + _client: &Client, + resource: &ZwlrOutputPowerV1, + request: ::Request, + _data: &(), + _dhandle: &DisplayHandle, + _data_init: &mut DataInit<'_, D>, + ) { + match request { + zwlr_output_power_v1::Request::SetMode { mode } => { + let Some(output) = state + .output_power_management_state() + .clients + .iter() + .find_map(|(output, power)| (power == resource).then_some(output.clone())) + else { + return; + }; + + state.set_mode( + &output, + match mode { + WEnum::Value(zwlr_output_power_v1::Mode::On) => true, + WEnum::Value(zwlr_output_power_v1::Mode::Off) => false, + mode => { + resource.post_error( + zwlr_output_power_v1::Error::InvalidMode, + format!("invalid mode {mode:?}"), + ); + return; + } + }, + ); + } + zwlr_output_power_v1::Request::Destroy => { + state + .output_power_management_state() + .clients + .retain(|_, power| power == resource); + } + _ => todo!(), + } + } + + fn destroyed(state: &mut D, _client: ClientId, resource: &ZwlrOutputPowerV1, _data: &()) { + state + .output_power_management_state() + .clients + .retain(|_, power| power == resource); + } +} + +#[macro_export] +macro_rules! delegate_output_power_management { + ($(@<$( $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::protocol::output_power_management::OutputPowerManagementGlobalData + ] => $crate::protocol::output_power_management::OutputPowerManagementState); + + 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::protocol::output_power_management::OutputPowerManagementState); + + 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::protocol::output_power_management::OutputPowerManagementState); + }; +} diff --git a/src/state.rs b/src/state.rs index 4c19287..dde8244 100644 --- a/src/state.rs +++ b/src/state.rs @@ -13,6 +13,7 @@ use crate::{ foreign_toplevel::{self, ForeignToplevelManagerState}, gamma_control::GammaControlManagerState, output_management::OutputManagementManagerState, + output_power_management::OutputPowerManagementState, screencopy::ScreencopyManagerState, }, window::WindowElement, @@ -109,6 +110,7 @@ pub struct Pinnacle { pub xwayland_shell_state: XWaylandShellState, pub idle_notifier_state: IdleNotifierState, pub output_management_manager_state: OutputManagementManagerState, + pub output_power_management_state: OutputPowerManagementState, pub lock_state: LockState, @@ -309,6 +311,10 @@ impl Pinnacle { &display_handle, filter_restricted_client, ), + output_power_management_state: OutputPowerManagementState::new::( + &display_handle, + filter_restricted_client, + ), lock_state: LockState::default(),