overlay.go RawHistoryFolder

1package drm
2
3import (
4	"errors"
5	"os"
6
7	"github.com/bnema/neferwl/internal/ports"
8	"golang.org/x/sys/unix"
9)
10
11// Overlay plane: a window drawn from one opaque dmabuf, with nothing but
12// the cursor above it, goes on an overlay plane at its place on screen;
13// the rest of the scene is composed into the primary plane without it.
14// The client buffer is shown with no GPU pass, like direct scanout, but
15// the window need not cover the output. A refusal (TEST_ONLY) keeps the
16// window composed and is cached per buffer.
17
18// pickOverlay takes an overlay plane above the primary one (zpos), when
19// the output has one.
20func (o *Output) pickOverlay() {
21	pz, ok := o.primary.zpos()
22	if !ok {
23		return
24	}
25	for _, p := range o.stray {
26		if p.typ != planeOverlay {
27			continue
28		}
29		if z, ok := p.zpos(); ok && z > pz {
30			o.overlay = p
31			return
32		}
33	}
34}
35
36// zpos is the plane's zpos value when known.
37func (p *plane) zpos() (uint64, bool) {
38	if p.props["zpos"] == 0 {
39		return 0, false
40	}
41	return p.zposValue, true
42}
43
44// overlayWin is the window on the overlay plane: its buffer and where it
45// shows, in physical pixels.
46type overlayWin struct {
47	id   ports.WindowID
48	fb   uint32
49	buf  uint64
50	rect ports.Rect // CRTC rect
51	w, h int        // buffer size
52	// acquire is the client's explicit-sync fence, a duplicate owned by
53	// the frame loop (closed after the commit); nil without one.
54	acquire *os.File
55}
56
57// overlayCandidate finds the one window the overlay can show: an opaque
58// dmabuf at integer physical coordinates, drawn 1:1, with no other window
59// or layer above it. reason is why none.
60func overlayCandidate(s ports.Scene, surfaces map[ports.WindowID]ports.SurfaceContent) (ports.SceneWindow, ports.SurfaceContent, string) {
61	if s.Transform != 0 {
62		return ports.SceneWindow{}, ports.SurfaceContent{}, "output_transform"
63	}
64	scale := s.Scale
65	if scale <= 0 {
66		scale = 1
67	}
68	var pick *ports.SceneWindow
69	for i := range s.Windows {
70		w := &s.Windows[i]
71		if w.Hidden || w.Rect.W <= 0 || w.Rect.H <= 0 {
72			continue
73		}
74		if w.Popup {
75			// Popups draw after every window, wherever they are listed.
76			return ports.SceneWindow{}, ports.SurfaceContent{}, "window_above"
77		}
78		c := surfaces[w.ID]
79		// A viewport crop or PQ content is composed: the plane would show
80		// the whole buffer, and raw PQ values on this SDR output. A dimmed
81		// window (a peeking stashed one) needs the veil drawn over it, and
82		// one with the focus effect needs its lift; an overview preview is
83		// drawn smaller than its buffer.
84		if w.Dim <= 0 && w.FocusEffect <= 0 && w.Preview <= 0 && c.DMABuf != nil && !isYUVFormat(c.DMABuf.Format) && c.Opaque && len(c.Children) == 0 && c.Transform == 0 && !cropped(c) && !c.Color.IsPQ2020() {
85			pick = w
86			continue
87		}
88		if pick != nil {
89			// Something is drawn above the candidate.
90			return ports.SceneWindow{}, ports.SurfaceContent{}, "window_above"
91		}
92	}
93	if pick == nil {
94		return ports.SceneWindow{}, ports.SurfaceContent{}, "no_candidate"
95	}
96	if s.Dim > 0 {
97		// The renderer composes the veil (under the first float, or under
98		// every window with DimBehind): a window on the plane would skip it.
99		return ports.SceneWindow{}, ports.SurfaceContent{}, "dim"
100	}
101	if len(s.DropHints) > 0 {
102		// Drag hints are drawn over the windows.
103		return ports.SceneWindow{}, ports.SurfaceContent{}, "drop_hint"
104	}
105	for _, l := range s.Layers {
106		if l.Layer >= ports.LayerTop && l.Rect.W > 0 && l.Rect.H > 0 {
107			return ports.SceneWindow{}, ports.SurfaceContent{}, "layer_above"
108		}
109	}
110	if !pick.Fullscreen && len(s.Separators) > 0 {
111		// The overlay shows the buffer alone; the lines would go with it.
112		return ports.SceneWindow{}, ports.SurfaceContent{}, "border"
113	}
114	if clip := s.WorkspaceClip; clip != (ports.Rect{}) &&
115		(pick.Rect.X < clip.X || pick.Rect.Y < clip.Y || pick.Rect.X+pick.Rect.W > clip.X+clip.W || pick.Rect.Y+pick.Rect.H > clip.Y+clip.H) {
116		// The plane cannot apply the workspace's logical viewport crop.
117		return ports.SceneWindow{}, ports.SurfaceContent{}, "workspace_clip"
118	}
119	c := surfaces[pick.ID]
120	pw, ph := float64(pick.Rect.W)*scale, float64(pick.Rect.H)*scale
121	if float64(c.Width) != pw || float64(c.Height) != ph || c.Geometry != (ports.Rect{}) && (c.Geometry.X != 0 || c.Geometry.Y != 0) {
122		return ports.SceneWindow{}, ports.SurfaceContent{}, "scaled"
123	}
124	x, y := float64(pick.Rect.X)*scale, float64(pick.Rect.Y)*scale
125	if x != float64(int(x)) || y != float64(int(y)) {
126		return ports.SceneWindow{}, ports.SurfaceContent{}, "fractional_position"
127	}
128	return *pick, c, ""
129}
130
131// overlayProps puts ov on the overlay plane, or turns it off.
132func (o *Output) overlayProps(req *atomicReq, ov overlayWin) {
133	p := o.overlay
134	if p == nil {
135		return
136	}
137	if ov.fb == 0 {
138		req.set(p.id, p.prop("FB_ID"), 0)
139		req.set(p.id, p.prop("CRTC_ID"), 0)
140		return
141	}
142	req.set(p.id, p.prop("FB_ID"), uint64(ov.fb))
143	req.set(p.id, p.prop("CRTC_ID"), uint64(o.crtc))
144	req.set(p.id, p.prop("SRC_X"), 0)
145	req.set(p.id, p.prop("SRC_Y"), 0)
146	req.set(p.id, p.prop("SRC_W"), uint64(ov.w)<<16)
147	req.set(p.id, p.prop("SRC_H"), uint64(ov.h)<<16)
148	req.set(p.id, p.prop("CRTC_X"), uint64(int64(ov.rect.X)))
149	req.set(p.id, p.prop("CRTC_Y"), uint64(int64(ov.rect.Y)))
150	req.set(p.id, p.prop("CRTC_W"), uint64(ov.rect.W))
151	req.set(p.id, p.prop("CRTC_H"), uint64(ov.rect.H))
152	if ov.acquire != nil {
153		// KMS waits for the client's GPU work before scanning it out.
154		req.set(p.id, p.prop("IN_FENCE_FD"), uint64(ov.acquire.Fd()))
155	}
156}
157
158// close releases the frame's duplicate of the acquire fence.
159func (ov overlayWin) close() {
160	if ov.acquire != nil {
161		ov.acquire.Close()
162	}
163}
164
165// testOverlay asks KMS whether it takes the frame with ov on the overlay,
166// the cursor at its current place included. A refusal is cached on the
167// buffer.
168func (o *Output) testOverlay(fb uint32, ov overlayWin) bool {
169	req := &atomicReq{}
170	o.primaryProps(req, fb)
171	o.overlayProps(req, ov)
172	if o.cursor != nil {
173		o.cursor.props(req, o.crtc, o.cursor.desired())
174	}
175	err := o.k.commit(req, atomicTestOnly, 0)
176	if err == nil {
177		return true
178	}
179	if cfb := o.clientFBs[ov.buf]; cfb != nil && refused(err) {
180		cfb.overlayFailed = "overlay_refused"
181	}
182	o.setOverlayReason("overlay_refused")
183	o.log.Info().Str("component", "render").Err(err).Str("connector", o.conn.name).Msg("overlay refused")
184	return false
185}
186
187// errOverlayDropped: a commit was refused with the overlay on; the frame is
188// composed again without it.
189var errOverlayDropped = errors.New("overlay dropped")
190
191// overlayConflict handles a commit refused with buffer buf on the overlay:
192// the buffer leaves the overlay for good (e.g. the cursor moved where the
193// plane forbids it) and the frame is composed. It reports whether it did.
194func (o *Output) overlayConflict(err error, buf uint64) bool {
195	if buf == 0 || !errors.Is(err, unix.EINVAL) {
196		return false
197	}
198	if cfb := o.clientFBs[buf]; cfb != nil {
199		cfb.overlayFailed = "cursor_conflict"
200	}
201	o.setOverlayReason("cursor_conflict")
202	return true
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 23

Tags 10

↑↓ moveenter openesc close