Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the wlr-output-power-management protocol #1108

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

agausmann
Copy link

@agausmann agausmann commented Feb 9, 2025

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 itself. 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.

Related to YaLTeR#108, YaLTeR#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.
@agausmann agausmann force-pushed the feat-wlr-output-power-management branch from 3cc1a5f to 0de4f47 Compare February 9, 2025 20:39
@agausmann
Copy link
Author

agausmann commented Feb 9, 2025

I've tested this using wlopm on my laptop, will do a test later with a multi-monitor setup to confirm it also works with that.

$ wlopm
eDP-1 on
$ wlopm --off eDP-1; sleep 1; wlopm; sleep 1; wlopm --on eDP-1
eDP-1 off


pub struct OutputPowerManagementManagerState {
// Active controls only. Failed ones are removed.
output_powers: HashMap<Output, ZwlrOutputPowerV1>,
Copy link
Author

@agausmann agausmann Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something felt off about this, and I think I figured it out.

I based this module off of gamma_control. After reading the docs for it, I found out it offers exclusive access to each output, so only one client can create a gamma control for it. I'm pretty sure that's not true for output power; it should allow multiple clients to connect (operating more like the output_management module.

@agausmann
Copy link
Author

agausmann commented Feb 10, 2025

While the implementation may be simpler, the behavior of all-off / all-on might be confusing/unexpected for users. Even if it is well-documented, it's not what they would expect from a protocol that seems to provide per-output power management.
Because of this, we might want to skip this PR and go straight to per-output power management; what do you all think?

@YaLTeR
Copy link
Owner

YaLTeR commented Feb 10, 2025

Sure, that makes sense to me

@YaLTeR
Copy link
Owner

YaLTeR commented Feb 11, 2025

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 itself. 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.

Perhaps you don't need to change activate_monitors() at all, but instead add a call into refresh_ipc_outputs() that just checks the current monitors_active and if it doesn't match what OutputPowerManagementState thinks it is, sends the events? Other protocols also do it this way, see output_management_state.notify_changes() or foreign_toplevel::refresh().

Comment on lines +59 to +60
// todo: set output power per-monitor. Currently, niri is written to power
// all monitors at the same time.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These callbacks on their own support it, so this comment is not needed here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, yeah

.contains_key(&output)
{
let zwlr_output_power = data_init.init(id, OutputPowerState {});
zwlr_output_power.mode(state.get_output_power_mode(&output).into());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So yeah as I wrote in the other comment, the way this should work is OutputPowerManagerState should itself store the current power mode (to its knowledge) and send that straight to clients. Then there should be a refresh() call that tells it the current up-to-date power mode from niri. If it changes, it will send that update to clients.


// Output not found,
// or output power instance already exists
data_init.init(id, OutputPowerState {}).failed();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd put this data_init at the top of the block so that you cannot miss it

};
let output = output.clone();

// todo: handle invalid enum values
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Send a protocol error or something, see how other impls do it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants