Commit a2cc6a5809c0
tarball5 files changed+231-0
@@ -632,6 +632,7 @@ fn disconnect(state: &mut State, crtc: crtc::Handle) {
632632
state.close_layers(&surface.output);633633
state.services.lock.forget(&surface.output);634634
state.services.power.forget(&surface.output.name());635
+ state.protocols.output_power.output_removed(&surface.output);635636
}636637
637638
impl Drm {@@ -2,6 +2,7 @@
22
//! own handler impl, the rest publish compositor state to clients.33
44
use crate::state::State;5
+use monolith_protocols::output_power::OutputPowerState;56
use monolith_protocols::toplevel::ForeignToplevelState;67
use monolith_protocols::workspace::WorkspaceState;78
use monolith_render_vk::VulkanRenderer;@@ -47,6 +48,7 @@ pub struct Protocols {
4748
pub xwayland_shell_state: XWaylandShellState,4849
pub workspaces: WorkspaceState,4950
pub foreign_toplevels: ForeignToplevelState,51
+ pub output_power: OutputPowerState,5052
pub toplevel_list: ForeignToplevelListState,5153
_image_capture_source: ImageCaptureSourceState,5254
_idle_inhibit: IdleInhibitManagerState,@@ -101,6 +103,7 @@ impl Protocols {
101103
xwayland_shell_state: XWaylandShellState::new::<State>(dh),102104
workspaces: WorkspaceState::new::<State>(dh),103105
foreign_toplevels: ForeignToplevelState::new::<State>(dh),106
+ output_power: OutputPowerState::new::<State>(dh),104107
toplevel_list: ForeignToplevelListState::new::<State>(dh),105108
_image_capture_source: ImageCaptureSourceState::new(),106109
_idle_inhibit: IdleInhibitManagerState::new::<State>(dh),@@ -3,6 +3,8 @@
33
//! came out.44
55
use crate::state::{State, now_ns};6
+use monolith_protocols::output_power::{OutputPowerHandler, OutputPowerState};7
+use smithay::output::Output;68
use smithay::reexports::calloop::RegistrationToken;79
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};810
use std::collections::HashSet;@@ -34,11 +36,49 @@ impl Power {
3436
}3537
}3638
39
+impl OutputPowerHandler for State {40
+ fn output_power_state(&mut self) -> &mut OutputPowerState {41
+ &mut self.protocols.output_power42
+ }43
+44
+ fn output_power_request(&mut self, output: &Output, on: bool) {45
+ let _ = self.set_output_power(Some(&output.name()), on);46
+ }47
+48
+ fn output_is_powered(&self, output: &Output) -> bool {49
+ self.output_powered(&output.name())50
+ }51
+}52
+3753
impl State {3854
pub fn output_powered(&self, name: &str) -> bool {3955
!self.services.power.dark(name)4056
}4157
58
+ /// Switches outputs on or off by name, every output when `name` is None.59
+ /// Turning on also ends an idle blank.60
+ pub fn set_output_power(&mut self, name: Option<&str>, on: bool) -> Result<(), String> {61
+ let known: Vec<String> = self.wm.space.outputs().map(|o| o.name()).collect();62
+ if let Some(n) = name63
+ && !known.iter().any(|k| k == n)64
+ {65
+ return Err(format!("no output named {n}"));66
+ }67
+ let power = &mut self.services.power;68
+ for k in known.into_iter().filter(|k| name.is_none_or(|n| n == k)) {69
+ if on {70
+ power.off.remove(&k);71
+ } else {72
+ power.off.insert(k);73
+ }74
+ }75
+ if on {76
+ self.wake_idle();77
+ }78
+ self.apply_power();79
+ Ok(())80
+ }81
+4282
/// Pushes the wanted power state of every output to the backend.4383
pub fn apply_power(&mut self) {4484
let names: Vec<String> = self.wm.space.outputs().map(|o| o.name()).collect();@@ -48,6 +88,10 @@ impl State {
4888
drm.set_powered(&name, on);4989
}5090
}91
+ let power = &self.services.power;92
+ self.protocols93
+ .output_power94
+ .sync(|o| !power.dark(&o.name()));5195
}5296
5397
/// Notes input, and lights the outputs again if idleness had blanked them.@@ -1,4 +1,5 @@
11
//! Wayland protocols monolith implements itself.22
3
+pub mod output_power;34
pub mod toplevel;45
pub mod workspace;@@ -0,0 +1,182 @@
1
+//! wlr-output-power-management-v1, so idle daemons like swayidle can switch2
+//! outputs off and on. The compositor owns the power state. This module3
+//! turns requests into handler calls and reports the state back to clients.4
+5
+use smithay::output::Output;6
+use smithay::reexports::wayland_protocols_wlr::output_power_management::v1::server::{7
+ zwlr_output_power_manager_v1::{self, ZwlrOutputPowerManagerV1},8
+ zwlr_output_power_v1::{self, ZwlrOutputPowerV1},9
+};10
+use smithay::reexports::wayland_server::backend::{ClientId, GlobalId};11
+use smithay::reexports::wayland_server::{12
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,13
+};14
+use smithay::wayland::{Dispatch2, GlobalDispatch2};15
+16
+pub trait OutputPowerHandler {17
+ fn output_power_state(&mut self) -> &mut OutputPowerState;18
+ /// A client asked to switch `output` on or off.19
+ fn output_power_request(&mut self, output: &Output, on: bool);20
+ fn output_is_powered(&self, output: &Output) -> bool;21
+}22
+23
+struct Control {24
+ handle: ZwlrOutputPowerV1,25
+ output: Output,26
+ last: bool,27
+}28
+29
+pub struct OutputPowerState {30
+ global: GlobalId,31
+ controls: Vec<Control>,32
+}33
+34
+pub struct ManagerGlobal;35
+36
+pub struct ManagerData;37
+38
+pub struct ControlData;39
+40
+fn mode(on: bool) -> zwlr_output_power_v1::Mode {41
+ if on {42
+ zwlr_output_power_v1::Mode::On43
+ } else {44
+ zwlr_output_power_v1::Mode::Off45
+ }46
+}47
+48
+impl OutputPowerState {49
+ pub fn new<D>(dh: &DisplayHandle) -> Self50
+ where51
+ D: GlobalDispatch<ZwlrOutputPowerManagerV1, ManagerGlobal> + 'static,52
+ {53
+ Self {54
+ global: dh.create_global::<D, ZwlrOutputPowerManagerV1, _>(1, ManagerGlobal),55
+ controls: Vec::new(),56
+ }57
+ }58
+59
+ pub fn global(&self) -> GlobalId {60
+ self.global.clone()61
+ }62
+63
+ /// Tells every client whose view of an output's power is out of date.64
+ pub fn sync(&mut self, powered: impl Fn(&Output) -> bool) {65
+ self.controls.retain(|c| c.handle.is_alive());66
+ for c in &mut self.controls {67
+ let on = powered(&c.output);68
+ if on != c.last {69
+ c.last = on;70
+ c.handle.mode(mode(on));71
+ }72
+ }73
+ }74
+75
+ /// The output is gone, so controls on it are no longer valid.76
+ pub fn output_removed(&mut self, output: &Output) {77
+ self.controls.retain(|c| {78
+ let gone = &c.output == output;79
+ if gone {80
+ c.handle.failed();81
+ }82
+ !gone83
+ });84
+ }85
+}86
+87
+impl<D> GlobalDispatch2<ZwlrOutputPowerManagerV1, D> for ManagerGlobal88
+where89
+ D: OutputPowerHandler + Dispatch<ZwlrOutputPowerManagerV1, ManagerData> + 'static,90
+{91
+ fn bind(92
+ &self,93
+ _state: &mut D,94
+ _dh: &DisplayHandle,95
+ _client: &Client,96
+ resource: New<ZwlrOutputPowerManagerV1>,97
+ data_init: &mut DataInit<'_, D>,98
+ ) {99
+ data_init.init(resource, ManagerData);100
+ }101
+}102
+103
+impl<D> Dispatch2<ZwlrOutputPowerManagerV1, D> for ManagerData104
+where105
+ D: OutputPowerHandler + Dispatch<ZwlrOutputPowerV1, ControlData> + 'static,106
+{107
+ fn request(108
+ &self,109
+ state: &mut D,110
+ _client: &Client,111
+ _resource: &ZwlrOutputPowerManagerV1,112
+ request: zwlr_output_power_manager_v1::Request,113
+ _dh: &DisplayHandle,114
+ data_init: &mut DataInit<'_, D>,115
+ ) {116
+ let zwlr_output_power_manager_v1::Request::GetOutputPower { id, output } = request else {117
+ return;118
+ };119
+ let handle = data_init.init(id, ControlData);120
+ let Some(output) = Output::from_resource(&output) else {121
+ handle.failed();122
+ return;123
+ };124
+ state.output_power_state().controls.retain(|c| c.handle.is_alive());125
+ if state126
+ .output_power_state()127
+ .controls128
+ .iter()129
+ .any(|c| c.output == output)130
+ {131
+ handle.failed();132
+ return;133
+ }134
+ let on = state.output_is_powered(&output);135
+ handle.mode(mode(on));136
+ state.output_power_state().controls.push(Control {137
+ handle,138
+ output,139
+ last: on,140
+ });141
+ }142
+}143
+144
+impl<D: OutputPowerHandler> Dispatch2<ZwlrOutputPowerV1, D> for ControlData {145
+ fn request(146
+ &self,147
+ state: &mut D,148
+ _client: &Client,149
+ resource: &ZwlrOutputPowerV1,150
+ request: zwlr_output_power_v1::Request,151
+ _dh: &DisplayHandle,152
+ _data_init: &mut DataInit<'_, D>,153
+ ) {154
+ let zwlr_output_power_v1::Request::SetMode { mode } = request else {155
+ return;156
+ };157
+ let on = match mode.into_result() {158
+ Ok(zwlr_output_power_v1::Mode::On) => true,159
+ Ok(zwlr_output_power_v1::Mode::Off) => false,160
+ _ => {161
+ resource.post_error(zwlr_output_power_v1::Error::InvalidMode, "invalid mode");162
+ return;163
+ }164
+ };165
+ let output = state166
+ .output_power_state()167
+ .controls168
+ .iter()169
+ .find(|c| c.handle == *resource)170
+ .map(|c| c.output.clone());171
+ if let Some(output) = output {172
+ state.output_power_request(&output, on);173
+ }174
+ }175
+176
+ fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrOutputPowerV1) {177
+ state178
+ .output_power_state()179
+ .controls180
+ .retain(|c| c.handle != *resource);181
+ }182
+}