presentation.go RawHistoryFolder

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

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