main 3d41bc5

capture.go RawHistoryFolder

1package testserver
2
3import (
4	"github.com/bnema/purego-libwayland/protocol/extimagecapturesource"
5	"github.com/bnema/purego-libwayland/protocol/extimagecopycapture"
6	"github.com/bnema/purego-libwayland/protocol/wayland"
7)
8
9// Pixel is the content the server paints: a function of the output pixel and
10// the frame sequence, so tests can tell which part of which frame they read.
11func Pixel(x, y int, seq uint32) [4]byte { return [4]byte{byte(x), byte(y), byte(seq), 0xff} }
12
13type copyManager struct{ s *Server }
14
15func (m copyManager) CreateSession(self *extimagecopycapture.ExtImageCopyCaptureManagerV1, id uint32, src *extimagecapturesource.ExtImageCaptureSourceV1, options uint32) {
16	s := m.s
17	c := &session{s: s, src: s.sources[src.Resource]}
18	if c.src == nil {
19		self.PostError(1, "unknown source")
20		return
21	}
22	res, err := extimagecopycapture.NewExtImageCopyCaptureSessionV1(self.Client(), 1, id, sessionHandler{c})
23	if err != nil {
24		return
25	}
26	c.res = res
27	c.out = c.src.out
28	if c.src.ws != nil {
29		c.out = s.outputNamed(c.src.ws.spec.Output)
30	}
31	s.sessions = append(s.sessions, c)
32	s.stats.Sessions++
33	s.stats.LastSource = c.src.kind
34	s.stats.LastRegion = c.src.region
35	res.OnDestroy = func() {
36		c.dead = true
37		s.stats.DestroyedSessions++
38	}
39	if s.cfg.StopAtCreate || c.src.gone || c.out == nil || c.out.removed {
40		c.stop()
41		return
42	}
43	c.sendConstraints()
44}
45func (copyManager) CreatePointerCursorSession(self *extimagecopycapture.ExtImageCopyCaptureManagerV1, _ uint32, _ *extimagecapturesource.ExtImageCaptureSourceV1, _ *wayland.Pointer) {
46	self.PostError(1, "cursor sessions are not supported")
47}
48func (copyManager) Destroy(*extimagecopycapture.ExtImageCopyCaptureManagerV1) {}
49
50func (s *Server) outputNamed(name string) *output {
51	for _, o := range s.outputs {
52		if o.spec.Name == name {
53			return o
54		}
55	}
56	return nil
57}
58
59type session struct {
60	s        *Server
61	res      *extimagecopycapture.ExtImageCopyCaptureSessionV1
62	src      *source
63	out      *output
64	frame    *frame // the live frame, nil when none
65	w, h     uint32 // the latest announced buffer size
66	dead     bool
67	stopped  bool
68	answered int // frames completed with ready
69}
70
71// size is the buffer size of the session's source.
72func (c *session) size() (w, h uint32) {
73	scale := uint32(max(c.out.spec.Scale, 1))
74	if c.src.kind == "region" {
75		return uint32(c.src.region[2]) * scale, uint32(c.src.region[3]) * scale
76	}
77	return c.out.spec.Width, c.out.spec.Height
78}
79
80func (c *session) sendConstraints() {
81	c.w, c.h = c.size()
82	c.res.SendBufferSize(c.w, c.h)
83	if !c.s.cfg.NoShmFormat {
84		for _, f := range c.s.cfg.Formats {
85			c.res.SendShmFormat(f)
86		}
87	}
88	c.res.SendDone()
89}
90
91func (c *session) stop() {
92	if c.stopped || c.dead {
93		return
94	}
95	c.stopped = true
96	c.res.SendStopped()
97}
98
99type sessionHandler struct{ c *session }
100
101func (h sessionHandler) CreateFrame(self *extimagecopycapture.ExtImageCopyCaptureSessionV1, id uint32) {
102	c := h.c
103	if c.frame != nil {
104		self.PostError(uint32(extimagecopycapture.ExtImageCopyCaptureSessionV1ErrorDuplicateFrame), "create_frame before destroying the previous frame")
105		return
106	}
107	f := &frame{c: c}
108	res, err := extimagecopycapture.NewExtImageCopyCaptureFrameV1(self.Client(), 1, id, f)
109	if err != nil {
110		return
111	}
112	f.res = res
113	c.frame = f
114	c.s.stats.Frames++
115	res.OnDestroy = func() {
116		if c.frame == f {
117			c.frame = nil
118		}
119		f.gone = true
120	}
121}
122func (sessionHandler) Destroy(*extimagecopycapture.ExtImageCopyCaptureSessionV1) {}
123
124type frame struct {
125	c        *session
126	res      *extimagecopycapture.ExtImageCopyCaptureFrameV1
127	buf      *buffer
128	captured bool
129	gone     bool
130}
131
132func (f *frame) Destroy(*extimagecopycapture.ExtImageCopyCaptureFrameV1) {}
133func (f *frame) AttachBuffer(self *extimagecopycapture.ExtImageCopyCaptureFrameV1, b *wayland.Buffer) {
134	f.buf = f.c.s.bufs[b.Resource]
135}
136func (f *frame) DamageBuffer(*extimagecopycapture.ExtImageCopyCaptureFrameV1, int32, int32, int32, int32) {
137}
138
139func (f *frame) Capture(self *extimagecopycapture.ExtImageCopyCaptureFrameV1) {
140	s := f.c.s
141	if f.buf == nil {
142		self.PostError(uint32(extimagecopycapture.ExtImageCopyCaptureFrameV1ErrorNoBuffer), "capture without buffer")
143		return
144	}
145	if f.captured {
146		self.PostError(uint32(extimagecopycapture.ExtImageCopyCaptureFrameV1ErrorAlreadyCaptured), "captured twice")
147		return
148	}
149	f.captured = true
150	s.stats.Captures++
151	if s.cfg.Hold || (s.cfg.HoldAfterFirst && f.c.answered > 0) {
152		s.held = append(s.held, f)
153		return
154	}
155	f.complete()
156}
157
158// complete answers a captured frame: failed, or pixels and ready.
159func (f *frame) complete() {
160	c, s := f.c, f.c.s
161	switch {
162	case f.gone || c.dead:
163		return
164	case c.stopped:
165		f.res.SendFailed(uint32(extimagecopycapture.ExtImageCopyCaptureFrameV1FailureReasonStopped))
166		return
167	case s.cfg.FailFrames > 0:
168		s.cfg.FailFrames--
169		f.res.SendFailed(s.cfg.FailReason)
170		return
171	case f.buf.width != c.w || f.buf.height != c.h || !f.formatOK():
172		f.res.SendFailed(uint32(extimagecopycapture.ExtImageCopyCaptureFrameV1FailureReasonBufferConstraints))
173		return
174	}
175	s.seq++
176	data := f.buf.bytes()
177	if data == nil {
178		f.res.SendFailed(uint32(extimagecopycapture.ExtImageCopyCaptureFrameV1FailureReasonUnknown))
179		return
180	}
181	ox, oy, scale := 0, 0, int(max(c.out.spec.Scale, 1))
182	if c.src.kind == "region" {
183		ox, oy = int(c.src.region[0])*scale, int(c.src.region[1])*scale
184	}
185	for y := 0; y < int(f.buf.height); y++ {
186		row := data[y*int(f.buf.stride):]
187		for x := 0; x < int(f.buf.width); x++ {
188			p := Pixel(ox+x, oy+y, s.seq)
189			copy(row[x*4:], p[:])
190		}
191	}
192	s.stats.Ready++
193	c.answered++
194	f.res.SendTransform(s.cfg.Transform)
195	f.res.SendDamage(0, 0, int32(f.buf.width), int32(f.buf.height))
196	f.res.SendReady()
197}
198
199func (f *frame) formatOK() bool {
200	for _, x := range f.c.s.cfg.Formats {
201		if x == f.buf.format {
202			return true
203		}
204	}
205	return false
206}
207
208// Release completes every held frame.
209func (s *Server) Release() {
210	s.Do(func() {
211		held := s.held
212		s.held = nil
213		for _, f := range held {
214			f.complete()
215		}
216	})
217}
218
219// Held reports how many captured frames wait for Release.
220func (s *Server) Held() int {
221	n := 0
222	s.Do(func() { n = len(s.held) })
223	return n
224}
225
226// Resize changes an output's mode and sends new constraints to its sessions.
227func (s *Server) Resize(name string, w, h uint32) {
228	s.Do(func() {
229		o := s.outputNamed(name)
230		if o == nil {
231			return
232		}
233		o.spec.Width, o.spec.Height = w, h
234		for _, c := range s.sessions {
235			if c.out == o && !c.dead && !c.stopped {
236				c.sendConstraints()
237			}
238		}
239	})
240}
241
242// StopSessions ends every live session with the stopped event.
243func (s *Server) StopSessions() {
244	s.Do(func() {
245		for _, c := range s.sessions {
246			c.stop()
247		}
248	})
249}
250
251// Fail makes the next n captures answer failed(reason).
252func (s *Server) Fail(n int, reason uint32) {
253	s.Do(func() { s.cfg.FailFrames, s.cfg.FailReason = n, reason })
254}

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 2

Tags 9

↑↓ moveenter openesc close