1package wayland
2
3import (
4 "time"
5
6 "github.com/bnema/go-wayland-bindings/server/extidlenotify"
7 "github.com/bnema/go-wayland-bindings/server/wayland"
8 "github.com/bnema/go-wayland-bindings/server/wlroutputpowermanagement"
9 "github.com/bnema/neferwl/internal/ports"
10 "github.com/bnema/purego-libwayland/server"
11)
12
13// Idle and output power. ext_idle_notifier_v1: each notification has its
14// own timeout, restarted by user activity (ports.UserActivity from core);
15// idle inhibitors hold every notification but input-idle ones.
16// zwlr_output_power_v1: a client such as wlopm turns a display off or on;
17// core decides and the scene carries it.
18
19// idleNotification is one ext_idle_notification_v1.
20type idleNotification struct {
21 res *extidlenotify.ExtIdleNotificationV1
22 timeout time.Duration
23 input bool // get_input_idle_notification: ignores idle inhibitors
24 idle bool // idled sent, resumed not yet
25 timer ports.Timer
26 gen uint64 // bumped on stop: a firing timer of an older gen is stale
27}
28
29// outputPower is one zwlr_output_power_v1.
30type outputPower struct {
31 res *wlroutputpowermanagement.ZwlrOutputPowerV1
32 output string
33}
34
35func registerIdle(d *server.Display, s *Server) error {
36 if err := extidlenotify.NewExtIdleNotifierV1Global(d, 2, func(c server.Client, v, id uint32) {
37 _, _ = extidlenotify.NewExtIdleNotifierV1(c, int32(v), id, idleNotifier{s})
38 }); err != nil {
39 return err
40 }
41 return wlroutputpowermanagement.NewZwlrOutputPowerManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
42 _, _ = wlroutputpowermanagement.NewZwlrOutputPowerManagerV1(c, int32(v), id, powerManager{s})
43 })
44}
45
46type idleNotifier struct{ server *Server }
47
48func (idleNotifier) Destroy(*extidlenotify.ExtIdleNotifierV1) {}
49
50func (n idleNotifier) GetIdleNotification(r *extidlenotify.ExtIdleNotifierV1, id, timeout uint32, _ *wayland.Seat) {
51 n.server.addIdle(r, id, timeout, false)
52}
53
54func (n idleNotifier) GetInputIdleNotification(r *extidlenotify.ExtIdleNotifierV1, id, timeout uint32, _ *wayland.Seat) {
55 n.server.addIdle(r, id, timeout, true)
56}
57
58type idleNotificationHandler struct{}
59
60func (idleNotificationHandler) Destroy(*extidlenotify.ExtIdleNotificationV1) {}
61
62func (s *Server) addIdle(r *extidlenotify.ExtIdleNotifierV1, id, timeout uint32, input bool) {
63 res, err := extidlenotify.NewExtIdleNotificationV1(r.Client(), r.Version(), id, idleNotificationHandler{})
64 if err != nil {
65 return
66 }
67 n := &idleNotification{res: res, timeout: time.Duration(timeout) * time.Millisecond, input: input}
68 s.idleNotes = append(s.idleNotes, n)
69 res.OnDestroy = func() {
70 n.stop()
71 for i, x := range s.idleNotes {
72 if x == n {
73 s.idleNotes = append(s.idleNotes[:i], s.idleNotes[i+1:]...)
74 break
75 }
76 }
77 }
78 s.armIdle(n)
79}
80
81// idleHeld reports whether an idle inhibitor keeps n from firing.
82func (s *Server) idleHeld(n *idleNotification) bool {
83 return !n.input && len(s.idleWindows) > 0
84}
85
86// armIdle restarts n's timer, unless an inhibitor holds it. The timer
87// fires on the display goroutine.
88func (s *Server) armIdle(n *idleNotification) {
89 n.stop()
90 if s.idleHeld(n) {
91 return
92 }
93 gen := n.gen
94 // Core reports activity at most once per ActivityInterval: a shorter
95 // timeout would fire while the user is still typing.
96 n.timer = s.clock.AfterFunc(max(n.timeout, ports.ActivityInterval), func() {
97 s.display.Do(func() {
98 // A timer stopped or replaced while it waited for Do is stale.
99 if n.gen != gen || !n.res.Resource.Alive() || n.idle || s.idleHeld(n) {
100 return
101 }
102 n.idle = true
103 n.res.SendIdled()
104 })
105 })
106}
107
108func (n *idleNotification) stop() {
109 n.gen++
110 if n.timer != nil {
111 n.timer.Stop()
112 n.timer = nil
113 }
114}
115
116// userActivity resumes idle notifications and restarts their timers.
117func (s *Server) userActivity() {
118 for _, n := range s.idleNotes {
119 if n.idle {
120 n.idle = false
121 n.res.SendResumed()
122 }
123 s.armIdle(n)
124 }
125}
126
127// syncIdle follows idle inhibitors: while one exists, inhibitable timers
128// stop; once the last one goes, they start again from zero.
129func (s *Server) syncIdle(wasHeld bool) {
130 if held := len(s.idleWindows) > 0; held != wasHeld {
131 for _, n := range s.idleNotes {
132 if !n.input {
133 s.armIdle(n)
134 }
135 }
136 }
137}
138
139type powerManager struct{ server *Server }
140
141func (powerManager) Destroy(*wlroutputpowermanagement.ZwlrOutputPowerManagerV1) {}
142
143func (m powerManager) GetOutputPower(r *wlroutputpowermanagement.ZwlrOutputPowerManagerV1, id uint32, wl *wayland.Output) {
144 s := m.server
145 o := s.outputOf(wl)
146 p := &outputPower{}
147 res, err := wlroutputpowermanagement.NewZwlrOutputPowerV1(r.Client(), r.Version(), id, powerHandler{s, p})
148 if err != nil {
149 return
150 }
151 p.res = res
152 if o == nil {
153 // Unplugged or unknown: the object is inert.
154 res.SendFailed()
155 return
156 }
157 p.output = o.name()
158 s.powers = append(s.powers, p)
159 res.OnDestroy = func() {
160 for i, x := range s.powers {
161 if x == p {
162 s.powers = append(s.powers[:i], s.powers[i+1:]...)
163 break
164 }
165 }
166 }
167 res.SendMode(powerMode(!s.outputsOff[p.output]))
168}
169
170type powerHandler struct {
171 server *Server
172 p *outputPower
173}
174
175func (powerHandler) Destroy(*wlroutputpowermanagement.ZwlrOutputPowerV1) {}
176
177func (h powerHandler) SetMode(r *wlroutputpowermanagement.ZwlrOutputPowerV1, mode uint32) {
178 if mode != uint32(wlroutputpowermanagement.ZwlrOutputPowerV1ModeOff) && mode != uint32(wlroutputpowermanagement.ZwlrOutputPowerV1ModeOn) {
179 r.PostError(uint32(wlroutputpowermanagement.ZwlrOutputPowerV1ErrorInvalidMode), "invalid mode")
180 return
181 }
182 if h.p.output == "" {
183 return
184 }
185 h.server.emit(ports.OutputPower{Output: h.p.output, On: mode == uint32(wlroutputpowermanagement.ZwlrOutputPowerV1ModeOn)})
186}
187
188func powerMode(on bool) uint32 {
189 if on {
190 return uint32(wlroutputpowermanagement.ZwlrOutputPowerV1ModeOn)
191 }
192 return uint32(wlroutputpowermanagement.ZwlrOutputPowerV1ModeOff)
193}
194
195// setOutputsOff applies core's power state: each object of an output
196// whose mode changed hears it; objects of gone outputs fail.
197func (s *Server) setOutputsOff(off map[string]bool) {
198 names := map[string]bool{}
199 for _, o := range s.outputs {
200 names[o.name()] = true
201 }
202 kept := s.powers[:0]
203 for _, p := range s.powers {
204 if !names[p.output] {
205 // Failed objects are inert, even if an output of that name
206 // comes back.
207 p.res.SendFailed()
208 p.res.OnDestroy = nil
209 p.output = ""
210 continue
211 }
212 if off[p.output] != s.outputsOff[p.output] {
213 p.res.SendMode(powerMode(!off[p.output]))
214 }
215 kept = append(kept, p)
216 }
217 s.powers = kept
218 s.outputsOff = off
219}