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}