v0.1.0 d9218d6

release.go RawHistoryFolder

1package wayland
2
3import (
4	"time"
5
6	"github.com/bnema/neferwl/internal/ports"
7	"github.com/bnema/purego-libwayland/protocol/wayland"
8)
9
10// Buffer release. Outputs read client buffers in place: the renderer
11// samples them when it draws, and an output scanning out a client buffer
12// (ADR 006) shows it until the next flip. A replaced wl_buffer therefore
13// goes back to the client (release) only when the output showing its
14// window reports (ports.OutputPresented) that it has a later content of
15// that window and neither shows nor queues the buffer. Outputs keep only
16// the latest content of a window, so they never draw an older buffer
17// again. heldTimeout guards against an output that stopped reporting
18// (switched away, stalled): a buffer it does not scan out is released then.
19
20// heldTimeout releases held buffers of an output that stopped reporting.
21const heldTimeout = 100 * time.Millisecond
22
23type heldBuffer struct {
24	res *wayland.Buffer
25	// sync is the buffer's explicit-sync state: its release point is
26	// signalled instead of wl_buffer.release.
27	sync   syncHold
28	window ports.WindowID
29	id     uint64 // DMABuf ID, 0 for wl_shm
30	after  uint64 // the window's content Seq when the buffer was replaced
31	at     time.Time
32}
33
34// releaseBuffer releases a replaced buffer, or holds it until the output
35// showing its window no longer reads it.
36func (s *Server) releaseBuffer(surf *surface, b *wayland.Buffer, sync syncHold) {
37	window, name := surf.root().windowID(), s.frameOutput(surf)
38	if !b.Resource.Alive() || window == 0 || name == "" {
39		// Not drawn (unmapped, hidden, a cursor): nothing reads it.
40		s.release(b, sync)
41		return
42	}
43	h := heldBuffer{res: b, sync: sync, window: window, at: time.Now()}
44	if d, ok := s.buffers[b.Resource].(*dmabufBuffer); ok {
45		h.id = d.buf.ID
46	}
47	s.contentMu.Lock()
48	h.after = s.contentSeq[window]
49	s.contentMu.Unlock()
50	s.held = append(s.held, h)
51	// Wake the pacer: it releases the buffer on the next output report.
52	select {
53	case s.frameReady <- struct{}{}:
54	default:
55	}
56}
57
58// release gives a buffer back: its release point is signalled when it
59// has one (explicit sync), else wl_buffer.release.
60func (s *Server) release(b *wayland.Buffer, sync syncHold) {
61	if sync.release.set() || sync.acquire != nil {
62		s.releaseSync(sync)
63		return
64	}
65	if b != nil && b.Resource.Alive() {
66		b.SendRelease()
67	}
68}
69
70// releaseBufferSync holds only a buffer's explicit-sync points: the same
71// buffer committed again with new points (clients reuse one buffer).
72func (s *Server) releaseBufferSync(surf *surface, sync syncHold) {
73	if !sync.release.set() && sync.acquire == nil {
74		return
75	}
76	window := surf.root().windowID()
77	if window == 0 || s.frameOutput(surf) == "" {
78		s.releaseSync(sync)
79		return
80	}
81	s.contentMu.Lock()
82	after := s.contentSeq[window]
83	s.contentMu.Unlock()
84	s.held = append(s.held, heldBuffer{sync: sync, window: window, after: after, at: time.Now()})
85	s.wakePacer()
86}
87
88// releaseHeld records the output reports and releases the buffers no
89// output reads. Every output reads every window's content, so a buffer
90// waits until no live output shows or queues it and all of them have seen
91// a later content of its window (or heldTimeout passed). It reports
92// whether buffers are still held.
93func (s *Server) releaseHeld(now time.Time, reports []ports.OutputPresented) bool {
94	for _, r := range reports {
95		s.reports[r.Output] = r
96	}
97	live := make([]ports.OutputPresented, 0, len(s.outputs))
98	for name := range s.reports {
99		if s.outputByNameExact(name) == nil {
100			delete(s.reports, name)
101		}
102	}
103	for _, r := range s.reports {
104		live = append(live, r)
105	}
106	kept := s.held[:0]
107	for _, h := range s.held {
108		if keepHeld(h, live, now) {
109			kept = append(kept, h)
110			continue
111		}
112		s.release(h.res, h.sync)
113	}
114	clear(s.held[len(kept):])
115	s.held = kept
116	return len(s.held) > 0
117}
118
119// keepHeld reports whether an output may still read a held buffer: one
120// scans it out or queued it, or one has not got a later content of its
121// window yet and heldTimeout has not passed.
122func keepHeld(h heldBuffer, reports []ports.OutputPresented, now time.Time) bool {
123	seen := true
124	for _, r := range reports {
125		if h.id != 0 && (r.Shown == h.id || r.Queued == h.id) {
126			return true
127		}
128		if r.Seen[h.window] <= h.after {
129			seen = false
130		}
131	}
132	if len(reports) == 0 {
133		seen = false
134	}
135	return !seen && now.Sub(h.at) < heldTimeout
136}

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