Skip to content

Commit fb86a81

Browse files
agausmannBot-wxt1221
authored andcommitted
Implement the wlr-output-power-management protocol
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 fb86a81

File tree

5 files changed

+308
-4
lines changed

5 files changed

+308
-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,
@@ -91,8 +94,8 @@ use crate::protocols::virtual_pointer::{
9194
use crate::utils::{output_size, send_scale_transform};
9295
use crate::{
9396
delegate_ext_workspace, delegate_foreign_toplevel, delegate_gamma_control,
94-
delegate_mutter_x11_interop, delegate_output_management, delegate_screencopy,
95-
delegate_virtual_pointer,
97+
delegate_mutter_x11_interop, delegate_output_management, delegate_output_power_management,
98+
delegate_screencopy, delegate_virtual_pointer,
9699
};
97100

98101
pub const XDG_ACTIVATION_TOKEN_TIMEOUT: Duration = Duration::from_secs(10);
@@ -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: 35 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,
@@ -3092,6 +3099,7 @@ impl Niri {
30923099
self.global_space.unmap_output(output);
30933100
self.reposition_outputs(None);
30943101
self.gamma_control_manager_state.output_removed(output);
3102+
self.output_power_management_state.output_removed(output);
30953103

30963104
let state = self.output_state.remove(output).unwrap();
30973105

@@ -3216,15 +3224,41 @@ impl Niri {
32163224

32173225
self.monitors_active = false;
32183226
backend.set_monitors_active(false);
3227+
for output in self.global_space.outputs() {
3228+
self.output_power_management_state
3229+
.output_power_mode_changed(output, output_power_management::Mode::Off);
3230+
}
32193231
}
32203232

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

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

32293263
self.queue_redraw_all();
32303264
}

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

0 commit comments

Comments
 (0)