v0.1.0 d9218d6

presentation.go RawHistoryFolder

1package wayland
2
3import (
4	"time"
5
6	"github.com/bnema/neferwl/internal/ports"
7	"github.com/bnema/purego-libwayland/protocol/presentationtime"
8	"github.com/bnema/purego-libwayland/protocol/wayland"
9	"github.com/bnema/purego-libwayland/server"
10	"golang.org/x/sys/unix"
11)
12
13// wp_presentation: a client learns when each commit reached the screen.
14// A feedback belongs to the commit it was requested before; on commit it
15// waits for the flip that shows its window's content (FlipInfo.Shows)
16// and gets that flip's kernel timestamp, vblank counter and refresh.
17// A content replaced before any flip showed it, a destroyed surface or a
18// surface on no output is discarded.
19
20const presentationVersion = 2
21
22// feedbackWait is a committed feedback: presented once a flip on the
23// surface's output shows content seq of window win.
24type feedbackWait struct {
25	fb   *presentationtime.WpPresentationFeedback
26	surf *surface
27	win  ports.WindowID
28	seq  uint64
29	at   time.Time
30	// replaced is the window content Seq of the surface's next commit (0:
31	// none yet): a flip showing it or later shows the replacement.
32	replaced uint64
33}
34
35func registerPresentation(d *server.Display, s *Server) error {
36	return presentationtime.NewWpPresentationGlobal(d, presentationVersion, func(c server.Client, v, id uint32) {
37		r, err := presentationtime.NewWpPresentation(c, int32(v), id, presentation{s})
38		if err == nil {
39			r.SendClockId(unix.CLOCK_MONOTONIC)
40		}
41	})
42}
43
44type presentation struct{ server *Server }
45
46func (presentation) Destroy(*presentationtime.WpPresentation) {}
47
48// Feedback attaches a feedback to the surface's next commit.
49func (p presentation) Feedback(r *presentationtime.WpPresentation, surf *wayland.Surface, id uint32) {
50	fb, err := presentationtime.NewWpPresentationFeedback(r.Client(), r.Version(), id, feedbackHandler{})
51	if err != nil {
52		return
53	}
54	state := p.server.surfaceOf(surf)
55	if state == nil || state.destroyed {
56		fb.SendDiscarded()
57		fb.Destroy()
58		return
59	}
60	state.pendingFeedback = append(state.pendingFeedback, fb)
61}
62
63type feedbackHandler struct{}
64
65// Destroy is not a request of wp_presentation_feedback; the handler
66// satisfies the generated interface.
67func (feedbackHandler) Destroy(*presentationtime.WpPresentationFeedback) {}
68
69// commitFeedback moves a surface's pending feedbacks to its window's
70// content just emitted, or discards them when the surface shows nowhere.
71// A commit with a new buffer (fresh) replaces the surface's earlier
72// waiting feedbacks.
73func (s *surface) commitFeedback(pending []*presentationtime.WpPresentationFeedback, fresh bool) {
74	srv := s.server
75	root := s.root()
76	win := root.windowID()
77	if win != 0 && fresh {
78		srv.contentMu.Lock()
79		seq := srv.contentSeq[win]
80		srv.contentMu.Unlock()
81		for i := range srv.feedbacks {
82			if w := &srv.feedbacks[i]; w.surf == s && w.replaced == 0 && seq > w.seq {
83				w.replaced = seq
84			}
85		}
86	}
87	if len(pending) == 0 {
88		return
89	}
90	output := srv.frameOutput(s)
91	if win == 0 || output == "" {
92		for _, fb := range pending {
93			discard(fb)
94		}
95		return
96	}
97	srv.contentMu.Lock()
98	seq := srv.contentSeq[win]
99	srv.contentMu.Unlock()
100	for _, fb := range pending {
101		srv.feedbacks = append(srv.feedbacks, feedbackWait{fb: fb, surf: s, win: win, seq: seq, at: time.Now()})
102	}
103}
104
105func discard(fb *presentationtime.WpPresentationFeedback) {
106	if fb.Resource.Alive() {
107		fb.SendDiscarded()
108		fb.Destroy()
109	}
110}
111
112// presentFlip answers the feedbacks a flip of output answers: a window
113// content at or after a feedback's is shown; the feedback is presented,
114// or discarded when its surface committed again by then.
115func (s *Server) presentFlip(output string, f *ports.FlipInfo) {
116	if f == nil || len(s.feedbacks) == 0 {
117		return
118	}
119	o := s.outputByNameExact(output)
120	kept := s.feedbacks[:0]
121	for _, w := range s.feedbacks {
122		shown, ok := f.Shows[w.win]
123		switch {
124		case !w.fb.Resource.Alive():
125		case w.surf.destroyed:
126			discard(w.fb)
127		case s.frameOutput(w.surf) != output || !ok || shown < w.seq:
128			kept = append(kept, w)
129		case f.Merged > 0:
130			// The reader fell behind: which flip showed it is unknown.
131			discard(w.fb)
132		default:
133			// Shown, unless the surface's next commit is shown too.
134			s.sendPresented(w, o, f, w.replaced == 0 || shown < w.replaced)
135		}
136	}
137	clear(s.feedbacks[len(kept):])
138	s.feedbacks = kept
139}
140
141// sendPresented sends presented for the flip; exact is false when a
142// later commit of the surface replaced it before the flip (then it is
143// discarded).
144func (s *Server) sendPresented(w feedbackWait, o *output, f *ports.FlipInfo, exact bool) {
145	if !exact {
146		discard(w.fb)
147		return
148	}
149	if o != nil {
150		for _, r := range o.resources {
151			if r.Resource.Alive() && r.Client() == w.fb.Client() {
152				w.fb.SendSyncOutput(r)
153			}
154		}
155	}
156	flags := uint32(0)
157	// A software clock (headless) claims nothing about the display.
158	if !f.Async && f.HardwareClock {
159		flags |= uint32(presentationtime.WpPresentationFeedbackKindVsync)
160	}
161	if f.HardwareClock {
162		flags |= uint32(presentationtime.WpPresentationFeedbackKindHwClock | presentationtime.WpPresentationFeedbackKindHwCompletion)
163	}
164	if f.ZeroCopy != 0 && f.ZeroCopy == w.win {
165		flags |= uint32(presentationtime.WpPresentationFeedbackKindZeroCopy)
166	}
167	sec, nsec := uint64(f.When/time.Second), uint32(f.When%time.Second)
168	w.fb.SendPresented(uint32(sec>>32), uint32(sec), nsec, uint32(f.Refresh), uint32(f.Seq>>32), uint32(f.Seq), flags)
169	w.fb.Destroy()
170}
171
172// feedbackTimeout discards a feedback no flip answered: its window is
173// hidden, on an output that does not flip (headless, switched away), or
174// was never drawn.
175const feedbackTimeout = time.Second
176
177// dropFeedbacks discards the feedbacks of destroyed surfaces, of outputs
178// that are gone, and those older than feedbackTimeout.
179func (s *Server) dropFeedbacks(now time.Time) {
180	kept := s.feedbacks[:0]
181	for _, w := range s.feedbacks {
182		if !w.fb.Resource.Alive() {
183			continue
184		}
185		if w.surf.destroyed || s.frameOutput(w.surf) == "" || now.Sub(w.at) > feedbackTimeout {
186			discard(w.fb)
187			continue
188		}
189		kept = append(kept, w)
190	}
191	clear(s.feedbacks[len(kept):])
192	s.feedbacks = kept
193}

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