test(wayland): add an in-process libwayland test compositor
Speaks wl_output, xdg-output, wl_shm, ext-image-capture-source, ext-image-copy-capture, ext-workspace and NeferWL's capture extension over a real socket, with hold, failure, resize, transform and hotplug controls. The extension's server bindings are generated from its XML (gen.sh). NEFERCAP_REQUIRE_WAYLAND=1 turns a missing libwayland-server into a failure.
- commit
- 311556438c14176b42c09987e4e71549d2ce6852
- author
- brice · · 2026-09-30 19:56 UTC
- committer
- brice · · 2026-10-01 02:17 UTC
- parent
- 709e2cd
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
11 files · +1745 -0
M go.mod +1 -0
| @@ -4,6 +4,7 @@ go 1.27 | |||
| 4 | 4 | ||
| 5 | 5 | require ( | |
| 6 | 6 | github.com/bnema/nefergui v0.2.0 | |
| 7 | + | github.com/bnema/purego-libwayland v0.7.0 | |
| 7 | 8 | github.com/bnema/wlturbo v0.3.0 | |
| 8 | 9 | github.com/bnema/zerowrap v1.4.1 | |
| 9 | 10 | github.com/stretchr/testify v1.12.1 | |
| @@ -4,6 +4,7 @@ go 1.27 | |||||
| 4 | 4 | ||||
| 5 | require ( | 5 | require ( | ||
| 6 | github.com/bnema/nefergui v0.2.0 | 6 | github.com/bnema/nefergui v0.2.0 | ||
| 7 | + | github.com/bnema/purego-libwayland v0.7.0 | |||
| 7 | github.com/bnema/wlturbo v0.3.0 | 8 | github.com/bnema/wlturbo v0.3.0 | ||
| 8 | github.com/bnema/zerowrap v1.4.1 | 9 | github.com/bnema/zerowrap v1.4.1 | ||
| 9 | github.com/stretchr/testify v1.12.1 | 10 | github.com/stretchr/testify v1.12.1 | ||
M go.sum +2 -0
| @@ -2,6 +2,8 @@ github.com/bnema/nefergui v0.2.0 h1:+kuePc+HyZFmlm0C0JkFq7TfL/Et4ZQjNlGw7lgjeXc= | |||
| 2 | 2 | github.com/bnema/nefergui v0.2.0/go.mod h1:JNjwv/YlesI0FflJvmHB5OqLd/bHfqFakpvJ2zJr+F4= | |
| 3 | 3 | github.com/bnema/purego v0.13.0-bnema.1 h1:fKRdztnvTmRqAjANMoN+utEIH73dvPqVPJh2iZCCAO8= | |
| 4 | 4 | github.com/bnema/purego v0.13.0-bnema.1/go.mod h1:xS1iX3noypxnbOUEUAqOvkfnUzgHuu19VtL4+dfUMJM= | |
| 5 | + | github.com/bnema/purego-libwayland v0.7.0 h1:Q25nd/KmcivEucUceEmmNt1EgiryugAXnI4c7OV9tnU= | |
| 6 | + | github.com/bnema/purego-libwayland v0.7.0/go.mod h1:+2zIEBX+iNlRB27xrm5Z8Dnd+cmqX+1AYvuwguxK2dg= | |
| 5 | 7 | github.com/bnema/purego-vulkan v0.6.0 h1:SJgu0smuDC2vtBv0dJ6dmRKewJW5EUXQwTXyb/p9JSE= | |
| 6 | 8 | github.com/bnema/purego-vulkan v0.6.0/go.mod h1:7kFoR1p01JbknuZlfA20oeImryBe8Fx+Zd9hm2uSUM4= | |
| 7 | 9 | github.com/bnema/purego-xkbcommon v0.0.0-20260928071113-e79265dfcdc2 h1:XePqfzZzlT1WEQ8G9atwlXjW5jFZAkEoUGnlYvsHAHg= | |
| @@ -2,6 +2,8 @@ github.com/bnema/nefergui v0.2.0 h1:+kuePc+HyZFmlm0C0JkFq7TfL/Et4ZQjNlGw7lgjeXc= | |||||
| 2 | github.com/bnema/nefergui v0.2.0/go.mod h1:JNjwv/YlesI0FflJvmHB5OqLd/bHfqFakpvJ2zJr+F4= | 2 | github.com/bnema/nefergui v0.2.0/go.mod h1:JNjwv/YlesI0FflJvmHB5OqLd/bHfqFakpvJ2zJr+F4= | ||
| 3 | github.com/bnema/purego v0.13.0-bnema.1 h1:fKRdztnvTmRqAjANMoN+utEIH73dvPqVPJh2iZCCAO8= | 3 | github.com/bnema/purego v0.13.0-bnema.1 h1:fKRdztnvTmRqAjANMoN+utEIH73dvPqVPJh2iZCCAO8= | ||
| 4 | github.com/bnema/purego v0.13.0-bnema.1/go.mod h1:xS1iX3noypxnbOUEUAqOvkfnUzgHuu19VtL4+dfUMJM= | 4 | github.com/bnema/purego v0.13.0-bnema.1/go.mod h1:xS1iX3noypxnbOUEUAqOvkfnUzgHuu19VtL4+dfUMJM= | ||
| 5 | + | github.com/bnema/purego-libwayland v0.7.0 h1:Q25nd/KmcivEucUceEmmNt1EgiryugAXnI4c7OV9tnU= | |||
| 6 | + | github.com/bnema/purego-libwayland v0.7.0/go.mod h1:+2zIEBX+iNlRB27xrm5Z8Dnd+cmqX+1AYvuwguxK2dg= | |||
| 5 | github.com/bnema/purego-vulkan v0.6.0 h1:SJgu0smuDC2vtBv0dJ6dmRKewJW5EUXQwTXyb/p9JSE= | 7 | github.com/bnema/purego-vulkan v0.6.0 h1:SJgu0smuDC2vtBv0dJ6dmRKewJW5EUXQwTXyb/p9JSE= | ||
| 6 | github.com/bnema/purego-vulkan v0.6.0/go.mod h1:7kFoR1p01JbknuZlfA20oeImryBe8Fx+Zd9hm2uSUM4= | 8 | github.com/bnema/purego-vulkan v0.6.0/go.mod h1:7kFoR1p01JbknuZlfA20oeImryBe8Fx+Zd9hm2uSUM4= | ||
| 7 | github.com/bnema/purego-xkbcommon v0.0.0-20260928071113-e79265dfcdc2 h1:XePqfzZzlT1WEQ8G9atwlXjW5jFZAkEoUGnlYvsHAHg= | 9 | github.com/bnema/purego-xkbcommon v0.0.0-20260928071113-e79265dfcdc2 h1:XePqfzZzlT1WEQ8G9atwlXjW5jFZAkEoUGnlYvsHAHg= | ||
A internal/adapters/wayland/testserver/capture.go +254 -0
| @@ -0,0 +1,254 @@ | |||
| 1 | + | package testserver | |
| 2 | + | ||
| 3 | + | import ( | |
| 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. | |
| 11 | + | func Pixel(x, y int, seq uint32) [4]byte { return [4]byte{byte(x), byte(y), byte(seq), 0xff} } | |
| 12 | + | ||
| 13 | + | type copyManager struct{ s *Server } | |
| 14 | + | ||
| 15 | + | func (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 | + | } | |
| 45 | + | func (copyManager) CreatePointerCursorSession(self *extimagecopycapture.ExtImageCopyCaptureManagerV1, _ uint32, _ *extimagecapturesource.ExtImageCaptureSourceV1, _ *wayland.Pointer) { | |
| 46 | + | self.PostError(1, "cursor sessions are not supported") | |
| 47 | + | } | |
| 48 | + | func (copyManager) Destroy(*extimagecopycapture.ExtImageCopyCaptureManagerV1) {} | |
| 49 | + | ||
| 50 | + | func (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 | + | ||
| 59 | + | type 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. | |
| 72 | + | func (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 | + | ||
| 80 | + | func (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 | + | ||
| 91 | + | func (c *session) stop() { | |
| 92 | + | if c.stopped || c.dead { | |
| 93 | + | return | |
| 94 | + | } | |
| 95 | + | c.stopped = true | |
| 96 | + | c.res.SendStopped() | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | type sessionHandler struct{ c *session } | |
| 100 | + | ||
| 101 | + | func (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 | + | } | |
| 122 | + | func (sessionHandler) Destroy(*extimagecopycapture.ExtImageCopyCaptureSessionV1) {} | |
| 123 | + | ||
| 124 | + | type frame struct { | |
| 125 | + | c *session | |
| 126 | + | res *extimagecopycapture.ExtImageCopyCaptureFrameV1 | |
| 127 | + | buf *buffer | |
| 128 | + | captured bool | |
| 129 | + | gone bool | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | func (f *frame) Destroy(*extimagecopycapture.ExtImageCopyCaptureFrameV1) {} | |
| 133 | + | func (f *frame) AttachBuffer(self *extimagecopycapture.ExtImageCopyCaptureFrameV1, b *wayland.Buffer) { | |
| 134 | + | f.buf = f.c.s.bufs[b.Resource] | |
| 135 | + | } | |
| 136 | + | func (f *frame) DamageBuffer(*extimagecopycapture.ExtImageCopyCaptureFrameV1, int32, int32, int32, int32) { | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | func (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. | |
| 159 | + | func (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 | + | ||
| 199 | + | func (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. | |
| 209 | + | func (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. | |
| 220 | + | func (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. | |
| 227 | + | func (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. | |
| 243 | + | func (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). | |
| 252 | + | func (s *Server) Fail(n int, reason uint32) { | |
| 253 | + | s.Do(func() { s.cfg.FailFrames, s.cfg.FailReason = n, reason }) | |
| 254 | + | } | |
| @@ -0,0 +1,254 @@ | |||||
| 1 | + | package testserver | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 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. | |||
| 11 | + | func Pixel(x, y int, seq uint32) [4]byte { return [4]byte{byte(x), byte(y), byte(seq), 0xff} } | |||
| 12 | + | ||||
| 13 | + | type copyManager struct{ s *Server } | |||
| 14 | + | ||||
| 15 | + | func (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 | + | } | |||
| 45 | + | func (copyManager) CreatePointerCursorSession(self *extimagecopycapture.ExtImageCopyCaptureManagerV1, _ uint32, _ *extimagecapturesource.ExtImageCaptureSourceV1, _ *wayland.Pointer) { | |||
| 46 | + | self.PostError(1, "cursor sessions are not supported") | |||
| 47 | + | } | |||
| 48 | + | func (copyManager) Destroy(*extimagecopycapture.ExtImageCopyCaptureManagerV1) {} | |||
| 49 | + | ||||
| 50 | + | func (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 | + | ||||
| 59 | + | type 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. | |||
| 72 | + | func (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 | + | ||||
| 80 | + | func (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 | + | ||||
| 91 | + | func (c *session) stop() { | |||
| 92 | + | if c.stopped || c.dead { | |||
| 93 | + | return | |||
| 94 | + | } | |||
| 95 | + | c.stopped = true | |||
| 96 | + | c.res.SendStopped() | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | type sessionHandler struct{ c *session } | |||
| 100 | + | ||||
| 101 | + | func (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 | + | } | |||
| 122 | + | func (sessionHandler) Destroy(*extimagecopycapture.ExtImageCopyCaptureSessionV1) {} | |||
| 123 | + | ||||
| 124 | + | type frame struct { | |||
| 125 | + | c *session | |||
| 126 | + | res *extimagecopycapture.ExtImageCopyCaptureFrameV1 | |||
| 127 | + | buf *buffer | |||
| 128 | + | captured bool | |||
| 129 | + | gone bool | |||
| 130 | + | } | |||
| 131 | + | ||||
| 132 | + | func (f *frame) Destroy(*extimagecopycapture.ExtImageCopyCaptureFrameV1) {} | |||
| 133 | + | func (f *frame) AttachBuffer(self *extimagecopycapture.ExtImageCopyCaptureFrameV1, b *wayland.Buffer) { | |||
| 134 | + | f.buf = f.c.s.bufs[b.Resource] | |||
| 135 | + | } | |||
| 136 | + | func (f *frame) DamageBuffer(*extimagecopycapture.ExtImageCopyCaptureFrameV1, int32, int32, int32, int32) { | |||
| 137 | + | } | |||
| 138 | + | ||||
| 139 | + | func (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. | |||
| 159 | + | func (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 | + | ||||
| 199 | + | func (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. | |||
| 209 | + | func (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. | |||
| 220 | + | func (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. | |||
| 227 | + | func (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. | |||
| 243 | + | func (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). | |||
| 252 | + | func (s *Server) Fail(n int, reason uint32) { | |||
| 253 | + | s.Do(func() { s.cfg.FailFrames, s.cfg.FailReason = n, reason }) | |||
| 254 | + | } | |||
A internal/adapters/wayland/testserver/exclusion.go +81 -0
| @@ -0,0 +1,81 @@ | |||
| 1 | + | package testserver | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/purego-libwayland/protocol/extimagecopycapture" | |
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/nefercap/internal/adapters/wayland/testserver/neferwl" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | type exclusionManager struct{ s *Server } | |
| 11 | + | ||
| 12 | + | func (exclusionManager) Destroy(*neferwl.NeferwlCaptureExclusionManagerV1) {} | |
| 13 | + | ||
| 14 | + | func (m exclusionManager) GetExclusion(self *neferwl.NeferwlCaptureExclusionManagerV1, id uint32, sess *extimagecopycapture.ExtImageCopyCaptureSessionV1) { | |
| 15 | + | s := m.s | |
| 16 | + | res, err := neferwl.NewNeferwlCaptureExclusionV1(self.Client(), 1, id, exclusionHandler{s}) | |
| 17 | + | if err != nil { | |
| 18 | + | return | |
| 19 | + | } | |
| 20 | + | s.stats.Exclusions++ | |
| 21 | + | res.OnDestroy = func() { s.stats.DestroyedExclusions++ } | |
| 22 | + | if s.cfg.ExclusionFail != 0 { | |
| 23 | + | res.SendFailed(s.cfg.ExclusionFail - 1) | |
| 24 | + | return | |
| 25 | + | } | |
| 26 | + | res.SendToken(Token) | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | func (m exclusionManager) AttachSurface(self *neferwl.NeferwlCaptureExclusionManagerV1, id uint32, token string, _ *wayland.Surface) { | |
| 30 | + | s := m.s | |
| 31 | + | res, err := neferwl.NewNeferwlCaptureLayerV1(self.Client(), 1, id, layerHandler{}) | |
| 32 | + | if err != nil { | |
| 33 | + | return | |
| 34 | + | } | |
| 35 | + | s.stats.Attached = append(s.stats.Attached, token) | |
| 36 | + | s.layers = append(s.layers, res.Resource) | |
| 37 | + | layer := res | |
| 38 | + | switch { | |
| 39 | + | case s.cfg.AttachSilent: | |
| 40 | + | case s.cfg.AttachFail != 0: | |
| 41 | + | layer.SendFailed(s.cfg.AttachFail - 1) | |
| 42 | + | default: | |
| 43 | + | layer.SendAttached() | |
| 44 | + | } | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | type exclusionHandler struct{ s *Server } | |
| 48 | + | ||
| 49 | + | func (exclusionHandler) Destroy(*neferwl.NeferwlCaptureExclusionV1) {} | |
| 50 | + | ||
| 51 | + | type layerHandler struct{} | |
| 52 | + | ||
| 53 | + | func (layerHandler) Destroy(*neferwl.NeferwlCaptureLayerV1) {} | |
| 54 | + | ||
| 55 | + | // Detach sends detached(reason) on the n-th attached layer. | |
| 56 | + | func (s *Server) Detach(n int, reason uint32) { | |
| 57 | + | s.Do(func() { | |
| 58 | + | if n < len(s.layers) { | |
| 59 | + | neferwl.WrapNeferwlCaptureLayerV1(s.layers[n]).SendDetached(reason) | |
| 60 | + | } | |
| 61 | + | }) | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | // SendRaw posts a layer event out of order, to test violations: opcode 0 | |
| 65 | + | // attached, 1 failed, 2 detached. | |
| 66 | + | func (s *Server) SendRaw(n int, opcode uint32, arg uint32) { | |
| 67 | + | s.Do(func() { | |
| 68 | + | if n >= len(s.layers) { | |
| 69 | + | return | |
| 70 | + | } | |
| 71 | + | l := neferwl.WrapNeferwlCaptureLayerV1(s.layers[n]) | |
| 72 | + | switch opcode { | |
| 73 | + | case 0: | |
| 74 | + | l.SendAttached() | |
| 75 | + | case 1: | |
| 76 | + | l.SendFailed(arg) | |
| 77 | + | default: | |
| 78 | + | l.SendDetached(arg) | |
| 79 | + | } | |
| 80 | + | }) | |
| 81 | + | } | |
| @@ -0,0 +1,81 @@ | |||||
| 1 | + | package testserver | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/purego-libwayland/protocol/extimagecopycapture" | |||
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/nefercap/internal/adapters/wayland/testserver/neferwl" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | type exclusionManager struct{ s *Server } | |||
| 11 | + | ||||
| 12 | + | func (exclusionManager) Destroy(*neferwl.NeferwlCaptureExclusionManagerV1) {} | |||
| 13 | + | ||||
| 14 | + | func (m exclusionManager) GetExclusion(self *neferwl.NeferwlCaptureExclusionManagerV1, id uint32, sess *extimagecopycapture.ExtImageCopyCaptureSessionV1) { | |||
| 15 | + | s := m.s | |||
| 16 | + | res, err := neferwl.NewNeferwlCaptureExclusionV1(self.Client(), 1, id, exclusionHandler{s}) | |||
| 17 | + | if err != nil { | |||
| 18 | + | return | |||
| 19 | + | } | |||
| 20 | + | s.stats.Exclusions++ | |||
| 21 | + | res.OnDestroy = func() { s.stats.DestroyedExclusions++ } | |||
| 22 | + | if s.cfg.ExclusionFail != 0 { | |||
| 23 | + | res.SendFailed(s.cfg.ExclusionFail - 1) | |||
| 24 | + | return | |||
| 25 | + | } | |||
| 26 | + | res.SendToken(Token) | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | func (m exclusionManager) AttachSurface(self *neferwl.NeferwlCaptureExclusionManagerV1, id uint32, token string, _ *wayland.Surface) { | |||
| 30 | + | s := m.s | |||
| 31 | + | res, err := neferwl.NewNeferwlCaptureLayerV1(self.Client(), 1, id, layerHandler{}) | |||
| 32 | + | if err != nil { | |||
| 33 | + | return | |||
| 34 | + | } | |||
| 35 | + | s.stats.Attached = append(s.stats.Attached, token) | |||
| 36 | + | s.layers = append(s.layers, res.Resource) | |||
| 37 | + | layer := res | |||
| 38 | + | switch { | |||
| 39 | + | case s.cfg.AttachSilent: | |||
| 40 | + | case s.cfg.AttachFail != 0: | |||
| 41 | + | layer.SendFailed(s.cfg.AttachFail - 1) | |||
| 42 | + | default: | |||
| 43 | + | layer.SendAttached() | |||
| 44 | + | } | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | type exclusionHandler struct{ s *Server } | |||
| 48 | + | ||||
| 49 | + | func (exclusionHandler) Destroy(*neferwl.NeferwlCaptureExclusionV1) {} | |||
| 50 | + | ||||
| 51 | + | type layerHandler struct{} | |||
| 52 | + | ||||
| 53 | + | func (layerHandler) Destroy(*neferwl.NeferwlCaptureLayerV1) {} | |||
| 54 | + | ||||
| 55 | + | // Detach sends detached(reason) on the n-th attached layer. | |||
| 56 | + | func (s *Server) Detach(n int, reason uint32) { | |||
| 57 | + | s.Do(func() { | |||
| 58 | + | if n < len(s.layers) { | |||
| 59 | + | neferwl.WrapNeferwlCaptureLayerV1(s.layers[n]).SendDetached(reason) | |||
| 60 | + | } | |||
| 61 | + | }) | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | // SendRaw posts a layer event out of order, to test violations: opcode 0 | |||
| 65 | + | // attached, 1 failed, 2 detached. | |||
| 66 | + | func (s *Server) SendRaw(n int, opcode uint32, arg uint32) { | |||
| 67 | + | s.Do(func() { | |||
| 68 | + | if n >= len(s.layers) { | |||
| 69 | + | return | |||
| 70 | + | } | |||
| 71 | + | l := neferwl.WrapNeferwlCaptureLayerV1(s.layers[n]) | |||
| 72 | + | switch opcode { | |||
| 73 | + | case 0: | |||
| 74 | + | l.SendAttached() | |||
| 75 | + | case 1: | |||
| 76 | + | l.SendFailed(arg) | |||
| 77 | + | default: | |||
| 78 | + | l.SendDetached(arg) | |||
| 79 | + | } | |||
| 80 | + | }) | |||
| 81 | + | } | |||
A internal/adapters/wayland/testserver/neferwl/gen.sh +14 -0
| @@ -0,0 +1,14 @@ | |||
| 1 | + | #!/bin/sh | |
| 2 | + | # Regenerates neferwl.go (test-only server bindings of NeferWL's capture | |
| 3 | + | # extension) from the vendored XML with the wlgen of the purego-libwayland | |
| 4 | + | # module in use. Offline: go.work or the module cache. | |
| 5 | + | set -eu | |
| 6 | + | cd "$(dirname "$0")" | |
| 7 | + | mod=$(go list -m -f '{{.Dir}}' github.com/bnema/purego-libwayland) | |
| 8 | + | p=github.com/bnema/purego-libwayland/protocol | |
| 9 | + | go run github.com/bnema/purego-libwayland/cmd/wlgen -package neferwl -out neferwl.go \ | |
| 10 | + | -import wayland=$p/wayland -import-xml wayland="$mod/protocols/wayland.xml" \ | |
| 11 | + | -import extimagecapturesource=$p/extimagecapturesource -import-xml extimagecapturesource="$mod/protocols/ext-image-capture-source-v1.xml" \ | |
| 12 | + | -import extimagecopycapture=$p/extimagecopycapture -import-xml extimagecopycapture="$mod/protocols/ext-image-copy-capture-v1.xml" \ | |
| 13 | + | -import extworkspace=$p/extworkspace -import-xml extworkspace="$mod/protocols/ext-workspace-v1.xml" \ | |
| 14 | + | neferwl-image-capture-v1.xml | |
| @@ -0,0 +1,14 @@ | |||||
| 1 | + | #!/bin/sh | |||
| 2 | + | # Regenerates neferwl.go (test-only server bindings of NeferWL's capture | |||
| 3 | + | # extension) from the vendored XML with the wlgen of the purego-libwayland | |||
| 4 | + | # module in use. Offline: go.work or the module cache. | |||
| 5 | + | set -eu | |||
| 6 | + | cd "$(dirname "$0")" | |||
| 7 | + | mod=$(go list -m -f '{{.Dir}}' github.com/bnema/purego-libwayland) | |||
| 8 | + | p=github.com/bnema/purego-libwayland/protocol | |||
| 9 | + | go run github.com/bnema/purego-libwayland/cmd/wlgen -package neferwl -out neferwl.go \ | |||
| 10 | + | -import wayland=$p/wayland -import-xml wayland="$mod/protocols/wayland.xml" \ | |||
| 11 | + | -import extimagecapturesource=$p/extimagecapturesource -import-xml extimagecapturesource="$mod/protocols/ext-image-capture-source-v1.xml" \ | |||
| 12 | + | -import extimagecopycapture=$p/extimagecopycapture -import-xml extimagecopycapture="$mod/protocols/ext-image-copy-capture-v1.xml" \ | |||
| 13 | + | -import extworkspace=$p/extworkspace -import-xml extworkspace="$mod/protocols/ext-workspace-v1.xml" \ | |||
| 14 | + | neferwl-image-capture-v1.xml | |||
A internal/adapters/wayland/testserver/neferwl/neferwl-image-capture-v1.xml +231 -0
| @@ -0,0 +1,231 @@ | |||
| 1 | + | <?xml version="1.0" encoding="UTF-8"?> | |
| 2 | + | <protocol name="neferwl_image_capture_v1"> | |
| 3 | + | <copyright> | |
| 4 | + | neferwl private protocol. No stability promise beyond the version of the | |
| 5 | + | interface. | |
| 6 | + | </copyright> | |
| 7 | + | ||
| 8 | + | <description summary="neferwl additions to ext-image-copy-capture"> | |
| 9 | + | Optional additions to ext-image-capture-source-v1, ext-workspace-v1 and | |
| 10 | + | ext-image-copy-capture-v1 for what those protocols do not cover: | |
| 11 | + | workspace and output-region capture sources, the frame of a workspace, | |
| 12 | + | and leaving a client's own surfaces (a recording HUD) out of its | |
| 13 | + | captures. | |
| 14 | + | ||
| 15 | + | Capture always goes through ext-image-copy-capture. The compositor applies | |
| 16 | + | the same rules to every capture, whatever its source: it may refuse a | |
| 17 | + | client (ext_image_copy_capture_session_v1.stopped), and it draws its own | |
| 18 | + | capture indicator on screen, never in a captured image, which no client | |
| 19 | + | can turn off. | |
| 20 | + | </description> | |
| 21 | + | ||
| 22 | + | <interface name="neferwl_image_capture_source_manager_v1" version="1"> | |
| 23 | + | <description summary="workspace and output-region capture sources"> | |
| 24 | + | Global. Sources created here are ext_image_capture_source_v1 objects, | |
| 25 | + | used with ext_image_copy_capture_manager_v1.create_session like any | |
| 26 | + | other source. | |
| 27 | + | </description> | |
| 28 | + | ||
| 29 | + | <enum name="error"> | |
| 30 | + | <entry name="invalid_region" value="0" summary="width or height is not positive"/> | |
| 31 | + | </enum> | |
| 32 | + | ||
| 33 | + | <request name="destroy" type="destructor"> | |
| 34 | + | <description summary="destroy the manager"> | |
| 35 | + | Sources created through it stay valid. | |
| 36 | + | </description> | |
| 37 | + | </request> | |
| 38 | + | ||
| 39 | + | <request name="create_workspace_source"> | |
| 40 | + | <description summary="capture a workspace"> | |
| 41 | + | The source is the workspace frame, without bars, whether or not the | |
| 42 | + | workspace is on screen: a workspace that is not on screen is rendered | |
| 43 | + | for capture only. Its buffer size follows the workspace frame. A | |
| 44 | + | session on it is stopped when the workspace is removed. A handle | |
| 45 | + | already removed gives a source whose sessions stop at once. | |
| 46 | + | </description> | |
| 47 | + | <arg name="source" type="new_id" interface="ext_image_capture_source_v1"/> | |
| 48 | + | <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/> | |
| 49 | + | </request> | |
| 50 | + | ||
| 51 | + | <request name="create_output_region_source"> | |
| 52 | + | <description summary="capture a rectangle of an output"> | |
| 53 | + | The rectangle is in output-local logical pixels and is clipped to the | |
| 54 | + | output. The buffer covers only that rectangle, in physical pixels. A | |
| 55 | + | session on it is stopped when the output goes away or when nothing of | |
| 56 | + | the rectangle is left on the output. | |
| 57 | + | </description> | |
| 58 | + | <arg name="source" type="new_id" interface="ext_image_capture_source_v1"/> | |
| 59 | + | <arg name="output" type="object" interface="wl_output"/> | |
| 60 | + | <arg name="x" type="int"/> | |
| 61 | + | <arg name="y" type="int"/> | |
| 62 | + | <arg name="width" type="int"/> | |
| 63 | + | <arg name="height" type="int"/> | |
| 64 | + | </request> | |
| 65 | + | ||
| 66 | + | <request name="get_workspace_frame"> | |
| 67 | + | <description summary="learn the frame of a workspace"> | |
| 68 | + | ext-workspace-v1 carries no geometry. The frame object reports the | |
| 69 | + | rectangle that a workspace source of the same handle captures. For a | |
| 70 | + | handle already removed, the object never sends frame. | |
| 71 | + | </description> | |
| 72 | + | <arg name="id" type="new_id" interface="neferwl_workspace_frame_v1"/> | |
| 73 | + | <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/> | |
| 74 | + | </request> | |
| 75 | + | </interface> | |
| 76 | + | ||
| 77 | + | <interface name="neferwl_workspace_frame_v1" version="1"> | |
| 78 | + | <description summary="the frame of one workspace"> | |
| 79 | + | The frame is the area of its output that the workspace draws in, | |
| 80 | + | without bars: the whole output, or the smaller size set for the | |
| 81 | + | workspace. | |
| 82 | + | </description> | |
| 83 | + | ||
| 84 | + | <request name="destroy" type="destructor"> | |
| 85 | + | <description summary="destroy the frame object"/> | |
| 86 | + | </request> | |
| 87 | + | ||
| 88 | + | <event name="frame"> | |
| 89 | + | <description summary="the workspace frame"> | |
| 90 | + | The frame in output-local logical pixels. Sent when the object is | |
| 91 | + | created and again whenever the frame changes. Not sent once the | |
| 92 | + | workspace is removed. | |
| 93 | + | </description> | |
| 94 | + | <arg name="x" type="int"/> | |
| 95 | + | <arg name="y" type="int"/> | |
| 96 | + | <arg name="width" type="int"/> | |
| 97 | + | <arg name="height" type="int"/> | |
| 98 | + | </event> | |
| 99 | + | </interface> | |
| 100 | + | ||
| 101 | + | <interface name="neferwl_capture_exclusion_manager_v1" version="1"> | |
| 102 | + | <description summary="leave a client's own surfaces out of its captures"> | |
| 103 | + | Global. A recorder asks for an exclusion on its capture session and | |
| 104 | + | gets a token; its HUD, usually on another connection of the same user, | |
| 105 | + | attaches layer surfaces with that token. Frames of that session leave | |
| 106 | + | the attached surfaces out. Every other capture shows them. | |
| 107 | + | </description> | |
| 108 | + | ||
| 109 | + | <enum name="error"> | |
| 110 | + | <entry name="surface_role" value="0" summary="the surface is not a wlr-layer-shell surface"/> | |
| 111 | + | <entry name="invalid_layer" value="1" summary="the layer is neither top nor overlay"/> | |
| 112 | + | <entry name="exclusive_keyboard" value="2" summary="the layer takes the keyboard exclusively"/> | |
| 113 | + | <entry name="invalid_surface" value="3" summary="the surface is already mapped"/> | |
| 114 | + | <entry name="already_excluded" value="4" summary="the capture session already has an exclusion"/> | |
| 115 | + | </enum> | |
| 116 | + | ||
| 117 | + | <request name="destroy" type="destructor"> | |
| 118 | + | <description summary="destroy the manager"> | |
| 119 | + | Exclusions and attachments created through it stay alive. | |
| 120 | + | </description> | |
| 121 | + | </request> | |
| 122 | + | ||
| 123 | + | <request name="get_exclusion"> | |
| 124 | + | <description summary="create the exclusion of a capture session"> | |
| 125 | + | The exclusion answers with exactly one of token or failed. It ends | |
| 126 | + | with the capture session. | |
| 127 | + | </description> | |
| 128 | + | <arg name="id" type="new_id" interface="neferwl_capture_exclusion_v1"/> | |
| 129 | + | <arg name="session" type="object" interface="ext_image_copy_capture_session_v1"/> | |
| 130 | + | </request> | |
| 131 | + | ||
| 132 | + | <request name="attach_surface"> | |
| 133 | + | <description summary="attach a layer surface to an exclusion by token"> | |
| 134 | + | Marks a layer surface of this client as excluded from the frames of | |
| 135 | + | the session that owns the token. The client must have the same user | |
| 136 | + | ID as the compositor and must also be allowed to capture: its | |
| 137 | + | executable is on the capture allowlist (or the allowlist is off), it | |
| 138 | + | is not sandboxed, and the session is not locked. The surface must already have the | |
| 139 | + | wlr-layer-shell role, on the top or overlay layer, must not have | |
| 140 | + | mapped yet and must not ask for exclusive keyboard interactivity. At | |
| 141 | + | most 4 layers per exclusion. An attached layer never gets keyboard | |
| 142 | + | interactivity; if it moves to another layer the compositor closes it | |
| 143 | + | and sends detached. | |
| 144 | + | ||
| 145 | + | The attachment answers with exactly one of attached or failed. The | |
| 146 | + | surface is excluded from this request on, before it can map; the | |
| 147 | + | client must not show it before attached. | |
| 148 | + | </description> | |
| 149 | + | <arg name="id" type="new_id" interface="neferwl_capture_layer_v1"/> | |
| 150 | + | <arg name="token" type="string"/> | |
| 151 | + | <arg name="surface" type="object" interface="wl_surface"/> | |
| 152 | + | </request> | |
| 153 | + | </interface> | |
| 154 | + | ||
| 155 | + | <interface name="neferwl_capture_exclusion_v1" version="1"> | |
| 156 | + | <description summary="the exclusion of one capture session"> | |
| 157 | + | Destroying it detaches every attached layer. | |
| 158 | + | </description> | |
| 159 | + | ||
| 160 | + | <enum name="failure"> | |
| 161 | + | <entry name="busy" value="0" summary="another exclusion is live in the compositor"/> | |
| 162 | + | <entry name="session_stopped" value="1" summary="the capture session is stopped"/> | |
| 163 | + | </enum> | |
| 164 | + | ||
| 165 | + | <request name="destroy" type="destructor"> | |
| 166 | + | <description summary="destroy the exclusion"/> | |
| 167 | + | </request> | |
| 168 | + | ||
| 169 | + | <event name="token"> | |
| 170 | + | <description summary="the attachment token"> | |
| 171 | + | 64 lowercase hexadecimal characters (32 random bytes). A secret for | |
| 172 | + | the owner and its HUD, never logged, valid until the exclusion ends. | |
| 173 | + | </description> | |
| 174 | + | <arg name="token" type="string"/> | |
| 175 | + | </event> | |
| 176 | + | ||
| 177 | + | <event name="failed"> | |
| 178 | + | <description summary="no exclusion"> | |
| 179 | + | Final. Destroy the object. | |
| 180 | + | </description> | |
| 181 | + | <arg name="reason" type="uint" enum="failure"/> | |
| 182 | + | </event> | |
| 183 | + | </interface> | |
| 184 | + | ||
| 185 | + | <interface name="neferwl_capture_layer_v1" version="1"> | |
| 186 | + | <description summary="a layer surface attached to an exclusion"> | |
| 187 | + | Created by attach_surface. Exactly one of attached or failed comes | |
| 188 | + | first; detached comes at most once, after attached. | |
| 189 | + | </description> | |
| 190 | + | ||
| 191 | + | <enum name="failure"> | |
| 192 | + | <entry name="unknown_token" value="0" summary="no live exclusion has this token"/> | |
| 193 | + | <entry name="unauthorized" value="1" summary="peer credentials differ from the compositor user's or are unreadable, or the client may not capture (not on the allowlist, sandboxed, or session locked)"/> | |
| 194 | + | <entry name="too_many_layers" value="2" summary="the exclusion already has 4 attached layers"/> | |
| 195 | + | <entry name="already_attached" value="3" summary="the surface is already attached"/> | |
| 196 | + | <entry name="exclusion_ended" value="4" summary="the exclusion ended before the compositor confirmed the layer"/> | |
| 197 | + | <entry name="layer_destroyed" value="5" summary="the layer role was destroyed before the compositor confirmed it"/> | |
| 198 | + | </enum> | |
| 199 | + | ||
| 200 | + | <enum name="detach_reason"> | |
| 201 | + | <entry name="exclusion_ended" value="0" summary="the exclusion ended; the compositor closed the layer surface"/> | |
| 202 | + | <entry name="layer_destroyed" value="1" summary="the layer role was destroyed"/> | |
| 203 | + | </enum> | |
| 204 | + | ||
| 205 | + | <request name="destroy" type="destructor"> | |
| 206 | + | <description summary="destroy the attachment object"> | |
| 207 | + | The surface stays excluded until its layer role is destroyed or the | |
| 208 | + | exclusion ends. | |
| 209 | + | </description> | |
| 210 | + | </request> | |
| 211 | + | ||
| 212 | + | <event name="attached"> | |
| 213 | + | <description summary="the layer is excluded"> | |
| 214 | + | The compositor confirmed the attachment. Safe to show. | |
| 215 | + | </description> | |
| 216 | + | </event> | |
| 217 | + | ||
| 218 | + | <event name="failed"> | |
| 219 | + | <description summary="the attachment was refused"/> | |
| 220 | + | <arg name="reason" type="uint" enum="failure"/> | |
| 221 | + | </event> | |
| 222 | + | ||
| 223 | + | <event name="detached"> | |
| 224 | + | <description summary="the layer left the exclusion"> | |
| 225 | + | Sent once after attached. When the exclusion ended, the compositor | |
| 226 | + | also closed the layer surface (zwlr_layer_surface_v1.closed). | |
| 227 | + | </description> | |
| 228 | + | <arg name="reason" type="uint" enum="detach_reason"/> | |
| 229 | + | </event> | |
| 230 | + | </interface> | |
| 231 | + | </protocol> | |
| @@ -0,0 +1,231 @@ | |||||
| 1 | + | <?xml version="1.0" encoding="UTF-8"?> | |||
| 2 | + | <protocol name="neferwl_image_capture_v1"> | |||
| 3 | + | <copyright> | |||
| 4 | + | neferwl private protocol. No stability promise beyond the version of the | |||
| 5 | + | interface. | |||
| 6 | + | </copyright> | |||
| 7 | + | ||||
| 8 | + | <description summary="neferwl additions to ext-image-copy-capture"> | |||
| 9 | + | Optional additions to ext-image-capture-source-v1, ext-workspace-v1 and | |||
| 10 | + | ext-image-copy-capture-v1 for what those protocols do not cover: | |||
| 11 | + | workspace and output-region capture sources, the frame of a workspace, | |||
| 12 | + | and leaving a client's own surfaces (a recording HUD) out of its | |||
| 13 | + | captures. | |||
| 14 | + | ||||
| 15 | + | Capture always goes through ext-image-copy-capture. The compositor applies | |||
| 16 | + | the same rules to every capture, whatever its source: it may refuse a | |||
| 17 | + | client (ext_image_copy_capture_session_v1.stopped), and it draws its own | |||
| 18 | + | capture indicator on screen, never in a captured image, which no client | |||
| 19 | + | can turn off. | |||
| 20 | + | </description> | |||
| 21 | + | ||||
| 22 | + | <interface name="neferwl_image_capture_source_manager_v1" version="1"> | |||
| 23 | + | <description summary="workspace and output-region capture sources"> | |||
| 24 | + | Global. Sources created here are ext_image_capture_source_v1 objects, | |||
| 25 | + | used with ext_image_copy_capture_manager_v1.create_session like any | |||
| 26 | + | other source. | |||
| 27 | + | </description> | |||
| 28 | + | ||||
| 29 | + | <enum name="error"> | |||
| 30 | + | <entry name="invalid_region" value="0" summary="width or height is not positive"/> | |||
| 31 | + | </enum> | |||
| 32 | + | ||||
| 33 | + | <request name="destroy" type="destructor"> | |||
| 34 | + | <description summary="destroy the manager"> | |||
| 35 | + | Sources created through it stay valid. | |||
| 36 | + | </description> | |||
| 37 | + | </request> | |||
| 38 | + | ||||
| 39 | + | <request name="create_workspace_source"> | |||
| 40 | + | <description summary="capture a workspace"> | |||
| 41 | + | The source is the workspace frame, without bars, whether or not the | |||
| 42 | + | workspace is on screen: a workspace that is not on screen is rendered | |||
| 43 | + | for capture only. Its buffer size follows the workspace frame. A | |||
| 44 | + | session on it is stopped when the workspace is removed. A handle | |||
| 45 | + | already removed gives a source whose sessions stop at once. | |||
| 46 | + | </description> | |||
| 47 | + | <arg name="source" type="new_id" interface="ext_image_capture_source_v1"/> | |||
| 48 | + | <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/> | |||
| 49 | + | </request> | |||
| 50 | + | ||||
| 51 | + | <request name="create_output_region_source"> | |||
| 52 | + | <description summary="capture a rectangle of an output"> | |||
| 53 | + | The rectangle is in output-local logical pixels and is clipped to the | |||
| 54 | + | output. The buffer covers only that rectangle, in physical pixels. A | |||
| 55 | + | session on it is stopped when the output goes away or when nothing of | |||
| 56 | + | the rectangle is left on the output. | |||
| 57 | + | </description> | |||
| 58 | + | <arg name="source" type="new_id" interface="ext_image_capture_source_v1"/> | |||
| 59 | + | <arg name="output" type="object" interface="wl_output"/> | |||
| 60 | + | <arg name="x" type="int"/> | |||
| 61 | + | <arg name="y" type="int"/> | |||
| 62 | + | <arg name="width" type="int"/> | |||
| 63 | + | <arg name="height" type="int"/> | |||
| 64 | + | </request> | |||
| 65 | + | ||||
| 66 | + | <request name="get_workspace_frame"> | |||
| 67 | + | <description summary="learn the frame of a workspace"> | |||
| 68 | + | ext-workspace-v1 carries no geometry. The frame object reports the | |||
| 69 | + | rectangle that a workspace source of the same handle captures. For a | |||
| 70 | + | handle already removed, the object never sends frame. | |||
| 71 | + | </description> | |||
| 72 | + | <arg name="id" type="new_id" interface="neferwl_workspace_frame_v1"/> | |||
| 73 | + | <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/> | |||
| 74 | + | </request> | |||
| 75 | + | </interface> | |||
| 76 | + | ||||
| 77 | + | <interface name="neferwl_workspace_frame_v1" version="1"> | |||
| 78 | + | <description summary="the frame of one workspace"> | |||
| 79 | + | The frame is the area of its output that the workspace draws in, | |||
| 80 | + | without bars: the whole output, or the smaller size set for the | |||
| 81 | + | workspace. | |||
| 82 | + | </description> | |||
| 83 | + | ||||
| 84 | + | <request name="destroy" type="destructor"> | |||
| 85 | + | <description summary="destroy the frame object"/> | |||
| 86 | + | </request> | |||
| 87 | + | ||||
| 88 | + | <event name="frame"> | |||
| 89 | + | <description summary="the workspace frame"> | |||
| 90 | + | The frame in output-local logical pixels. Sent when the object is | |||
| 91 | + | created and again whenever the frame changes. Not sent once the | |||
| 92 | + | workspace is removed. | |||
| 93 | + | </description> | |||
| 94 | + | <arg name="x" type="int"/> | |||
| 95 | + | <arg name="y" type="int"/> | |||
| 96 | + | <arg name="width" type="int"/> | |||
| 97 | + | <arg name="height" type="int"/> | |||
| 98 | + | </event> | |||
| 99 | + | </interface> | |||
| 100 | + | ||||
| 101 | + | <interface name="neferwl_capture_exclusion_manager_v1" version="1"> | |||
| 102 | + | <description summary="leave a client's own surfaces out of its captures"> | |||
| 103 | + | Global. A recorder asks for an exclusion on its capture session and | |||
| 104 | + | gets a token; its HUD, usually on another connection of the same user, | |||
| 105 | + | attaches layer surfaces with that token. Frames of that session leave | |||
| 106 | + | the attached surfaces out. Every other capture shows them. | |||
| 107 | + | </description> | |||
| 108 | + | ||||
| 109 | + | <enum name="error"> | |||
| 110 | + | <entry name="surface_role" value="0" summary="the surface is not a wlr-layer-shell surface"/> | |||
| 111 | + | <entry name="invalid_layer" value="1" summary="the layer is neither top nor overlay"/> | |||
| 112 | + | <entry name="exclusive_keyboard" value="2" summary="the layer takes the keyboard exclusively"/> | |||
| 113 | + | <entry name="invalid_surface" value="3" summary="the surface is already mapped"/> | |||
| 114 | + | <entry name="already_excluded" value="4" summary="the capture session already has an exclusion"/> | |||
| 115 | + | </enum> | |||
| 116 | + | ||||
| 117 | + | <request name="destroy" type="destructor"> | |||
| 118 | + | <description summary="destroy the manager"> | |||
| 119 | + | Exclusions and attachments created through it stay alive. | |||
| 120 | + | </description> | |||
| 121 | + | </request> | |||
| 122 | + | ||||
| 123 | + | <request name="get_exclusion"> | |||
| 124 | + | <description summary="create the exclusion of a capture session"> | |||
| 125 | + | The exclusion answers with exactly one of token or failed. It ends | |||
| 126 | + | with the capture session. | |||
| 127 | + | </description> | |||
| 128 | + | <arg name="id" type="new_id" interface="neferwl_capture_exclusion_v1"/> | |||
| 129 | + | <arg name="session" type="object" interface="ext_image_copy_capture_session_v1"/> | |||
| 130 | + | </request> | |||
| 131 | + | ||||
| 132 | + | <request name="attach_surface"> | |||
| 133 | + | <description summary="attach a layer surface to an exclusion by token"> | |||
| 134 | + | Marks a layer surface of this client as excluded from the frames of | |||
| 135 | + | the session that owns the token. The client must have the same user | |||
| 136 | + | ID as the compositor and must also be allowed to capture: its | |||
| 137 | + | executable is on the capture allowlist (or the allowlist is off), it | |||
| 138 | + | is not sandboxed, and the session is not locked. The surface must already have the | |||
| 139 | + | wlr-layer-shell role, on the top or overlay layer, must not have | |||
| 140 | + | mapped yet and must not ask for exclusive keyboard interactivity. At | |||
| 141 | + | most 4 layers per exclusion. An attached layer never gets keyboard | |||
| 142 | + | interactivity; if it moves to another layer the compositor closes it | |||
| 143 | + | and sends detached. | |||
| 144 | + | ||||
| 145 | + | The attachment answers with exactly one of attached or failed. The | |||
| 146 | + | surface is excluded from this request on, before it can map; the | |||
| 147 | + | client must not show it before attached. | |||
| 148 | + | </description> | |||
| 149 | + | <arg name="id" type="new_id" interface="neferwl_capture_layer_v1"/> | |||
| 150 | + | <arg name="token" type="string"/> | |||
| 151 | + | <arg name="surface" type="object" interface="wl_surface"/> | |||
| 152 | + | </request> | |||
| 153 | + | </interface> | |||
| 154 | + | ||||
| 155 | + | <interface name="neferwl_capture_exclusion_v1" version="1"> | |||
| 156 | + | <description summary="the exclusion of one capture session"> | |||
| 157 | + | Destroying it detaches every attached layer. | |||
| 158 | + | </description> | |||
| 159 | + | ||||
| 160 | + | <enum name="failure"> | |||
| 161 | + | <entry name="busy" value="0" summary="another exclusion is live in the compositor"/> | |||
| 162 | + | <entry name="session_stopped" value="1" summary="the capture session is stopped"/> | |||
| 163 | + | </enum> | |||
| 164 | + | ||||
| 165 | + | <request name="destroy" type="destructor"> | |||
| 166 | + | <description summary="destroy the exclusion"/> | |||
| 167 | + | </request> | |||
| 168 | + | ||||
| 169 | + | <event name="token"> | |||
| 170 | + | <description summary="the attachment token"> | |||
| 171 | + | 64 lowercase hexadecimal characters (32 random bytes). A secret for | |||
| 172 | + | the owner and its HUD, never logged, valid until the exclusion ends. | |||
| 173 | + | </description> | |||
| 174 | + | <arg name="token" type="string"/> | |||
| 175 | + | </event> | |||
| 176 | + | ||||
| 177 | + | <event name="failed"> | |||
| 178 | + | <description summary="no exclusion"> | |||
| 179 | + | Final. Destroy the object. | |||
| 180 | + | </description> | |||
| 181 | + | <arg name="reason" type="uint" enum="failure"/> | |||
| 182 | + | </event> | |||
| 183 | + | </interface> | |||
| 184 | + | ||||
| 185 | + | <interface name="neferwl_capture_layer_v1" version="1"> | |||
| 186 | + | <description summary="a layer surface attached to an exclusion"> | |||
| 187 | + | Created by attach_surface. Exactly one of attached or failed comes | |||
| 188 | + | first; detached comes at most once, after attached. | |||
| 189 | + | </description> | |||
| 190 | + | ||||
| 191 | + | <enum name="failure"> | |||
| 192 | + | <entry name="unknown_token" value="0" summary="no live exclusion has this token"/> | |||
| 193 | + | <entry name="unauthorized" value="1" summary="peer credentials differ from the compositor user's or are unreadable, or the client may not capture (not on the allowlist, sandboxed, or session locked)"/> | |||
| 194 | + | <entry name="too_many_layers" value="2" summary="the exclusion already has 4 attached layers"/> | |||
| 195 | + | <entry name="already_attached" value="3" summary="the surface is already attached"/> | |||
| 196 | + | <entry name="exclusion_ended" value="4" summary="the exclusion ended before the compositor confirmed the layer"/> | |||
| 197 | + | <entry name="layer_destroyed" value="5" summary="the layer role was destroyed before the compositor confirmed it"/> | |||
| 198 | + | </enum> | |||
| 199 | + | ||||
| 200 | + | <enum name="detach_reason"> | |||
| 201 | + | <entry name="exclusion_ended" value="0" summary="the exclusion ended; the compositor closed the layer surface"/> | |||
| 202 | + | <entry name="layer_destroyed" value="1" summary="the layer role was destroyed"/> | |||
| 203 | + | </enum> | |||
| 204 | + | ||||
| 205 | + | <request name="destroy" type="destructor"> | |||
| 206 | + | <description summary="destroy the attachment object"> | |||
| 207 | + | The surface stays excluded until its layer role is destroyed or the | |||
| 208 | + | exclusion ends. | |||
| 209 | + | </description> | |||
| 210 | + | </request> | |||
| 211 | + | ||||
| 212 | + | <event name="attached"> | |||
| 213 | + | <description summary="the layer is excluded"> | |||
| 214 | + | The compositor confirmed the attachment. Safe to show. | |||
| 215 | + | </description> | |||
| 216 | + | </event> | |||
| 217 | + | ||||
| 218 | + | <event name="failed"> | |||
| 219 | + | <description summary="the attachment was refused"/> | |||
| 220 | + | <arg name="reason" type="uint" enum="failure"/> | |||
| 221 | + | </event> | |||
| 222 | + | ||||
| 223 | + | <event name="detached"> | |||
| 224 | + | <description summary="the layer left the exclusion"> | |||
| 225 | + | Sent once after attached. When the exclusion ended, the compositor | |||
| 226 | + | also closed the layer surface (zwlr_layer_surface_v1.closed). | |||
| 227 | + | </description> | |||
| 228 | + | <arg name="reason" type="uint" enum="detach_reason"/> | |||
| 229 | + | </event> | |||
| 230 | + | </interface> | |||
| 231 | + | </protocol> | |||
A internal/adapters/wayland/testserver/neferwl/neferwl.go +403 -0
| @@ -0,0 +1,403 @@ | |||
| 1 | + | // Code generated by wlgen; DO NOT EDIT. | |
| 2 | + | package neferwl | |
| 3 | + | ||
| 4 | + | import ( | |
| 5 | + | "fmt" | |
| 6 | + | extimagecapturesource "github.com/bnema/purego-libwayland/protocol/extimagecapturesource" | |
| 7 | + | extimagecopycapture "github.com/bnema/purego-libwayland/protocol/extimagecopycapture" | |
| 8 | + | extworkspace "github.com/bnema/purego-libwayland/protocol/extworkspace" | |
| 9 | + | wayland "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 10 | + | "github.com/bnema/purego-libwayland/server" | |
| 11 | + | "runtime" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | var NeferwlImageCaptureSourceManagerV1Interface *server.Interface | |
| 15 | + | var NeferwlWorkspaceFrameV1Interface *server.Interface | |
| 16 | + | var NeferwlCaptureExclusionManagerV1Interface *server.Interface | |
| 17 | + | var NeferwlCaptureExclusionV1Interface *server.Interface | |
| 18 | + | var NeferwlCaptureLayerV1Interface *server.Interface | |
| 19 | + | ||
| 20 | + | func init() { | |
| 21 | + | NeferwlImageCaptureSourceManagerV1Interface = &server.Interface{Name: "neferwl_image_capture_source_manager_v1", Version: 1} | |
| 22 | + | NeferwlWorkspaceFrameV1Interface = &server.Interface{Name: "neferwl_workspace_frame_v1", Version: 1} | |
| 23 | + | NeferwlCaptureExclusionManagerV1Interface = &server.Interface{Name: "neferwl_capture_exclusion_manager_v1", Version: 1} | |
| 24 | + | NeferwlCaptureExclusionV1Interface = &server.Interface{Name: "neferwl_capture_exclusion_v1", Version: 1} | |
| 25 | + | NeferwlCaptureLayerV1Interface = &server.Interface{Name: "neferwl_capture_layer_v1", Version: 1} | |
| 26 | + | NeferwlImageCaptureSourceManagerV1Interface.Requests = []server.Message{ | |
| 27 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |
| 28 | + | {Name: "create_workspace_source", Signature: "no", Types: []*server.Interface{extimagecapturesource.ExtImageCaptureSourceV1Interface, extworkspace.ExtWorkspaceHandleV1Interface}}, | |
| 29 | + | {Name: "create_output_region_source", Signature: "noiiii", Types: []*server.Interface{extimagecapturesource.ExtImageCaptureSourceV1Interface, wayland.OutputInterface, nil, nil, nil, nil}}, | |
| 30 | + | {Name: "get_workspace_frame", Signature: "no", Types: []*server.Interface{NeferwlWorkspaceFrameV1Interface, extworkspace.ExtWorkspaceHandleV1Interface}}, | |
| 31 | + | } | |
| 32 | + | NeferwlImageCaptureSourceManagerV1Interface.Events = []server.Message{} | |
| 33 | + | NeferwlWorkspaceFrameV1Interface.Requests = []server.Message{ | |
| 34 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |
| 35 | + | } | |
| 36 | + | NeferwlWorkspaceFrameV1Interface.Events = []server.Message{ | |
| 37 | + | {Name: "frame", Signature: "iiii", Types: []*server.Interface{nil, nil, nil, nil}}, | |
| 38 | + | } | |
| 39 | + | NeferwlCaptureExclusionManagerV1Interface.Requests = []server.Message{ | |
| 40 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |
| 41 | + | {Name: "get_exclusion", Signature: "no", Types: []*server.Interface{NeferwlCaptureExclusionV1Interface, extimagecopycapture.ExtImageCopyCaptureSessionV1Interface}}, | |
| 42 | + | {Name: "attach_surface", Signature: "nso", Types: []*server.Interface{NeferwlCaptureLayerV1Interface, nil, wayland.SurfaceInterface}}, | |
| 43 | + | } | |
| 44 | + | NeferwlCaptureExclusionManagerV1Interface.Events = []server.Message{} | |
| 45 | + | NeferwlCaptureExclusionV1Interface.Requests = []server.Message{ | |
| 46 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |
| 47 | + | } | |
| 48 | + | NeferwlCaptureExclusionV1Interface.Events = []server.Message{ | |
| 49 | + | {Name: "token", Signature: "s", Types: []*server.Interface{nil}}, | |
| 50 | + | {Name: "failed", Signature: "u", Types: []*server.Interface{nil}}, | |
| 51 | + | } | |
| 52 | + | NeferwlCaptureLayerV1Interface.Requests = []server.Message{ | |
| 53 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |
| 54 | + | } | |
| 55 | + | NeferwlCaptureLayerV1Interface.Events = []server.Message{ | |
| 56 | + | {Name: "attached", Signature: "", Types: []*server.Interface{}}, | |
| 57 | + | {Name: "failed", Signature: "u", Types: []*server.Interface{nil}}, | |
| 58 | + | {Name: "detached", Signature: "u", Types: []*server.Interface{nil}}, | |
| 59 | + | } | |
| 60 | + | if err := server.NewInterfaces(NeferwlImageCaptureSourceManagerV1Interface, NeferwlWorkspaceFrameV1Interface, NeferwlCaptureExclusionManagerV1Interface, NeferwlCaptureExclusionV1Interface, NeferwlCaptureLayerV1Interface); err != nil { | |
| 61 | + | panic(err) | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | ||
| 65 | + | // NeferwlImageCaptureSourceManagerV1 workspace and output-region capture sources. | |
| 66 | + | type NeferwlImageCaptureSourceManagerV1 struct{ *server.Resource } | |
| 67 | + | ||
| 68 | + | func WrapNeferwlImageCaptureSourceManagerV1(r *server.Resource) *NeferwlImageCaptureSourceManagerV1 { | |
| 69 | + | if r == nil { | |
| 70 | + | return nil | |
| 71 | + | } | |
| 72 | + | return &NeferwlImageCaptureSourceManagerV1{r} | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | const NeferwlImageCaptureSourceManagerV1RequestDestroy uint32 = 0 | |
| 76 | + | const NeferwlImageCaptureSourceManagerV1RequestCreateWorkspaceSource uint32 = 1 | |
| 77 | + | const NeferwlImageCaptureSourceManagerV1RequestCreateOutputRegionSource uint32 = 2 | |
| 78 | + | const NeferwlImageCaptureSourceManagerV1RequestGetWorkspaceFrame uint32 = 3 | |
| 79 | + | ||
| 80 | + | // NeferwlImageCaptureSourceManagerV1Error . | |
| 81 | + | type NeferwlImageCaptureSourceManagerV1Error uint32 | |
| 82 | + | ||
| 83 | + | const ( | |
| 84 | + | NeferwlImageCaptureSourceManagerV1ErrorInvalidRegion NeferwlImageCaptureSourceManagerV1Error = 0 | |
| 85 | + | ) | |
| 86 | + | ||
| 87 | + | // NeferwlImageCaptureSourceManagerV1Handler handles neferwl_image_capture_source_manager_v1 requests. | |
| 88 | + | type NeferwlImageCaptureSourceManagerV1Handler interface { | |
| 89 | + | Destroy(self *NeferwlImageCaptureSourceManagerV1) | |
| 90 | + | CreateWorkspaceSource(self *NeferwlImageCaptureSourceManagerV1, source uint32, workspace *extworkspace.ExtWorkspaceHandleV1) | |
| 91 | + | CreateOutputRegionSource(self *NeferwlImageCaptureSourceManagerV1, source uint32, output *wayland.Output, x int32, y int32, width int32, height int32) | |
| 92 | + | GetWorkspaceFrame(self *NeferwlImageCaptureSourceManagerV1, id uint32, workspace *extworkspace.ExtWorkspaceHandleV1) | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | func NewNeferwlImageCaptureSourceManagerV1(c server.Client, version int32, id uint32, h NeferwlImageCaptureSourceManagerV1Handler) (*NeferwlImageCaptureSourceManagerV1, error) { | |
| 96 | + | if h == nil { | |
| 97 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_image_capture_source_manager_v1") | |
| 98 | + | } | |
| 99 | + | var self *NeferwlImageCaptureSourceManagerV1 | |
| 100 | + | r, err := c.CreateResource(NeferwlImageCaptureSourceManagerV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |
| 101 | + | switch opcode { | |
| 102 | + | case 0: | |
| 103 | + | h.Destroy(self) | |
| 104 | + | self.Destroy() | |
| 105 | + | case 1: | |
| 106 | + | h.CreateWorkspaceSource(self, args[0].NewID(), extworkspace.WrapExtWorkspaceHandleV1(args[1].Resource())) | |
| 107 | + | case 2: | |
| 108 | + | h.CreateOutputRegionSource(self, args[0].NewID(), wayland.WrapOutput(args[1].Resource()), args[2].Int(), args[3].Int(), args[4].Int(), args[5].Int()) | |
| 109 | + | case 3: | |
| 110 | + | h.GetWorkspaceFrame(self, args[0].NewID(), extworkspace.WrapExtWorkspaceHandleV1(args[1].Resource())) | |
| 111 | + | } | |
| 112 | + | }) | |
| 113 | + | if err != nil { | |
| 114 | + | return nil, err | |
| 115 | + | } | |
| 116 | + | self = WrapNeferwlImageCaptureSourceManagerV1(r) | |
| 117 | + | r.Data = self | |
| 118 | + | return self, nil | |
| 119 | + | } | |
| 120 | + | func NewNeferwlImageCaptureSourceManagerV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |
| 121 | + | return d.CreateGlobal(NeferwlImageCaptureSourceManagerV1Interface, version, bind) | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | // NeferwlWorkspaceFrameV1 the frame of one workspace. | |
| 125 | + | type NeferwlWorkspaceFrameV1 struct{ *server.Resource } | |
| 126 | + | ||
| 127 | + | func WrapNeferwlWorkspaceFrameV1(r *server.Resource) *NeferwlWorkspaceFrameV1 { | |
| 128 | + | if r == nil { | |
| 129 | + | return nil | |
| 130 | + | } | |
| 131 | + | return &NeferwlWorkspaceFrameV1{r} | |
| 132 | + | } | |
| 133 | + | ||
| 134 | + | const NeferwlWorkspaceFrameV1RequestDestroy uint32 = 0 | |
| 135 | + | const NeferwlWorkspaceFrameV1EventFrame uint32 = 0 | |
| 136 | + | ||
| 137 | + | // NeferwlWorkspaceFrameV1Handler handles neferwl_workspace_frame_v1 requests. | |
| 138 | + | type NeferwlWorkspaceFrameV1Handler interface { | |
| 139 | + | Destroy(self *NeferwlWorkspaceFrameV1) | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | func NewNeferwlWorkspaceFrameV1(c server.Client, version int32, id uint32, h NeferwlWorkspaceFrameV1Handler) (*NeferwlWorkspaceFrameV1, error) { | |
| 143 | + | if h == nil { | |
| 144 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_workspace_frame_v1") | |
| 145 | + | } | |
| 146 | + | var self *NeferwlWorkspaceFrameV1 | |
| 147 | + | r, err := c.CreateResource(NeferwlWorkspaceFrameV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |
| 148 | + | switch opcode { | |
| 149 | + | case 0: | |
| 150 | + | h.Destroy(self) | |
| 151 | + | self.Destroy() | |
| 152 | + | } | |
| 153 | + | }) | |
| 154 | + | if err != nil { | |
| 155 | + | return nil, err | |
| 156 | + | } | |
| 157 | + | self = WrapNeferwlWorkspaceFrameV1(r) | |
| 158 | + | r.Data = self | |
| 159 | + | return self, nil | |
| 160 | + | } | |
| 161 | + | ||
| 162 | + | // SendFrame the workspace frame. | |
| 163 | + | func (self *NeferwlWorkspaceFrameV1) SendFrame(x int32, y int32, width int32, height int32) { | |
| 164 | + | self.PostEvent(NeferwlWorkspaceFrameV1EventFrame, server.Int(x), server.Int(y), server.Int(width), server.Int(height)) | |
| 165 | + | } | |
| 166 | + | func NewNeferwlWorkspaceFrameV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |
| 167 | + | return d.CreateGlobal(NeferwlWorkspaceFrameV1Interface, version, bind) | |
| 168 | + | } | |
| 169 | + | ||
| 170 | + | // NeferwlCaptureExclusionManagerV1 leave a client's own surfaces out of its captures. | |
| 171 | + | type NeferwlCaptureExclusionManagerV1 struct{ *server.Resource } | |
| 172 | + | ||
| 173 | + | func WrapNeferwlCaptureExclusionManagerV1(r *server.Resource) *NeferwlCaptureExclusionManagerV1 { | |
| 174 | + | if r == nil { | |
| 175 | + | return nil | |
| 176 | + | } | |
| 177 | + | return &NeferwlCaptureExclusionManagerV1{r} | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | const NeferwlCaptureExclusionManagerV1RequestDestroy uint32 = 0 | |
| 181 | + | const NeferwlCaptureExclusionManagerV1RequestGetExclusion uint32 = 1 | |
| 182 | + | const NeferwlCaptureExclusionManagerV1RequestAttachSurface uint32 = 2 | |
| 183 | + | ||
| 184 | + | // NeferwlCaptureExclusionManagerV1Error . | |
| 185 | + | type NeferwlCaptureExclusionManagerV1Error uint32 | |
| 186 | + | ||
| 187 | + | const ( | |
| 188 | + | NeferwlCaptureExclusionManagerV1ErrorSurfaceRole NeferwlCaptureExclusionManagerV1Error = 0 | |
| 189 | + | NeferwlCaptureExclusionManagerV1ErrorInvalidLayer NeferwlCaptureExclusionManagerV1Error = 1 | |
| 190 | + | NeferwlCaptureExclusionManagerV1ErrorExclusiveKeyboard NeferwlCaptureExclusionManagerV1Error = 2 | |
| 191 | + | NeferwlCaptureExclusionManagerV1ErrorInvalidSurface NeferwlCaptureExclusionManagerV1Error = 3 | |
| 192 | + | NeferwlCaptureExclusionManagerV1ErrorAlreadyExcluded NeferwlCaptureExclusionManagerV1Error = 4 | |
| 193 | + | ) | |
| 194 | + | ||
| 195 | + | // NeferwlCaptureExclusionManagerV1Handler handles neferwl_capture_exclusion_manager_v1 requests. | |
| 196 | + | type NeferwlCaptureExclusionManagerV1Handler interface { | |
| 197 | + | Destroy(self *NeferwlCaptureExclusionManagerV1) | |
| 198 | + | GetExclusion(self *NeferwlCaptureExclusionManagerV1, id uint32, session *extimagecopycapture.ExtImageCopyCaptureSessionV1) | |
| 199 | + | AttachSurface(self *NeferwlCaptureExclusionManagerV1, id uint32, token string, surface *wayland.Surface) | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | func NewNeferwlCaptureExclusionManagerV1(c server.Client, version int32, id uint32, h NeferwlCaptureExclusionManagerV1Handler) (*NeferwlCaptureExclusionManagerV1, error) { | |
| 203 | + | if h == nil { | |
| 204 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_capture_exclusion_manager_v1") | |
| 205 | + | } | |
| 206 | + | var self *NeferwlCaptureExclusionManagerV1 | |
| 207 | + | r, err := c.CreateResource(NeferwlCaptureExclusionManagerV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |
| 208 | + | switch opcode { | |
| 209 | + | case 0: | |
| 210 | + | h.Destroy(self) | |
| 211 | + | self.Destroy() | |
| 212 | + | case 1: | |
| 213 | + | h.GetExclusion(self, args[0].NewID(), extimagecopycapture.WrapExtImageCopyCaptureSessionV1(args[1].Resource())) | |
| 214 | + | case 2: | |
| 215 | + | h.AttachSurface(self, args[0].NewID(), args[1].String(), wayland.WrapSurface(args[2].Resource())) | |
| 216 | + | } | |
| 217 | + | }) | |
| 218 | + | if err != nil { | |
| 219 | + | return nil, err | |
| 220 | + | } | |
| 221 | + | self = WrapNeferwlCaptureExclusionManagerV1(r) | |
| 222 | + | r.Data = self | |
| 223 | + | return self, nil | |
| 224 | + | } | |
| 225 | + | func NewNeferwlCaptureExclusionManagerV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |
| 226 | + | return d.CreateGlobal(NeferwlCaptureExclusionManagerV1Interface, version, bind) | |
| 227 | + | } | |
| 228 | + | ||
| 229 | + | // NeferwlCaptureExclusionV1 the exclusion of one capture session. | |
| 230 | + | type NeferwlCaptureExclusionV1 struct{ *server.Resource } | |
| 231 | + | ||
| 232 | + | func WrapNeferwlCaptureExclusionV1(r *server.Resource) *NeferwlCaptureExclusionV1 { | |
| 233 | + | if r == nil { | |
| 234 | + | return nil | |
| 235 | + | } | |
| 236 | + | return &NeferwlCaptureExclusionV1{r} | |
| 237 | + | } | |
| 238 | + | ||
| 239 | + | const NeferwlCaptureExclusionV1RequestDestroy uint32 = 0 | |
| 240 | + | const NeferwlCaptureExclusionV1EventToken uint32 = 0 | |
| 241 | + | const NeferwlCaptureExclusionV1EventFailed uint32 = 1 | |
| 242 | + | ||
| 243 | + | // NeferwlCaptureExclusionV1Failure . | |
| 244 | + | type NeferwlCaptureExclusionV1Failure uint32 | |
| 245 | + | ||
| 246 | + | const ( | |
| 247 | + | NeferwlCaptureExclusionV1FailureBusy NeferwlCaptureExclusionV1Failure = 0 | |
| 248 | + | NeferwlCaptureExclusionV1FailureSessionStopped NeferwlCaptureExclusionV1Failure = 1 | |
| 249 | + | ) | |
| 250 | + | ||
| 251 | + | // NeferwlCaptureExclusionV1Handler handles neferwl_capture_exclusion_v1 requests. | |
| 252 | + | type NeferwlCaptureExclusionV1Handler interface { | |
| 253 | + | Destroy(self *NeferwlCaptureExclusionV1) | |
| 254 | + | } | |
| 255 | + | ||
| 256 | + | func NewNeferwlCaptureExclusionV1(c server.Client, version int32, id uint32, h NeferwlCaptureExclusionV1Handler) (*NeferwlCaptureExclusionV1, error) { | |
| 257 | + | if h == nil { | |
| 258 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_capture_exclusion_v1") | |
| 259 | + | } | |
| 260 | + | var self *NeferwlCaptureExclusionV1 | |
| 261 | + | r, err := c.CreateResource(NeferwlCaptureExclusionV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |
| 262 | + | switch opcode { | |
| 263 | + | case 0: | |
| 264 | + | h.Destroy(self) | |
| 265 | + | self.Destroy() | |
| 266 | + | } | |
| 267 | + | }) | |
| 268 | + | if err != nil { | |
| 269 | + | return nil, err | |
| 270 | + | } | |
| 271 | + | self = WrapNeferwlCaptureExclusionV1(r) | |
| 272 | + | r.Data = self | |
| 273 | + | return self, nil | |
| 274 | + | } | |
| 275 | + | ||
| 276 | + | // SendToken the attachment token. | |
| 277 | + | func (self *NeferwlCaptureExclusionV1) SendToken(token string) { | |
| 278 | + | var p runtime.Pinner | |
| 279 | + | defer p.Unpin() | |
| 280 | + | self.PostEvent(NeferwlCaptureExclusionV1EventToken, server.String(&p, token, false)) | |
| 281 | + | } | |
| 282 | + | ||
| 283 | + | // SendFailed no exclusion. | |
| 284 | + | func (self *NeferwlCaptureExclusionV1) SendFailed(reason uint32) { | |
| 285 | + | self.PostEvent(NeferwlCaptureExclusionV1EventFailed, server.Uint(reason)) | |
| 286 | + | } | |
| 287 | + | func NewNeferwlCaptureExclusionV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |
| 288 | + | return d.CreateGlobal(NeferwlCaptureExclusionV1Interface, version, bind) | |
| 289 | + | } | |
| 290 | + | ||
| 291 | + | // NeferwlCaptureLayerV1 a layer surface attached to an exclusion. | |
| 292 | + | type NeferwlCaptureLayerV1 struct{ *server.Resource } | |
| 293 | + | ||
| 294 | + | func WrapNeferwlCaptureLayerV1(r *server.Resource) *NeferwlCaptureLayerV1 { | |
| 295 | + | if r == nil { | |
| 296 | + | return nil | |
| 297 | + | } | |
| 298 | + | return &NeferwlCaptureLayerV1{r} | |
| 299 | + | } | |
| 300 | + | ||
| 301 | + | const NeferwlCaptureLayerV1RequestDestroy uint32 = 0 | |
| 302 | + | const NeferwlCaptureLayerV1EventAttached uint32 = 0 | |
| 303 | + | const NeferwlCaptureLayerV1EventFailed uint32 = 1 | |
| 304 | + | const NeferwlCaptureLayerV1EventDetached uint32 = 2 | |
| 305 | + | ||
| 306 | + | // NeferwlCaptureLayerV1Failure . | |
| 307 | + | type NeferwlCaptureLayerV1Failure uint32 | |
| 308 | + | ||
| 309 | + | const ( | |
| 310 | + | NeferwlCaptureLayerV1FailureUnknownToken NeferwlCaptureLayerV1Failure = 0 | |
| 311 | + | NeferwlCaptureLayerV1FailureUnauthorized NeferwlCaptureLayerV1Failure = 1 | |
| 312 | + | NeferwlCaptureLayerV1FailureTooManyLayers NeferwlCaptureLayerV1Failure = 2 | |
| 313 | + | NeferwlCaptureLayerV1FailureAlreadyAttached NeferwlCaptureLayerV1Failure = 3 | |
| 314 | + | NeferwlCaptureLayerV1FailureExclusionEnded NeferwlCaptureLayerV1Failure = 4 | |
| 315 | + | NeferwlCaptureLayerV1FailureLayerDestroyed NeferwlCaptureLayerV1Failure = 5 | |
| 316 | + | ) | |
| 317 | + | ||
| 318 | + | // NeferwlCaptureLayerV1DetachReason . | |
| 319 | + | type NeferwlCaptureLayerV1DetachReason uint32 | |
| 320 | + | ||
| 321 | + | const ( | |
| 322 | + | NeferwlCaptureLayerV1DetachReasonExclusionEnded NeferwlCaptureLayerV1DetachReason = 0 | |
| 323 | + | NeferwlCaptureLayerV1DetachReasonLayerDestroyed NeferwlCaptureLayerV1DetachReason = 1 | |
| 324 | + | ) | |
| 325 | + | ||
| 326 | + | // NeferwlCaptureLayerV1Handler handles neferwl_capture_layer_v1 requests. | |
| 327 | + | type NeferwlCaptureLayerV1Handler interface { | |
| 328 | + | Destroy(self *NeferwlCaptureLayerV1) | |
| 329 | + | } | |
| 330 | + | ||
| 331 | + | func NewNeferwlCaptureLayerV1(c server.Client, version int32, id uint32, h NeferwlCaptureLayerV1Handler) (*NeferwlCaptureLayerV1, error) { | |
| 332 | + | if h == nil { | |
| 333 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_capture_layer_v1") | |
| 334 | + | } | |
| 335 | + | var self *NeferwlCaptureLayerV1 | |
| 336 | + | r, err := c.CreateResource(NeferwlCaptureLayerV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |
| 337 | + | switch opcode { | |
| 338 | + | case 0: | |
| 339 | + | h.Destroy(self) | |
| 340 | + | self.Destroy() | |
| 341 | + | } | |
| 342 | + | }) | |
| 343 | + | if err != nil { | |
| 344 | + | return nil, err | |
| 345 | + | } | |
| 346 | + | self = WrapNeferwlCaptureLayerV1(r) | |
| 347 | + | r.Data = self | |
| 348 | + | return self, nil | |
| 349 | + | } | |
| 350 | + | ||
| 351 | + | // SendAttached the layer is excluded. | |
| 352 | + | func (self *NeferwlCaptureLayerV1) SendAttached() { | |
| 353 | + | self.PostEvent(NeferwlCaptureLayerV1EventAttached) | |
| 354 | + | } | |
| 355 | + | ||
| 356 | + | // SendFailed the attachment was refused. | |
| 357 | + | func (self *NeferwlCaptureLayerV1) SendFailed(reason uint32) { | |
| 358 | + | self.PostEvent(NeferwlCaptureLayerV1EventFailed, server.Uint(reason)) | |
| 359 | + | } | |
| 360 | + | ||
| 361 | + | // SendDetached the layer left the exclusion. | |
| 362 | + | func (self *NeferwlCaptureLayerV1) SendDetached(reason uint32) { | |
| 363 | + | self.PostEvent(NeferwlCaptureLayerV1EventDetached, server.Uint(reason)) | |
| 364 | + | } | |
| 365 | + | func NewNeferwlCaptureLayerV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |
| 366 | + | return d.CreateGlobal(NeferwlCaptureLayerV1Interface, version, bind) | |
| 367 | + | } | |
| 368 | + | func resourceOf(v interface{ GetResource() *server.Resource }) *server.Resource { | |
| 369 | + | if v == nil { | |
| 370 | + | return nil | |
| 371 | + | } | |
| 372 | + | return v.GetResource() | |
| 373 | + | } | |
| 374 | + | func (self *NeferwlImageCaptureSourceManagerV1) GetResource() *server.Resource { | |
| 375 | + | if self == nil { | |
| 376 | + | return nil | |
| 377 | + | } | |
| 378 | + | return self.Resource | |
| 379 | + | } | |
| 380 | + | func (self *NeferwlWorkspaceFrameV1) GetResource() *server.Resource { | |
| 381 | + | if self == nil { | |
| 382 | + | return nil | |
| 383 | + | } | |
| 384 | + | return self.Resource | |
| 385 | + | } | |
| 386 | + | func (self *NeferwlCaptureExclusionManagerV1) GetResource() *server.Resource { | |
| 387 | + | if self == nil { | |
| 388 | + | return nil | |
| 389 | + | } | |
| 390 | + | return self.Resource | |
| 391 | + | } | |
| 392 | + | func (self *NeferwlCaptureExclusionV1) GetResource() *server.Resource { | |
| 393 | + | if self == nil { | |
| 394 | + | return nil | |
| 395 | + | } | |
| 396 | + | return self.Resource | |
| 397 | + | } | |
| 398 | + | func (self *NeferwlCaptureLayerV1) GetResource() *server.Resource { | |
| 399 | + | if self == nil { | |
| 400 | + | return nil | |
| 401 | + | } | |
| 402 | + | return self.Resource | |
| 403 | + | } | |
| @@ -0,0 +1,403 @@ | |||||
| 1 | + | // Code generated by wlgen; DO NOT EDIT. | |||
| 2 | + | package neferwl | |||
| 3 | + | ||||
| 4 | + | import ( | |||
| 5 | + | "fmt" | |||
| 6 | + | extimagecapturesource "github.com/bnema/purego-libwayland/protocol/extimagecapturesource" | |||
| 7 | + | extimagecopycapture "github.com/bnema/purego-libwayland/protocol/extimagecopycapture" | |||
| 8 | + | extworkspace "github.com/bnema/purego-libwayland/protocol/extworkspace" | |||
| 9 | + | wayland "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 10 | + | "github.com/bnema/purego-libwayland/server" | |||
| 11 | + | "runtime" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | var NeferwlImageCaptureSourceManagerV1Interface *server.Interface | |||
| 15 | + | var NeferwlWorkspaceFrameV1Interface *server.Interface | |||
| 16 | + | var NeferwlCaptureExclusionManagerV1Interface *server.Interface | |||
| 17 | + | var NeferwlCaptureExclusionV1Interface *server.Interface | |||
| 18 | + | var NeferwlCaptureLayerV1Interface *server.Interface | |||
| 19 | + | ||||
| 20 | + | func init() { | |||
| 21 | + | NeferwlImageCaptureSourceManagerV1Interface = &server.Interface{Name: "neferwl_image_capture_source_manager_v1", Version: 1} | |||
| 22 | + | NeferwlWorkspaceFrameV1Interface = &server.Interface{Name: "neferwl_workspace_frame_v1", Version: 1} | |||
| 23 | + | NeferwlCaptureExclusionManagerV1Interface = &server.Interface{Name: "neferwl_capture_exclusion_manager_v1", Version: 1} | |||
| 24 | + | NeferwlCaptureExclusionV1Interface = &server.Interface{Name: "neferwl_capture_exclusion_v1", Version: 1} | |||
| 25 | + | NeferwlCaptureLayerV1Interface = &server.Interface{Name: "neferwl_capture_layer_v1", Version: 1} | |||
| 26 | + | NeferwlImageCaptureSourceManagerV1Interface.Requests = []server.Message{ | |||
| 27 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |||
| 28 | + | {Name: "create_workspace_source", Signature: "no", Types: []*server.Interface{extimagecapturesource.ExtImageCaptureSourceV1Interface, extworkspace.ExtWorkspaceHandleV1Interface}}, | |||
| 29 | + | {Name: "create_output_region_source", Signature: "noiiii", Types: []*server.Interface{extimagecapturesource.ExtImageCaptureSourceV1Interface, wayland.OutputInterface, nil, nil, nil, nil}}, | |||
| 30 | + | {Name: "get_workspace_frame", Signature: "no", Types: []*server.Interface{NeferwlWorkspaceFrameV1Interface, extworkspace.ExtWorkspaceHandleV1Interface}}, | |||
| 31 | + | } | |||
| 32 | + | NeferwlImageCaptureSourceManagerV1Interface.Events = []server.Message{} | |||
| 33 | + | NeferwlWorkspaceFrameV1Interface.Requests = []server.Message{ | |||
| 34 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |||
| 35 | + | } | |||
| 36 | + | NeferwlWorkspaceFrameV1Interface.Events = []server.Message{ | |||
| 37 | + | {Name: "frame", Signature: "iiii", Types: []*server.Interface{nil, nil, nil, nil}}, | |||
| 38 | + | } | |||
| 39 | + | NeferwlCaptureExclusionManagerV1Interface.Requests = []server.Message{ | |||
| 40 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |||
| 41 | + | {Name: "get_exclusion", Signature: "no", Types: []*server.Interface{NeferwlCaptureExclusionV1Interface, extimagecopycapture.ExtImageCopyCaptureSessionV1Interface}}, | |||
| 42 | + | {Name: "attach_surface", Signature: "nso", Types: []*server.Interface{NeferwlCaptureLayerV1Interface, nil, wayland.SurfaceInterface}}, | |||
| 43 | + | } | |||
| 44 | + | NeferwlCaptureExclusionManagerV1Interface.Events = []server.Message{} | |||
| 45 | + | NeferwlCaptureExclusionV1Interface.Requests = []server.Message{ | |||
| 46 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |||
| 47 | + | } | |||
| 48 | + | NeferwlCaptureExclusionV1Interface.Events = []server.Message{ | |||
| 49 | + | {Name: "token", Signature: "s", Types: []*server.Interface{nil}}, | |||
| 50 | + | {Name: "failed", Signature: "u", Types: []*server.Interface{nil}}, | |||
| 51 | + | } | |||
| 52 | + | NeferwlCaptureLayerV1Interface.Requests = []server.Message{ | |||
| 53 | + | {Name: "destroy", Signature: "", Types: []*server.Interface{}}, | |||
| 54 | + | } | |||
| 55 | + | NeferwlCaptureLayerV1Interface.Events = []server.Message{ | |||
| 56 | + | {Name: "attached", Signature: "", Types: []*server.Interface{}}, | |||
| 57 | + | {Name: "failed", Signature: "u", Types: []*server.Interface{nil}}, | |||
| 58 | + | {Name: "detached", Signature: "u", Types: []*server.Interface{nil}}, | |||
| 59 | + | } | |||
| 60 | + | if err := server.NewInterfaces(NeferwlImageCaptureSourceManagerV1Interface, NeferwlWorkspaceFrameV1Interface, NeferwlCaptureExclusionManagerV1Interface, NeferwlCaptureExclusionV1Interface, NeferwlCaptureLayerV1Interface); err != nil { | |||
| 61 | + | panic(err) | |||
| 62 | + | } | |||
| 63 | + | } | |||
| 64 | + | ||||
| 65 | + | // NeferwlImageCaptureSourceManagerV1 workspace and output-region capture sources. | |||
| 66 | + | type NeferwlImageCaptureSourceManagerV1 struct{ *server.Resource } | |||
| 67 | + | ||||
| 68 | + | func WrapNeferwlImageCaptureSourceManagerV1(r *server.Resource) *NeferwlImageCaptureSourceManagerV1 { | |||
| 69 | + | if r == nil { | |||
| 70 | + | return nil | |||
| 71 | + | } | |||
| 72 | + | return &NeferwlImageCaptureSourceManagerV1{r} | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | const NeferwlImageCaptureSourceManagerV1RequestDestroy uint32 = 0 | |||
| 76 | + | const NeferwlImageCaptureSourceManagerV1RequestCreateWorkspaceSource uint32 = 1 | |||
| 77 | + | const NeferwlImageCaptureSourceManagerV1RequestCreateOutputRegionSource uint32 = 2 | |||
| 78 | + | const NeferwlImageCaptureSourceManagerV1RequestGetWorkspaceFrame uint32 = 3 | |||
| 79 | + | ||||
| 80 | + | // NeferwlImageCaptureSourceManagerV1Error . | |||
| 81 | + | type NeferwlImageCaptureSourceManagerV1Error uint32 | |||
| 82 | + | ||||
| 83 | + | const ( | |||
| 84 | + | NeferwlImageCaptureSourceManagerV1ErrorInvalidRegion NeferwlImageCaptureSourceManagerV1Error = 0 | |||
| 85 | + | ) | |||
| 86 | + | ||||
| 87 | + | // NeferwlImageCaptureSourceManagerV1Handler handles neferwl_image_capture_source_manager_v1 requests. | |||
| 88 | + | type NeferwlImageCaptureSourceManagerV1Handler interface { | |||
| 89 | + | Destroy(self *NeferwlImageCaptureSourceManagerV1) | |||
| 90 | + | CreateWorkspaceSource(self *NeferwlImageCaptureSourceManagerV1, source uint32, workspace *extworkspace.ExtWorkspaceHandleV1) | |||
| 91 | + | CreateOutputRegionSource(self *NeferwlImageCaptureSourceManagerV1, source uint32, output *wayland.Output, x int32, y int32, width int32, height int32) | |||
| 92 | + | GetWorkspaceFrame(self *NeferwlImageCaptureSourceManagerV1, id uint32, workspace *extworkspace.ExtWorkspaceHandleV1) | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | func NewNeferwlImageCaptureSourceManagerV1(c server.Client, version int32, id uint32, h NeferwlImageCaptureSourceManagerV1Handler) (*NeferwlImageCaptureSourceManagerV1, error) { | |||
| 96 | + | if h == nil { | |||
| 97 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_image_capture_source_manager_v1") | |||
| 98 | + | } | |||
| 99 | + | var self *NeferwlImageCaptureSourceManagerV1 | |||
| 100 | + | r, err := c.CreateResource(NeferwlImageCaptureSourceManagerV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |||
| 101 | + | switch opcode { | |||
| 102 | + | case 0: | |||
| 103 | + | h.Destroy(self) | |||
| 104 | + | self.Destroy() | |||
| 105 | + | case 1: | |||
| 106 | + | h.CreateWorkspaceSource(self, args[0].NewID(), extworkspace.WrapExtWorkspaceHandleV1(args[1].Resource())) | |||
| 107 | + | case 2: | |||
| 108 | + | h.CreateOutputRegionSource(self, args[0].NewID(), wayland.WrapOutput(args[1].Resource()), args[2].Int(), args[3].Int(), args[4].Int(), args[5].Int()) | |||
| 109 | + | case 3: | |||
| 110 | + | h.GetWorkspaceFrame(self, args[0].NewID(), extworkspace.WrapExtWorkspaceHandleV1(args[1].Resource())) | |||
| 111 | + | } | |||
| 112 | + | }) | |||
| 113 | + | if err != nil { | |||
| 114 | + | return nil, err | |||
| 115 | + | } | |||
| 116 | + | self = WrapNeferwlImageCaptureSourceManagerV1(r) | |||
| 117 | + | r.Data = self | |||
| 118 | + | return self, nil | |||
| 119 | + | } | |||
| 120 | + | func NewNeferwlImageCaptureSourceManagerV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |||
| 121 | + | return d.CreateGlobal(NeferwlImageCaptureSourceManagerV1Interface, version, bind) | |||
| 122 | + | } | |||
| 123 | + | ||||
| 124 | + | // NeferwlWorkspaceFrameV1 the frame of one workspace. | |||
| 125 | + | type NeferwlWorkspaceFrameV1 struct{ *server.Resource } | |||
| 126 | + | ||||
| 127 | + | func WrapNeferwlWorkspaceFrameV1(r *server.Resource) *NeferwlWorkspaceFrameV1 { | |||
| 128 | + | if r == nil { | |||
| 129 | + | return nil | |||
| 130 | + | } | |||
| 131 | + | return &NeferwlWorkspaceFrameV1{r} | |||
| 132 | + | } | |||
| 133 | + | ||||
| 134 | + | const NeferwlWorkspaceFrameV1RequestDestroy uint32 = 0 | |||
| 135 | + | const NeferwlWorkspaceFrameV1EventFrame uint32 = 0 | |||
| 136 | + | ||||
| 137 | + | // NeferwlWorkspaceFrameV1Handler handles neferwl_workspace_frame_v1 requests. | |||
| 138 | + | type NeferwlWorkspaceFrameV1Handler interface { | |||
| 139 | + | Destroy(self *NeferwlWorkspaceFrameV1) | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | func NewNeferwlWorkspaceFrameV1(c server.Client, version int32, id uint32, h NeferwlWorkspaceFrameV1Handler) (*NeferwlWorkspaceFrameV1, error) { | |||
| 143 | + | if h == nil { | |||
| 144 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_workspace_frame_v1") | |||
| 145 | + | } | |||
| 146 | + | var self *NeferwlWorkspaceFrameV1 | |||
| 147 | + | r, err := c.CreateResource(NeferwlWorkspaceFrameV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |||
| 148 | + | switch opcode { | |||
| 149 | + | case 0: | |||
| 150 | + | h.Destroy(self) | |||
| 151 | + | self.Destroy() | |||
| 152 | + | } | |||
| 153 | + | }) | |||
| 154 | + | if err != nil { | |||
| 155 | + | return nil, err | |||
| 156 | + | } | |||
| 157 | + | self = WrapNeferwlWorkspaceFrameV1(r) | |||
| 158 | + | r.Data = self | |||
| 159 | + | return self, nil | |||
| 160 | + | } | |||
| 161 | + | ||||
| 162 | + | // SendFrame the workspace frame. | |||
| 163 | + | func (self *NeferwlWorkspaceFrameV1) SendFrame(x int32, y int32, width int32, height int32) { | |||
| 164 | + | self.PostEvent(NeferwlWorkspaceFrameV1EventFrame, server.Int(x), server.Int(y), server.Int(width), server.Int(height)) | |||
| 165 | + | } | |||
| 166 | + | func NewNeferwlWorkspaceFrameV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |||
| 167 | + | return d.CreateGlobal(NeferwlWorkspaceFrameV1Interface, version, bind) | |||
| 168 | + | } | |||
| 169 | + | ||||
| 170 | + | // NeferwlCaptureExclusionManagerV1 leave a client's own surfaces out of its captures. | |||
| 171 | + | type NeferwlCaptureExclusionManagerV1 struct{ *server.Resource } | |||
| 172 | + | ||||
| 173 | + | func WrapNeferwlCaptureExclusionManagerV1(r *server.Resource) *NeferwlCaptureExclusionManagerV1 { | |||
| 174 | + | if r == nil { | |||
| 175 | + | return nil | |||
| 176 | + | } | |||
| 177 | + | return &NeferwlCaptureExclusionManagerV1{r} | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | const NeferwlCaptureExclusionManagerV1RequestDestroy uint32 = 0 | |||
| 181 | + | const NeferwlCaptureExclusionManagerV1RequestGetExclusion uint32 = 1 | |||
| 182 | + | const NeferwlCaptureExclusionManagerV1RequestAttachSurface uint32 = 2 | |||
| 183 | + | ||||
| 184 | + | // NeferwlCaptureExclusionManagerV1Error . | |||
| 185 | + | type NeferwlCaptureExclusionManagerV1Error uint32 | |||
| 186 | + | ||||
| 187 | + | const ( | |||
| 188 | + | NeferwlCaptureExclusionManagerV1ErrorSurfaceRole NeferwlCaptureExclusionManagerV1Error = 0 | |||
| 189 | + | NeferwlCaptureExclusionManagerV1ErrorInvalidLayer NeferwlCaptureExclusionManagerV1Error = 1 | |||
| 190 | + | NeferwlCaptureExclusionManagerV1ErrorExclusiveKeyboard NeferwlCaptureExclusionManagerV1Error = 2 | |||
| 191 | + | NeferwlCaptureExclusionManagerV1ErrorInvalidSurface NeferwlCaptureExclusionManagerV1Error = 3 | |||
| 192 | + | NeferwlCaptureExclusionManagerV1ErrorAlreadyExcluded NeferwlCaptureExclusionManagerV1Error = 4 | |||
| 193 | + | ) | |||
| 194 | + | ||||
| 195 | + | // NeferwlCaptureExclusionManagerV1Handler handles neferwl_capture_exclusion_manager_v1 requests. | |||
| 196 | + | type NeferwlCaptureExclusionManagerV1Handler interface { | |||
| 197 | + | Destroy(self *NeferwlCaptureExclusionManagerV1) | |||
| 198 | + | GetExclusion(self *NeferwlCaptureExclusionManagerV1, id uint32, session *extimagecopycapture.ExtImageCopyCaptureSessionV1) | |||
| 199 | + | AttachSurface(self *NeferwlCaptureExclusionManagerV1, id uint32, token string, surface *wayland.Surface) | |||
| 200 | + | } | |||
| 201 | + | ||||
| 202 | + | func NewNeferwlCaptureExclusionManagerV1(c server.Client, version int32, id uint32, h NeferwlCaptureExclusionManagerV1Handler) (*NeferwlCaptureExclusionManagerV1, error) { | |||
| 203 | + | if h == nil { | |||
| 204 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_capture_exclusion_manager_v1") | |||
| 205 | + | } | |||
| 206 | + | var self *NeferwlCaptureExclusionManagerV1 | |||
| 207 | + | r, err := c.CreateResource(NeferwlCaptureExclusionManagerV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |||
| 208 | + | switch opcode { | |||
| 209 | + | case 0: | |||
| 210 | + | h.Destroy(self) | |||
| 211 | + | self.Destroy() | |||
| 212 | + | case 1: | |||
| 213 | + | h.GetExclusion(self, args[0].NewID(), extimagecopycapture.WrapExtImageCopyCaptureSessionV1(args[1].Resource())) | |||
| 214 | + | case 2: | |||
| 215 | + | h.AttachSurface(self, args[0].NewID(), args[1].String(), wayland.WrapSurface(args[2].Resource())) | |||
| 216 | + | } | |||
| 217 | + | }) | |||
| 218 | + | if err != nil { | |||
| 219 | + | return nil, err | |||
| 220 | + | } | |||
| 221 | + | self = WrapNeferwlCaptureExclusionManagerV1(r) | |||
| 222 | + | r.Data = self | |||
| 223 | + | return self, nil | |||
| 224 | + | } | |||
| 225 | + | func NewNeferwlCaptureExclusionManagerV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |||
| 226 | + | return d.CreateGlobal(NeferwlCaptureExclusionManagerV1Interface, version, bind) | |||
| 227 | + | } | |||
| 228 | + | ||||
| 229 | + | // NeferwlCaptureExclusionV1 the exclusion of one capture session. | |||
| 230 | + | type NeferwlCaptureExclusionV1 struct{ *server.Resource } | |||
| 231 | + | ||||
| 232 | + | func WrapNeferwlCaptureExclusionV1(r *server.Resource) *NeferwlCaptureExclusionV1 { | |||
| 233 | + | if r == nil { | |||
| 234 | + | return nil | |||
| 235 | + | } | |||
| 236 | + | return &NeferwlCaptureExclusionV1{r} | |||
| 237 | + | } | |||
| 238 | + | ||||
| 239 | + | const NeferwlCaptureExclusionV1RequestDestroy uint32 = 0 | |||
| 240 | + | const NeferwlCaptureExclusionV1EventToken uint32 = 0 | |||
| 241 | + | const NeferwlCaptureExclusionV1EventFailed uint32 = 1 | |||
| 242 | + | ||||
| 243 | + | // NeferwlCaptureExclusionV1Failure . | |||
| 244 | + | type NeferwlCaptureExclusionV1Failure uint32 | |||
| 245 | + | ||||
| 246 | + | const ( | |||
| 247 | + | NeferwlCaptureExclusionV1FailureBusy NeferwlCaptureExclusionV1Failure = 0 | |||
| 248 | + | NeferwlCaptureExclusionV1FailureSessionStopped NeferwlCaptureExclusionV1Failure = 1 | |||
| 249 | + | ) | |||
| 250 | + | ||||
| 251 | + | // NeferwlCaptureExclusionV1Handler handles neferwl_capture_exclusion_v1 requests. | |||
| 252 | + | type NeferwlCaptureExclusionV1Handler interface { | |||
| 253 | + | Destroy(self *NeferwlCaptureExclusionV1) | |||
| 254 | + | } | |||
| 255 | + | ||||
| 256 | + | func NewNeferwlCaptureExclusionV1(c server.Client, version int32, id uint32, h NeferwlCaptureExclusionV1Handler) (*NeferwlCaptureExclusionV1, error) { | |||
| 257 | + | if h == nil { | |||
| 258 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_capture_exclusion_v1") | |||
| 259 | + | } | |||
| 260 | + | var self *NeferwlCaptureExclusionV1 | |||
| 261 | + | r, err := c.CreateResource(NeferwlCaptureExclusionV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |||
| 262 | + | switch opcode { | |||
| 263 | + | case 0: | |||
| 264 | + | h.Destroy(self) | |||
| 265 | + | self.Destroy() | |||
| 266 | + | } | |||
| 267 | + | }) | |||
| 268 | + | if err != nil { | |||
| 269 | + | return nil, err | |||
| 270 | + | } | |||
| 271 | + | self = WrapNeferwlCaptureExclusionV1(r) | |||
| 272 | + | r.Data = self | |||
| 273 | + | return self, nil | |||
| 274 | + | } | |||
| 275 | + | ||||
| 276 | + | // SendToken the attachment token. | |||
| 277 | + | func (self *NeferwlCaptureExclusionV1) SendToken(token string) { | |||
| 278 | + | var p runtime.Pinner | |||
| 279 | + | defer p.Unpin() | |||
| 280 | + | self.PostEvent(NeferwlCaptureExclusionV1EventToken, server.String(&p, token, false)) | |||
| 281 | + | } | |||
| 282 | + | ||||
| 283 | + | // SendFailed no exclusion. | |||
| 284 | + | func (self *NeferwlCaptureExclusionV1) SendFailed(reason uint32) { | |||
| 285 | + | self.PostEvent(NeferwlCaptureExclusionV1EventFailed, server.Uint(reason)) | |||
| 286 | + | } | |||
| 287 | + | func NewNeferwlCaptureExclusionV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |||
| 288 | + | return d.CreateGlobal(NeferwlCaptureExclusionV1Interface, version, bind) | |||
| 289 | + | } | |||
| 290 | + | ||||
| 291 | + | // NeferwlCaptureLayerV1 a layer surface attached to an exclusion. | |||
| 292 | + | type NeferwlCaptureLayerV1 struct{ *server.Resource } | |||
| 293 | + | ||||
| 294 | + | func WrapNeferwlCaptureLayerV1(r *server.Resource) *NeferwlCaptureLayerV1 { | |||
| 295 | + | if r == nil { | |||
| 296 | + | return nil | |||
| 297 | + | } | |||
| 298 | + | return &NeferwlCaptureLayerV1{r} | |||
| 299 | + | } | |||
| 300 | + | ||||
| 301 | + | const NeferwlCaptureLayerV1RequestDestroy uint32 = 0 | |||
| 302 | + | const NeferwlCaptureLayerV1EventAttached uint32 = 0 | |||
| 303 | + | const NeferwlCaptureLayerV1EventFailed uint32 = 1 | |||
| 304 | + | const NeferwlCaptureLayerV1EventDetached uint32 = 2 | |||
| 305 | + | ||||
| 306 | + | // NeferwlCaptureLayerV1Failure . | |||
| 307 | + | type NeferwlCaptureLayerV1Failure uint32 | |||
| 308 | + | ||||
| 309 | + | const ( | |||
| 310 | + | NeferwlCaptureLayerV1FailureUnknownToken NeferwlCaptureLayerV1Failure = 0 | |||
| 311 | + | NeferwlCaptureLayerV1FailureUnauthorized NeferwlCaptureLayerV1Failure = 1 | |||
| 312 | + | NeferwlCaptureLayerV1FailureTooManyLayers NeferwlCaptureLayerV1Failure = 2 | |||
| 313 | + | NeferwlCaptureLayerV1FailureAlreadyAttached NeferwlCaptureLayerV1Failure = 3 | |||
| 314 | + | NeferwlCaptureLayerV1FailureExclusionEnded NeferwlCaptureLayerV1Failure = 4 | |||
| 315 | + | NeferwlCaptureLayerV1FailureLayerDestroyed NeferwlCaptureLayerV1Failure = 5 | |||
| 316 | + | ) | |||
| 317 | + | ||||
| 318 | + | // NeferwlCaptureLayerV1DetachReason . | |||
| 319 | + | type NeferwlCaptureLayerV1DetachReason uint32 | |||
| 320 | + | ||||
| 321 | + | const ( | |||
| 322 | + | NeferwlCaptureLayerV1DetachReasonExclusionEnded NeferwlCaptureLayerV1DetachReason = 0 | |||
| 323 | + | NeferwlCaptureLayerV1DetachReasonLayerDestroyed NeferwlCaptureLayerV1DetachReason = 1 | |||
| 324 | + | ) | |||
| 325 | + | ||||
| 326 | + | // NeferwlCaptureLayerV1Handler handles neferwl_capture_layer_v1 requests. | |||
| 327 | + | type NeferwlCaptureLayerV1Handler interface { | |||
| 328 | + | Destroy(self *NeferwlCaptureLayerV1) | |||
| 329 | + | } | |||
| 330 | + | ||||
| 331 | + | func NewNeferwlCaptureLayerV1(c server.Client, version int32, id uint32, h NeferwlCaptureLayerV1Handler) (*NeferwlCaptureLayerV1, error) { | |||
| 332 | + | if h == nil { | |||
| 333 | + | return nil, fmt.Errorf("purego-libwayland: nil handler for neferwl_capture_layer_v1") | |||
| 334 | + | } | |||
| 335 | + | var self *NeferwlCaptureLayerV1 | |||
| 336 | + | r, err := c.CreateResource(NeferwlCaptureLayerV1Interface, version, id, func(_ *server.Resource, opcode uint32, args []server.Arg) { | |||
| 337 | + | switch opcode { | |||
| 338 | + | case 0: | |||
| 339 | + | h.Destroy(self) | |||
| 340 | + | self.Destroy() | |||
| 341 | + | } | |||
| 342 | + | }) | |||
| 343 | + | if err != nil { | |||
| 344 | + | return nil, err | |||
| 345 | + | } | |||
| 346 | + | self = WrapNeferwlCaptureLayerV1(r) | |||
| 347 | + | r.Data = self | |||
| 348 | + | return self, nil | |||
| 349 | + | } | |||
| 350 | + | ||||
| 351 | + | // SendAttached the layer is excluded. | |||
| 352 | + | func (self *NeferwlCaptureLayerV1) SendAttached() { | |||
| 353 | + | self.PostEvent(NeferwlCaptureLayerV1EventAttached) | |||
| 354 | + | } | |||
| 355 | + | ||||
| 356 | + | // SendFailed the attachment was refused. | |||
| 357 | + | func (self *NeferwlCaptureLayerV1) SendFailed(reason uint32) { | |||
| 358 | + | self.PostEvent(NeferwlCaptureLayerV1EventFailed, server.Uint(reason)) | |||
| 359 | + | } | |||
| 360 | + | ||||
| 361 | + | // SendDetached the layer left the exclusion. | |||
| 362 | + | func (self *NeferwlCaptureLayerV1) SendDetached(reason uint32) { | |||
| 363 | + | self.PostEvent(NeferwlCaptureLayerV1EventDetached, server.Uint(reason)) | |||
| 364 | + | } | |||
| 365 | + | func NewNeferwlCaptureLayerV1Global(d *server.Display, version int32, bind server.BindFunc) error { | |||
| 366 | + | return d.CreateGlobal(NeferwlCaptureLayerV1Interface, version, bind) | |||
| 367 | + | } | |||
| 368 | + | func resourceOf(v interface{ GetResource() *server.Resource }) *server.Resource { | |||
| 369 | + | if v == nil { | |||
| 370 | + | return nil | |||
| 371 | + | } | |||
| 372 | + | return v.GetResource() | |||
| 373 | + | } | |||
| 374 | + | func (self *NeferwlImageCaptureSourceManagerV1) GetResource() *server.Resource { | |||
| 375 | + | if self == nil { | |||
| 376 | + | return nil | |||
| 377 | + | } | |||
| 378 | + | return self.Resource | |||
| 379 | + | } | |||
| 380 | + | func (self *NeferwlWorkspaceFrameV1) GetResource() *server.Resource { | |||
| 381 | + | if self == nil { | |||
| 382 | + | return nil | |||
| 383 | + | } | |||
| 384 | + | return self.Resource | |||
| 385 | + | } | |||
| 386 | + | func (self *NeferwlCaptureExclusionManagerV1) GetResource() *server.Resource { | |||
| 387 | + | if self == nil { | |||
| 388 | + | return nil | |||
| 389 | + | } | |||
| 390 | + | return self.Resource | |||
| 391 | + | } | |||
| 392 | + | func (self *NeferwlCaptureExclusionV1) GetResource() *server.Resource { | |||
| 393 | + | if self == nil { | |||
| 394 | + | return nil | |||
| 395 | + | } | |||
| 396 | + | return self.Resource | |||
| 397 | + | } | |||
| 398 | + | func (self *NeferwlCaptureLayerV1) GetResource() *server.Resource { | |||
| 399 | + | if self == nil { | |||
| 400 | + | return nil | |||
| 401 | + | } | |||
| 402 | + | return self.Resource | |||
| 403 | + | } | |||
A internal/adapters/wayland/testserver/outputs.go +174 -0
| @@ -0,0 +1,174 @@ | |||
| 1 | + | package testserver | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/purego-libwayland/protocol/extimagecapturesource" | |
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 6 | + | "github.com/bnema/purego-libwayland/server" | |
| 7 | + | "golang.org/x/sys/unix" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func (s *Server) addOutput(spec OutputSpec, must func(error)) { | |
| 11 | + | o := &output{spec: spec} | |
| 12 | + | v := spec.Version | |
| 13 | + | if v == 0 { | |
| 14 | + | v = 4 | |
| 15 | + | } | |
| 16 | + | g, err := s.d.AddGlobal(wayland.OutputInterface, int32(v), func(c server.Client, ver, id uint32) { | |
| 17 | + | res, err := wayland.NewOutput(c, int32(ver), id, outputHandler{}) | |
| 18 | + | if err != nil { | |
| 19 | + | must(err) | |
| 20 | + | return | |
| 21 | + | } | |
| 22 | + | o.res = append(o.res, res) | |
| 23 | + | res.SendGeometry(0, 0, 0, 0, 0, "test", "test", 0) | |
| 24 | + | res.SendMode(1, int32(spec.Width), int32(spec.Height), 60000) | |
| 25 | + | if ver >= 2 { | |
| 26 | + | scale := spec.Scale | |
| 27 | + | if scale == 0 { | |
| 28 | + | scale = 1 | |
| 29 | + | } | |
| 30 | + | res.SendScale(scale) | |
| 31 | + | } | |
| 32 | + | if ver >= 4 { | |
| 33 | + | res.SendName(spec.Name) | |
| 34 | + | } | |
| 35 | + | if ver >= 2 { | |
| 36 | + | res.SendDone() | |
| 37 | + | } | |
| 38 | + | }) | |
| 39 | + | must(err) | |
| 40 | + | o.global = g | |
| 41 | + | s.outputs = append(s.outputs, o) | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | type outputHandler struct{} | |
| 45 | + | ||
| 46 | + | func (outputHandler) Release(*wayland.Output) {} | |
| 47 | + | ||
| 48 | + | func (s *Server) outputOf(r *server.Resource) *output { | |
| 49 | + | for _, o := range s.outputs { | |
| 50 | + | for _, res := range o.res { | |
| 51 | + | if res.Resource == r { | |
| 52 | + | return o | |
| 53 | + | } | |
| 54 | + | } | |
| 55 | + | } | |
| 56 | + | return nil | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | // RemoveOutput withdraws the named output's global and stops its sessions. | |
| 60 | + | func (s *Server) RemoveOutput(name string) { | |
| 61 | + | s.Do(func() { | |
| 62 | + | for _, o := range s.outputs { | |
| 63 | + | if o.spec.Name == name && !o.removed { | |
| 64 | + | o.removed = true | |
| 65 | + | o.global.Remove() | |
| 66 | + | for _, c := range s.sessions { | |
| 67 | + | if c.out == o { | |
| 68 | + | c.stop() | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | } | |
| 72 | + | } | |
| 73 | + | }) | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | // pool is one wl_shm_pool mapping, shared by the buffers made from it. | |
| 77 | + | type pool struct { | |
| 78 | + | data []byte | |
| 79 | + | refs int | |
| 80 | + | destroyed bool | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | func (p *pool) release() { | |
| 84 | + | if p.refs == 0 && p.destroyed && p.data != nil { | |
| 85 | + | _ = unix.Munmap(p.data) | |
| 86 | + | p.data = nil | |
| 87 | + | } | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | type buffer struct { | |
| 91 | + | p *pool | |
| 92 | + | offset int | |
| 93 | + | width, height, stride, format uint32 | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | func (b *buffer) bytes() []byte { | |
| 97 | + | n := int(b.stride) * int(b.height) | |
| 98 | + | if b.p.data == nil || b.offset+n > len(b.p.data) { | |
| 99 | + | return nil | |
| 100 | + | } | |
| 101 | + | return b.p.data[b.offset : b.offset+n] | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | type shmHandler struct{ s *Server } | |
| 105 | + | ||
| 106 | + | func (h shmHandler) CreatePool(self *wayland.Shm, id uint32, fd int, size int32) { | |
| 107 | + | defer unix.Close(fd) | |
| 108 | + | data, err := unix.Mmap(fd, 0, int(size), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) | |
| 109 | + | if err != nil { | |
| 110 | + | self.PostError(0, "mmap failed") | |
| 111 | + | return | |
| 112 | + | } | |
| 113 | + | _, _ = wayland.NewShmPool(self.Client(), 1, id, &poolHandler{s: h.s, p: &pool{data: data}}) | |
| 114 | + | } | |
| 115 | + | func (shmHandler) Release(*wayland.Shm) {} | |
| 116 | + | ||
| 117 | + | type poolHandler struct { | |
| 118 | + | s *Server | |
| 119 | + | p *pool | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | func (h *poolHandler) CreateBuffer(self *wayland.ShmPool, id uint32, offset, width, height, stride int32, format uint32) { | |
| 123 | + | b := &buffer{p: h.p, offset: int(offset), width: uint32(width), height: uint32(height), stride: uint32(stride), format: format} | |
| 124 | + | res, err := wayland.NewBuffer(self.Client(), 1, id, bufferHandler{h.s, b}) | |
| 125 | + | if err != nil { | |
| 126 | + | return | |
| 127 | + | } | |
| 128 | + | h.p.refs++ | |
| 129 | + | h.s.bufs[res.Resource] = b | |
| 130 | + | h.s.stats.Buffers++ | |
| 131 | + | res.OnDestroy = func() { | |
| 132 | + | delete(h.s.bufs, res.Resource) | |
| 133 | + | h.s.stats.DestroyedBuffers++ | |
| 134 | + | h.p.refs-- | |
| 135 | + | h.p.release() | |
| 136 | + | } | |
| 137 | + | } | |
| 138 | + | func (h *poolHandler) Destroy(*wayland.ShmPool) { h.p.destroyed = true; h.p.release() } | |
| 139 | + | func (h *poolHandler) Resize(*wayland.ShmPool, int32) {} | |
| 140 | + | ||
| 141 | + | type bufferHandler struct { | |
| 142 | + | s *Server | |
| 143 | + | b *buffer | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | func (bufferHandler) Destroy(*wayland.Buffer) {} | |
| 147 | + | ||
| 148 | + | type outputSourceManager struct{ s *Server } | |
| 149 | + | ||
| 150 | + | func (m outputSourceManager) CreateSource(self *extimagecapturesource.ExtOutputImageCaptureSourceManagerV1, id uint32, out *wayland.Output) { | |
| 151 | + | m.s.newSource(self.Client(), id, &source{kind: "output", out: m.s.outputOf(out.Resource)}) | |
| 152 | + | } | |
| 153 | + | func (outputSourceManager) Destroy(*extimagecapturesource.ExtOutputImageCaptureSourceManagerV1) {} | |
| 154 | + | ||
| 155 | + | type source struct { | |
| 156 | + | kind string // output, region, workspace | |
| 157 | + | out *output | |
| 158 | + | region [4]int32 | |
| 159 | + | ws *workspace | |
| 160 | + | gone bool // the workspace handle was already removed | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | func (s *Server) newSource(c server.Client, id uint32, src *source) { | |
| 164 | + | res, err := extimagecapturesource.NewExtImageCaptureSourceV1(c, 1, id, sourceHandler{}) | |
| 165 | + | if err != nil { | |
| 166 | + | return | |
| 167 | + | } | |
| 168 | + | s.sources[res.Resource] = src | |
| 169 | + | res.OnDestroy = func() { delete(s.sources, res.Resource) } // sessions keep their own pointer | |
| 170 | + | } | |
| 171 | + | ||
| 172 | + | type sourceHandler struct{} | |
| 173 | + | ||
| 174 | + | func (sourceHandler) Destroy(*extimagecapturesource.ExtImageCaptureSourceV1) {} | |
| @@ -0,0 +1,174 @@ | |||||
| 1 | + | package testserver | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/purego-libwayland/protocol/extimagecapturesource" | |||
| 5 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 6 | + | "github.com/bnema/purego-libwayland/server" | |||
| 7 | + | "golang.org/x/sys/unix" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func (s *Server) addOutput(spec OutputSpec, must func(error)) { | |||
| 11 | + | o := &output{spec: spec} | |||
| 12 | + | v := spec.Version | |||
| 13 | + | if v == 0 { | |||
| 14 | + | v = 4 | |||
| 15 | + | } | |||
| 16 | + | g, err := s.d.AddGlobal(wayland.OutputInterface, int32(v), func(c server.Client, ver, id uint32) { | |||
| 17 | + | res, err := wayland.NewOutput(c, int32(ver), id, outputHandler{}) | |||
| 18 | + | if err != nil { | |||
| 19 | + | must(err) | |||
| 20 | + | return | |||
| 21 | + | } | |||
| 22 | + | o.res = append(o.res, res) | |||
| 23 | + | res.SendGeometry(0, 0, 0, 0, 0, "test", "test", 0) | |||
| 24 | + | res.SendMode(1, int32(spec.Width), int32(spec.Height), 60000) | |||
| 25 | + | if ver >= 2 { | |||
| 26 | + | scale := spec.Scale | |||
| 27 | + | if scale == 0 { | |||
| 28 | + | scale = 1 | |||
| 29 | + | } | |||
| 30 | + | res.SendScale(scale) | |||
| 31 | + | } | |||
| 32 | + | if ver >= 4 { | |||
| 33 | + | res.SendName(spec.Name) | |||
| 34 | + | } | |||
| 35 | + | if ver >= 2 { | |||
| 36 | + | res.SendDone() | |||
| 37 | + | } | |||
| 38 | + | }) | |||
| 39 | + | must(err) | |||
| 40 | + | o.global = g | |||
| 41 | + | s.outputs = append(s.outputs, o) | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | type outputHandler struct{} | |||
| 45 | + | ||||
| 46 | + | func (outputHandler) Release(*wayland.Output) {} | |||
| 47 | + | ||||
| 48 | + | func (s *Server) outputOf(r *server.Resource) *output { | |||
| 49 | + | for _, o := range s.outputs { | |||
| 50 | + | for _, res := range o.res { | |||
| 51 | + | if res.Resource == r { | |||
| 52 | + | return o | |||
| 53 | + | } | |||
| 54 | + | } | |||
| 55 | + | } | |||
| 56 | + | return nil | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | // RemoveOutput withdraws the named output's global and stops its sessions. | |||
| 60 | + | func (s *Server) RemoveOutput(name string) { | |||
| 61 | + | s.Do(func() { | |||
| 62 | + | for _, o := range s.outputs { | |||
| 63 | + | if o.spec.Name == name && !o.removed { | |||
| 64 | + | o.removed = true | |||
| 65 | + | o.global.Remove() | |||
| 66 | + | for _, c := range s.sessions { | |||
| 67 | + | if c.out == o { | |||
| 68 | + | c.stop() | |||
| 69 | + | } | |||
| 70 | + | } | |||
| 71 | + | } | |||
| 72 | + | } | |||
| 73 | + | }) | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | // pool is one wl_shm_pool mapping, shared by the buffers made from it. | |||
| 77 | + | type pool struct { | |||
| 78 | + | data []byte | |||
| 79 | + | refs int | |||
| 80 | + | destroyed bool | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | func (p *pool) release() { | |||
| 84 | + | if p.refs == 0 && p.destroyed && p.data != nil { | |||
| 85 | + | _ = unix.Munmap(p.data) | |||
| 86 | + | p.data = nil | |||
| 87 | + | } | |||
| 88 | + | } | |||
| 89 | + | ||||
| 90 | + | type buffer struct { | |||
| 91 | + | p *pool | |||
| 92 | + | offset int | |||
| 93 | + | width, height, stride, format uint32 | |||
| 94 | + | } | |||
| 95 | + | ||||
| 96 | + | func (b *buffer) bytes() []byte { | |||
| 97 | + | n := int(b.stride) * int(b.height) | |||
| 98 | + | if b.p.data == nil || b.offset+n > len(b.p.data) { | |||
| 99 | + | return nil | |||
| 100 | + | } | |||
| 101 | + | return b.p.data[b.offset : b.offset+n] | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | type shmHandler struct{ s *Server } | |||
| 105 | + | ||||
| 106 | + | func (h shmHandler) CreatePool(self *wayland.Shm, id uint32, fd int, size int32) { | |||
| 107 | + | defer unix.Close(fd) | |||
| 108 | + | data, err := unix.Mmap(fd, 0, int(size), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) | |||
| 109 | + | if err != nil { | |||
| 110 | + | self.PostError(0, "mmap failed") | |||
| 111 | + | return | |||
| 112 | + | } | |||
| 113 | + | _, _ = wayland.NewShmPool(self.Client(), 1, id, &poolHandler{s: h.s, p: &pool{data: data}}) | |||
| 114 | + | } | |||
| 115 | + | func (shmHandler) Release(*wayland.Shm) {} | |||
| 116 | + | ||||
| 117 | + | type poolHandler struct { | |||
| 118 | + | s *Server | |||
| 119 | + | p *pool | |||
| 120 | + | } | |||
| 121 | + | ||||
| 122 | + | func (h *poolHandler) CreateBuffer(self *wayland.ShmPool, id uint32, offset, width, height, stride int32, format uint32) { | |||
| 123 | + | b := &buffer{p: h.p, offset: int(offset), width: uint32(width), height: uint32(height), stride: uint32(stride), format: format} | |||
| 124 | + | res, err := wayland.NewBuffer(self.Client(), 1, id, bufferHandler{h.s, b}) | |||
| 125 | + | if err != nil { | |||
| 126 | + | return | |||
| 127 | + | } | |||
| 128 | + | h.p.refs++ | |||
| 129 | + | h.s.bufs[res.Resource] = b | |||
| 130 | + | h.s.stats.Buffers++ | |||
| 131 | + | res.OnDestroy = func() { | |||
| 132 | + | delete(h.s.bufs, res.Resource) | |||
| 133 | + | h.s.stats.DestroyedBuffers++ | |||
| 134 | + | h.p.refs-- | |||
| 135 | + | h.p.release() | |||
| 136 | + | } | |||
| 137 | + | } | |||
| 138 | + | func (h *poolHandler) Destroy(*wayland.ShmPool) { h.p.destroyed = true; h.p.release() } | |||
| 139 | + | func (h *poolHandler) Resize(*wayland.ShmPool, int32) {} | |||
| 140 | + | ||||
| 141 | + | type bufferHandler struct { | |||
| 142 | + | s *Server | |||
| 143 | + | b *buffer | |||
| 144 | + | } | |||
| 145 | + | ||||
| 146 | + | func (bufferHandler) Destroy(*wayland.Buffer) {} | |||
| 147 | + | ||||
| 148 | + | type outputSourceManager struct{ s *Server } | |||
| 149 | + | ||||
| 150 | + | func (m outputSourceManager) CreateSource(self *extimagecapturesource.ExtOutputImageCaptureSourceManagerV1, id uint32, out *wayland.Output) { | |||
| 151 | + | m.s.newSource(self.Client(), id, &source{kind: "output", out: m.s.outputOf(out.Resource)}) | |||
| 152 | + | } | |||
| 153 | + | func (outputSourceManager) Destroy(*extimagecapturesource.ExtOutputImageCaptureSourceManagerV1) {} | |||
| 154 | + | ||||
| 155 | + | type source struct { | |||
| 156 | + | kind string // output, region, workspace | |||
| 157 | + | out *output | |||
| 158 | + | region [4]int32 | |||
| 159 | + | ws *workspace | |||
| 160 | + | gone bool // the workspace handle was already removed | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | func (s *Server) newSource(c server.Client, id uint32, src *source) { | |||
| 164 | + | res, err := extimagecapturesource.NewExtImageCaptureSourceV1(c, 1, id, sourceHandler{}) | |||
| 165 | + | if err != nil { | |||
| 166 | + | return | |||
| 167 | + | } | |||
| 168 | + | s.sources[res.Resource] = src | |||
| 169 | + | res.OnDestroy = func() { delete(s.sources, res.Resource) } // sessions keep their own pointer | |||
| 170 | + | } | |||
| 171 | + | ||||
| 172 | + | type sourceHandler struct{} | |||
| 173 | + | ||||
| 174 | + | func (sourceHandler) Destroy(*extimagecapturesource.ExtImageCaptureSourceV1) {} | |||
A internal/adapters/wayland/testserver/server.go +301 -0
| @@ -0,0 +1,301 @@ | |||
| 1 | + | // Package testserver is a minimal in-process Wayland compositor for tests: a | |
| 2 | + | // real libwayland server (purego-libwayland) that speaks wl_output, wl_shm, | |
| 3 | + | // ext-image-capture-source-v1, ext-image-copy-capture-v1, ext-workspace-v1 and | |
| 4 | + | // NeferWL's capture extension. It is a protocol peer, not a substitute for a | |
| 5 | + | // project type: clients connect to its socket like to any compositor. | |
| 6 | + | // | |
| 7 | + | // All server state is touched only on the display goroutine: handlers run | |
| 8 | + | // there, and tests reach it through Do. | |
| 9 | + | package testserver | |
| 10 | + | ||
| 11 | + | import ( | |
| 12 | + | "context" | |
| 13 | + | "os" | |
| 14 | + | "path/filepath" | |
| 15 | + | "testing" | |
| 16 | + | ||
| 17 | + | "github.com/bnema/purego-libwayland/protocol/extimagecapturesource" | |
| 18 | + | "github.com/bnema/purego-libwayland/protocol/extimagecopycapture" | |
| 19 | + | "github.com/bnema/purego-libwayland/protocol/extworkspace" | |
| 20 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 21 | + | "github.com/bnema/purego-libwayland/server" | |
| 22 | + | "golang.org/x/sys/unix" | |
| 23 | + | ||
| 24 | + | "github.com/bnema/nefercap/internal/adapters/wayland/testserver/neferwl" | |
| 25 | + | ) | |
| 26 | + | ||
| 27 | + | // Token is the exclusion token the server hands out. | |
| 28 | + | const Token = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" | |
| 29 | + | ||
| 30 | + | // wl_shm formats. | |
| 31 | + | const ( | |
| 32 | + | ARGB8888 = 0 | |
| 33 | + | XRGB8888 = 1 | |
| 34 | + | ) | |
| 35 | + | ||
| 36 | + | // OutputSpec describes one wl_output. The capture buffer of an output source | |
| 37 | + | // is its mode size. | |
| 38 | + | type OutputSpec struct { | |
| 39 | + | Name string | |
| 40 | + | Width, Height uint32 | |
| 41 | + | Scale int32 | |
| 42 | + | Version uint32 // 0 selects 4 | |
| 43 | + | // LogicalWidth/Height, when set, are announced through xdg-output. | |
| 44 | + | LogicalWidth, LogicalHeight int32 | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | // WorkspaceSpec describes one ext-workspace handle; all of them live in one | |
| 48 | + | // group that holds every output. | |
| 49 | + | type WorkspaceSpec struct { | |
| 50 | + | ID string // empty: a temporary workspace, no id event | |
| 51 | + | Name string | |
| 52 | + | Output string // the output whose buffer size a workspace source has | |
| 53 | + | Active bool | |
| 54 | + | Hidden bool | |
| 55 | + | // Frame is the output-local logical rectangle (x, y, width, height) that | |
| 56 | + | // neferwl_workspace_frame_v1 reports; zero reports nothing. | |
| 57 | + | Frame [4]int32 | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | // Config selects what the server offers. | |
| 61 | + | type Config struct { | |
| 62 | + | Outputs []OutputSpec // default: one 8x4 output "TEST-1" | |
| 63 | + | // NoCopyCapture leaves out ext-image-copy-capture and the output source. | |
| 64 | + | NoCopyCapture bool | |
| 65 | + | Formats []uint32 // shm formats announced, default XRGB8888 | |
| 66 | + | // NoShmFormat announces size and done but no shm_format. | |
| 67 | + | NoShmFormat bool | |
| 68 | + | // Hold keeps every captured frame pending until Release. | |
| 69 | + | Hold bool | |
| 70 | + | // HoldAfterFirst answers the first frame of each session at once and holds | |
| 71 | + | // the following ones until Release, like a compositor that waits for | |
| 72 | + | // damage on a static screen. | |
| 73 | + | HoldAfterFirst bool | |
| 74 | + | // Workspaces enables ext-workspace-v1 when non-nil (possibly empty). | |
| 75 | + | Workspaces []WorkspaceSpec | |
| 76 | + | HasWorkspaces bool | |
| 77 | + | // NeferwlSource and NeferwlExclusion enable the extension's two globals. | |
| 78 | + | NeferwlSource bool | |
| 79 | + | NeferwlExclusion bool | |
| 80 | + | // ExclusionFail answers get_exclusion with failed(ExclusionFailReason-1) | |
| 81 | + | // when non-zero. | |
| 82 | + | ExclusionFail uint32 | |
| 83 | + | // AttachFail answers attach_surface with failed(AttachFail-1) when non-zero. | |
| 84 | + | AttachFail uint32 | |
| 85 | + | // AttachSilent never answers attach_surface. | |
| 86 | + | AttachSilent bool | |
| 87 | + | // StopAtCreate answers every create_session with stopped and no | |
| 88 | + | // constraints, like a compositor that refuses the client. | |
| 89 | + | StopAtCreate bool | |
| 90 | + | // Transform is reported by every ready frame (wl_output.transform). | |
| 91 | + | Transform uint32 | |
| 92 | + | // FailFrames answers that many capture requests with failed(FailReason). | |
| 93 | + | FailFrames int | |
| 94 | + | FailReason uint32 | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | // Stats are counters observed by tests. | |
| 98 | + | type Stats struct { | |
| 99 | + | Sessions, Frames, Captures, Ready int | |
| 100 | + | DestroyedSessions int | |
| 101 | + | Buffers, DestroyedBuffers int | |
| 102 | + | LastSource string // "output", "region", "workspace" | |
| 103 | + | LastRegion [4]int32 | |
| 104 | + | Exclusions int | |
| 105 | + | Attached []string | |
| 106 | + | DestroyedExclusions int | |
| 107 | + | WorkspaceFrames int // neferwl_workspace_frame_v1 objects created | |
| 108 | + | DestroyedWorkspaceFrames int | |
| 109 | + | DestroyedXdgOutputs int | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | // Server is a running test compositor. | |
| 113 | + | type Server struct { | |
| 114 | + | // Path is the socket path for wayland.New. | |
| 115 | + | Path string | |
| 116 | + | ||
| 117 | + | d *server.Display | |
| 118 | + | cfg Config | |
| 119 | + | ||
| 120 | + | outputs []*output | |
| 121 | + | bufs map[*server.Resource]*buffer | |
| 122 | + | sources map[*server.Resource]*source | |
| 123 | + | sessions []*session | |
| 124 | + | workspaces []*workspace | |
| 125 | + | wsMgrs []*wsManager | |
| 126 | + | layers []*server.Resource | |
| 127 | + | frames []*frameObject | |
| 128 | + | stats Stats | |
| 129 | + | held []*frame | |
| 130 | + | seq uint32 | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | type output struct { | |
| 134 | + | spec OutputSpec | |
| 135 | + | global *server.Global | |
| 136 | + | res []*wayland.Output | |
| 137 | + | removed bool | |
| 138 | + | } | |
| 139 | + | ||
| 140 | + | // Start runs a compositor on a fresh socket until the test ends. | |
| 141 | + | func Start(t testing.TB, cfg Config) *Server { | |
| 142 | + | t.Helper() | |
| 143 | + | if len(cfg.Outputs) == 0 { | |
| 144 | + | cfg.Outputs = []OutputSpec{{Name: "TEST-1", Width: 8, Height: 4, Scale: 1}} | |
| 145 | + | } | |
| 146 | + | if len(cfg.Formats) == 0 { | |
| 147 | + | cfg.Formats = []uint32{XRGB8888} | |
| 148 | + | } | |
| 149 | + | d, err := server.NewDisplay() | |
| 150 | + | if err != nil { | |
| 151 | + | // NEFERCAP_REQUIRE_WAYLAND=1 (set by make test and make race) turns a | |
| 152 | + | // missing libwayland-server into a failure, so the suite cannot pass | |
| 153 | + | // without its protocol tests. | |
| 154 | + | if os.Getenv("NEFERCAP_REQUIRE_WAYLAND") == "1" { | |
| 155 | + | t.Fatalf("libwayland-server unavailable: %v", err) | |
| 156 | + | } | |
| 157 | + | t.Skipf("libwayland-server unavailable: %v", err) | |
| 158 | + | } | |
| 159 | + | dir, err := filepath.EvalSymlinks(t.TempDir()) | |
| 160 | + | if err != nil { | |
| 161 | + | t.Fatal(err) | |
| 162 | + | } | |
| 163 | + | path := filepath.Join(dir, "wl-test") | |
| 164 | + | fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) | |
| 165 | + | if err != nil { | |
| 166 | + | t.Fatal(err) | |
| 167 | + | } | |
| 168 | + | if err := unix.Bind(fd, &unix.SockaddrUnix{Name: path}); err != nil { | |
| 169 | + | t.Fatal(err) | |
| 170 | + | } | |
| 171 | + | if err := unix.Listen(fd, 16); err != nil { | |
| 172 | + | t.Fatal(err) | |
| 173 | + | } | |
| 174 | + | if err := d.AddSocketFD(fd); err != nil { // libwayland owns fd now | |
| 175 | + | t.Fatal(err) | |
| 176 | + | } | |
| 177 | + | s := &Server{Path: path, d: d, cfg: cfg, bufs: map[*server.Resource]*buffer{}, sources: map[*server.Resource]*source{}} | |
| 178 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 179 | + | done := make(chan error, 1) | |
| 180 | + | go func() { done <- d.Run(ctx) }() | |
| 181 | + | if !d.Do(func() { s.setup(t) }) { | |
| 182 | + | t.Fatal("test compositor stopped") | |
| 183 | + | } | |
| 184 | + | t.Cleanup(func() { | |
| 185 | + | cancel() | |
| 186 | + | <-done | |
| 187 | + | }) | |
| 188 | + | return s | |
| 189 | + | } | |
| 190 | + | ||
| 191 | + | // Do runs f on the display goroutine, where server state may be touched. | |
| 192 | + | func (s *Server) Do(f func()) { | |
| 193 | + | if !s.d.Do(f) { | |
| 194 | + | // stopped: nothing to observe | |
| 195 | + | return | |
| 196 | + | } | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | // Stats returns a snapshot of the counters. | |
| 200 | + | func (s *Server) Stats() Stats { | |
| 201 | + | var st Stats | |
| 202 | + | s.Do(func() { | |
| 203 | + | st = s.stats | |
| 204 | + | st.Attached = append([]string(nil), s.stats.Attached...) | |
| 205 | + | }) | |
| 206 | + | return st | |
| 207 | + | } | |
| 208 | + | ||
| 209 | + | func (s *Server) setup(t testing.TB) { | |
| 210 | + | must := func(err error) { | |
| 211 | + | if err != nil { | |
| 212 | + | t.Error(err) | |
| 213 | + | } | |
| 214 | + | } | |
| 215 | + | for _, spec := range s.cfg.Outputs { | |
| 216 | + | s.addOutput(spec, must) | |
| 217 | + | } | |
| 218 | + | must(wayland.NewShmGlobal(s.d, 1, func(c server.Client, v, id uint32) { | |
| 219 | + | shm, err := wayland.NewShm(c, int32(v), id, shmHandler{s}) | |
| 220 | + | if err != nil { | |
| 221 | + | t.Error(err) | |
| 222 | + | return | |
| 223 | + | } | |
| 224 | + | for _, f := range []uint32{ARGB8888, XRGB8888} { | |
| 225 | + | shm.SendFormat(f) | |
| 226 | + | } | |
| 227 | + | })) | |
| 228 | + | if s.xdgOutputWanted() { | |
| 229 | + | s.addXdgOutput(must) | |
| 230 | + | } | |
| 231 | + | must(wayland.NewCompositorGlobal(s.d, 4, func(c server.Client, v, id uint32) { | |
| 232 | + | if _, err := wayland.NewCompositor(c, int32(v), id, compositorHandler{}); err != nil { | |
| 233 | + | t.Error(err) | |
| 234 | + | } | |
| 235 | + | })) | |
| 236 | + | if !s.cfg.NoCopyCapture { | |
| 237 | + | must(extimagecapturesource.NewExtOutputImageCaptureSourceManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |
| 238 | + | if _, err := extimagecapturesource.NewExtOutputImageCaptureSourceManagerV1(c, int32(v), id, outputSourceManager{s}); err != nil { | |
| 239 | + | t.Error(err) | |
| 240 | + | } | |
| 241 | + | })) | |
| 242 | + | must(extimagecopycapture.NewExtImageCopyCaptureManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |
| 243 | + | if _, err := extimagecopycapture.NewExtImageCopyCaptureManagerV1(c, int32(v), id, copyManager{s}); err != nil { | |
| 244 | + | t.Error(err) | |
| 245 | + | } | |
| 246 | + | })) | |
| 247 | + | } | |
| 248 | + | if s.cfg.HasWorkspaces { | |
| 249 | + | for _, w := range s.cfg.Workspaces { | |
| 250 | + | s.workspaces = append(s.workspaces, &workspace{spec: w}) | |
| 251 | + | } | |
| 252 | + | must(extworkspace.NewExtWorkspaceManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |
| 253 | + | m := &wsManager{s: s} | |
| 254 | + | res, err := extworkspace.NewExtWorkspaceManagerV1(c, int32(v), id, m) | |
| 255 | + | if err != nil { | |
| 256 | + | t.Error(err) | |
| 257 | + | return | |
| 258 | + | } | |
| 259 | + | m.res = res | |
| 260 | + | s.wsMgrs = append(s.wsMgrs, m) | |
| 261 | + | m.announce(c) | |
| 262 | + | })) | |
| 263 | + | } | |
| 264 | + | if s.cfg.NeferwlSource { | |
| 265 | + | must(neferwl.NewNeferwlImageCaptureSourceManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |
| 266 | + | if _, err := neferwl.NewNeferwlImageCaptureSourceManagerV1(c, int32(v), id, neferwlSources{s}); err != nil { | |
| 267 | + | t.Error(err) | |
| 268 | + | } | |
| 269 | + | })) | |
| 270 | + | } | |
| 271 | + | if s.cfg.NeferwlExclusion { | |
| 272 | + | must(neferwl.NewNeferwlCaptureExclusionManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |
| 273 | + | if _, err := neferwl.NewNeferwlCaptureExclusionManagerV1(c, int32(v), id, exclusionManager{s}); err != nil { | |
| 274 | + | t.Error(err) | |
| 275 | + | } | |
| 276 | + | })) | |
| 277 | + | } | |
| 278 | + | } | |
| 279 | + | ||
| 280 | + | type compositorHandler struct{} | |
| 281 | + | ||
| 282 | + | func (compositorHandler) CreateSurface(self *wayland.Compositor, id uint32) { | |
| 283 | + | _, _ = wayland.NewSurface(self.Client(), 4, id, surfaceHandler{}) | |
| 284 | + | } | |
| 285 | + | func (compositorHandler) CreateRegion(*wayland.Compositor, uint32) {} | |
| 286 | + | func (compositorHandler) Release(*wayland.Compositor) {} | |
| 287 | + | ||
| 288 | + | type surfaceHandler struct{} | |
| 289 | + | ||
| 290 | + | func (surfaceHandler) Destroy(*wayland.Surface) {} | |
| 291 | + | func (surfaceHandler) Attach(*wayland.Surface, *wayland.Buffer, int32, int32) {} | |
| 292 | + | func (surfaceHandler) Damage(*wayland.Surface, int32, int32, int32, int32) {} | |
| 293 | + | func (surfaceHandler) Frame(*wayland.Surface, uint32) {} | |
| 294 | + | func (surfaceHandler) SetOpaqueRegion(*wayland.Surface, *wayland.Region) {} | |
| 295 | + | func (surfaceHandler) SetInputRegion(*wayland.Surface, *wayland.Region) {} | |
| 296 | + | func (surfaceHandler) Commit(*wayland.Surface) {} | |
| 297 | + | func (surfaceHandler) SetBufferTransform(*wayland.Surface, int32) {} | |
| 298 | + | func (surfaceHandler) SetBufferScale(*wayland.Surface, int32) {} | |
| 299 | + | func (surfaceHandler) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | |
| 300 | + | func (surfaceHandler) Offset(*wayland.Surface, int32, int32) {} | |
| 301 | + | func (surfaceHandler) GetRelease(*wayland.Surface, uint32) {} | |
| @@ -0,0 +1,301 @@ | |||||
| 1 | + | // Package testserver is a minimal in-process Wayland compositor for tests: a | |||
| 2 | + | // real libwayland server (purego-libwayland) that speaks wl_output, wl_shm, | |||
| 3 | + | // ext-image-capture-source-v1, ext-image-copy-capture-v1, ext-workspace-v1 and | |||
| 4 | + | // NeferWL's capture extension. It is a protocol peer, not a substitute for a | |||
| 5 | + | // project type: clients connect to its socket like to any compositor. | |||
| 6 | + | // | |||
| 7 | + | // All server state is touched only on the display goroutine: handlers run | |||
| 8 | + | // there, and tests reach it through Do. | |||
| 9 | + | package testserver | |||
| 10 | + | ||||
| 11 | + | import ( | |||
| 12 | + | "context" | |||
| 13 | + | "os" | |||
| 14 | + | "path/filepath" | |||
| 15 | + | "testing" | |||
| 16 | + | ||||
| 17 | + | "github.com/bnema/purego-libwayland/protocol/extimagecapturesource" | |||
| 18 | + | "github.com/bnema/purego-libwayland/protocol/extimagecopycapture" | |||
| 19 | + | "github.com/bnema/purego-libwayland/protocol/extworkspace" | |||
| 20 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 21 | + | "github.com/bnema/purego-libwayland/server" | |||
| 22 | + | "golang.org/x/sys/unix" | |||
| 23 | + | ||||
| 24 | + | "github.com/bnema/nefercap/internal/adapters/wayland/testserver/neferwl" | |||
| 25 | + | ) | |||
| 26 | + | ||||
| 27 | + | // Token is the exclusion token the server hands out. | |||
| 28 | + | const Token = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" | |||
| 29 | + | ||||
| 30 | + | // wl_shm formats. | |||
| 31 | + | const ( | |||
| 32 | + | ARGB8888 = 0 | |||
| 33 | + | XRGB8888 = 1 | |||
| 34 | + | ) | |||
| 35 | + | ||||
| 36 | + | // OutputSpec describes one wl_output. The capture buffer of an output source | |||
| 37 | + | // is its mode size. | |||
| 38 | + | type OutputSpec struct { | |||
| 39 | + | Name string | |||
| 40 | + | Width, Height uint32 | |||
| 41 | + | Scale int32 | |||
| 42 | + | Version uint32 // 0 selects 4 | |||
| 43 | + | // LogicalWidth/Height, when set, are announced through xdg-output. | |||
| 44 | + | LogicalWidth, LogicalHeight int32 | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | // WorkspaceSpec describes one ext-workspace handle; all of them live in one | |||
| 48 | + | // group that holds every output. | |||
| 49 | + | type WorkspaceSpec struct { | |||
| 50 | + | ID string // empty: a temporary workspace, no id event | |||
| 51 | + | Name string | |||
| 52 | + | Output string // the output whose buffer size a workspace source has | |||
| 53 | + | Active bool | |||
| 54 | + | Hidden bool | |||
| 55 | + | // Frame is the output-local logical rectangle (x, y, width, height) that | |||
| 56 | + | // neferwl_workspace_frame_v1 reports; zero reports nothing. | |||
| 57 | + | Frame [4]int32 | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | // Config selects what the server offers. | |||
| 61 | + | type Config struct { | |||
| 62 | + | Outputs []OutputSpec // default: one 8x4 output "TEST-1" | |||
| 63 | + | // NoCopyCapture leaves out ext-image-copy-capture and the output source. | |||
| 64 | + | NoCopyCapture bool | |||
| 65 | + | Formats []uint32 // shm formats announced, default XRGB8888 | |||
| 66 | + | // NoShmFormat announces size and done but no shm_format. | |||
| 67 | + | NoShmFormat bool | |||
| 68 | + | // Hold keeps every captured frame pending until Release. | |||
| 69 | + | Hold bool | |||
| 70 | + | // HoldAfterFirst answers the first frame of each session at once and holds | |||
| 71 | + | // the following ones until Release, like a compositor that waits for | |||
| 72 | + | // damage on a static screen. | |||
| 73 | + | HoldAfterFirst bool | |||
| 74 | + | // Workspaces enables ext-workspace-v1 when non-nil (possibly empty). | |||
| 75 | + | Workspaces []WorkspaceSpec | |||
| 76 | + | HasWorkspaces bool | |||
| 77 | + | // NeferwlSource and NeferwlExclusion enable the extension's two globals. | |||
| 78 | + | NeferwlSource bool | |||
| 79 | + | NeferwlExclusion bool | |||
| 80 | + | // ExclusionFail answers get_exclusion with failed(ExclusionFailReason-1) | |||
| 81 | + | // when non-zero. | |||
| 82 | + | ExclusionFail uint32 | |||
| 83 | + | // AttachFail answers attach_surface with failed(AttachFail-1) when non-zero. | |||
| 84 | + | AttachFail uint32 | |||
| 85 | + | // AttachSilent never answers attach_surface. | |||
| 86 | + | AttachSilent bool | |||
| 87 | + | // StopAtCreate answers every create_session with stopped and no | |||
| 88 | + | // constraints, like a compositor that refuses the client. | |||
| 89 | + | StopAtCreate bool | |||
| 90 | + | // Transform is reported by every ready frame (wl_output.transform). | |||
| 91 | + | Transform uint32 | |||
| 92 | + | // FailFrames answers that many capture requests with failed(FailReason). | |||
| 93 | + | FailFrames int | |||
| 94 | + | FailReason uint32 | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | // Stats are counters observed by tests. | |||
| 98 | + | type Stats struct { | |||
| 99 | + | Sessions, Frames, Captures, Ready int | |||
| 100 | + | DestroyedSessions int | |||
| 101 | + | Buffers, DestroyedBuffers int | |||
| 102 | + | LastSource string // "output", "region", "workspace" | |||
| 103 | + | LastRegion [4]int32 | |||
| 104 | + | Exclusions int | |||
| 105 | + | Attached []string | |||
| 106 | + | DestroyedExclusions int | |||
| 107 | + | WorkspaceFrames int // neferwl_workspace_frame_v1 objects created | |||
| 108 | + | DestroyedWorkspaceFrames int | |||
| 109 | + | DestroyedXdgOutputs int | |||
| 110 | + | } | |||
| 111 | + | ||||
| 112 | + | // Server is a running test compositor. | |||
| 113 | + | type Server struct { | |||
| 114 | + | // Path is the socket path for wayland.New. | |||
| 115 | + | Path string | |||
| 116 | + | ||||
| 117 | + | d *server.Display | |||
| 118 | + | cfg Config | |||
| 119 | + | ||||
| 120 | + | outputs []*output | |||
| 121 | + | bufs map[*server.Resource]*buffer | |||
| 122 | + | sources map[*server.Resource]*source | |||
| 123 | + | sessions []*session | |||
| 124 | + | workspaces []*workspace | |||
| 125 | + | wsMgrs []*wsManager | |||
| 126 | + | layers []*server.Resource | |||
| 127 | + | frames []*frameObject | |||
| 128 | + | stats Stats | |||
| 129 | + | held []*frame | |||
| 130 | + | seq uint32 | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | type output struct { | |||
| 134 | + | spec OutputSpec | |||
| 135 | + | global *server.Global | |||
| 136 | + | res []*wayland.Output | |||
| 137 | + | removed bool | |||
| 138 | + | } | |||
| 139 | + | ||||
| 140 | + | // Start runs a compositor on a fresh socket until the test ends. | |||
| 141 | + | func Start(t testing.TB, cfg Config) *Server { | |||
| 142 | + | t.Helper() | |||
| 143 | + | if len(cfg.Outputs) == 0 { | |||
| 144 | + | cfg.Outputs = []OutputSpec{{Name: "TEST-1", Width: 8, Height: 4, Scale: 1}} | |||
| 145 | + | } | |||
| 146 | + | if len(cfg.Formats) == 0 { | |||
| 147 | + | cfg.Formats = []uint32{XRGB8888} | |||
| 148 | + | } | |||
| 149 | + | d, err := server.NewDisplay() | |||
| 150 | + | if err != nil { | |||
| 151 | + | // NEFERCAP_REQUIRE_WAYLAND=1 (set by make test and make race) turns a | |||
| 152 | + | // missing libwayland-server into a failure, so the suite cannot pass | |||
| 153 | + | // without its protocol tests. | |||
| 154 | + | if os.Getenv("NEFERCAP_REQUIRE_WAYLAND") == "1" { | |||
| 155 | + | t.Fatalf("libwayland-server unavailable: %v", err) | |||
| 156 | + | } | |||
| 157 | + | t.Skipf("libwayland-server unavailable: %v", err) | |||
| 158 | + | } | |||
| 159 | + | dir, err := filepath.EvalSymlinks(t.TempDir()) | |||
| 160 | + | if err != nil { | |||
| 161 | + | t.Fatal(err) | |||
| 162 | + | } | |||
| 163 | + | path := filepath.Join(dir, "wl-test") | |||
| 164 | + | fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) | |||
| 165 | + | if err != nil { | |||
| 166 | + | t.Fatal(err) | |||
| 167 | + | } | |||
| 168 | + | if err := unix.Bind(fd, &unix.SockaddrUnix{Name: path}); err != nil { | |||
| 169 | + | t.Fatal(err) | |||
| 170 | + | } | |||
| 171 | + | if err := unix.Listen(fd, 16); err != nil { | |||
| 172 | + | t.Fatal(err) | |||
| 173 | + | } | |||
| 174 | + | if err := d.AddSocketFD(fd); err != nil { // libwayland owns fd now | |||
| 175 | + | t.Fatal(err) | |||
| 176 | + | } | |||
| 177 | + | s := &Server{Path: path, d: d, cfg: cfg, bufs: map[*server.Resource]*buffer{}, sources: map[*server.Resource]*source{}} | |||
| 178 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 179 | + | done := make(chan error, 1) | |||
| 180 | + | go func() { done <- d.Run(ctx) }() | |||
| 181 | + | if !d.Do(func() { s.setup(t) }) { | |||
| 182 | + | t.Fatal("test compositor stopped") | |||
| 183 | + | } | |||
| 184 | + | t.Cleanup(func() { | |||
| 185 | + | cancel() | |||
| 186 | + | <-done | |||
| 187 | + | }) | |||
| 188 | + | return s | |||
| 189 | + | } | |||
| 190 | + | ||||
| 191 | + | // Do runs f on the display goroutine, where server state may be touched. | |||
| 192 | + | func (s *Server) Do(f func()) { | |||
| 193 | + | if !s.d.Do(f) { | |||
| 194 | + | // stopped: nothing to observe | |||
| 195 | + | return | |||
| 196 | + | } | |||
| 197 | + | } | |||
| 198 | + | ||||
| 199 | + | // Stats returns a snapshot of the counters. | |||
| 200 | + | func (s *Server) Stats() Stats { | |||
| 201 | + | var st Stats | |||
| 202 | + | s.Do(func() { | |||
| 203 | + | st = s.stats | |||
| 204 | + | st.Attached = append([]string(nil), s.stats.Attached...) | |||
| 205 | + | }) | |||
| 206 | + | return st | |||
| 207 | + | } | |||
| 208 | + | ||||
| 209 | + | func (s *Server) setup(t testing.TB) { | |||
| 210 | + | must := func(err error) { | |||
| 211 | + | if err != nil { | |||
| 212 | + | t.Error(err) | |||
| 213 | + | } | |||
| 214 | + | } | |||
| 215 | + | for _, spec := range s.cfg.Outputs { | |||
| 216 | + | s.addOutput(spec, must) | |||
| 217 | + | } | |||
| 218 | + | must(wayland.NewShmGlobal(s.d, 1, func(c server.Client, v, id uint32) { | |||
| 219 | + | shm, err := wayland.NewShm(c, int32(v), id, shmHandler{s}) | |||
| 220 | + | if err != nil { | |||
| 221 | + | t.Error(err) | |||
| 222 | + | return | |||
| 223 | + | } | |||
| 224 | + | for _, f := range []uint32{ARGB8888, XRGB8888} { | |||
| 225 | + | shm.SendFormat(f) | |||
| 226 | + | } | |||
| 227 | + | })) | |||
| 228 | + | if s.xdgOutputWanted() { | |||
| 229 | + | s.addXdgOutput(must) | |||
| 230 | + | } | |||
| 231 | + | must(wayland.NewCompositorGlobal(s.d, 4, func(c server.Client, v, id uint32) { | |||
| 232 | + | if _, err := wayland.NewCompositor(c, int32(v), id, compositorHandler{}); err != nil { | |||
| 233 | + | t.Error(err) | |||
| 234 | + | } | |||
| 235 | + | })) | |||
| 236 | + | if !s.cfg.NoCopyCapture { | |||
| 237 | + | must(extimagecapturesource.NewExtOutputImageCaptureSourceManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |||
| 238 | + | if _, err := extimagecapturesource.NewExtOutputImageCaptureSourceManagerV1(c, int32(v), id, outputSourceManager{s}); err != nil { | |||
| 239 | + | t.Error(err) | |||
| 240 | + | } | |||
| 241 | + | })) | |||
| 242 | + | must(extimagecopycapture.NewExtImageCopyCaptureManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |||
| 243 | + | if _, err := extimagecopycapture.NewExtImageCopyCaptureManagerV1(c, int32(v), id, copyManager{s}); err != nil { | |||
| 244 | + | t.Error(err) | |||
| 245 | + | } | |||
| 246 | + | })) | |||
| 247 | + | } | |||
| 248 | + | if s.cfg.HasWorkspaces { | |||
| 249 | + | for _, w := range s.cfg.Workspaces { | |||
| 250 | + | s.workspaces = append(s.workspaces, &workspace{spec: w}) | |||
| 251 | + | } | |||
| 252 | + | must(extworkspace.NewExtWorkspaceManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |||
| 253 | + | m := &wsManager{s: s} | |||
| 254 | + | res, err := extworkspace.NewExtWorkspaceManagerV1(c, int32(v), id, m) | |||
| 255 | + | if err != nil { | |||
| 256 | + | t.Error(err) | |||
| 257 | + | return | |||
| 258 | + | } | |||
| 259 | + | m.res = res | |||
| 260 | + | s.wsMgrs = append(s.wsMgrs, m) | |||
| 261 | + | m.announce(c) | |||
| 262 | + | })) | |||
| 263 | + | } | |||
| 264 | + | if s.cfg.NeferwlSource { | |||
| 265 | + | must(neferwl.NewNeferwlImageCaptureSourceManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |||
| 266 | + | if _, err := neferwl.NewNeferwlImageCaptureSourceManagerV1(c, int32(v), id, neferwlSources{s}); err != nil { | |||
| 267 | + | t.Error(err) | |||
| 268 | + | } | |||
| 269 | + | })) | |||
| 270 | + | } | |||
| 271 | + | if s.cfg.NeferwlExclusion { | |||
| 272 | + | must(neferwl.NewNeferwlCaptureExclusionManagerV1Global(s.d, 1, func(c server.Client, v, id uint32) { | |||
| 273 | + | if _, err := neferwl.NewNeferwlCaptureExclusionManagerV1(c, int32(v), id, exclusionManager{s}); err != nil { | |||
| 274 | + | t.Error(err) | |||
| 275 | + | } | |||
| 276 | + | })) | |||
| 277 | + | } | |||
| 278 | + | } | |||
| 279 | + | ||||
| 280 | + | type compositorHandler struct{} | |||
| 281 | + | ||||
| 282 | + | func (compositorHandler) CreateSurface(self *wayland.Compositor, id uint32) { | |||
| 283 | + | _, _ = wayland.NewSurface(self.Client(), 4, id, surfaceHandler{}) | |||
| 284 | + | } | |||
| 285 | + | func (compositorHandler) CreateRegion(*wayland.Compositor, uint32) {} | |||
| 286 | + | func (compositorHandler) Release(*wayland.Compositor) {} | |||
| 287 | + | ||||
| 288 | + | type surfaceHandler struct{} | |||
| 289 | + | ||||
| 290 | + | func (surfaceHandler) Destroy(*wayland.Surface) {} | |||
| 291 | + | func (surfaceHandler) Attach(*wayland.Surface, *wayland.Buffer, int32, int32) {} | |||
| 292 | + | func (surfaceHandler) Damage(*wayland.Surface, int32, int32, int32, int32) {} | |||
| 293 | + | func (surfaceHandler) Frame(*wayland.Surface, uint32) {} | |||
| 294 | + | func (surfaceHandler) SetOpaqueRegion(*wayland.Surface, *wayland.Region) {} | |||
| 295 | + | func (surfaceHandler) SetInputRegion(*wayland.Surface, *wayland.Region) {} | |||
| 296 | + | func (surfaceHandler) Commit(*wayland.Surface) {} | |||
| 297 | + | func (surfaceHandler) SetBufferTransform(*wayland.Surface, int32) {} | |||
| 298 | + | func (surfaceHandler) SetBufferScale(*wayland.Surface, int32) {} | |||
| 299 | + | func (surfaceHandler) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | |||
| 300 | + | func (surfaceHandler) Offset(*wayland.Surface, int32, int32) {} | |||
| 301 | + | func (surfaceHandler) GetRelease(*wayland.Surface, uint32) {} | |||
A internal/adapters/wayland/testserver/workspaces.go +239 -0
| @@ -0,0 +1,239 @@ | |||
| 1 | + | package testserver | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "encoding/binary" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/purego-libwayland/protocol/extworkspace" | |
| 7 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 8 | + | "github.com/bnema/purego-libwayland/server" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/nefercap/internal/adapters/wayland/testserver/neferwl" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | type workspace struct { | |
| 14 | + | spec WorkspaceSpec | |
| 15 | + | removed bool | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | type wsHandle struct { | |
| 19 | + | w *workspace | |
| 20 | + | res *extworkspace.ExtWorkspaceHandleV1 | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | type wsManager struct { | |
| 24 | + | s *Server | |
| 25 | + | res *extworkspace.ExtWorkspaceManagerV1 | |
| 26 | + | group *extworkspace.ExtWorkspaceGroupHandleV1 | |
| 27 | + | handles []*wsHandle | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | func (m *wsManager) Commit(*extworkspace.ExtWorkspaceManagerV1) {} | |
| 31 | + | func (m *wsManager) Stop(*extworkspace.ExtWorkspaceManagerV1) {} | |
| 32 | + | ||
| 33 | + | type groupHandler struct{} | |
| 34 | + | ||
| 35 | + | func (groupHandler) CreateWorkspace(*extworkspace.ExtWorkspaceGroupHandleV1, string) {} | |
| 36 | + | func (groupHandler) Destroy(*extworkspace.ExtWorkspaceGroupHandleV1) {} | |
| 37 | + | ||
| 38 | + | type handleHandler struct{} | |
| 39 | + | ||
| 40 | + | func (handleHandler) Destroy(*extworkspace.ExtWorkspaceHandleV1) {} | |
| 41 | + | func (handleHandler) Activate(*extworkspace.ExtWorkspaceHandleV1) {} | |
| 42 | + | func (handleHandler) Deactivate(*extworkspace.ExtWorkspaceHandleV1) {} | |
| 43 | + | func (handleHandler) Assign(*extworkspace.ExtWorkspaceHandleV1, *extworkspace.ExtWorkspaceGroupHandleV1) { | |
| 44 | + | } | |
| 45 | + | func (handleHandler) Remove(*extworkspace.ExtWorkspaceHandleV1) {} | |
| 46 | + | ||
| 47 | + | func stateBits(w WorkspaceSpec) uint32 { | |
| 48 | + | var st uint32 | |
| 49 | + | if w.Active { | |
| 50 | + | st |= uint32(extworkspace.ExtWorkspaceHandleV1StateActive) | |
| 51 | + | } | |
| 52 | + | if w.Hidden { | |
| 53 | + | st |= uint32(extworkspace.ExtWorkspaceHandleV1StateHidden) | |
| 54 | + | } | |
| 55 | + | return st | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | // announce sends the group, its outputs and every workspace, then done. | |
| 59 | + | func (m *wsManager) announce(c server.Client) { | |
| 60 | + | g, err := extworkspace.NewExtWorkspaceGroupHandleV1(c, 1, 0, groupHandler{}) | |
| 61 | + | if err != nil { | |
| 62 | + | return | |
| 63 | + | } | |
| 64 | + | m.group = g | |
| 65 | + | m.res.SendWorkspaceGroup(g) | |
| 66 | + | g.SendCapabilities(0) | |
| 67 | + | for _, o := range m.s.outputs { | |
| 68 | + | for _, r := range o.res { | |
| 69 | + | if r.Client() == c { | |
| 70 | + | g.SendOutputEnter(r) | |
| 71 | + | } | |
| 72 | + | } | |
| 73 | + | } | |
| 74 | + | for i, w := range m.s.workspaces { | |
| 75 | + | if !w.removed { | |
| 76 | + | m.add(c, g, w, i) | |
| 77 | + | } | |
| 78 | + | } | |
| 79 | + | m.res.SendDone() | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | func (m *wsManager) add(c server.Client, g *extworkspace.ExtWorkspaceGroupHandleV1, w *workspace, index int) { | |
| 83 | + | r, err := extworkspace.NewExtWorkspaceHandleV1(c, 1, 0, handleHandler{}) | |
| 84 | + | if err != nil { | |
| 85 | + | return | |
| 86 | + | } | |
| 87 | + | m.handles = append(m.handles, &wsHandle{w: w, res: r}) | |
| 88 | + | m.res.SendWorkspace(r) | |
| 89 | + | if w.spec.ID != "" { | |
| 90 | + | r.SendId(w.spec.ID) | |
| 91 | + | } | |
| 92 | + | r.SendName(w.spec.Name) | |
| 93 | + | var b [4]byte | |
| 94 | + | binary.NativeEndian.PutUint32(b[:], uint32(index)) | |
| 95 | + | r.SendCoordinates(b[:]) | |
| 96 | + | r.SendState(stateBits(w.spec)) | |
| 97 | + | r.SendCapabilities(0) | |
| 98 | + | g.SendWorkspaceEnter(r) | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | func (s *Server) workspaceNamed(name string) *workspace { | |
| 102 | + | for _, w := range s.workspaces { | |
| 103 | + | if w.spec.Name == name { | |
| 104 | + | return w | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | return nil | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | // SetActive changes the active state of the named workspace. | |
| 111 | + | func (s *Server) SetActive(name string, active bool) { | |
| 112 | + | s.Do(func() { | |
| 113 | + | w := s.workspaceNamed(name) | |
| 114 | + | if w == nil { | |
| 115 | + | return | |
| 116 | + | } | |
| 117 | + | w.spec.Active = active | |
| 118 | + | for _, m := range s.wsMgrs { | |
| 119 | + | for _, h := range m.handles { | |
| 120 | + | if h.w == w && !w.removed { | |
| 121 | + | h.res.SendState(stateBits(w.spec)) | |
| 122 | + | } | |
| 123 | + | } | |
| 124 | + | m.res.SendDone() | |
| 125 | + | } | |
| 126 | + | }) | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | // RemoveWorkspace removes the named workspace from every manager. | |
| 130 | + | func (s *Server) RemoveWorkspace(name string) { | |
| 131 | + | s.Do(func() { | |
| 132 | + | w := s.workspaceNamed(name) | |
| 133 | + | if w == nil || w.removed { | |
| 134 | + | return | |
| 135 | + | } | |
| 136 | + | w.removed = true | |
| 137 | + | for _, m := range s.wsMgrs { | |
| 138 | + | for _, h := range m.handles { | |
| 139 | + | if h.w == w { | |
| 140 | + | m.group.SendWorkspaceLeave(h.res) | |
| 141 | + | h.res.SendRemoved() | |
| 142 | + | } | |
| 143 | + | } | |
| 144 | + | m.res.SendDone() | |
| 145 | + | } | |
| 146 | + | for _, c := range s.sessions { | |
| 147 | + | if c.src.ws == w { | |
| 148 | + | c.stop() | |
| 149 | + | } | |
| 150 | + | } | |
| 151 | + | }) | |
| 152 | + | } | |
| 153 | + | ||
| 154 | + | // workspaceOf finds the workspace behind a handle resource. | |
| 155 | + | func (s *Server) workspaceOf(r *server.Resource) *workspace { | |
| 156 | + | for _, m := range s.wsMgrs { | |
| 157 | + | for _, h := range m.handles { | |
| 158 | + | if h.res.Resource == r { | |
| 159 | + | return h.w | |
| 160 | + | } | |
| 161 | + | } | |
| 162 | + | } | |
| 163 | + | return nil | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | type neferwlSources struct{ s *Server } | |
| 167 | + | ||
| 168 | + | // frameObject is one neferwl_workspace_frame_v1. | |
| 169 | + | type frameObject struct { | |
| 170 | + | w *workspace | |
| 171 | + | res *neferwl.NeferwlWorkspaceFrameV1 | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | func (frameObject) Destroy(*neferwl.NeferwlWorkspaceFrameV1) {} | |
| 175 | + | ||
| 176 | + | func (f *frameObject) send() { | |
| 177 | + | if r := f.w.spec.Frame; r != ([4]int32{}) { | |
| 178 | + | f.res.SendFrame(r[0], r[1], r[2], r[3]) | |
| 179 | + | } | |
| 180 | + | } | |
| 181 | + | ||
| 182 | + | func (m neferwlSources) GetWorkspaceFrame(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, h *extworkspace.ExtWorkspaceHandleV1) { | |
| 183 | + | s := m.s | |
| 184 | + | f := &frameObject{w: s.workspaceOf(h.Resource)} | |
| 185 | + | res, err := neferwl.NewNeferwlWorkspaceFrameV1(self.Client(), 1, id, f) | |
| 186 | + | if err != nil { | |
| 187 | + | return | |
| 188 | + | } | |
| 189 | + | f.res = res | |
| 190 | + | s.stats.WorkspaceFrames++ | |
| 191 | + | res.OnDestroy = func() { | |
| 192 | + | s.stats.DestroyedWorkspaceFrames++ | |
| 193 | + | s.frames = removeFrame(s.frames, f) | |
| 194 | + | } | |
| 195 | + | if f.w == nil || f.w.removed { | |
| 196 | + | return | |
| 197 | + | } | |
| 198 | + | s.frames = append(s.frames, f) | |
| 199 | + | f.send() | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | func removeFrame(list []*frameObject, f *frameObject) []*frameObject { | |
| 203 | + | for i, g := range list { | |
| 204 | + | if g == f { | |
| 205 | + | return append(list[:i], list[i+1:]...) | |
| 206 | + | } | |
| 207 | + | } | |
| 208 | + | return list | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | // SetFrame changes the frame of the named workspace and reports it to every | |
| 212 | + | // frame object that watches it. | |
| 213 | + | func (s *Server) SetFrame(name string, frame [4]int32) { | |
| 214 | + | s.Do(func() { | |
| 215 | + | w := s.workspaceNamed(name) | |
| 216 | + | if w == nil { | |
| 217 | + | return | |
| 218 | + | } | |
| 219 | + | w.spec.Frame = frame | |
| 220 | + | for _, f := range s.frames { | |
| 221 | + | if f.w == w && !w.removed { | |
| 222 | + | f.send() | |
| 223 | + | } | |
| 224 | + | } | |
| 225 | + | }) | |
| 226 | + | } | |
| 227 | + | ||
| 228 | + | func (neferwlSources) Destroy(*neferwl.NeferwlImageCaptureSourceManagerV1) {} | |
| 229 | + | func (m neferwlSources) CreateWorkspaceSource(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, h *extworkspace.ExtWorkspaceHandleV1) { | |
| 230 | + | w := m.s.workspaceOf(h.Resource) | |
| 231 | + | m.s.newSource(self.Client(), id, &source{kind: "workspace", ws: w, gone: w == nil || w.removed}) | |
| 232 | + | } | |
| 233 | + | func (m neferwlSources) CreateOutputRegionSource(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, out *wayland.Output, x, y, w, h int32) { | |
| 234 | + | if w < 1 || h < 1 { | |
| 235 | + | self.PostError(uint32(neferwl.NeferwlImageCaptureSourceManagerV1ErrorInvalidRegion), "empty region") | |
| 236 | + | return | |
| 237 | + | } | |
| 238 | + | m.s.newSource(self.Client(), id, &source{kind: "region", out: m.s.outputOf(out.Resource), region: [4]int32{x, y, w, h}}) | |
| 239 | + | } | |
| @@ -0,0 +1,239 @@ | |||||
| 1 | + | package testserver | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "encoding/binary" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/purego-libwayland/protocol/extworkspace" | |||
| 7 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 8 | + | "github.com/bnema/purego-libwayland/server" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/nefercap/internal/adapters/wayland/testserver/neferwl" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | type workspace struct { | |||
| 14 | + | spec WorkspaceSpec | |||
| 15 | + | removed bool | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | type wsHandle struct { | |||
| 19 | + | w *workspace | |||
| 20 | + | res *extworkspace.ExtWorkspaceHandleV1 | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | type wsManager struct { | |||
| 24 | + | s *Server | |||
| 25 | + | res *extworkspace.ExtWorkspaceManagerV1 | |||
| 26 | + | group *extworkspace.ExtWorkspaceGroupHandleV1 | |||
| 27 | + | handles []*wsHandle | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | func (m *wsManager) Commit(*extworkspace.ExtWorkspaceManagerV1) {} | |||
| 31 | + | func (m *wsManager) Stop(*extworkspace.ExtWorkspaceManagerV1) {} | |||
| 32 | + | ||||
| 33 | + | type groupHandler struct{} | |||
| 34 | + | ||||
| 35 | + | func (groupHandler) CreateWorkspace(*extworkspace.ExtWorkspaceGroupHandleV1, string) {} | |||
| 36 | + | func (groupHandler) Destroy(*extworkspace.ExtWorkspaceGroupHandleV1) {} | |||
| 37 | + | ||||
| 38 | + | type handleHandler struct{} | |||
| 39 | + | ||||
| 40 | + | func (handleHandler) Destroy(*extworkspace.ExtWorkspaceHandleV1) {} | |||
| 41 | + | func (handleHandler) Activate(*extworkspace.ExtWorkspaceHandleV1) {} | |||
| 42 | + | func (handleHandler) Deactivate(*extworkspace.ExtWorkspaceHandleV1) {} | |||
| 43 | + | func (handleHandler) Assign(*extworkspace.ExtWorkspaceHandleV1, *extworkspace.ExtWorkspaceGroupHandleV1) { | |||
| 44 | + | } | |||
| 45 | + | func (handleHandler) Remove(*extworkspace.ExtWorkspaceHandleV1) {} | |||
| 46 | + | ||||
| 47 | + | func stateBits(w WorkspaceSpec) uint32 { | |||
| 48 | + | var st uint32 | |||
| 49 | + | if w.Active { | |||
| 50 | + | st |= uint32(extworkspace.ExtWorkspaceHandleV1StateActive) | |||
| 51 | + | } | |||
| 52 | + | if w.Hidden { | |||
| 53 | + | st |= uint32(extworkspace.ExtWorkspaceHandleV1StateHidden) | |||
| 54 | + | } | |||
| 55 | + | return st | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | // announce sends the group, its outputs and every workspace, then done. | |||
| 59 | + | func (m *wsManager) announce(c server.Client) { | |||
| 60 | + | g, err := extworkspace.NewExtWorkspaceGroupHandleV1(c, 1, 0, groupHandler{}) | |||
| 61 | + | if err != nil { | |||
| 62 | + | return | |||
| 63 | + | } | |||
| 64 | + | m.group = g | |||
| 65 | + | m.res.SendWorkspaceGroup(g) | |||
| 66 | + | g.SendCapabilities(0) | |||
| 67 | + | for _, o := range m.s.outputs { | |||
| 68 | + | for _, r := range o.res { | |||
| 69 | + | if r.Client() == c { | |||
| 70 | + | g.SendOutputEnter(r) | |||
| 71 | + | } | |||
| 72 | + | } | |||
| 73 | + | } | |||
| 74 | + | for i, w := range m.s.workspaces { | |||
| 75 | + | if !w.removed { | |||
| 76 | + | m.add(c, g, w, i) | |||
| 77 | + | } | |||
| 78 | + | } | |||
| 79 | + | m.res.SendDone() | |||
| 80 | + | } | |||
| 81 | + | ||||
| 82 | + | func (m *wsManager) add(c server.Client, g *extworkspace.ExtWorkspaceGroupHandleV1, w *workspace, index int) { | |||
| 83 | + | r, err := extworkspace.NewExtWorkspaceHandleV1(c, 1, 0, handleHandler{}) | |||
| 84 | + | if err != nil { | |||
| 85 | + | return | |||
| 86 | + | } | |||
| 87 | + | m.handles = append(m.handles, &wsHandle{w: w, res: r}) | |||
| 88 | + | m.res.SendWorkspace(r) | |||
| 89 | + | if w.spec.ID != "" { | |||
| 90 | + | r.SendId(w.spec.ID) | |||
| 91 | + | } | |||
| 92 | + | r.SendName(w.spec.Name) | |||
| 93 | + | var b [4]byte | |||
| 94 | + | binary.NativeEndian.PutUint32(b[:], uint32(index)) | |||
| 95 | + | r.SendCoordinates(b[:]) | |||
| 96 | + | r.SendState(stateBits(w.spec)) | |||
| 97 | + | r.SendCapabilities(0) | |||
| 98 | + | g.SendWorkspaceEnter(r) | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | func (s *Server) workspaceNamed(name string) *workspace { | |||
| 102 | + | for _, w := range s.workspaces { | |||
| 103 | + | if w.spec.Name == name { | |||
| 104 | + | return w | |||
| 105 | + | } | |||
| 106 | + | } | |||
| 107 | + | return nil | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | // SetActive changes the active state of the named workspace. | |||
| 111 | + | func (s *Server) SetActive(name string, active bool) { | |||
| 112 | + | s.Do(func() { | |||
| 113 | + | w := s.workspaceNamed(name) | |||
| 114 | + | if w == nil { | |||
| 115 | + | return | |||
| 116 | + | } | |||
| 117 | + | w.spec.Active = active | |||
| 118 | + | for _, m := range s.wsMgrs { | |||
| 119 | + | for _, h := range m.handles { | |||
| 120 | + | if h.w == w && !w.removed { | |||
| 121 | + | h.res.SendState(stateBits(w.spec)) | |||
| 122 | + | } | |||
| 123 | + | } | |||
| 124 | + | m.res.SendDone() | |||
| 125 | + | } | |||
| 126 | + | }) | |||
| 127 | + | } | |||
| 128 | + | ||||
| 129 | + | // RemoveWorkspace removes the named workspace from every manager. | |||
| 130 | + | func (s *Server) RemoveWorkspace(name string) { | |||
| 131 | + | s.Do(func() { | |||
| 132 | + | w := s.workspaceNamed(name) | |||
| 133 | + | if w == nil || w.removed { | |||
| 134 | + | return | |||
| 135 | + | } | |||
| 136 | + | w.removed = true | |||
| 137 | + | for _, m := range s.wsMgrs { | |||
| 138 | + | for _, h := range m.handles { | |||
| 139 | + | if h.w == w { | |||
| 140 | + | m.group.SendWorkspaceLeave(h.res) | |||
| 141 | + | h.res.SendRemoved() | |||
| 142 | + | } | |||
| 143 | + | } | |||
| 144 | + | m.res.SendDone() | |||
| 145 | + | } | |||
| 146 | + | for _, c := range s.sessions { | |||
| 147 | + | if c.src.ws == w { | |||
| 148 | + | c.stop() | |||
| 149 | + | } | |||
| 150 | + | } | |||
| 151 | + | }) | |||
| 152 | + | } | |||
| 153 | + | ||||
| 154 | + | // workspaceOf finds the workspace behind a handle resource. | |||
| 155 | + | func (s *Server) workspaceOf(r *server.Resource) *workspace { | |||
| 156 | + | for _, m := range s.wsMgrs { | |||
| 157 | + | for _, h := range m.handles { | |||
| 158 | + | if h.res.Resource == r { | |||
| 159 | + | return h.w | |||
| 160 | + | } | |||
| 161 | + | } | |||
| 162 | + | } | |||
| 163 | + | return nil | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | type neferwlSources struct{ s *Server } | |||
| 167 | + | ||||
| 168 | + | // frameObject is one neferwl_workspace_frame_v1. | |||
| 169 | + | type frameObject struct { | |||
| 170 | + | w *workspace | |||
| 171 | + | res *neferwl.NeferwlWorkspaceFrameV1 | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | func (frameObject) Destroy(*neferwl.NeferwlWorkspaceFrameV1) {} | |||
| 175 | + | ||||
| 176 | + | func (f *frameObject) send() { | |||
| 177 | + | if r := f.w.spec.Frame; r != ([4]int32{}) { | |||
| 178 | + | f.res.SendFrame(r[0], r[1], r[2], r[3]) | |||
| 179 | + | } | |||
| 180 | + | } | |||
| 181 | + | ||||
| 182 | + | func (m neferwlSources) GetWorkspaceFrame(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, h *extworkspace.ExtWorkspaceHandleV1) { | |||
| 183 | + | s := m.s | |||
| 184 | + | f := &frameObject{w: s.workspaceOf(h.Resource)} | |||
| 185 | + | res, err := neferwl.NewNeferwlWorkspaceFrameV1(self.Client(), 1, id, f) | |||
| 186 | + | if err != nil { | |||
| 187 | + | return | |||
| 188 | + | } | |||
| 189 | + | f.res = res | |||
| 190 | + | s.stats.WorkspaceFrames++ | |||
| 191 | + | res.OnDestroy = func() { | |||
| 192 | + | s.stats.DestroyedWorkspaceFrames++ | |||
| 193 | + | s.frames = removeFrame(s.frames, f) | |||
| 194 | + | } | |||
| 195 | + | if f.w == nil || f.w.removed { | |||
| 196 | + | return | |||
| 197 | + | } | |||
| 198 | + | s.frames = append(s.frames, f) | |||
| 199 | + | f.send() | |||
| 200 | + | } | |||
| 201 | + | ||||
| 202 | + | func removeFrame(list []*frameObject, f *frameObject) []*frameObject { | |||
| 203 | + | for i, g := range list { | |||
| 204 | + | if g == f { | |||
| 205 | + | return append(list[:i], list[i+1:]...) | |||
| 206 | + | } | |||
| 207 | + | } | |||
| 208 | + | return list | |||
| 209 | + | } | |||
| 210 | + | ||||
| 211 | + | // SetFrame changes the frame of the named workspace and reports it to every | |||
| 212 | + | // frame object that watches it. | |||
| 213 | + | func (s *Server) SetFrame(name string, frame [4]int32) { | |||
| 214 | + | s.Do(func() { | |||
| 215 | + | w := s.workspaceNamed(name) | |||
| 216 | + | if w == nil { | |||
| 217 | + | return | |||
| 218 | + | } | |||
| 219 | + | w.spec.Frame = frame | |||
| 220 | + | for _, f := range s.frames { | |||
| 221 | + | if f.w == w && !w.removed { | |||
| 222 | + | f.send() | |||
| 223 | + | } | |||
| 224 | + | } | |||
| 225 | + | }) | |||
| 226 | + | } | |||
| 227 | + | ||||
| 228 | + | func (neferwlSources) Destroy(*neferwl.NeferwlImageCaptureSourceManagerV1) {} | |||
| 229 | + | func (m neferwlSources) CreateWorkspaceSource(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, h *extworkspace.ExtWorkspaceHandleV1) { | |||
| 230 | + | w := m.s.workspaceOf(h.Resource) | |||
| 231 | + | m.s.newSource(self.Client(), id, &source{kind: "workspace", ws: w, gone: w == nil || w.removed}) | |||
| 232 | + | } | |||
| 233 | + | func (m neferwlSources) CreateOutputRegionSource(self *neferwl.NeferwlImageCaptureSourceManagerV1, id uint32, out *wayland.Output, x, y, w, h int32) { | |||
| 234 | + | if w < 1 || h < 1 { | |||
| 235 | + | self.PostError(uint32(neferwl.NeferwlImageCaptureSourceManagerV1ErrorInvalidRegion), "empty region") | |||
| 236 | + | return | |||
| 237 | + | } | |||
| 238 | + | m.s.newSource(self.Client(), id, &source{kind: "region", out: m.s.outputOf(out.Resource), region: [4]int32{x, y, w, h}}) | |||
| 239 | + | } | |||
A internal/adapters/wayland/testserver/xdgoutput.go +45 -0
| @@ -0,0 +1,45 @@ | |||
| 1 | + | package testserver | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 5 | + | "github.com/bnema/purego-libwayland/protocol/xdgoutput" | |
| 6 | + | "github.com/bnema/purego-libwayland/server" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // xdg-output is offered only when an output announces a logical size. | |
| 10 | + | func (s *Server) xdgOutputWanted() bool { | |
| 11 | + | for _, o := range s.outputs { | |
| 12 | + | if o.spec.LogicalWidth > 0 { | |
| 13 | + | return true | |
| 14 | + | } | |
| 15 | + | } | |
| 16 | + | return false | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | func (s *Server) addXdgOutput(must func(error)) { | |
| 20 | + | must(xdgoutput.NewZxdgOutputManagerV1Global(s.d, 3, func(c server.Client, v, id uint32) { | |
| 21 | + | if _, err := xdgoutput.NewZxdgOutputManagerV1(c, int32(v), id, xdgManager{s}); err != nil { | |
| 22 | + | must(err) | |
| 23 | + | } | |
| 24 | + | })) | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | type xdgManager struct{ s *Server } | |
| 28 | + | ||
| 29 | + | func (xdgManager) Destroy(*xdgoutput.ZxdgOutputManagerV1) {} | |
| 30 | + | func (m xdgManager) GetXdgOutput(self *xdgoutput.ZxdgOutputManagerV1, id uint32, out *wayland.Output) { | |
| 31 | + | res, err := xdgoutput.NewZxdgOutputV1(self.Client(), int32(self.Version()), id, xdgHandler{}) | |
| 32 | + | if err != nil { | |
| 33 | + | return | |
| 34 | + | } | |
| 35 | + | res.OnDestroy = func() { m.s.stats.DestroyedXdgOutputs++ } | |
| 36 | + | if o := m.s.outputOf(out.Resource); o != nil && o.spec.LogicalWidth > 0 { | |
| 37 | + | res.SendLogicalPosition(0, 0) | |
| 38 | + | res.SendLogicalSize(o.spec.LogicalWidth, o.spec.LogicalHeight) | |
| 39 | + | res.SendDone() | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | type xdgHandler struct{} | |
| 44 | + | ||
| 45 | + | func (xdgHandler) Destroy(*xdgoutput.ZxdgOutputV1) {} | |
| @@ -0,0 +1,45 @@ | |||||
| 1 | + | package testserver | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 5 | + | "github.com/bnema/purego-libwayland/protocol/xdgoutput" | |||
| 6 | + | "github.com/bnema/purego-libwayland/server" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | // xdg-output is offered only when an output announces a logical size. | |||
| 10 | + | func (s *Server) xdgOutputWanted() bool { | |||
| 11 | + | for _, o := range s.outputs { | |||
| 12 | + | if o.spec.LogicalWidth > 0 { | |||
| 13 | + | return true | |||
| 14 | + | } | |||
| 15 | + | } | |||
| 16 | + | return false | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | func (s *Server) addXdgOutput(must func(error)) { | |||
| 20 | + | must(xdgoutput.NewZxdgOutputManagerV1Global(s.d, 3, func(c server.Client, v, id uint32) { | |||
| 21 | + | if _, err := xdgoutput.NewZxdgOutputManagerV1(c, int32(v), id, xdgManager{s}); err != nil { | |||
| 22 | + | must(err) | |||
| 23 | + | } | |||
| 24 | + | })) | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | type xdgManager struct{ s *Server } | |||
| 28 | + | ||||
| 29 | + | func (xdgManager) Destroy(*xdgoutput.ZxdgOutputManagerV1) {} | |||
| 30 | + | func (m xdgManager) GetXdgOutput(self *xdgoutput.ZxdgOutputManagerV1, id uint32, out *wayland.Output) { | |||
| 31 | + | res, err := xdgoutput.NewZxdgOutputV1(self.Client(), int32(self.Version()), id, xdgHandler{}) | |||
| 32 | + | if err != nil { | |||
| 33 | + | return | |||
| 34 | + | } | |||
| 35 | + | res.OnDestroy = func() { m.s.stats.DestroyedXdgOutputs++ } | |||
| 36 | + | if o := m.s.outputOf(out.Resource); o != nil && o.spec.LogicalWidth > 0 { | |||
| 37 | + | res.SendLogicalPosition(0, 0) | |||
| 38 | + | res.SendLogicalSize(o.spec.LogicalWidth, o.spec.LogicalHeight) | |||
| 39 | + | res.SendDone() | |||
| 40 | + | } | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | type xdgHandler struct{} | |||
| 44 | + | ||||
| 45 | + | func (xdgHandler) Destroy(*xdgoutput.ZxdgOutputV1) {} | |||