v0.4.0 b6be3a8

idle.go RawHistoryFolder

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

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close