fence.go RawHistoryFolder

1package capture
2
3import (
4	"fmt"
5	"image"
6	"math"
7	"time"
8
9	"github.com/bnema/neferwl/internal/ports"
10)
11
12// The indicator fence. A capture is requested straight from wayland to the
13// output owner, while core learns of it (CaptureFrameTaken) on its own
14// channel and only then publishes a scene that carries the capture indicator.
15// An output that served the request from the scene it already had would copy
16// pixels before the border or pill was ever on screen. So the output owner
17// holds every request until its current scene shows the mark the request
18// needs, and serves it from that scene: the indicator is composed in the same
19// frame as the capture or in an earlier one, never after it.
20//
21// Wayland stamps every capture it requests with Indicate. The mark needed is
22// derived from the request alone:
23//   - OffScreen (a workspace rendered for capture only): a pill;
24//   - any other: a border whose rectangle covers the request's region.
25//
26// A mark that draws nothing (an empty rectangle, a border that would be 0
27// wide, a pill not fully inside the output: ports.CaptureIndicator.DrawableIn) never counts as shown. Core
28// inflates thin targets so their mark is drawable.
29//
30// Delivery is gated on the display (Pipeline.BeginGate, EndGate). The frame
31// of a capture is rendered without the indicator and copied before the
32// indicated frame, but its pixels are written and its reply sent only after
33// the owner committed (DRM) or presented (headless) the frame that carries
34// the indicator. When that frame fails to render or to commit, or its path
35// ends in any other way, the capture fails with ErrIndicatorMissing (and the
36// cause): a capture is never delivered for an indicator that was not shown.
37// A scene without an indicator gates nothing and costs nothing.
38//
39// A request whose mark does not come (the target vanished, so core has
40// nothing to mark) fails with ErrIndicatorMissing after HoldFor: nothing is
41// ever served without its indicator. The fence costs nothing while no request
42// is pending.
43
44// HoldFor bounds how long a request waits for its indicator.
45const HoldFor = 250 * time.Millisecond
46
47// ErrIndicatorMissing fails a request whose indicator was not on screen in
48// time.
49var ErrIndicatorMissing = fmt.Errorf("%w: capture indicator is not on screen", ports.ErrCaptureTransient)
50
51// IndicatorShown reports whether scene s shows the indicator request q needs.
52// A request that does not ask for one (Indicate false) needs none.
53func IndicatorShown(s ports.Scene, q ports.CaptureRequest) bool {
54	if !q.Indicate {
55		return true
56	}
57	for _, m := range s.CaptureIndicators {
58		if !m.DrawableIn(s.OutputWidth, s.OutputHeight) {
59			// Defense in depth: a mark whose border would be empty shows
60			// nothing, so it vouches for no capture.
61			continue
62		}
63		if q.OffScreen {
64			if m.Pill {
65				return true
66			}
67			continue
68		}
69		if !m.Pill && covers(s, m.Rect, q.Region) {
70			return true
71		}
72	}
73	return false
74}
75
76// covers reports whether the border of logical rect m, drawn over a target
77// region, covers the physical region r: m is projected to physical pixels the
78// way wayland projects a logical target (rounded outwards). An edge of m on an
79// output edge extends to it: the output size is not always a multiple of the
80// scale.
81func covers(s ports.Scene, m ports.Rect, r image.Rectangle) bool {
82	scale := s.Scale
83	if scale <= 0 {
84		scale = 1
85	}
86	x0, y0 := int(math.Floor(float64(m.X)*scale)), int(math.Floor(float64(m.Y)*scale))
87	x1, y1 := int(math.Ceil(float64(m.X+m.W)*scale)), int(math.Ceil(float64(m.Y+m.H)*scale))
88	if m.X <= 0 {
89		x0 = math.MinInt
90	}
91	if m.Y <= 0 {
92		y0 = math.MinInt
93	}
94	if m.X+m.W >= s.OutputWidth {
95		x1 = math.MaxInt
96	}
97	if m.Y+m.H >= s.OutputHeight {
98		y1 = math.MaxInt
99	}
100	return r.Min.X >= x0 && r.Min.Y >= y0 && r.Max.X <= x1 && r.Max.Y <= y1
101}
102
103// expired reports whether q has waited for its indicator for HoldFor.
104func expired(q ports.CaptureRequest, now time.Time) bool {
105	return now.Sub(q.Since) >= HoldFor
106}
107
108// Expire fails the requests that waited HoldFor without their indicator
109// being shown by s, and returns the others, in order, in place (the entries
110// past them are zeroed). wait is how long until the next one expires, 0 when
111// none waits for its indicator. No allocation.
112func (p *Pipeline) Expire(s ports.Scene, reqs []ports.CaptureRequest, now time.Time) (kept []ports.CaptureRequest, wait time.Duration) {
113	n := 0
114	for _, q := range reqs {
115		if !IndicatorShown(s, q) {
116			if expired(q, now) {
117				Fail(p.ctx, q, ErrIndicatorMissing, p.replies)
118				continue
119			}
120			if d := HoldFor - now.Sub(q.Since); wait == 0 || d < wait {
121				wait = d
122			}
123		}
124		reqs[n] = q
125		n++
126	}
127	clear(reqs[n:])
128	return reqs[:n], wait
129}
130
131// Hold splits the requests of one output frame by the indicator of s: ready
132// is the prefix whose indicator s shows (they go to Split), held the requests
133// that keep waiting for a scene that shows theirs. Expired ones are failed
134// first (see Expire). ready and held are consecutive in reqs; the entries
135// past them are zeroed. No allocation.
136func (p *Pipeline) Hold(s ports.Scene, reqs []ports.CaptureRequest, now time.Time) (ready, held []ports.CaptureRequest) {
137	reqs, _ = p.Expire(s, reqs, now)
138	j := 0
139	for i := range reqs {
140		if IndicatorShown(s, reqs[i]) {
141			reqs[i], reqs[j] = reqs[j], reqs[i]
142			j++
143		}
144	}
145	return reqs[:j], reqs[j:]
146}
147
148// Waiting keeps, in place, the requests of a frame that still wait for the
149// indicator of s: the ones neither handed over nor failed, whose indicator s
150// does not show. The entries past them are zeroed. The capacity is kept, so
151// the result replaces the owner's request list.
152func Waiting(reqs []ports.CaptureRequest, s ports.Scene) []ports.CaptureRequest {
153	n := 0
154	for _, q := range reqs {
155		if !Handed(q) && !IndicatorShown(s, q) {
156			reqs[n] = q
157			n++
158		}
159	}
160	clear(reqs[n:])
161	return reqs[:n]
162}

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