1package wayland
2
3import (
4 "errors"
5 "fmt"
6
7 "github.com/bnema/wlturbo"
8 "github.com/bnema/wlturbo/protocol/core"
9 "golang.org/x/sys/unix"
10
11 "github.com/bnema/nefercap/internal/ports"
12)
13
14// bufGeom is the buffer layout announced by the compositor. It is reused only
15// while the compositor keeps announcing the same one.
16type bufGeom struct {
17 format, width, height, stride uint32
18 size int
19}
20
21// validate bounds every dimension before any memory is created or mapped.
22func (g *bufGeom) validate() error {
23 if g.width < 1 || g.height < 1 || g.width > ports.MaxDimension || g.height > ports.MaxDimension {
24 return fmt.Errorf("wayland: invalid buffer dimensions %dx%d", g.width, g.height)
25 }
26 if g.stride < g.width*ports.BytesPerPixel || g.stride%ports.BytesPerPixel != 0 || g.stride > ports.MaxFrameBytes/g.height {
27 return fmt.Errorf("wayland: invalid buffer stride %d for %dx%d", g.stride, g.width, g.height)
28 }
29 g.size = int(g.stride) * int(g.height)
30 return nil
31}
32
33// shmBuffer is one wl_shm mapping and wl_buffer reused between captures. A
34// Source has two: one holds the last frame while the next is written. The pool
35// proxy is destroyed right after buffer creation, which the protocol allows:
36// the buffer keeps the compositor-side mapping alive.
37type shmBuffer struct {
38 geom bufGeom
39 data []byte
40 buffer *core.Buffer
41}
42
43// ensureBuffer makes s.bufs[i] match g, reusing it when unchanged.
44func (s *Source) ensureBuffer(i int, g bufGeom) error {
45 if s.bufs[i].buffer != nil && s.bufs[i].geom == g {
46 return nil
47 }
48 if err := s.releaseBuffer(i, true); err != nil {
49 s.terminate()
50 return err
51 }
52 fd, err := wlturbo.CreateAnonymousFile(int64(g.size))
53 if err != nil {
54 return fmt.Errorf("wayland: create shm file: %w", err)
55 }
56 data, err := wlturbo.MapMemory(fd, g.size)
57 if err != nil {
58 _ = unix.Close(fd)
59 return fmt.Errorf("wayland: map shm file: %w", err)
60 }
61 // Context.Request closes fd once the message is sent, so on success the
62 // descriptor belongs to the transport (the mapping outlives it). On error
63 // the caller still owns it.
64 pool, err := s.shm.CreatePool(fd, int32(g.size))
65 if err != nil {
66 _ = unix.Close(fd)
67 _ = wlturbo.UnmapMemory(data)
68 s.terminate()
69 return fmt.Errorf("wayland: create pool: %w", err)
70 }
71 buffer, err := pool.CreateBuffer(0, int32(g.width), int32(g.height), int32(g.stride), g.format)
72 _ = pool.Destroy()
73 if err != nil {
74 _ = wlturbo.UnmapMemory(data)
75 s.terminate()
76 return fmt.Errorf("wayland: create buffer: %w", err)
77 }
78 s.bufs[i] = shmBuffer{geom: g, data: data, buffer: buffer}
79 return nil
80}
81
82// releaseBuffer unmaps the storage of s.bufs[i]. With destroy set, and while
83// connected, it also destroys the wl_buffer. The caller holds opMu.
84func (s *Source) releaseBuffer(i int, destroy bool) error {
85 b := s.bufs[i]
86 s.bufs[i] = shmBuffer{}
87 var err error
88 if b.buffer != nil && destroy && !s.dead.Load() {
89 err = b.buffer.Destroy()
90 }
91 if b.data != nil {
92 err = errors.Join(err, wlturbo.UnmapMemory(b.data))
93 }
94 return err
95}
96
97// releaseBuffers releases both buffers without destroying the wl_buffers: the
98// connection is ending.
99func (s *Source) releaseBuffers() error {
100 return errors.Join(s.releaseBuffer(0, false), s.releaseBuffer(1, false))
101}