Skip to content

Commit 1d91c70

Browse files
agausmannBot-wxt1221
authored andcommitted
Implement the wlr-output-power-management protocol
Related to #108, #856 This protocol allows external programs (e.g. wlopm) to control and query the output power state (on/off). In the current implementation, when the client sends a `set_mode` request, the handler calls `activate_monitors()` or `deactivate_monitors()` to set that state for all monitors. This is probably simpler than adding full support for per-output power management, and is good enough for the most common use cases like idle screensavers. The `output_power.mode` event is now emitted by `activate_monitors()` and `deactivate_monitors()` when the state changes. A bit of a rewrite was needed because the tty backend tries to activate monitors on resume, but previously it wasn't calling `activate_monitors()` because it is unable to provide a reference to the `Backend` that wraps it. I worked around this by adding a second method that does not take a backend parameter, instead assuming that the backend will take care of calling itself.
1 parent 19e55a2 commit 1d91c70

File tree

5 files changed

+306
-4
lines changed

5 files changed

+306
-4
lines changed

src/backend/tty.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -711,8 +711,11 @@ impl Tty {
711711
self.refresh_ipc_outputs(niri);
712712

713713
niri.notify_activity();
714-
niri.monitors_active = true;
714+
niri.idle_notifier_state.notify_activity(&niri.seat);
715+
715716
self.set_monitors_active(true);
717+
niri.activate_monitors_without_backend();
718+
716719
niri.queue_redraw_all();
717720
}
718721
}

src/handlers/mod.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ use crate::protocols::foreign_toplevel::{
8282
use crate::protocols::gamma_control::{GammaControlHandler, GammaControlManagerState};
8383
use crate::protocols::mutter_x11_interop::MutterX11InteropHandler;
8484
use crate::protocols::output_management::{OutputManagementHandler, OutputManagementManagerState};
85+
use crate::protocols::output_power_management::{
86+
self, OutputPowerManagementHandler, OutputPowerManagementManagerState,
87+
};
8588
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler, ScreencopyManagerState};
8689
use crate::protocols::virtual_pointer::{
8790
VirtualPointerAxisEvent, VirtualPointerButtonEvent, VirtualPointerHandler,
@@ -90,8 +93,8 @@ use crate::protocols::virtual_pointer::{
9093
};
9194
use crate::utils::{output_size, send_scale_transform};
9295
use crate::{
93-
delegate_ext_workspace, delegate_foreign_toplevel, delegate_gamma_control,
94-
delegate_mutter_x11_interop, delegate_output_management, delegate_screencopy,
96+
delegate_foreign_toplevel, delegate_gamma_control,delegate_ext_workspace,
97+
delegate_mutter_x11_interop, delegate_output_management, delegate_output_power_management, delegate_screencopy,
9598
delegate_virtual_pointer,
9699
};
97100

@@ -841,6 +844,34 @@ impl OutputManagementHandler for State {
841844
}
842845
delegate_output_management!(State);
843846

847+
impl OutputPowerManagementHandler for State {
848+
fn output_power_manager_state(&mut self) -> &mut OutputPowerManagementManagerState {
849+
&mut self.niri.output_power_management_state
850+
}
851+
852+
fn get_output_power_mode(&mut self, output: &Output) -> output_power_management::Mode {
853+
// todo: per-output power management
854+
let _ = output;
855+
856+
self.niri.monitors_active.into()
857+
}
858+
859+
fn set_output_power_mode(&mut self, output: &Output, mode: output_power_management::Mode) {
860+
// todo: per-output power management
861+
let _ = output;
862+
863+
match mode {
864+
output_power_management::Mode::Off => {
865+
self.niri.deactivate_monitors(&mut self.backend);
866+
}
867+
output_power_management::Mode::On => {
868+
self.niri.activate_monitors(&mut self.backend);
869+
}
870+
}
871+
}
872+
}
873+
delegate_output_power_management!(State);
874+
844875
impl MutterX11InteropHandler for State {}
845876
delegate_mutter_x11_interop!(State);
846877

src/niri.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState};
148148
use crate::protocols::gamma_control::GammaControlManagerState;
149149
use crate::protocols::mutter_x11_interop::MutterX11InteropManagerState;
150150
use crate::protocols::output_management::OutputManagementManagerState;
151+
use crate::protocols::output_power_management::{self, OutputPowerManagementManagerState};
151152
use crate::protocols::screencopy::{Screencopy, ScreencopyBuffer, ScreencopyManagerState};
152153
use crate::protocols::virtual_pointer::VirtualPointerManagerState;
153154
use crate::pw_utils::{Cast, PipeWire};
@@ -278,6 +279,7 @@ pub struct Niri {
278279
pub ext_workspace_state: ExtWorkspaceManagerState,
279280
pub screencopy_state: ScreencopyManagerState,
280281
pub output_management_state: OutputManagementManagerState,
282+
pub output_power_management_state: OutputPowerManagementManagerState,
281283
pub viewporter_state: ViewporterState,
282284
pub xdg_foreign_state: XdgForeignState,
283285
pub shm_state: ShmState,
@@ -2525,6 +2527,10 @@ impl Niri {
25252527
let mut output_management_state =
25262528
OutputManagementManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
25272529
output_management_state.on_config_changed(config_.outputs.clone());
2530+
let output_power_management_state = OutputPowerManagementManagerState::new::<State, _>(
2531+
&display_handle,
2532+
client_is_unrestricted,
2533+
);
25282534
let screencopy_state =
25292535
ScreencopyManagerState::new::<State, _>(&display_handle, client_is_unrestricted);
25302536
let viewporter_state = ViewporterState::new::<State>(&display_handle);
@@ -2718,6 +2724,7 @@ impl Niri {
27182724
foreign_toplevel_state,
27192725
ext_workspace_state,
27202726
output_management_state,
2727+
output_power_management_state,
27212728
screencopy_state,
27222729
viewporter_state,
27232730
xdg_foreign_state,
@@ -3216,15 +3223,41 @@ impl Niri {
32163223

32173224
self.monitors_active = false;
32183225
backend.set_monitors_active(false);
3226+
for output in self.global_space.outputs() {
3227+
self.output_power_management_state
3228+
.output_power_mode_changed(output, output_power_management::Mode::Off);
3229+
}
32193230
}
32203231

32213232
pub fn activate_monitors(&mut self, backend: &mut Backend) {
32223233
if self.monitors_active {
32233234
return;
32243235
}
3236+
backend.set_monitors_active(true);
3237+
self.activate_monitors_without_backend();
3238+
}
3239+
3240+
/// Note: This is intended to only be directly used by the backend module,
3241+
/// when the backend would want to call `activate_monitors()` but is unable
3242+
/// to provide a reference to the whole `Backend` type.
3243+
///
3244+
/// The backend needs to call its own `set_monitors_active(true)` before
3245+
/// calling this.
3246+
///
3247+
/// One possible long-term solution: The type of the backend argument could
3248+
/// be turned into a trait bound, where the trait is implemented by both the
3249+
/// top-level `Backend` and its inner variant types.
3250+
pub fn activate_monitors_without_backend(&mut self) {
3251+
if self.monitors_active {
3252+
return;
3253+
}
32253254

32263255
self.monitors_active = true;
3227-
backend.set_monitors_active(true);
3256+
3257+
for output in self.global_space.outputs() {
3258+
self.output_power_management_state
3259+
.output_power_mode_changed(output, output_power_management::Mode::On);
3260+
}
32283261

32293262
self.queue_redraw_all();
32303263
}

src/protocols/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod foreign_toplevel;
33
pub mod gamma_control;
44
pub mod mutter_x11_interop;
55
pub mod output_management;
6+
pub mod output_power_management;
67
pub mod screencopy;
78
pub mod virtual_pointer;
89

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
use std::collections::HashMap;
2+
3+
use smithay::{
4+
output::Output,
5+
reexports::{
6+
wayland_protocols_wlr::output_power_management::v1::server::{
7+
zwlr_output_power_manager_v1, zwlr_output_power_v1,
8+
},
9+
wayland_server::{
10+
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
11+
},
12+
},
13+
};
14+
use zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1;
15+
use zwlr_output_power_v1::ZwlrOutputPowerV1;
16+
17+
const VERSION: u32 = 1;
18+
19+
#[derive(Clone, Copy)]
20+
pub enum Mode {
21+
Off,
22+
On,
23+
}
24+
25+
impl From<bool> for Mode {
26+
fn from(value: bool) -> Self {
27+
match value {
28+
false => Self::Off,
29+
true => Self::On,
30+
}
31+
}
32+
}
33+
34+
impl From<zwlr_output_power_v1::Mode> for Mode {
35+
fn from(value: zwlr_output_power_v1::Mode) -> Self {
36+
match value {
37+
zwlr_output_power_v1::Mode::Off => Self::Off,
38+
zwlr_output_power_v1::Mode::On => Self::On,
39+
_ => unreachable!(),
40+
}
41+
}
42+
}
43+
44+
impl From<Mode> for zwlr_output_power_v1::Mode {
45+
fn from(value: Mode) -> Self {
46+
match value {
47+
Mode::Off => Self::Off,
48+
Mode::On => Self::On,
49+
}
50+
}
51+
}
52+
53+
pub struct OutputPowerManagerGlobalData {
54+
filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>,
55+
}
56+
57+
pub trait OutputPowerManagementHandler {
58+
fn output_power_manager_state(&mut self) -> &mut OutputPowerManagementManagerState;
59+
fn get_output_power_mode(&mut self, output: &Output) -> Mode;
60+
fn set_output_power_mode(&mut self, output: &Output, mode: Mode);
61+
}
62+
63+
pub struct OutputPowerManagementManagerState {
64+
// Active controls only. Failed ones are removed.
65+
output_powers: HashMap<Output, Vec<ZwlrOutputPowerV1>>,
66+
}
67+
68+
pub struct OutputPowerState {}
69+
70+
impl OutputPowerManagementManagerState {
71+
pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
72+
where
73+
D: GlobalDispatch<ZwlrOutputPowerManagerV1, OutputPowerManagerGlobalData>,
74+
D: Dispatch<ZwlrOutputPowerManagerV1, ()>,
75+
D: Dispatch<ZwlrOutputPowerV1, OutputPowerState>,
76+
D: OutputPowerManagementHandler,
77+
D: 'static,
78+
F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static,
79+
{
80+
let global_data = OutputPowerManagerGlobalData {
81+
filter: Box::new(filter),
82+
};
83+
display.create_global::<D, ZwlrOutputPowerManagerV1, _>(VERSION, global_data);
84+
Self {
85+
output_powers: HashMap::new(),
86+
}
87+
}
88+
89+
pub fn output_removed(&mut self, output: &Output) {
90+
if let Some(list) = self.output_powers.remove(output) {
91+
for num in list {
92+
num.failed();
93+
}
94+
}
95+
}
96+
97+
pub fn output_power_mode_changed(&mut self, output: &Output, mode: Mode) {
98+
match self.output_powers.get_mut(output) {
99+
Some(list) => {
100+
for num in list {
101+
num.mode(mode.into());
102+
}
103+
}
104+
None => {
105+
}
106+
}
107+
}
108+
}
109+
110+
impl<D> GlobalDispatch<ZwlrOutputPowerManagerV1, OutputPowerManagerGlobalData, D>
111+
for OutputPowerManagementManagerState
112+
where
113+
D: GlobalDispatch<ZwlrOutputPowerManagerV1, OutputPowerManagerGlobalData>,
114+
D: Dispatch<ZwlrOutputPowerManagerV1, ()>,
115+
D: 'static,
116+
{
117+
fn bind(
118+
_state: &mut D,
119+
_handle: &DisplayHandle,
120+
_client: &Client,
121+
manager: New<ZwlrOutputPowerManagerV1>,
122+
_global_data: &OutputPowerManagerGlobalData,
123+
data_init: &mut DataInit<'_, D>,
124+
) {
125+
data_init.init(manager, ());
126+
}
127+
128+
fn can_view(client: Client, global_data: &OutputPowerManagerGlobalData) -> bool {
129+
(global_data.filter)(&client)
130+
}
131+
}
132+
133+
impl<D> Dispatch<ZwlrOutputPowerManagerV1, (), D> for OutputPowerManagementManagerState
134+
where
135+
D: Dispatch<ZwlrOutputPowerManagerV1, ()>,
136+
D: Dispatch<ZwlrOutputPowerV1, OutputPowerState>,
137+
D: OutputPowerManagementHandler,
138+
D: 'static,
139+
{
140+
fn request(
141+
state: &mut D,
142+
_client: &Client,
143+
_resource: &ZwlrOutputPowerManagerV1,
144+
request: <ZwlrOutputPowerManagerV1 as Resource>::Request,
145+
_data: &(),
146+
_dhandle: &DisplayHandle,
147+
data_init: &mut DataInit<'_, D>,
148+
) {
149+
match request {
150+
zwlr_output_power_manager_v1::Request::GetOutputPower { id, output } => {
151+
let zwlr_output_power = data_init.init(id, OutputPowerState {});
152+
if let Some(output) = Output::from_resource(&output) {
153+
state.output_power_manager_state().output_powers.entry(output.clone()).or_insert_with(Vec::new).push(zwlr_output_power.clone());
154+
zwlr_output_power.mode(state.get_output_power_mode(&output).into());
155+
return ;
156+
}
157+
158+
// Output not found,
159+
// or output power instance already exists
160+
zwlr_output_power.failed();
161+
}
162+
zwlr_output_power_manager_v1::Request::Destroy => (),
163+
_ => unreachable!(),
164+
}
165+
}
166+
}
167+
168+
impl<D> Dispatch<ZwlrOutputPowerV1, OutputPowerState, D> for OutputPowerManagementManagerState
169+
where
170+
D: Dispatch<ZwlrOutputPowerV1, OutputPowerState>,
171+
D: OutputPowerManagementHandler,
172+
D: 'static,
173+
{
174+
fn request(
175+
state: &mut D,
176+
_client: &Client,
177+
resource: &ZwlrOutputPowerV1,
178+
request: <ZwlrOutputPowerV1 as Resource>::Request,
179+
_data: &OutputPowerState,
180+
_dhandle: &DisplayHandle,
181+
_data_init: &mut DataInit<'_, D>,
182+
) {
183+
match request {
184+
zwlr_output_power_v1::Request::SetMode { mode } => {
185+
let output_powers = &mut state.output_power_manager_state().output_powers;
186+
let Some((output, _)) = output_powers.iter().find(|(_, x)| x.contains( resource)) else {
187+
return;
188+
};
189+
let output = output.clone();
190+
191+
if let Ok(mode) = mode.into_result() {
192+
// note: if set_output_power_mode becomes fallible, this should
193+
// fail the resource; see gamma_control for an example of how to
194+
// implement that.
195+
state.set_output_power_mode(&output, mode.into());
196+
} else {
197+
resource.failed();
198+
}
199+
}
200+
zwlr_output_power_v1::Request::Destroy => (),
201+
_ => unreachable!(),
202+
}
203+
}
204+
205+
fn destroyed(
206+
state: &mut D,
207+
_client: wayland_backend::server::ClientId,
208+
resource: &ZwlrOutputPowerV1,
209+
_data: &OutputPowerState,
210+
) {
211+
let output_powers = &mut state.output_power_manager_state().output_powers;
212+
let Some((output, _)) = output_powers.iter().find(|(_, x)| x.contains( resource)) else {
213+
return;
214+
};
215+
output_powers.remove(&output.clone());
216+
}
217+
}
218+
219+
#[macro_export]
220+
macro_rules! delegate_output_power_management {
221+
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
222+
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
223+
smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1: $crate::protocols::output_power_management::OutputPowerManagerGlobalData
224+
] => $crate::protocols::output_power_management::OutputPowerManagementManagerState);
225+
226+
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
227+
smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_manager_v1::ZwlrOutputPowerManagerV1: ()
228+
] => $crate::protocols::output_power_management::OutputPowerManagementManagerState);
229+
230+
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
231+
smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::zwlr_output_power_v1::ZwlrOutputPowerV1: $crate::protocols::output_power_management::OutputPowerState
232+
] => $crate::protocols::output_power_management::OutputPowerManagementManagerState);
233+
};
234+
}

0 commit comments

Comments
 (0)