From d76930013763fa59852a1515653ad7156fabbb87 Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 17 Nov 2025 00:30:31 -0500 Subject: [PATCH] core/cli: add dpms off/on via wlr-output-power-management --- core/cmd/dms/commands_common.go | 1 + core/cmd/dms/commands_dpms.go | 84 +++++ core/cmd/dms/dpms_client.go | 345 ++++++++++++++++++ .../proto/wlr_output_power/output_power.go | 283 ++++++++++++++ ...lr-output-power-management-unstable-v1.xml | 128 +++++++ 5 files changed, 841 insertions(+) create mode 100644 core/cmd/dms/commands_dpms.go create mode 100644 core/cmd/dms/dpms_client.go create mode 100644 core/internal/proto/wlr_output_power/output_power.go create mode 100644 core/internal/proto/xml/wlr-output-power-management-unstable-v1.xml diff --git a/core/cmd/dms/commands_common.go b/core/cmd/dms/commands_common.go index 79054feb..b262a269 100644 --- a/core/cmd/dms/commands_common.go +++ b/core/cmd/dms/commands_common.go @@ -368,6 +368,7 @@ func getCommonCommands() []*cobra.Command { pluginsCmd, dank16Cmd, brightnessCmd, + dpmsCmd, keybindsCmd, greeterCmd, setupCmd, diff --git a/core/cmd/dms/commands_dpms.go b/core/cmd/dms/commands_dpms.go new file mode 100644 index 00000000..41d602ef --- /dev/null +++ b/core/cmd/dms/commands_dpms.go @@ -0,0 +1,84 @@ +package main + +import ( + "fmt" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/log" + "github.com/spf13/cobra" +) + +var dpmsCmd = &cobra.Command{ + Use: "dpms", + Short: "Control display power management", +} + +var dpmsOnCmd = &cobra.Command{ + Use: "on [output]", + Short: "Turn display(s) on", + Args: cobra.MaximumNArgs(1), + Run: runDPMSOn, +} + +var dpmsOffCmd = &cobra.Command{ + Use: "off [output]", + Short: "Turn display(s) off", + Args: cobra.MaximumNArgs(1), + Run: runDPMSOff, +} + +var dpmsListCmd = &cobra.Command{ + Use: "list", + Short: "List outputs", + Args: cobra.NoArgs, + Run: runDPMSList, +} + +func init() { + dpmsCmd.AddCommand(dpmsOnCmd, dpmsOffCmd, dpmsListCmd) +} + +func runDPMSOn(cmd *cobra.Command, args []string) { + outputName := "" + if len(args) > 0 { + outputName = args[0] + } + + client, err := newDPMSClient() + if err != nil { + log.Fatalf("%v", err) + } + defer client.Close() + + if err := client.SetDPMS(outputName, true); err != nil { + log.Fatalf("%v", err) + } +} + +func runDPMSOff(cmd *cobra.Command, args []string) { + outputName := "" + if len(args) > 0 { + outputName = args[0] + } + + client, err := newDPMSClient() + if err != nil { + log.Fatalf("%v", err) + } + defer client.Close() + + if err := client.SetDPMS(outputName, false); err != nil { + log.Fatalf("%v", err) + } +} + +func runDPMSList(cmd *cobra.Command, args []string) { + client, err := newDPMSClient() + if err != nil { + log.Fatalf("%v", err) + } + defer client.Close() + + for _, output := range client.ListOutputs() { + fmt.Println(output) + } +} diff --git a/core/cmd/dms/dpms_client.go b/core/cmd/dms/dpms_client.go new file mode 100644 index 00000000..7087bb66 --- /dev/null +++ b/core/cmd/dms/dpms_client.go @@ -0,0 +1,345 @@ +package main + +import ( + "fmt" + "sync" + "time" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_power" + wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" +) + +type cmd struct { + fn func() + done chan error +} + +type dpmsClient struct { + display *wlclient.Display + ctx *wlclient.Context + powerMgr *wlr_output_power.ZwlrOutputPowerManagerV1 + outputs map[string]*outputState + mu sync.Mutex + syncRound int + done bool + err error + cmdq chan cmd + stopChan chan struct{} + wg sync.WaitGroup +} + +type outputState struct { + wlOutput *wlclient.Output + powerCtrl *wlr_output_power.ZwlrOutputPowerV1 + name string + mode uint32 + failed bool + waitCh chan struct{} + wantMode *uint32 +} + +func (c *dpmsClient) post(fn func()) { + done := make(chan error, 1) + select { + case c.cmdq <- cmd{fn: fn, done: done}: + <-done + case <-c.stopChan: + } +} + +func (c *dpmsClient) waylandActor() { + defer c.wg.Done() + for { + select { + case <-c.stopChan: + return + case cmd := <-c.cmdq: + cmd.fn() + close(cmd.done) + } + } +} + +func newDPMSClient() (*dpmsClient, error) { + display, err := wlclient.Connect("") + if err != nil { + return nil, fmt.Errorf("failed to connect to Wayland: %w", err) + } + + c := &dpmsClient{ + display: display, + ctx: display.Context(), + outputs: make(map[string]*outputState), + cmdq: make(chan cmd, 128), + stopChan: make(chan struct{}), + } + + c.wg.Add(1) + go c.waylandActor() + + registry, err := display.GetRegistry() + if err != nil { + display.Context().Close() + return nil, fmt.Errorf("failed to get registry: %w", err) + } + + registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { + switch e.Interface { + case wlr_output_power.ZwlrOutputPowerManagerV1InterfaceName: + powerMgr := wlr_output_power.NewZwlrOutputPowerManagerV1(c.ctx) + version := e.Version + if version > 1 { + version = 1 + } + if err := registry.Bind(e.Name, e.Interface, version, powerMgr); err == nil { + c.powerMgr = powerMgr + } + + case "wl_output": + output := wlclient.NewOutput(c.ctx) + version := e.Version + if version > 4 { + version = 4 + } + if err := registry.Bind(e.Name, e.Interface, version, output); err == nil { + outputID := fmt.Sprintf("output-%d", output.ID()) + state := &outputState{ + wlOutput: output, + name: outputID, + } + + c.mu.Lock() + c.outputs[outputID] = state + c.mu.Unlock() + + output.SetNameHandler(func(ev wlclient.OutputNameEvent) { + c.mu.Lock() + delete(c.outputs, state.name) + state.name = ev.Name + c.outputs[ev.Name] = state + c.mu.Unlock() + }) + } + } + }) + + syncCallback, err := display.Sync() + if err != nil { + c.Close() + return nil, fmt.Errorf("failed to sync display: %w", err) + } + syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) { + c.handleSync() + }) + + for !c.done { + if err := c.ctx.Dispatch(); err != nil { + c.Close() + return nil, fmt.Errorf("dispatch error: %w", err) + } + } + + if c.err != nil { + c.Close() + return nil, c.err + } + + return c, nil +} + +func (c *dpmsClient) handleSync() { + c.syncRound++ + + switch c.syncRound { + case 1: + if c.powerMgr == nil { + c.err = fmt.Errorf("wlr-output-power-management protocol not supported by compositor") + c.done = true + return + } + + c.mu.Lock() + for _, state := range c.outputs { + powerCtrl, err := c.powerMgr.GetOutputPower(state.wlOutput) + if err != nil { + continue + } + state.powerCtrl = powerCtrl + + powerCtrl.SetModeHandler(func(e wlr_output_power.ZwlrOutputPowerV1ModeEvent) { + c.mu.Lock() + defer c.mu.Unlock() + if state.powerCtrl == nil { + return + } + state.mode = e.Mode + if state.wantMode != nil && e.Mode == *state.wantMode && state.waitCh != nil { + close(state.waitCh) + state.wantMode = nil + } + }) + + powerCtrl.SetFailedHandler(func(e wlr_output_power.ZwlrOutputPowerV1FailedEvent) { + c.mu.Lock() + defer c.mu.Unlock() + if state.powerCtrl == nil { + return + } + state.failed = true + if state.waitCh != nil { + close(state.waitCh) + state.wantMode = nil + } + }) + } + c.mu.Unlock() + + syncCallback, err := c.display.Sync() + if err != nil { + c.err = fmt.Errorf("failed to sync display: %w", err) + c.done = true + return + } + syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) { + c.handleSync() + }) + + default: + c.done = true + } +} + +func (c *dpmsClient) ListOutputs() []string { + c.mu.Lock() + defer c.mu.Unlock() + + names := make([]string, 0, len(c.outputs)) + for name := range c.outputs { + names = append(names, name) + } + return names +} + +func (c *dpmsClient) SetDPMS(outputName string, on bool) error { + var mode uint32 + if on { + mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOn) + } else { + mode = uint32(wlr_output_power.ZwlrOutputPowerV1ModeOff) + } + + var setErr error + c.post(func() { + c.mu.Lock() + var waitStates []*outputState + + if outputName == "" || outputName == "all" { + if len(c.outputs) == 0 { + c.mu.Unlock() + setErr = fmt.Errorf("no outputs found") + return + } + + for _, state := range c.outputs { + if state.powerCtrl == nil { + continue + } + state.wantMode = &mode + state.waitCh = make(chan struct{}) + state.failed = false + waitStates = append(waitStates, state) + state.powerCtrl.SetMode(mode) + } + } else { + state, ok := c.outputs[outputName] + if !ok { + c.mu.Unlock() + setErr = fmt.Errorf("output not found: %s", outputName) + return + } + if state.powerCtrl == nil { + c.mu.Unlock() + setErr = fmt.Errorf("output %s has nil powerCtrl", outputName) + return + } + state.wantMode = &mode + state.waitCh = make(chan struct{}) + state.failed = false + waitStates = append(waitStates, state) + state.powerCtrl.SetMode(mode) + } + c.mu.Unlock() + + deadline := time.Now().Add(10 * time.Second) + + for _, state := range waitStates { + c.mu.Lock() + ch := state.waitCh + c.mu.Unlock() + + done := false + for !done { + if err := c.ctx.Dispatch(); err != nil { + setErr = fmt.Errorf("dispatch error: %w", err) + return + } + + select { + case <-ch: + c.mu.Lock() + if state.failed { + setErr = fmt.Errorf("compositor reported failed for %s", state.name) + c.mu.Unlock() + return + } + c.mu.Unlock() + done = true + default: + if time.Now().After(deadline) { + setErr = fmt.Errorf("timeout waiting for mode change on %s", state.name) + return + } + time.Sleep(10 * time.Millisecond) + } + } + } + + c.mu.Lock() + for _, state := range waitStates { + if state.powerCtrl != nil { + state.powerCtrl.Destroy() + state.powerCtrl = nil + } + } + c.mu.Unlock() + + c.display.Roundtrip() + }) + + return setErr +} + +func (c *dpmsClient) Close() { + close(c.stopChan) + c.wg.Wait() + + c.mu.Lock() + defer c.mu.Unlock() + + for _, state := range c.outputs { + if state.powerCtrl != nil { + state.powerCtrl.Destroy() + } + } + c.outputs = nil + + if c.powerMgr != nil { + c.powerMgr.Destroy() + c.powerMgr = nil + } + + if c.display != nil { + c.ctx.Close() + c.display = nil + } +} diff --git a/core/internal/proto/wlr_output_power/output_power.go b/core/internal/proto/wlr_output_power/output_power.go new file mode 100644 index 00000000..7ae2fbf3 --- /dev/null +++ b/core/internal/proto/wlr_output_power/output_power.go @@ -0,0 +1,283 @@ +// Generated by go-wayland-scanner +// https://github.com/yaslama/go-wayland/cmd/go-wayland-scanner +// XML file : internal/proto/xml/wlr-output-power-management-unstable-v1.xml +// +// wlr_output_power_management_unstable_v1 Protocol Copyright: +// +// Copyright © 2019 Purism SPC +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice (including the next +// paragraph) shall be included in all copies or substantial portions of the +// Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +package wlr_output_power + +import "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" + +// ZwlrOutputPowerManagerV1InterfaceName is the name of the interface as it appears in the [client.Registry]. +// It can be used to match the [client.RegistryGlobalEvent.Interface] in the +// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies. +const ZwlrOutputPowerManagerV1InterfaceName = "zwlr_output_power_manager_v1" + +// ZwlrOutputPowerManagerV1 : manager to create per-output power management +// +// This interface is a manager that allows creating per-output power +// management mode controls. +type ZwlrOutputPowerManagerV1 struct { + client.BaseProxy +} + +// NewZwlrOutputPowerManagerV1 : manager to create per-output power management +// +// This interface is a manager that allows creating per-output power +// management mode controls. +func NewZwlrOutputPowerManagerV1(ctx *client.Context) *ZwlrOutputPowerManagerV1 { + zwlrOutputPowerManagerV1 := &ZwlrOutputPowerManagerV1{} + ctx.Register(zwlrOutputPowerManagerV1) + return zwlrOutputPowerManagerV1 +} + +// GetOutputPower : get a power management for an output +// +// Create an output power management mode control that can be used to +// adjust the power management mode for a given output. +func (i *ZwlrOutputPowerManagerV1) GetOutputPower(output *client.Output) (*ZwlrOutputPowerV1, error) { + id := NewZwlrOutputPowerV1(i.Context()) + const opcode = 0 + const _reqBufLen = 8 + 4 + 4 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], id.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], output.ID()) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return id, err +} + +// Destroy : destroy the manager +// +// All objects created by the manager will still remain valid, until their +// appropriate destroy request has been called. +func (i *ZwlrOutputPowerManagerV1) Destroy() error { + defer i.Context().Unregister(i) + const opcode = 1 + const _reqBufLen = 8 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// ZwlrOutputPowerV1InterfaceName is the name of the interface as it appears in the [client.Registry]. +// It can be used to match the [client.RegistryGlobalEvent.Interface] in the +// [Registry.SetGlobalHandler] and can be used in [Registry.Bind] if this applies. +const ZwlrOutputPowerV1InterfaceName = "zwlr_output_power_v1" + +// ZwlrOutputPowerV1 : adjust power management mode for an output +// +// This object offers requests to set the power management mode of +// an output. +type ZwlrOutputPowerV1 struct { + client.BaseProxy + modeHandler ZwlrOutputPowerV1ModeHandlerFunc + failedHandler ZwlrOutputPowerV1FailedHandlerFunc +} + +// NewZwlrOutputPowerV1 : adjust power management mode for an output +// +// This object offers requests to set the power management mode of +// an output. +func NewZwlrOutputPowerV1(ctx *client.Context) *ZwlrOutputPowerV1 { + zwlrOutputPowerV1 := &ZwlrOutputPowerV1{} + ctx.Register(zwlrOutputPowerV1) + return zwlrOutputPowerV1 +} + +// SetMode : Set an outputs power save mode +// +// Set an output's power save mode to the given mode. The mode change +// is effective immediately. If the output does not support the given +// mode a failed event is sent. +// +// mode: the power save mode to set +func (i *ZwlrOutputPowerV1) SetMode(mode uint32) error { + const opcode = 0 + const _reqBufLen = 8 + 4 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(mode)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +// Destroy : destroy this power management +// +// Destroys the output power management mode control object. +func (i *ZwlrOutputPowerV1) Destroy() error { + defer i.Context().Unregister(i) + const opcode = 1 + const _reqBufLen = 8 + var _reqBuf [_reqBufLen]byte + l := 0 + client.PutUint32(_reqBuf[l:4], i.ID()) + l += 4 + client.PutUint32(_reqBuf[l:l+4], uint32(_reqBufLen<<16|opcode&0x0000ffff)) + l += 4 + err := i.Context().WriteMsg(_reqBuf[:], nil) + return err +} + +type ZwlrOutputPowerV1Mode uint32 + +// ZwlrOutputPowerV1Mode : +const ( + // ZwlrOutputPowerV1ModeOff : Output is turned off. + ZwlrOutputPowerV1ModeOff ZwlrOutputPowerV1Mode = 0 + // ZwlrOutputPowerV1ModeOn : Output is turned on, no power saving + ZwlrOutputPowerV1ModeOn ZwlrOutputPowerV1Mode = 1 +) + +func (e ZwlrOutputPowerV1Mode) Name() string { + switch e { + case ZwlrOutputPowerV1ModeOff: + return "off" + case ZwlrOutputPowerV1ModeOn: + return "on" + default: + return "" + } +} + +func (e ZwlrOutputPowerV1Mode) Value() string { + switch e { + case ZwlrOutputPowerV1ModeOff: + return "0" + case ZwlrOutputPowerV1ModeOn: + return "1" + default: + return "" + } +} + +func (e ZwlrOutputPowerV1Mode) String() string { + return e.Name() + "=" + e.Value() +} + +type ZwlrOutputPowerV1Error uint32 + +// ZwlrOutputPowerV1Error : +const ( + // ZwlrOutputPowerV1ErrorInvalidMode : nonexistent power save mode + ZwlrOutputPowerV1ErrorInvalidMode ZwlrOutputPowerV1Error = 1 +) + +func (e ZwlrOutputPowerV1Error) Name() string { + switch e { + case ZwlrOutputPowerV1ErrorInvalidMode: + return "invalid_mode" + default: + return "" + } +} + +func (e ZwlrOutputPowerV1Error) Value() string { + switch e { + case ZwlrOutputPowerV1ErrorInvalidMode: + return "1" + default: + return "" + } +} + +func (e ZwlrOutputPowerV1Error) String() string { + return e.Name() + "=" + e.Value() +} + +// ZwlrOutputPowerV1ModeEvent : Report a power management mode change +// +// Report the power management mode change of an output. +// +// The mode event is sent after an output changed its power +// management mode. The reason can be a client using set_mode or the +// compositor deciding to change an output's mode. +// This event is also sent immediately when the object is created +// so the client is informed about the current power management mode. +type ZwlrOutputPowerV1ModeEvent struct { + Mode uint32 +} +type ZwlrOutputPowerV1ModeHandlerFunc func(ZwlrOutputPowerV1ModeEvent) + +// SetModeHandler : sets handler for ZwlrOutputPowerV1ModeEvent +func (i *ZwlrOutputPowerV1) SetModeHandler(f ZwlrOutputPowerV1ModeHandlerFunc) { + i.modeHandler = f +} + +// ZwlrOutputPowerV1FailedEvent : object no longer valid +// +// This event indicates that the output power management mode control +// is no longer valid. This can happen for a number of reasons, +// including: +// - The output doesn't support power management +// - Another client already has exclusive power management mode control +// for this output +// - The output disappeared +// +// Upon receiving this event, the client should destroy this object. +type ZwlrOutputPowerV1FailedEvent struct{} +type ZwlrOutputPowerV1FailedHandlerFunc func(ZwlrOutputPowerV1FailedEvent) + +// SetFailedHandler : sets handler for ZwlrOutputPowerV1FailedEvent +func (i *ZwlrOutputPowerV1) SetFailedHandler(f ZwlrOutputPowerV1FailedHandlerFunc) { + i.failedHandler = f +} + +func (i *ZwlrOutputPowerV1) Dispatch(opcode uint32, fd int, data []byte) { + switch opcode { + case 0: + if i.modeHandler == nil { + return + } + var e ZwlrOutputPowerV1ModeEvent + l := 0 + e.Mode = client.Uint32(data[l : l+4]) + l += 4 + + i.modeHandler(e) + case 1: + if i.failedHandler == nil { + return + } + var e ZwlrOutputPowerV1FailedEvent + + i.failedHandler(e) + } +} diff --git a/core/internal/proto/xml/wlr-output-power-management-unstable-v1.xml b/core/internal/proto/xml/wlr-output-power-management-unstable-v1.xml new file mode 100644 index 00000000..20dbb776 --- /dev/null +++ b/core/internal/proto/xml/wlr-output-power-management-unstable-v1.xml @@ -0,0 +1,128 @@ + + + + Copyright © 2019 Purism SPC + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol allows clients to control power management modes + of outputs that are currently part of the compositor space. The + intent is to allow special clients like desktop shells to power + down outputs when the system is idle. + + To modify outputs not currently part of the compositor space see + wlr-output-management. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + This interface is a manager that allows creating per-output power + management mode controls. + + + + + Create an output power management mode control that can be used to + adjust the power management mode for a given output. + + + + + + + + All objects created by the manager will still remain valid, until their + appropriate destroy request has been called. + + + + + + + This object offers requests to set the power management mode of + an output. + + + + + + + + + + + + + + Set an output's power save mode to the given mode. The mode change + is effective immediately. If the output does not support the given + mode a failed event is sent. + + + + + + + Report the power management mode change of an output. + + The mode event is sent after an output changed its power + management mode. The reason can be a client using set_mode or the + compositor deciding to change an output's mode. + This event is also sent immediately when the object is created + so the client is informed about the current power management mode. + + + + + + + This event indicates that the output power management mode control + is no longer valid. This can happen for a number of reasons, + including: + - The output doesn't support power management + - Another client already has exclusive power management mode control + for this output + - The output disappeared + + Upon receiving this event, the client should destroy this object. + + + + + + Destroys the output power management mode control object. + + + +