v0.2.0 13c6a90

idle.go RawHistoryFolder

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}

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 23

Tags 10

↑↓ moveenter openesc close