test: cover present ordering, feedback parsing, lock rules, headless surfaces and present allocations
- commit
- 71357f814bd2a0ea7300dcab04fbd8a9364e35c0
- author
- brice · · 2026-10-01 16:36 UTC
- parent
- d08c2b9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
9 files · +1053 -44
M .mockery.yml +5 -0
| @@ -13,3 +13,8 @@ packages: | |||
| 13 | 13 | filename: '{{.InterfaceFile | base | trimSuffix ".go"}}_mock.go' | |
| 14 | 14 | interfaces: | |
| 15 | 15 | Handler: | |
| 16 | + | surfaceRequests: | |
| 17 | + | config: | |
| 18 | + | dir: . | |
| 19 | + | pkgname: neferclient | |
| 20 | + | filename: surface_mock_test.go | |
| @@ -13,3 +13,8 @@ packages: | |||||
| 13 | filename: '{{.InterfaceFile | base | trimSuffix ".go"}}_mock.go' | 13 | filename: '{{.InterfaceFile | base | trimSuffix ".go"}}_mock.go' | ||
| 14 | interfaces: | 14 | interfaces: | ||
| 15 | Handler: | 15 | Handler: | ||
| 16 | + | surfaceRequests: | |||
| 17 | + | config: | |||
| 18 | + | dir: . | |||
| 19 | + | pkgname: neferclient | |||
| 20 | + | filename: surface_mock_test.go | |||
M alloc_test.go +88 -3
| @@ -1,9 +1,11 @@ | |||
| 1 | 1 | package neferclient_test | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "encoding/binary" | |
| 4 | 5 | "runtime" | |
| 5 | 6 | "testing" | |
| 6 | 7 | ||
| 8 | + | "github.com/bnema/go-wayland-bindings/client/wayland" | |
| 7 | 9 | "github.com/stretchr/testify/require" | |
| 8 | 10 | "golang.org/x/sys/unix" | |
| 9 | 11 | ||
| @@ -14,11 +16,12 @@ import ( | |||
| 14 | 16 | // only counts, so the test sees the cost of the library alone. | |
| 15 | 17 | type countingHandler struct { | |
| 16 | 18 | neferclient.NopHandler | |
| 17 | - | fdReady, errs int | |
| 19 | + | fdReady, errs, frames int | |
| 18 | 20 | } | |
| 19 | 21 | ||
| 20 | - | func (h *countingHandler) FDReady(uint64) { h.fdReady++ } | |
| 21 | - | func (h *countingHandler) Error(error) { h.errs++ } | |
| 22 | + | func (h *countingHandler) FDReady(uint64) { h.fdReady++ } | |
| 23 | + | func (h *countingHandler) Error(error) { h.errs++ } | |
| 24 | + | func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ } | |
| 22 | 25 | ||
| 23 | 26 | // TestAllocDispatch measures the whole steady-state pipeline over a real | |
| 24 | 27 | // socketpair: wire frames in, reader decode, queue, Dispatch (output state | |
| @@ -65,3 +68,85 @@ func TestAllocDispatch(t *testing.T) { | |||
| 65 | 68 | require.Positive(t, h.fdReady) | |
| 66 | 69 | require.Zero(t, h.errs) | |
| 67 | 70 | } | |
| 71 | + | ||
| 72 | + | // TestAllocPresent measures the steady-state present loop over a real | |
| 73 | + | // socketpair: Present (all requests, including the frame callback), the peer | |
| 74 | + | // answering the callback, reader decode, queue and Dispatch applying Frame. | |
| 75 | + | // The peer parses and discards everything else. | |
| 76 | + | func TestAllocPresent(t *testing.T) { | |
| 77 | + | skipUnderRace(t) | |
| 78 | + | globals := []wireGlobal{{1, wayland.CompositorInterface, 6}, {2, "zwp_linux_dmabuf_v1", 4}, | |
| 79 | + | {3, "wp_linux_drm_syncobj_manager_v1", 1}, {4, "wp_viewporter", 1}, {5, "xdg_wm_base", 6}} | |
| 80 | + | c, srv := connectWire(t, globals, nil) | |
| 81 | + | s, err := c.NewToplevel("alloc", 64, 48) | |
| 82 | + | require.NoError(t, err) | |
| 83 | + | s.MarkPresentable(64, 48) | |
| 84 | + | ||
| 85 | + | surfaceID := s.SurfaceObjectID() | |
| 86 | + | srv.mu.Lock() | |
| 87 | + | srv.hook = func(obj uint32, op uint16, body []byte) { | |
| 88 | + | if obj == surfaceID && op == 3 && len(body) == 4 { // wl_surface.frame(new_id) | |
| 89 | + | srv.write(frame(binary.LittleEndian.Uint32(body), 0, appendU32(nil, 1))) | |
| 90 | + | srv.write(frame(1, 1, body)) // wl_display.delete_id: the id is recycled | |
| 91 | + | } | |
| 92 | + | } | |
| 93 | + | srv.mu.Unlock() | |
| 94 | + | ||
| 95 | + | memfd, err := unix.MemfdCreate("alloc", unix.MFD_CLOEXEC) | |
| 96 | + | require.NoError(t, err) | |
| 97 | + | defer unix.Close(memfd) | |
| 98 | + | require.NoError(t, s.ImportBuffer(1, &neferclient.Buffer{Width: 64, Height: 48, FourCC: 0x34325258, | |
| 99 | + | Planes: [4]neferclient.Plane{{FD: memfd, Stride: 256}}, PlaneCount: 1})) | |
| 100 | + | require.NoError(t, s.ImportTimeline(1, memfd)) | |
| 101 | + | require.NoError(t, s.ImportTimeline(2, memfd)) | |
| 102 | + | ||
| 103 | + | var point uint64 | |
| 104 | + | damage := []neferclient.Rect{{X: 0, Y: 0, Width: 16, Height: 16}} | |
| 105 | + | present := func() { | |
| 106 | + | point++ | |
| 107 | + | if err := s.Present(&neferclient.Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, | |
| 108 | + | AcquirePoint: point, ReleasePoint: point, Damage: damage, Opaque: true}); err != nil { | |
| 109 | + | t.Fatal(err) | |
| 110 | + | } | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | // The request path: every request Present sends, over the real socket, | |
| 114 | + | // without the frame callback. | |
| 115 | + | s.SkipFrameRequests() | |
| 116 | + | requests := func() { | |
| 117 | + | s.ResetFrame() | |
| 118 | + | present() | |
| 119 | + | } | |
| 120 | + | for range 4 { // warm-up beyond AllocsPerRun's own | |
| 121 | + | requests() | |
| 122 | + | } | |
| 123 | + | allocs := testing.AllocsPerRun(100, requests) | |
| 124 | + | t.Logf("allocs per Present (requests only): %v", allocs) | |
| 125 | + | require.Zero(t, allocs) | |
| 126 | + | ||
| 127 | + | // The complete cycle adds the frame callback. wlturbo registers each | |
| 128 | + | // server-created wl_callback with two sync.Map stores (Context.proxies and | |
| 129 | + | // Display.objects) and a sync.Map store always allocates a node, so two | |
| 130 | + | // allocations per frame are inherent to the transport (wlturbo is out of | |
| 131 | + | // scope for this change). They are pinned so a regression above that shows. | |
| 132 | + | s.UseWireRequests() | |
| 133 | + | h := &countingHandler{} | |
| 134 | + | cycle := func() { | |
| 135 | + | present() | |
| 136 | + | for c.QueueLen() < 1 { | |
| 137 | + | runtime.Gosched() | |
| 138 | + | } | |
| 139 | + | if err := c.Dispatch(h); err != nil { | |
| 140 | + | t.Fatal(err) | |
| 141 | + | } | |
| 142 | + | } | |
| 143 | + | s.ResetFrame() | |
| 144 | + | for range 4 { | |
| 145 | + | cycle() | |
| 146 | + | } | |
| 147 | + | full := testing.AllocsPerRun(100, cycle) | |
| 148 | + | t.Logf("allocs per Present+frame round trip: %v (wlturbo callback registration)", full) | |
| 149 | + | require.LessOrEqual(t, full, 2.0) | |
| 150 | + | require.Positive(t, h.frames) | |
| 151 | + | require.Zero(t, h.errs) | |
| 152 | + | } | |
| @@ -1,9 +1,11 @@ | |||||
| 1 | package neferclient_test | 1 | package neferclient_test | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "encoding/binary" | |||
| 4 | "runtime" | 5 | "runtime" | ||
| 5 | "testing" | 6 | "testing" | ||
| 6 | 7 | ||||
| 8 | + | "github.com/bnema/go-wayland-bindings/client/wayland" | |||
| 7 | "github.com/stretchr/testify/require" | 9 | "github.com/stretchr/testify/require" | ||
| 8 | "golang.org/x/sys/unix" | 10 | "golang.org/x/sys/unix" | ||
| 9 | 11 | ||||
| @@ -14,11 +16,12 @@ import ( | |||||
| 14 | // only counts, so the test sees the cost of the library alone. | 16 | // only counts, so the test sees the cost of the library alone. | ||
| 15 | type countingHandler struct { | 17 | type countingHandler struct { | ||
| 16 | neferclient.NopHandler | 18 | neferclient.NopHandler | ||
| 17 | - | fdReady, errs int | 19 | + | fdReady, errs, frames int |
| 18 | } | 20 | } | ||
| 19 | 21 | ||||
| 20 | - | func (h *countingHandler) FDReady(uint64) { h.fdReady++ } | 22 | + | func (h *countingHandler) FDReady(uint64) { h.fdReady++ } |
| 21 | - | func (h *countingHandler) Error(error) { h.errs++ } | 23 | + | func (h *countingHandler) Error(error) { h.errs++ } |
| 24 | + | func (h *countingHandler) Frame(neferclient.SurfaceID) { h.frames++ } | |||
| 22 | 25 | ||||
| 23 | // TestAllocDispatch measures the whole steady-state pipeline over a real | 26 | // TestAllocDispatch measures the whole steady-state pipeline over a real | ||
| 24 | // socketpair: wire frames in, reader decode, queue, Dispatch (output state | 27 | // socketpair: wire frames in, reader decode, queue, Dispatch (output state | ||
| @@ -65,3 +68,85 @@ func TestAllocDispatch(t *testing.T) { | |||||
| 65 | require.Positive(t, h.fdReady) | 68 | require.Positive(t, h.fdReady) | ||
| 66 | require.Zero(t, h.errs) | 69 | require.Zero(t, h.errs) | ||
| 67 | } | 70 | } | ||
| 71 | + | ||||
| 72 | + | // TestAllocPresent measures the steady-state present loop over a real | |||
| 73 | + | // socketpair: Present (all requests, including the frame callback), the peer | |||
| 74 | + | // answering the callback, reader decode, queue and Dispatch applying Frame. | |||
| 75 | + | // The peer parses and discards everything else. | |||
| 76 | + | func TestAllocPresent(t *testing.T) { | |||
| 77 | + | skipUnderRace(t) | |||
| 78 | + | globals := []wireGlobal{{1, wayland.CompositorInterface, 6}, {2, "zwp_linux_dmabuf_v1", 4}, | |||
| 79 | + | {3, "wp_linux_drm_syncobj_manager_v1", 1}, {4, "wp_viewporter", 1}, {5, "xdg_wm_base", 6}} | |||
| 80 | + | c, srv := connectWire(t, globals, nil) | |||
| 81 | + | s, err := c.NewToplevel("alloc", 64, 48) | |||
| 82 | + | require.NoError(t, err) | |||
| 83 | + | s.MarkPresentable(64, 48) | |||
| 84 | + | ||||
| 85 | + | surfaceID := s.SurfaceObjectID() | |||
| 86 | + | srv.mu.Lock() | |||
| 87 | + | srv.hook = func(obj uint32, op uint16, body []byte) { | |||
| 88 | + | if obj == surfaceID && op == 3 && len(body) == 4 { // wl_surface.frame(new_id) | |||
| 89 | + | srv.write(frame(binary.LittleEndian.Uint32(body), 0, appendU32(nil, 1))) | |||
| 90 | + | srv.write(frame(1, 1, body)) // wl_display.delete_id: the id is recycled | |||
| 91 | + | } | |||
| 92 | + | } | |||
| 93 | + | srv.mu.Unlock() | |||
| 94 | + | ||||
| 95 | + | memfd, err := unix.MemfdCreate("alloc", unix.MFD_CLOEXEC) | |||
| 96 | + | require.NoError(t, err) | |||
| 97 | + | defer unix.Close(memfd) | |||
| 98 | + | require.NoError(t, s.ImportBuffer(1, &neferclient.Buffer{Width: 64, Height: 48, FourCC: 0x34325258, | |||
| 99 | + | Planes: [4]neferclient.Plane{{FD: memfd, Stride: 256}}, PlaneCount: 1})) | |||
| 100 | + | require.NoError(t, s.ImportTimeline(1, memfd)) | |||
| 101 | + | require.NoError(t, s.ImportTimeline(2, memfd)) | |||
| 102 | + | ||||
| 103 | + | var point uint64 | |||
| 104 | + | damage := []neferclient.Rect{{X: 0, Y: 0, Width: 16, Height: 16}} | |||
| 105 | + | present := func() { | |||
| 106 | + | point++ | |||
| 107 | + | if err := s.Present(&neferclient.Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, | |||
| 108 | + | AcquirePoint: point, ReleasePoint: point, Damage: damage, Opaque: true}); err != nil { | |||
| 109 | + | t.Fatal(err) | |||
| 110 | + | } | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | // The request path: every request Present sends, over the real socket, | |||
| 114 | + | // without the frame callback. | |||
| 115 | + | s.SkipFrameRequests() | |||
| 116 | + | requests := func() { | |||
| 117 | + | s.ResetFrame() | |||
| 118 | + | present() | |||
| 119 | + | } | |||
| 120 | + | for range 4 { // warm-up beyond AllocsPerRun's own | |||
| 121 | + | requests() | |||
| 122 | + | } | |||
| 123 | + | allocs := testing.AllocsPerRun(100, requests) | |||
| 124 | + | t.Logf("allocs per Present (requests only): %v", allocs) | |||
| 125 | + | require.Zero(t, allocs) | |||
| 126 | + | ||||
| 127 | + | // The complete cycle adds the frame callback. wlturbo registers each | |||
| 128 | + | // server-created wl_callback with two sync.Map stores (Context.proxies and | |||
| 129 | + | // Display.objects) and a sync.Map store always allocates a node, so two | |||
| 130 | + | // allocations per frame are inherent to the transport (wlturbo is out of | |||
| 131 | + | // scope for this change). They are pinned so a regression above that shows. | |||
| 132 | + | s.UseWireRequests() | |||
| 133 | + | h := &countingHandler{} | |||
| 134 | + | cycle := func() { | |||
| 135 | + | present() | |||
| 136 | + | for c.QueueLen() < 1 { | |||
| 137 | + | runtime.Gosched() | |||
| 138 | + | } | |||
| 139 | + | if err := c.Dispatch(h); err != nil { | |||
| 140 | + | t.Fatal(err) | |||
| 141 | + | } | |||
| 142 | + | } | |||
| 143 | + | s.ResetFrame() | |||
| 144 | + | for range 4 { | |||
| 145 | + | cycle() | |||
| 146 | + | } | |||
| 147 | + | full := testing.AllocsPerRun(100, cycle) | |||
| 148 | + | t.Logf("allocs per Present+frame round trip: %v (wlturbo callback registration)", full) | |||
| 149 | + | require.LessOrEqual(t, full, 2.0) | |||
| 150 | + | require.Positive(t, h.frames) | |||
| 151 | + | require.Zero(t, h.errs) | |||
| 152 | + | } | |||
M export_test.go +18 -0
| @@ -15,3 +15,21 @@ func ConnectConn(ctx context.Context, sock net.Conn) (*Conn, error) { return con | |||
| 15 | 15 | func SocketPath(name string) (string, error) { return socketPath(name) } | |
| 16 | 16 | ||
| 17 | 17 | func (c *Conn) QueueLen() int { return c.q.length() } | |
| 18 | + | ||
| 19 | + | // MarkPresentable stands in for the configure and feedback rounds, which the | |
| 20 | + | // allocation test does not drive over the wire. | |
| 21 | + | func (s *Surface) MarkPresentable(w, h int32) { | |
| 22 | + | s.width, s.height = w, h | |
| 23 | + | s.configured, s.frameReady, s.fbDone = true, true, true | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | func (s *Surface) SurfaceObjectID() uint32 { return s.surf.ID() } | |
| 27 | + | ||
| 28 | + | // SkipFrameRequests makes Present omit wl_surface.frame. | |
| 29 | + | func (s *Surface) SkipFrameRequests() { s.req = wireRequests{s: s, skipFrame: true} } | |
| 30 | + | ||
| 31 | + | // ResetFrame re-arms Present when frame requests are skipped. | |
| 32 | + | func (s *Surface) ResetFrame() { s.frameReady = true } | |
| 33 | + | ||
| 34 | + | // UseWireRequests restores the full request path. | |
| 35 | + | func (s *Surface) UseWireRequests() { s.req = wireRequests{s: s} } | |
| @@ -15,3 +15,21 @@ func ConnectConn(ctx context.Context, sock net.Conn) (*Conn, error) { return con | |||||
| 15 | func SocketPath(name string) (string, error) { return socketPath(name) } | 15 | func SocketPath(name string) (string, error) { return socketPath(name) } | ||
| 16 | 16 | ||||
| 17 | func (c *Conn) QueueLen() int { return c.q.length() } | 17 | func (c *Conn) QueueLen() int { return c.q.length() } | ||
| 18 | + | ||||
| 19 | + | // MarkPresentable stands in for the configure and feedback rounds, which the | |||
| 20 | + | // allocation test does not drive over the wire. | |||
| 21 | + | func (s *Surface) MarkPresentable(w, h int32) { | |||
| 22 | + | s.width, s.height = w, h | |||
| 23 | + | s.configured, s.frameReady, s.fbDone = true, true, true | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | func (s *Surface) SurfaceObjectID() uint32 { return s.surf.ID() } | |||
| 27 | + | ||||
| 28 | + | // SkipFrameRequests makes Present omit wl_surface.frame. | |||
| 29 | + | func (s *Surface) SkipFrameRequests() { s.req = wireRequests{s: s, skipFrame: true} } | |||
| 30 | + | ||||
| 31 | + | // ResetFrame re-arms Present when frame requests are skipped. | |||
| 32 | + | func (s *Surface) ResetFrame() { s.frameReady = true } | |||
| 33 | + | ||||
| 34 | + | // UseWireRequests restores the full request path. | |||
| 35 | + | func (s *Surface) UseWireRequests() { s.req = wireRequests{s: s} } | |||
A headless_surface_test.go +106 -0
| @@ -0,0 +1,106 @@ | |||
| 1 | + | package neferclient_test | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "testing" | |
| 6 | + | "time" | |
| 7 | + | ||
| 8 | + | "github.com/stretchr/testify/mock" | |
| 9 | + | "github.com/stretchr/testify/require" | |
| 10 | + | ||
| 11 | + | "github.com/bnema/neferclient" | |
| 12 | + | neferclientmocks "github.com/bnema/neferclient/mocks" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | func connectHeadless(t *testing.T) *neferclient.Conn { | |
| 16 | + | t.Helper() | |
| 17 | + | socket := startHeadless(t) | |
| 18 | + | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) | |
| 19 | + | defer cancel() | |
| 20 | + | for { | |
| 21 | + | c, err := neferclient.Connect(ctx, socket) | |
| 22 | + | if err == nil { | |
| 23 | + | t.Cleanup(func() { _ = c.Close() }) | |
| 24 | + | for len(c.Outputs()) == 0 { // the virtual output may arrive late | |
| 25 | + | select { | |
| 26 | + | case <-c.Wake(): | |
| 27 | + | require.NoError(t, c.Dispatch(nil)) | |
| 28 | + | case <-ctx.Done(): | |
| 29 | + | t.Fatal("no output announced") | |
| 30 | + | } | |
| 31 | + | } | |
| 32 | + | return c | |
| 33 | + | } | |
| 34 | + | if ctx.Err() != nil { | |
| 35 | + | t.Fatal(err) | |
| 36 | + | } | |
| 37 | + | time.Sleep(5 * time.Millisecond) | |
| 38 | + | } | |
| 39 | + | } | |
| 40 | + | ||
| 41 | + | func TestHeadlessLayerSurface(t *testing.T) { | |
| 42 | + | c := connectHeadless(t) | |
| 43 | + | s, err := c.NewLayerSurface(neferclient.LayerConfig{ | |
| 44 | + | Level: neferclient.LayerTop, Anchors: neferclient.AnchorTop | neferclient.AnchorLeft, | |
| 45 | + | Width: 200, Height: 50, | |
| 46 | + | }) | |
| 47 | + | require.NoError(t, err) | |
| 48 | + | ||
| 49 | + | var configured, feedback bool | |
| 50 | + | var cw, ch int32 | |
| 51 | + | h := neferclientmocks.NewMockHandler(t) | |
| 52 | + | h.EXPECT().Configure(s.ID(), mock.Anything, mock.Anything).Run(func(_ neferclient.SurfaceID, w, hh int32) { | |
| 53 | + | configured, cw, ch = true, w, hh | |
| 54 | + | }).Return().Maybe() | |
| 55 | + | h.EXPECT().FeedbackDone(s.ID()).Run(func(neferclient.SurfaceID) { feedback = true }).Return().Maybe() | |
| 56 | + | h.EXPECT().Scale(s.ID(), mock.Anything).Return().Maybe() | |
| 57 | + | dispatchUntil(t, c, h, func() bool { return configured && feedback }) | |
| 58 | + | ||
| 59 | + | require.Equal(t, int32(200), cw) | |
| 60 | + | require.Equal(t, int32(50), ch) | |
| 61 | + | fb := s.Feedback() | |
| 62 | + | require.NotNil(t, fb) | |
| 63 | + | require.NotZero(t, fb.MainDevice) | |
| 64 | + | require.NotEmpty(t, fb.Formats) | |
| 65 | + | t.Logf("main device %#x, %d formats", fb.MainDevice, len(fb.Formats)) | |
| 66 | + | pw, ph, err := s.PhysicalSize() | |
| 67 | + | require.NoError(t, err) | |
| 68 | + | require.Positive(t, pw) | |
| 69 | + | require.Positive(t, ph) | |
| 70 | + | require.NoError(t, s.SetInputRegion([]neferclient.Rect{{X: 0, Y: 0, Width: 10, Height: 10}})) | |
| 71 | + | require.NoError(t, s.Close()) | |
| 72 | + | require.NoError(t, c.Roundtrip()) | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | func TestHeadlessLockUnlock(t *testing.T) { | |
| 76 | + | c := connectHeadless(t) | |
| 77 | + | l, err := c.Lock() | |
| 78 | + | require.NoError(t, err) | |
| 79 | + | require.Error(t, l.Unlock(), "unlock before locked must be refused") | |
| 80 | + | ||
| 81 | + | var locked bool | |
| 82 | + | var surfaces []*neferclient.Surface | |
| 83 | + | for _, o := range c.Outputs() { | |
| 84 | + | s, err := l.NewSurface(o.Global) | |
| 85 | + | require.NoError(t, err) | |
| 86 | + | surfaces = append(surfaces, s) | |
| 87 | + | } | |
| 88 | + | h := neferclientmocks.NewMockHandler(t) | |
| 89 | + | h.EXPECT().Locked().Run(func() { locked = true }).Return().Maybe() | |
| 90 | + | h.EXPECT().Configure(mock.Anything, mock.Anything, mock.Anything).Return().Maybe() | |
| 91 | + | h.EXPECT().FeedbackDone(mock.Anything).Return().Maybe() | |
| 92 | + | h.EXPECT().Scale(mock.Anything, mock.Anything).Return().Maybe() | |
| 93 | + | dispatchUntil(t, c, h, func() bool { return locked }) | |
| 94 | + | require.True(t, l.Locked()) | |
| 95 | + | for _, s := range surfaces { | |
| 96 | + | w, hh, _ := s.Size() | |
| 97 | + | t.Logf("lock surface %d: %dx%d", s.ID(), w, hh) | |
| 98 | + | } | |
| 99 | + | require.Error(t, l.Close(), "a locked session is not closed, only unlocked") | |
| 100 | + | require.NoError(t, l.Unlock()) | |
| 101 | + | require.False(t, l.Locked()) | |
| 102 | + | for _, s := range surfaces { | |
| 103 | + | require.NoError(t, s.Close()) | |
| 104 | + | } | |
| 105 | + | require.NoError(t, c.Roundtrip()) | |
| 106 | + | } | |
| @@ -0,0 +1,106 @@ | |||||
| 1 | + | package neferclient_test | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "testing" | |||
| 6 | + | "time" | |||
| 7 | + | ||||
| 8 | + | "github.com/stretchr/testify/mock" | |||
| 9 | + | "github.com/stretchr/testify/require" | |||
| 10 | + | ||||
| 11 | + | "github.com/bnema/neferclient" | |||
| 12 | + | neferclientmocks "github.com/bnema/neferclient/mocks" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | func connectHeadless(t *testing.T) *neferclient.Conn { | |||
| 16 | + | t.Helper() | |||
| 17 | + | socket := startHeadless(t) | |||
| 18 | + | ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) | |||
| 19 | + | defer cancel() | |||
| 20 | + | for { | |||
| 21 | + | c, err := neferclient.Connect(ctx, socket) | |||
| 22 | + | if err == nil { | |||
| 23 | + | t.Cleanup(func() { _ = c.Close() }) | |||
| 24 | + | for len(c.Outputs()) == 0 { // the virtual output may arrive late | |||
| 25 | + | select { | |||
| 26 | + | case <-c.Wake(): | |||
| 27 | + | require.NoError(t, c.Dispatch(nil)) | |||
| 28 | + | case <-ctx.Done(): | |||
| 29 | + | t.Fatal("no output announced") | |||
| 30 | + | } | |||
| 31 | + | } | |||
| 32 | + | return c | |||
| 33 | + | } | |||
| 34 | + | if ctx.Err() != nil { | |||
| 35 | + | t.Fatal(err) | |||
| 36 | + | } | |||
| 37 | + | time.Sleep(5 * time.Millisecond) | |||
| 38 | + | } | |||
| 39 | + | } | |||
| 40 | + | ||||
| 41 | + | func TestHeadlessLayerSurface(t *testing.T) { | |||
| 42 | + | c := connectHeadless(t) | |||
| 43 | + | s, err := c.NewLayerSurface(neferclient.LayerConfig{ | |||
| 44 | + | Level: neferclient.LayerTop, Anchors: neferclient.AnchorTop | neferclient.AnchorLeft, | |||
| 45 | + | Width: 200, Height: 50, | |||
| 46 | + | }) | |||
| 47 | + | require.NoError(t, err) | |||
| 48 | + | ||||
| 49 | + | var configured, feedback bool | |||
| 50 | + | var cw, ch int32 | |||
| 51 | + | h := neferclientmocks.NewMockHandler(t) | |||
| 52 | + | h.EXPECT().Configure(s.ID(), mock.Anything, mock.Anything).Run(func(_ neferclient.SurfaceID, w, hh int32) { | |||
| 53 | + | configured, cw, ch = true, w, hh | |||
| 54 | + | }).Return().Maybe() | |||
| 55 | + | h.EXPECT().FeedbackDone(s.ID()).Run(func(neferclient.SurfaceID) { feedback = true }).Return().Maybe() | |||
| 56 | + | h.EXPECT().Scale(s.ID(), mock.Anything).Return().Maybe() | |||
| 57 | + | dispatchUntil(t, c, h, func() bool { return configured && feedback }) | |||
| 58 | + | ||||
| 59 | + | require.Equal(t, int32(200), cw) | |||
| 60 | + | require.Equal(t, int32(50), ch) | |||
| 61 | + | fb := s.Feedback() | |||
| 62 | + | require.NotNil(t, fb) | |||
| 63 | + | require.NotZero(t, fb.MainDevice) | |||
| 64 | + | require.NotEmpty(t, fb.Formats) | |||
| 65 | + | t.Logf("main device %#x, %d formats", fb.MainDevice, len(fb.Formats)) | |||
| 66 | + | pw, ph, err := s.PhysicalSize() | |||
| 67 | + | require.NoError(t, err) | |||
| 68 | + | require.Positive(t, pw) | |||
| 69 | + | require.Positive(t, ph) | |||
| 70 | + | require.NoError(t, s.SetInputRegion([]neferclient.Rect{{X: 0, Y: 0, Width: 10, Height: 10}})) | |||
| 71 | + | require.NoError(t, s.Close()) | |||
| 72 | + | require.NoError(t, c.Roundtrip()) | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | func TestHeadlessLockUnlock(t *testing.T) { | |||
| 76 | + | c := connectHeadless(t) | |||
| 77 | + | l, err := c.Lock() | |||
| 78 | + | require.NoError(t, err) | |||
| 79 | + | require.Error(t, l.Unlock(), "unlock before locked must be refused") | |||
| 80 | + | ||||
| 81 | + | var locked bool | |||
| 82 | + | var surfaces []*neferclient.Surface | |||
| 83 | + | for _, o := range c.Outputs() { | |||
| 84 | + | s, err := l.NewSurface(o.Global) | |||
| 85 | + | require.NoError(t, err) | |||
| 86 | + | surfaces = append(surfaces, s) | |||
| 87 | + | } | |||
| 88 | + | h := neferclientmocks.NewMockHandler(t) | |||
| 89 | + | h.EXPECT().Locked().Run(func() { locked = true }).Return().Maybe() | |||
| 90 | + | h.EXPECT().Configure(mock.Anything, mock.Anything, mock.Anything).Return().Maybe() | |||
| 91 | + | h.EXPECT().FeedbackDone(mock.Anything).Return().Maybe() | |||
| 92 | + | h.EXPECT().Scale(mock.Anything, mock.Anything).Return().Maybe() | |||
| 93 | + | dispatchUntil(t, c, h, func() bool { return locked }) | |||
| 94 | + | require.True(t, l.Locked()) | |||
| 95 | + | for _, s := range surfaces { | |||
| 96 | + | w, hh, _ := s.Size() | |||
| 97 | + | t.Logf("lock surface %d: %dx%d", s.ID(), w, hh) | |||
| 98 | + | } | |||
| 99 | + | require.Error(t, l.Close(), "a locked session is not closed, only unlocked") | |||
| 100 | + | require.NoError(t, l.Unlock()) | |||
| 101 | + | require.False(t, l.Locked()) | |||
| 102 | + | for _, s := range surfaces { | |||
| 103 | + | require.NoError(t, s.Close()) | |||
| 104 | + | } | |||
| 105 | + | require.NoError(t, c.Roundtrip()) | |||
| 106 | + | } | |||
M present.go +82 -38
| @@ -133,94 +133,138 @@ func (s *Surface) Present(p *Present) error { | |||
| 133 | 133 | if !s.frameReady { | |
| 134 | 134 | return errors.New("neferclient: frame callback pending") | |
| 135 | 135 | } | |
| 136 | - | buf, ok := s.buffers[p.Buffer] | |
| 137 | - | if !ok { | |
| 136 | + | if _, ok := s.buffers[p.Buffer]; !ok { | |
| 138 | 137 | return fmt.Errorf("neferclient: unknown buffer %d", p.Buffer) | |
| 139 | 138 | } | |
| 140 | - | acq, ok := s.timelines[p.AcquireTimeline] | |
| 141 | - | if !ok { | |
| 142 | - | return fmt.Errorf("neferclient: unknown timeline %d", p.AcquireTimeline) | |
| 143 | - | } | |
| 144 | - | rel, ok := s.timelines[p.ReleaseTimeline] | |
| 145 | - | if !ok { | |
| 146 | - | return fmt.Errorf("neferclient: unknown timeline %d", p.ReleaseTimeline) | |
| 139 | + | for _, id := range [...]uint64{p.AcquireTimeline, p.ReleaseTimeline} { | |
| 140 | + | if _, ok := s.timelines[id]; !ok { | |
| 141 | + | return fmt.Errorf("neferclient: unknown timeline %d", id) | |
| 142 | + | } | |
| 147 | 143 | } | |
| 148 | 144 | pw, ph, err := s.PhysicalSize() | |
| 149 | 145 | if err != nil { | |
| 150 | 146 | return err | |
| 151 | 147 | } | |
| 148 | + | r := s.req | |
| 152 | 149 | if s.viewport != nil { | |
| 153 | 150 | if s.sentDestW != s.width || s.sentDestH != s.height { | |
| 154 | - | if err = s.viewport.SetDestination(s.width, s.height); err != nil { | |
| 151 | + | if err = r.setDestination(s.width, s.height); err != nil { | |
| 155 | 152 | return err | |
| 156 | 153 | } | |
| 157 | 154 | s.sentDestW, s.sentDestH = s.width, s.height | |
| 158 | 155 | } | |
| 159 | 156 | } else if s.scale > 1 && s.sentBufScale != int32(s.scale) { | |
| 160 | - | if err = s.surf.SetBufferScale(int32(s.scale)); err != nil { | |
| 157 | + | if err = r.setBufferScale(int32(s.scale)); err != nil { | |
| 161 | 158 | return err | |
| 162 | 159 | } | |
| 163 | 160 | s.sentBufScale = int32(s.scale) | |
| 164 | 161 | } | |
| 165 | - | if err = s.setOpaque(p.Opaque); err != nil { | |
| 166 | - | return err | |
| 162 | + | if p.Opaque { | |
| 163 | + | if !s.opaqueSet || s.opaqueW != s.width || s.opaqueH != s.height { | |
| 164 | + | if err = r.setOpaqueRegion(s.width, s.height, true); err != nil { | |
| 165 | + | return err | |
| 166 | + | } | |
| 167 | + | s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height | |
| 168 | + | } | |
| 169 | + | } else if s.opaqueSet { | |
| 170 | + | if err = r.setOpaqueRegion(0, 0, false); err != nil { | |
| 171 | + | return err | |
| 172 | + | } | |
| 173 | + | s.opaqueSet = false | |
| 167 | 174 | } | |
| 168 | - | if err = s.sync.SetAcquirePoint(acq, uint32(p.AcquirePoint>>32), uint32(p.AcquirePoint)); err != nil { | |
| 175 | + | if err = r.setAcquire(p.AcquireTimeline, p.AcquirePoint); err != nil { | |
| 169 | 176 | return err | |
| 170 | 177 | } | |
| 171 | - | if err = s.sync.SetReleasePoint(rel, uint32(p.ReleasePoint>>32), uint32(p.ReleasePoint)); err != nil { | |
| 178 | + | if err = r.setRelease(p.ReleaseTimeline, p.ReleasePoint); err != nil { | |
| 172 | 179 | return err | |
| 173 | 180 | } | |
| 174 | - | if err = s.surf.Attach(buf, 0, 0); err != nil { | |
| 181 | + | if err = r.attach(p.Buffer); err != nil { | |
| 175 | 182 | return err | |
| 176 | 183 | } | |
| 177 | 184 | if len(p.Damage) == 0 { | |
| 178 | - | err = s.surf.DamageBuffer(0, 0, pw, ph) | |
| 185 | + | err = r.damageBuffer(Rect{0, 0, pw, ph}) | |
| 179 | 186 | } | |
| 180 | - | for _, r := range p.Damage { | |
| 187 | + | for _, d := range p.Damage { | |
| 181 | 188 | if err != nil { | |
| 182 | 189 | break | |
| 183 | 190 | } | |
| 184 | - | err = s.surf.DamageBuffer(r.X, r.Y, r.Width, r.Height) | |
| 191 | + | err = r.damageBuffer(d) | |
| 185 | 192 | } | |
| 186 | 193 | if err != nil { | |
| 187 | 194 | return err | |
| 188 | 195 | } | |
| 189 | - | // The one frame callback object is reused: only one is ever pending. | |
| 190 | - | if err = s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb)); err != nil { | |
| 196 | + | if err = r.frame(); err != nil { | |
| 191 | 197 | return err | |
| 192 | 198 | } | |
| 193 | - | if err = s.surf.Commit(); err != nil { | |
| 199 | + | if err = r.commit(); err != nil { | |
| 194 | 200 | return err | |
| 195 | 201 | } | |
| 196 | 202 | s.frameReady = false | |
| 197 | 203 | return nil | |
| 198 | 204 | } | |
| 199 | 205 | ||
| 200 | - | // setOpaque keeps the opaque region equal to the full logical surface when | |
| 201 | - | // opaque, and empty otherwise, sending requests only on change. | |
| 202 | - | func (s *Surface) setOpaque(opaque bool) error { | |
| 206 | + | // surfaceRequests is the seam between the Present ordering rules and the | |
| 207 | + | // wire: the production implementation sends the protocol requests. | |
| 208 | + | type surfaceRequests interface { | |
| 209 | + | setDestination(w, h int32) error | |
| 210 | + | setBufferScale(n int32) error | |
| 211 | + | setOpaqueRegion(w, h int32, opaque bool) error | |
| 212 | + | setAcquire(timeline, point uint64) error | |
| 213 | + | setRelease(timeline, point uint64) error | |
| 214 | + | attach(buffer uint64) error | |
| 215 | + | damageBuffer(r Rect) error | |
| 216 | + | frame() error | |
| 217 | + | commit() error | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | // wireRequests sends the requests. skipFrame is set by tests that isolate the | |
| 221 | + | // request path from the frame callback registration. | |
| 222 | + | type wireRequests struct { | |
| 223 | + | s *Surface | |
| 224 | + | skipFrame bool | |
| 225 | + | } | |
| 226 | + | ||
| 227 | + | func (r wireRequests) setDestination(w, h int32) error { return r.s.viewport.SetDestination(w, h) } | |
| 228 | + | func (r wireRequests) setBufferScale(n int32) error { return r.s.surf.SetBufferScale(n) } | |
| 229 | + | ||
| 230 | + | func (r wireRequests) setOpaqueRegion(w, h int32, opaque bool) error { | |
| 231 | + | s := r.s | |
| 203 | 232 | if !opaque { | |
| 204 | - | if s.opaqueSet { | |
| 205 | - | s.opaqueSet = false | |
| 206 | - | return s.surf.SetOpaqueRegion(nil) | |
| 207 | - | } | |
| 208 | - | return nil | |
| 209 | - | } | |
| 210 | - | if s.opaqueSet && s.opaqueW == s.width && s.opaqueH == s.height { | |
| 211 | - | return nil | |
| 233 | + | return s.surf.SetOpaqueRegion(nil) | |
| 212 | 234 | } | |
| 213 | 235 | region, err := s.c.g.compositor.CreateRegion() | |
| 214 | 236 | if err != nil { | |
| 215 | 237 | return err | |
| 216 | 238 | } | |
| 217 | - | if err = region.Add(0, 0, s.width, s.height); err == nil { | |
| 239 | + | if err = region.Add(0, 0, w, h); err == nil { | |
| 218 | 240 | err = s.surf.SetOpaqueRegion(region) | |
| 219 | 241 | } | |
| 220 | 242 | _ = region.Destroy() | |
| 221 | - | if err != nil { | |
| 222 | - | return err | |
| 243 | + | return err | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | func (r wireRequests) setAcquire(timeline, point uint64) error { | |
| 247 | + | return r.s.sync.SetAcquirePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point)) | |
| 248 | + | } | |
| 249 | + | ||
| 250 | + | func (r wireRequests) setRelease(timeline, point uint64) error { | |
| 251 | + | return r.s.sync.SetReleasePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point)) | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | func (r wireRequests) attach(buffer uint64) error { return r.s.surf.Attach(r.s.buffers[buffer], 0, 0) } | |
| 255 | + | ||
| 256 | + | func (r wireRequests) damageBuffer(d Rect) error { | |
| 257 | + | return r.s.surf.DamageBuffer(d.X, d.Y, d.Width, d.Height) | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | // frame requests the frame callback on the surface's single reusable | |
| 261 | + | // callback object: only one is ever pending. | |
| 262 | + | func (r wireRequests) frame() error { | |
| 263 | + | s := r.s | |
| 264 | + | if r.skipFrame { | |
| 265 | + | return nil | |
| 223 | 266 | } | |
| 224 | - | s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height | |
| 225 | - | return nil | |
| 267 | + | return s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb)) | |
| 226 | 268 | } | |
| 269 | + | ||
| 270 | + | func (r wireRequests) commit() error { return r.s.surf.Commit() } | |
| @@ -133,94 +133,138 @@ func (s *Surface) Present(p *Present) error { | |||||
| 133 | if !s.frameReady { | 133 | if !s.frameReady { | ||
| 134 | return errors.New("neferclient: frame callback pending") | 134 | return errors.New("neferclient: frame callback pending") | ||
| 135 | } | 135 | } | ||
| 136 | - | buf, ok := s.buffers[p.Buffer] | 136 | + | if _, ok := s.buffers[p.Buffer]; !ok { |
| 137 | - | if !ok { | |||
| 138 | return fmt.Errorf("neferclient: unknown buffer %d", p.Buffer) | 137 | return fmt.Errorf("neferclient: unknown buffer %d", p.Buffer) | ||
| 139 | } | 138 | } | ||
| 140 | - | acq, ok := s.timelines[p.AcquireTimeline] | 139 | + | for _, id := range [...]uint64{p.AcquireTimeline, p.ReleaseTimeline} { |
| 141 | - | if !ok { | 140 | + | if _, ok := s.timelines[id]; !ok { |
| 142 | - | return fmt.Errorf("neferclient: unknown timeline %d", p.AcquireTimeline) | 141 | + | return fmt.Errorf("neferclient: unknown timeline %d", id) |
| 143 | - | } | 142 | + | } |
| 144 | - | rel, ok := s.timelines[p.ReleaseTimeline] | |||
| 145 | - | if !ok { | |||
| 146 | - | return fmt.Errorf("neferclient: unknown timeline %d", p.ReleaseTimeline) | |||
| 147 | } | 143 | } | ||
| 148 | pw, ph, err := s.PhysicalSize() | 144 | pw, ph, err := s.PhysicalSize() | ||
| 149 | if err != nil { | 145 | if err != nil { | ||
| 150 | return err | 146 | return err | ||
| 151 | } | 147 | } | ||
| 148 | + | r := s.req | |||
| 152 | if s.viewport != nil { | 149 | if s.viewport != nil { | ||
| 153 | if s.sentDestW != s.width || s.sentDestH != s.height { | 150 | if s.sentDestW != s.width || s.sentDestH != s.height { | ||
| 154 | - | if err = s.viewport.SetDestination(s.width, s.height); err != nil { | 151 | + | if err = r.setDestination(s.width, s.height); err != nil { |
| 155 | return err | 152 | return err | ||
| 156 | } | 153 | } | ||
| 157 | s.sentDestW, s.sentDestH = s.width, s.height | 154 | s.sentDestW, s.sentDestH = s.width, s.height | ||
| 158 | } | 155 | } | ||
| 159 | } else if s.scale > 1 && s.sentBufScale != int32(s.scale) { | 156 | } else if s.scale > 1 && s.sentBufScale != int32(s.scale) { | ||
| 160 | - | if err = s.surf.SetBufferScale(int32(s.scale)); err != nil { | 157 | + | if err = r.setBufferScale(int32(s.scale)); err != nil { |
| 161 | return err | 158 | return err | ||
| 162 | } | 159 | } | ||
| 163 | s.sentBufScale = int32(s.scale) | 160 | s.sentBufScale = int32(s.scale) | ||
| 164 | } | 161 | } | ||
| 165 | - | if err = s.setOpaque(p.Opaque); err != nil { | 162 | + | if p.Opaque { |
| 166 | - | return err | 163 | + | if !s.opaqueSet || s.opaqueW != s.width || s.opaqueH != s.height { |
| 164 | + | if err = r.setOpaqueRegion(s.width, s.height, true); err != nil { | |||
| 165 | + | return err | |||
| 166 | + | } | |||
| 167 | + | s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height | |||
| 168 | + | } | |||
| 169 | + | } else if s.opaqueSet { | |||
| 170 | + | if err = r.setOpaqueRegion(0, 0, false); err != nil { | |||
| 171 | + | return err | |||
| 172 | + | } | |||
| 173 | + | s.opaqueSet = false | |||
| 167 | } | 174 | } | ||
| 168 | - | if err = s.sync.SetAcquirePoint(acq, uint32(p.AcquirePoint>>32), uint32(p.AcquirePoint)); err != nil { | 175 | + | if err = r.setAcquire(p.AcquireTimeline, p.AcquirePoint); err != nil { |
| 169 | return err | 176 | return err | ||
| 170 | } | 177 | } | ||
| 171 | - | if err = s.sync.SetReleasePoint(rel, uint32(p.ReleasePoint>>32), uint32(p.ReleasePoint)); err != nil { | 178 | + | if err = r.setRelease(p.ReleaseTimeline, p.ReleasePoint); err != nil { |
| 172 | return err | 179 | return err | ||
| 173 | } | 180 | } | ||
| 174 | - | if err = s.surf.Attach(buf, 0, 0); err != nil { | 181 | + | if err = r.attach(p.Buffer); err != nil { |
| 175 | return err | 182 | return err | ||
| 176 | } | 183 | } | ||
| 177 | if len(p.Damage) == 0 { | 184 | if len(p.Damage) == 0 { | ||
| 178 | - | err = s.surf.DamageBuffer(0, 0, pw, ph) | 185 | + | err = r.damageBuffer(Rect{0, 0, pw, ph}) |
| 179 | } | 186 | } | ||
| 180 | - | for _, r := range p.Damage { | 187 | + | for _, d := range p.Damage { |
| 181 | if err != nil { | 188 | if err != nil { | ||
| 182 | break | 189 | break | ||
| 183 | } | 190 | } | ||
| 184 | - | err = s.surf.DamageBuffer(r.X, r.Y, r.Width, r.Height) | 191 | + | err = r.damageBuffer(d) |
| 185 | } | 192 | } | ||
| 186 | if err != nil { | 193 | if err != nil { | ||
| 187 | return err | 194 | return err | ||
| 188 | } | 195 | } | ||
| 189 | - | // The one frame callback object is reused: only one is ever pending. | 196 | + | if err = r.frame(); err != nil { |
| 190 | - | if err = s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb)); err != nil { | |||
| 191 | return err | 197 | return err | ||
| 192 | } | 198 | } | ||
| 193 | - | if err = s.surf.Commit(); err != nil { | 199 | + | if err = r.commit(); err != nil { |
| 194 | return err | 200 | return err | ||
| 195 | } | 201 | } | ||
| 196 | s.frameReady = false | 202 | s.frameReady = false | ||
| 197 | return nil | 203 | return nil | ||
| 198 | } | 204 | } | ||
| 199 | 205 | ||||
| 200 | - | // setOpaque keeps the opaque region equal to the full logical surface when | 206 | + | // surfaceRequests is the seam between the Present ordering rules and the |
| 201 | - | // opaque, and empty otherwise, sending requests only on change. | 207 | + | // wire: the production implementation sends the protocol requests. |
| 202 | - | func (s *Surface) setOpaque(opaque bool) error { | 208 | + | type surfaceRequests interface { |
| 209 | + | setDestination(w, h int32) error | |||
| 210 | + | setBufferScale(n int32) error | |||
| 211 | + | setOpaqueRegion(w, h int32, opaque bool) error | |||
| 212 | + | setAcquire(timeline, point uint64) error | |||
| 213 | + | setRelease(timeline, point uint64) error | |||
| 214 | + | attach(buffer uint64) error | |||
| 215 | + | damageBuffer(r Rect) error | |||
| 216 | + | frame() error | |||
| 217 | + | commit() error | |||
| 218 | + | } | |||
| 219 | + | ||||
| 220 | + | // wireRequests sends the requests. skipFrame is set by tests that isolate the | |||
| 221 | + | // request path from the frame callback registration. | |||
| 222 | + | type wireRequests struct { | |||
| 223 | + | s *Surface | |||
| 224 | + | skipFrame bool | |||
| 225 | + | } | |||
| 226 | + | ||||
| 227 | + | func (r wireRequests) setDestination(w, h int32) error { return r.s.viewport.SetDestination(w, h) } | |||
| 228 | + | func (r wireRequests) setBufferScale(n int32) error { return r.s.surf.SetBufferScale(n) } | |||
| 229 | + | ||||
| 230 | + | func (r wireRequests) setOpaqueRegion(w, h int32, opaque bool) error { | |||
| 231 | + | s := r.s | |||
| 203 | if !opaque { | 232 | if !opaque { | ||
| 204 | - | if s.opaqueSet { | 233 | + | return s.surf.SetOpaqueRegion(nil) |
| 205 | - | s.opaqueSet = false | |||
| 206 | - | return s.surf.SetOpaqueRegion(nil) | |||
| 207 | - | } | |||
| 208 | - | return nil | |||
| 209 | - | } | |||
| 210 | - | if s.opaqueSet && s.opaqueW == s.width && s.opaqueH == s.height { | |||
| 211 | - | return nil | |||
| 212 | } | 234 | } | ||
| 213 | region, err := s.c.g.compositor.CreateRegion() | 235 | region, err := s.c.g.compositor.CreateRegion() | ||
| 214 | if err != nil { | 236 | if err != nil { | ||
| 215 | return err | 237 | return err | ||
| 216 | } | 238 | } | ||
| 217 | - | if err = region.Add(0, 0, s.width, s.height); err == nil { | 239 | + | if err = region.Add(0, 0, w, h); err == nil { |
| 218 | err = s.surf.SetOpaqueRegion(region) | 240 | err = s.surf.SetOpaqueRegion(region) | ||
| 219 | } | 241 | } | ||
| 220 | _ = region.Destroy() | 242 | _ = region.Destroy() | ||
| 221 | - | if err != nil { | 243 | + | return err |
| 222 | - | return err | 244 | + | } |
| 245 | + | ||||
| 246 | + | func (r wireRequests) setAcquire(timeline, point uint64) error { | |||
| 247 | + | return r.s.sync.SetAcquirePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point)) | |||
| 248 | + | } | |||
| 249 | + | ||||
| 250 | + | func (r wireRequests) setRelease(timeline, point uint64) error { | |||
| 251 | + | return r.s.sync.SetReleasePoint(r.s.timelines[timeline], uint32(point>>32), uint32(point)) | |||
| 252 | + | } | |||
| 253 | + | ||||
| 254 | + | func (r wireRequests) attach(buffer uint64) error { return r.s.surf.Attach(r.s.buffers[buffer], 0, 0) } | |||
| 255 | + | ||||
| 256 | + | func (r wireRequests) damageBuffer(d Rect) error { | |||
| 257 | + | return r.s.surf.DamageBuffer(d.X, d.Y, d.Width, d.Height) | |||
| 258 | + | } | |||
| 259 | + | ||||
| 260 | + | // frame requests the frame callback on the surface's single reusable | |||
| 261 | + | // callback object: only one is ever pending. | |||
| 262 | + | func (r wireRequests) frame() error { | |||
| 263 | + | s := r.s | |||
| 264 | + | if r.skipFrame { | |||
| 265 | + | return nil | |||
| 223 | } | 266 | } | ||
| 224 | - | s.opaqueSet, s.opaqueW, s.opaqueH = true, s.width, s.height | 267 | + | return s.c.wlctx.RequestArgs(wl.Request{Proxy: s.surf, Opcode: 3, Name: "wl_surface.frame", Child: s.cb}, wl.ArgObject(s.cb)) |
| 225 | - | return nil | |||
| 226 | } | 268 | } | ||
| 269 | + | ||||
| 270 | + | func (r wireRequests) commit() error { return r.s.surf.Commit() } | |||
M surface.go +3 -1
| @@ -118,7 +118,8 @@ type Surface struct { | |||
| 118 | 118 | opaque *wayland.Region | |
| 119 | 119 | opaqueW, opaqueH int32 | |
| 120 | 120 | ||
| 121 | - | cb *frameCallback | |
| 121 | + | cb *frameCallback | |
| 122 | + | req surfaceRequests | |
| 122 | 123 | ||
| 123 | 124 | inputRects []Rect | |
| 124 | 125 | inputCustom bool | |
| @@ -270,6 +271,7 @@ func (c *Conn) newSurface(role surfaceRole, w, h int32) (*Surface, error) { | |||
| 270 | 271 | // The role objects come next (callers); sync surface and feedback after. | |
| 271 | 272 | s.cb = &frameCallback{q: q, id: s.id} | |
| 272 | 273 | s.cb.SetContext(c.wlctx) | |
| 274 | + | s.req = wireRequests{s: s} | |
| 273 | 275 | c.surfaces[s.id] = s | |
| 274 | 276 | ok = true | |
| 275 | 277 | return s, nil | |
| @@ -118,7 +118,8 @@ type Surface struct { | |||||
| 118 | opaque *wayland.Region | 118 | opaque *wayland.Region | ||
| 119 | opaqueW, opaqueH int32 | 119 | opaqueW, opaqueH int32 | ||
| 120 | 120 | ||||
| 121 | - | cb *frameCallback | 121 | + | cb *frameCallback |
| 122 | + | req surfaceRequests | |||
| 122 | 123 | ||||
| 123 | inputRects []Rect | 124 | inputRects []Rect | ||
| 124 | inputCustom bool | 125 | inputCustom bool | ||
| @@ -270,6 +271,7 @@ func (c *Conn) newSurface(role surfaceRole, w, h int32) (*Surface, error) { | |||||
| 270 | // The role objects come next (callers); sync surface and feedback after. | 271 | // The role objects come next (callers); sync surface and feedback after. | ||
| 271 | s.cb = &frameCallback{q: q, id: s.id} | 272 | s.cb = &frameCallback{q: q, id: s.id} | ||
| 272 | s.cb.SetContext(c.wlctx) | 273 | s.cb.SetContext(c.wlctx) | ||
| 274 | + | s.req = wireRequests{s: s} | |||
| 273 | c.surfaces[s.id] = s | 275 | c.surfaces[s.id] = s | ||
| 274 | ok = true | 276 | ok = true | ||
| 275 | return s, nil | 277 | return s, nil | ||
A surface_internal_test.go +217 -0
| @@ -0,0 +1,217 @@ | |||
| 1 | + | package neferclient | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "encoding/binary" | |
| 5 | + | "testing" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/go-wayland-bindings/client/linuxdrmsyncobj" | |
| 8 | + | "github.com/bnema/go-wayland-bindings/client/viewporter" | |
| 9 | + | "github.com/bnema/go-wayland-bindings/client/wayland" | |
| 10 | + | "github.com/stretchr/testify/mock" | |
| 11 | + | "github.com/stretchr/testify/require" | |
| 12 | + | "golang.org/x/sys/unix" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | // presentable returns a configured surface that sends through req. | |
| 16 | + | func presentable(req surfaceRequests, scale float64) *Surface { | |
| 17 | + | return &Surface{id: 1, req: req, width: 100, height: 50, scale: scale, configured: true, frameReady: true, fbDone: true, | |
| 18 | + | buffers: map[uint64]*wayland.Buffer{1: nil}, | |
| 19 | + | timelines: map[uint64]*linuxdrmsyncobj.WpLinuxDrmSyncobjTimeline{1: nil, 2: nil}, | |
| 20 | + | viewport: &viewporter.WpViewport{}} | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | func TestPresentOrdering(t *testing.T) { | |
| 24 | + | m := newMocksurfaceRequests(t) | |
| 25 | + | s := presentable(m, 1.5) | |
| 26 | + | // NotBefore makes testify fail a call that arrives before its predecessor. | |
| 27 | + | dest := m.EXPECT().setDestination(int32(100), int32(50)).Return(nil).Once() | |
| 28 | + | opaque := m.EXPECT().setOpaqueRegion(int32(100), int32(50), true).Return(nil).Once().NotBefore(dest) | |
| 29 | + | acquire := m.EXPECT().setAcquire(uint64(1), uint64(7)).Return(nil).Once().NotBefore(opaque) | |
| 30 | + | release := m.EXPECT().setRelease(uint64(2), uint64(8)).Return(nil).Once().NotBefore(acquire) | |
| 31 | + | attach := m.EXPECT().attach(uint64(1)).Return(nil).Once().NotBefore(release) | |
| 32 | + | damage := m.EXPECT().damageBuffer(Rect{1, 2, 3, 4}).Return(nil).Once().NotBefore(attach) | |
| 33 | + | frame := m.EXPECT().frame().Return(nil).Once().NotBefore(damage) | |
| 34 | + | m.EXPECT().commit().Return(nil).Once().NotBefore(frame) | |
| 35 | + | ||
| 36 | + | p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, AcquirePoint: 7, ReleasePoint: 8, | |
| 37 | + | Damage: []Rect{{1, 2, 3, 4}}, Opaque: true} | |
| 38 | + | require.NoError(t, s.Present(p)) | |
| 39 | + | require.False(t, s.frameReady) | |
| 40 | + | require.ErrorContains(t, s.Present(p), "frame callback pending") // no further request: strict mock | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | func TestPresentSkipsUnchangedState(t *testing.T) { | |
| 44 | + | m := newMocksurfaceRequests(t) | |
| 45 | + | s := presentable(m, 2) | |
| 46 | + | m.EXPECT().setDestination(int32(100), int32(50)).Return(nil).Once() | |
| 47 | + | m.EXPECT().setOpaqueRegion(int32(100), int32(50), true).Return(nil).Once() | |
| 48 | + | m.EXPECT().setAcquire(mock.Anything, mock.Anything).Return(nil).Twice() | |
| 49 | + | m.EXPECT().setRelease(mock.Anything, mock.Anything).Return(nil).Twice() | |
| 50 | + | m.EXPECT().attach(uint64(1)).Return(nil).Twice() | |
| 51 | + | m.EXPECT().damageBuffer(Rect{0, 0, 200, 100}).Return(nil).Twice() // no damage: whole physical buffer | |
| 52 | + | m.EXPECT().frame().Return(nil).Twice() | |
| 53 | + | m.EXPECT().commit().Return(nil).Twice() | |
| 54 | + | p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, Opaque: true} | |
| 55 | + | require.NoError(t, s.Present(p)) | |
| 56 | + | s.frameReady = true | |
| 57 | + | require.NoError(t, s.Present(p)) // destination and opaque region are not resent | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | func TestPresentRefusals(t *testing.T) { | |
| 61 | + | m := newMocksurfaceRequests(t) // strict: a refused Present sends nothing | |
| 62 | + | p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2} | |
| 63 | + | for name, mutate := range map[string]func(*Surface){ | |
| 64 | + | "before configure": func(s *Surface) { s.configured = false }, | |
| 65 | + | "before feedback": func(s *Surface) { s.fbDone = false }, | |
| 66 | + | "unknown buffer": func(s *Surface) { delete(s.buffers, 1) }, | |
| 67 | + | "unknown timeline": func(s *Surface) { delete(s.timelines, 2) }, | |
| 68 | + | "closed": func(s *Surface) { s.closed = true }, | |
| 69 | + | "bad geometry": func(s *Surface) { s.scale = 0 }, | |
| 70 | + | "fractional without viewporter": func(s *Surface) { | |
| 71 | + | s.scale, s.viewport = 1.5, nil | |
| 72 | + | }, | |
| 73 | + | } { | |
| 74 | + | s := presentable(m, 1) | |
| 75 | + | mutate(s) | |
| 76 | + | require.Error(t, s.Present(p), name) | |
| 77 | + | } | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | func TestPhysicalSize(t *testing.T) { | |
| 81 | + | s := presentable(nil, 1.25) | |
| 82 | + | w, h, err := s.PhysicalSize() | |
| 83 | + | require.NoError(t, err) | |
| 84 | + | require.Equal(t, [2]int32{125, 63}, [2]int32{w, h}) // 62.5 rounds up | |
| 85 | + | } | |
| 86 | + | ||
| 87 | + | // feedbackHandler is the measured input, not a double: it keeps what the | |
| 88 | + | // owner-side feedback parsing produced. | |
| 89 | + | type feedbackHandler struct { | |
| 90 | + | NopHandler | |
| 91 | + | done int | |
| 92 | + | errs []error | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | func (h *feedbackHandler) FeedbackDone(SurfaceID) { h.done++ } | |
| 96 | + | func (h *feedbackHandler) Error(err error) { h.errs = append(h.errs, err) } | |
| 97 | + | ||
| 98 | + | func tableFD(t *testing.T, entries []Format) int { | |
| 99 | + | t.Helper() | |
| 100 | + | fd, err := unix.MemfdCreate("table", unix.MFD_CLOEXEC) | |
| 101 | + | require.NoError(t, err) | |
| 102 | + | var b []byte | |
| 103 | + | for _, e := range entries { | |
| 104 | + | b = binary.NativeEndian.AppendUint32(b, e.FourCC) | |
| 105 | + | b = binary.NativeEndian.AppendUint32(b, 0) | |
| 106 | + | b = binary.NativeEndian.AppendUint64(b, e.Modifier) | |
| 107 | + | } | |
| 108 | + | _, err = unix.Write(fd, b) | |
| 109 | + | require.NoError(t, err) | |
| 110 | + | return fd | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | func postRound(c *Conn, s *Surface, fd int, size uint32, device uint64, indices ...uint16) { | |
| 114 | + | id := uint32(s.id) | |
| 115 | + | c.q.post(&event{kind: evFeedbackTable, id: id, fd: int32(fd), flags: size}) | |
| 116 | + | c.q.post(&event{kind: evFeedbackMainDevice, id: id, dev: device}) | |
| 117 | + | e := event{kind: evFeedbackFormats, id: id} | |
| 118 | + | for _, i := range indices { | |
| 119 | + | binary.NativeEndian.PutUint16(e.name[e.nameLen:], i) | |
| 120 | + | e.nameLen += 2 | |
| 121 | + | } | |
| 122 | + | c.q.post(&e) | |
| 123 | + | c.q.post(&event{kind: evFeedbackDone, id: id}) | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | func newOwnerConn() (*Conn, *Surface) { | |
| 127 | + | c := &Conn{q: newQueue(false), surfaces: map[SurfaceID]*Surface{}, watched: map[int32]uint64{}, efd: -1, epfd: -1} | |
| 128 | + | s := presentable(nil, 1) | |
| 129 | + | c.surfaces[s.id], s.c, s.fbDone = s, c, false | |
| 130 | + | return c, s | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | func TestFeedbackParsedOnOwner(t *testing.T) { | |
| 134 | + | c, s := newOwnerConn() | |
| 135 | + | table := []Format{{0x34325258, 0}, {0x34325241, 7}, {0x34325258, 1 << 40}} | |
| 136 | + | postRound(c, s, tableFD(t, table), 16*3, 0xe280, 2, 0) | |
| 137 | + | h := &feedbackHandler{} | |
| 138 | + | require.NoError(t, c.Dispatch(h)) | |
| 139 | + | require.Empty(t, h.errs) | |
| 140 | + | require.Equal(t, 1, h.done) | |
| 141 | + | fb := s.Feedback() | |
| 142 | + | require.NotNil(t, fb) | |
| 143 | + | require.Equal(t, uint64(0xe280), fb.MainDevice) | |
| 144 | + | require.Equal(t, []Format{table[2], table[0]}, fb.Formats, "tranche order") | |
| 145 | + | ||
| 146 | + | // A second round replaces the first without leaking the first one's formats. | |
| 147 | + | postRound(c, s, tableFD(t, table), 16*3, 0xe281, 1) | |
| 148 | + | require.NoError(t, c.Dispatch(h)) | |
| 149 | + | require.Equal(t, &Feedback{MainDevice: 0xe281, Formats: []Format{table[1]}}, s.Feedback()) | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | func TestFeedbackBadIndexAndSize(t *testing.T) { | |
| 153 | + | c, s := newOwnerConn() | |
| 154 | + | postRound(c, s, tableFD(t, []Format{{1, 1}}), 16, 5, 3) | |
| 155 | + | postRound(c, s, tableFD(t, nil), 15, 5) | |
| 156 | + | h := &feedbackHandler{} | |
| 157 | + | require.NoError(t, c.Dispatch(h)) | |
| 158 | + | require.Len(t, h.errs, 2) | |
| 159 | + | require.ErrorContains(t, h.errs[0], "out of range") | |
| 160 | + | require.ErrorContains(t, h.errs[1], "invalid dmabuf format table size") | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | func fdOpen(fd int) bool { _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0); return err == nil } | |
| 164 | + | ||
| 165 | + | func TestCloseClosesUndrainedFeedbackFDs(t *testing.T) { | |
| 166 | + | c, s := newOwnerConn() | |
| 167 | + | ringed := tableFD(t, nil) | |
| 168 | + | c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(ringed)}) | |
| 169 | + | held := tableFD(t, nil) | |
| 170 | + | c.q.pausing = true | |
| 171 | + | c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(held)}) // parked in held | |
| 172 | + | require.True(t, fdOpen(ringed)) | |
| 173 | + | require.True(t, fdOpen(held)) | |
| 174 | + | c.q.closeFDs() | |
| 175 | + | require.False(t, fdOpen(ringed), "ring event's descriptor") | |
| 176 | + | require.False(t, fdOpen(held), "parked event's descriptor") | |
| 177 | + | ||
| 178 | + | // An event that is drained owns its descriptor no more: closeFDs must not | |
| 179 | + | // touch a number the process may have reused. | |
| 180 | + | drained := tableFD(t, nil) | |
| 181 | + | c.q.post(&event{kind: evFeedbackTable, id: 99, fd: int32(drained)}) // pausing: held | |
| 182 | + | c.q.pausing = false | |
| 183 | + | c.q.endPause() | |
| 184 | + | var ev event | |
| 185 | + | for c.q.pop(&ev) { // drain everything: the earlier events and this one | |
| 186 | + | } | |
| 187 | + | c.q.closeFDs() | |
| 188 | + | require.True(t, fdOpen(drained)) | |
| 189 | + | _ = unix.Close(drained) | |
| 190 | + | } | |
| 191 | + | ||
| 192 | + | func TestEventForClosedSurfaceDropsFD(t *testing.T) { | |
| 193 | + | c, s := newOwnerConn() | |
| 194 | + | fd := tableFD(t, nil) | |
| 195 | + | s.closed = true | |
| 196 | + | c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(fd)}) | |
| 197 | + | require.NoError(t, c.Dispatch(nil)) | |
| 198 | + | require.False(t, fdOpen(fd)) | |
| 199 | + | } | |
| 200 | + | ||
| 201 | + | func TestLockRules(t *testing.T) { | |
| 202 | + | c := &Conn{q: newQueue(false), surfaces: map[SurfaceID]*Surface{}} | |
| 203 | + | l := &Lock{c: c, gen: 1} | |
| 204 | + | c.curLock = l | |
| 205 | + | require.ErrorContains(t, l.Unlock(), "before the locked event") | |
| 206 | + | c.q.post(&event{kind: evLocked, id: 2}) // another generation: ignored | |
| 207 | + | require.NoError(t, c.Dispatch(nil)) | |
| 208 | + | require.False(t, l.Locked()) | |
| 209 | + | c.q.post(&event{kind: evLocked, id: 1}) | |
| 210 | + | require.NoError(t, c.Dispatch(nil)) | |
| 211 | + | require.True(t, l.Locked()) | |
| 212 | + | require.ErrorContains(t, l.Close(), "use Unlock") | |
| 213 | + | c.q.post(&event{kind: evLockFinished, id: 1}) | |
| 214 | + | require.NoError(t, c.Dispatch(nil)) | |
| 215 | + | require.False(t, l.Locked()) | |
| 216 | + | require.ErrorContains(t, l.Unlock(), "before the locked event") | |
| 217 | + | } | |
| @@ -0,0 +1,217 @@ | |||||
| 1 | + | package neferclient | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "encoding/binary" | |||
| 5 | + | "testing" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/go-wayland-bindings/client/linuxdrmsyncobj" | |||
| 8 | + | "github.com/bnema/go-wayland-bindings/client/viewporter" | |||
| 9 | + | "github.com/bnema/go-wayland-bindings/client/wayland" | |||
| 10 | + | "github.com/stretchr/testify/mock" | |||
| 11 | + | "github.com/stretchr/testify/require" | |||
| 12 | + | "golang.org/x/sys/unix" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | // presentable returns a configured surface that sends through req. | |||
| 16 | + | func presentable(req surfaceRequests, scale float64) *Surface { | |||
| 17 | + | return &Surface{id: 1, req: req, width: 100, height: 50, scale: scale, configured: true, frameReady: true, fbDone: true, | |||
| 18 | + | buffers: map[uint64]*wayland.Buffer{1: nil}, | |||
| 19 | + | timelines: map[uint64]*linuxdrmsyncobj.WpLinuxDrmSyncobjTimeline{1: nil, 2: nil}, | |||
| 20 | + | viewport: &viewporter.WpViewport{}} | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | func TestPresentOrdering(t *testing.T) { | |||
| 24 | + | m := newMocksurfaceRequests(t) | |||
| 25 | + | s := presentable(m, 1.5) | |||
| 26 | + | // NotBefore makes testify fail a call that arrives before its predecessor. | |||
| 27 | + | dest := m.EXPECT().setDestination(int32(100), int32(50)).Return(nil).Once() | |||
| 28 | + | opaque := m.EXPECT().setOpaqueRegion(int32(100), int32(50), true).Return(nil).Once().NotBefore(dest) | |||
| 29 | + | acquire := m.EXPECT().setAcquire(uint64(1), uint64(7)).Return(nil).Once().NotBefore(opaque) | |||
| 30 | + | release := m.EXPECT().setRelease(uint64(2), uint64(8)).Return(nil).Once().NotBefore(acquire) | |||
| 31 | + | attach := m.EXPECT().attach(uint64(1)).Return(nil).Once().NotBefore(release) | |||
| 32 | + | damage := m.EXPECT().damageBuffer(Rect{1, 2, 3, 4}).Return(nil).Once().NotBefore(attach) | |||
| 33 | + | frame := m.EXPECT().frame().Return(nil).Once().NotBefore(damage) | |||
| 34 | + | m.EXPECT().commit().Return(nil).Once().NotBefore(frame) | |||
| 35 | + | ||||
| 36 | + | p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, AcquirePoint: 7, ReleasePoint: 8, | |||
| 37 | + | Damage: []Rect{{1, 2, 3, 4}}, Opaque: true} | |||
| 38 | + | require.NoError(t, s.Present(p)) | |||
| 39 | + | require.False(t, s.frameReady) | |||
| 40 | + | require.ErrorContains(t, s.Present(p), "frame callback pending") // no further request: strict mock | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | func TestPresentSkipsUnchangedState(t *testing.T) { | |||
| 44 | + | m := newMocksurfaceRequests(t) | |||
| 45 | + | s := presentable(m, 2) | |||
| 46 | + | m.EXPECT().setDestination(int32(100), int32(50)).Return(nil).Once() | |||
| 47 | + | m.EXPECT().setOpaqueRegion(int32(100), int32(50), true).Return(nil).Once() | |||
| 48 | + | m.EXPECT().setAcquire(mock.Anything, mock.Anything).Return(nil).Twice() | |||
| 49 | + | m.EXPECT().setRelease(mock.Anything, mock.Anything).Return(nil).Twice() | |||
| 50 | + | m.EXPECT().attach(uint64(1)).Return(nil).Twice() | |||
| 51 | + | m.EXPECT().damageBuffer(Rect{0, 0, 200, 100}).Return(nil).Twice() // no damage: whole physical buffer | |||
| 52 | + | m.EXPECT().frame().Return(nil).Twice() | |||
| 53 | + | m.EXPECT().commit().Return(nil).Twice() | |||
| 54 | + | p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2, Opaque: true} | |||
| 55 | + | require.NoError(t, s.Present(p)) | |||
| 56 | + | s.frameReady = true | |||
| 57 | + | require.NoError(t, s.Present(p)) // destination and opaque region are not resent | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | func TestPresentRefusals(t *testing.T) { | |||
| 61 | + | m := newMocksurfaceRequests(t) // strict: a refused Present sends nothing | |||
| 62 | + | p := &Present{Buffer: 1, AcquireTimeline: 1, ReleaseTimeline: 2} | |||
| 63 | + | for name, mutate := range map[string]func(*Surface){ | |||
| 64 | + | "before configure": func(s *Surface) { s.configured = false }, | |||
| 65 | + | "before feedback": func(s *Surface) { s.fbDone = false }, | |||
| 66 | + | "unknown buffer": func(s *Surface) { delete(s.buffers, 1) }, | |||
| 67 | + | "unknown timeline": func(s *Surface) { delete(s.timelines, 2) }, | |||
| 68 | + | "closed": func(s *Surface) { s.closed = true }, | |||
| 69 | + | "bad geometry": func(s *Surface) { s.scale = 0 }, | |||
| 70 | + | "fractional without viewporter": func(s *Surface) { | |||
| 71 | + | s.scale, s.viewport = 1.5, nil | |||
| 72 | + | }, | |||
| 73 | + | } { | |||
| 74 | + | s := presentable(m, 1) | |||
| 75 | + | mutate(s) | |||
| 76 | + | require.Error(t, s.Present(p), name) | |||
| 77 | + | } | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | func TestPhysicalSize(t *testing.T) { | |||
| 81 | + | s := presentable(nil, 1.25) | |||
| 82 | + | w, h, err := s.PhysicalSize() | |||
| 83 | + | require.NoError(t, err) | |||
| 84 | + | require.Equal(t, [2]int32{125, 63}, [2]int32{w, h}) // 62.5 rounds up | |||
| 85 | + | } | |||
| 86 | + | ||||
| 87 | + | // feedbackHandler is the measured input, not a double: it keeps what the | |||
| 88 | + | // owner-side feedback parsing produced. | |||
| 89 | + | type feedbackHandler struct { | |||
| 90 | + | NopHandler | |||
| 91 | + | done int | |||
| 92 | + | errs []error | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | func (h *feedbackHandler) FeedbackDone(SurfaceID) { h.done++ } | |||
| 96 | + | func (h *feedbackHandler) Error(err error) { h.errs = append(h.errs, err) } | |||
| 97 | + | ||||
| 98 | + | func tableFD(t *testing.T, entries []Format) int { | |||
| 99 | + | t.Helper() | |||
| 100 | + | fd, err := unix.MemfdCreate("table", unix.MFD_CLOEXEC) | |||
| 101 | + | require.NoError(t, err) | |||
| 102 | + | var b []byte | |||
| 103 | + | for _, e := range entries { | |||
| 104 | + | b = binary.NativeEndian.AppendUint32(b, e.FourCC) | |||
| 105 | + | b = binary.NativeEndian.AppendUint32(b, 0) | |||
| 106 | + | b = binary.NativeEndian.AppendUint64(b, e.Modifier) | |||
| 107 | + | } | |||
| 108 | + | _, err = unix.Write(fd, b) | |||
| 109 | + | require.NoError(t, err) | |||
| 110 | + | return fd | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | func postRound(c *Conn, s *Surface, fd int, size uint32, device uint64, indices ...uint16) { | |||
| 114 | + | id := uint32(s.id) | |||
| 115 | + | c.q.post(&event{kind: evFeedbackTable, id: id, fd: int32(fd), flags: size}) | |||
| 116 | + | c.q.post(&event{kind: evFeedbackMainDevice, id: id, dev: device}) | |||
| 117 | + | e := event{kind: evFeedbackFormats, id: id} | |||
| 118 | + | for _, i := range indices { | |||
| 119 | + | binary.NativeEndian.PutUint16(e.name[e.nameLen:], i) | |||
| 120 | + | e.nameLen += 2 | |||
| 121 | + | } | |||
| 122 | + | c.q.post(&e) | |||
| 123 | + | c.q.post(&event{kind: evFeedbackDone, id: id}) | |||
| 124 | + | } | |||
| 125 | + | ||||
| 126 | + | func newOwnerConn() (*Conn, *Surface) { | |||
| 127 | + | c := &Conn{q: newQueue(false), surfaces: map[SurfaceID]*Surface{}, watched: map[int32]uint64{}, efd: -1, epfd: -1} | |||
| 128 | + | s := presentable(nil, 1) | |||
| 129 | + | c.surfaces[s.id], s.c, s.fbDone = s, c, false | |||
| 130 | + | return c, s | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | func TestFeedbackParsedOnOwner(t *testing.T) { | |||
| 134 | + | c, s := newOwnerConn() | |||
| 135 | + | table := []Format{{0x34325258, 0}, {0x34325241, 7}, {0x34325258, 1 << 40}} | |||
| 136 | + | postRound(c, s, tableFD(t, table), 16*3, 0xe280, 2, 0) | |||
| 137 | + | h := &feedbackHandler{} | |||
| 138 | + | require.NoError(t, c.Dispatch(h)) | |||
| 139 | + | require.Empty(t, h.errs) | |||
| 140 | + | require.Equal(t, 1, h.done) | |||
| 141 | + | fb := s.Feedback() | |||
| 142 | + | require.NotNil(t, fb) | |||
| 143 | + | require.Equal(t, uint64(0xe280), fb.MainDevice) | |||
| 144 | + | require.Equal(t, []Format{table[2], table[0]}, fb.Formats, "tranche order") | |||
| 145 | + | ||||
| 146 | + | // A second round replaces the first without leaking the first one's formats. | |||
| 147 | + | postRound(c, s, tableFD(t, table), 16*3, 0xe281, 1) | |||
| 148 | + | require.NoError(t, c.Dispatch(h)) | |||
| 149 | + | require.Equal(t, &Feedback{MainDevice: 0xe281, Formats: []Format{table[1]}}, s.Feedback()) | |||
| 150 | + | } | |||
| 151 | + | ||||
| 152 | + | func TestFeedbackBadIndexAndSize(t *testing.T) { | |||
| 153 | + | c, s := newOwnerConn() | |||
| 154 | + | postRound(c, s, tableFD(t, []Format{{1, 1}}), 16, 5, 3) | |||
| 155 | + | postRound(c, s, tableFD(t, nil), 15, 5) | |||
| 156 | + | h := &feedbackHandler{} | |||
| 157 | + | require.NoError(t, c.Dispatch(h)) | |||
| 158 | + | require.Len(t, h.errs, 2) | |||
| 159 | + | require.ErrorContains(t, h.errs[0], "out of range") | |||
| 160 | + | require.ErrorContains(t, h.errs[1], "invalid dmabuf format table size") | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | func fdOpen(fd int) bool { _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0); return err == nil } | |||
| 164 | + | ||||
| 165 | + | func TestCloseClosesUndrainedFeedbackFDs(t *testing.T) { | |||
| 166 | + | c, s := newOwnerConn() | |||
| 167 | + | ringed := tableFD(t, nil) | |||
| 168 | + | c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(ringed)}) | |||
| 169 | + | held := tableFD(t, nil) | |||
| 170 | + | c.q.pausing = true | |||
| 171 | + | c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(held)}) // parked in held | |||
| 172 | + | require.True(t, fdOpen(ringed)) | |||
| 173 | + | require.True(t, fdOpen(held)) | |||
| 174 | + | c.q.closeFDs() | |||
| 175 | + | require.False(t, fdOpen(ringed), "ring event's descriptor") | |||
| 176 | + | require.False(t, fdOpen(held), "parked event's descriptor") | |||
| 177 | + | ||||
| 178 | + | // An event that is drained owns its descriptor no more: closeFDs must not | |||
| 179 | + | // touch a number the process may have reused. | |||
| 180 | + | drained := tableFD(t, nil) | |||
| 181 | + | c.q.post(&event{kind: evFeedbackTable, id: 99, fd: int32(drained)}) // pausing: held | |||
| 182 | + | c.q.pausing = false | |||
| 183 | + | c.q.endPause() | |||
| 184 | + | var ev event | |||
| 185 | + | for c.q.pop(&ev) { // drain everything: the earlier events and this one | |||
| 186 | + | } | |||
| 187 | + | c.q.closeFDs() | |||
| 188 | + | require.True(t, fdOpen(drained)) | |||
| 189 | + | _ = unix.Close(drained) | |||
| 190 | + | } | |||
| 191 | + | ||||
| 192 | + | func TestEventForClosedSurfaceDropsFD(t *testing.T) { | |||
| 193 | + | c, s := newOwnerConn() | |||
| 194 | + | fd := tableFD(t, nil) | |||
| 195 | + | s.closed = true | |||
| 196 | + | c.q.post(&event{kind: evFeedbackTable, id: uint32(s.id), fd: int32(fd)}) | |||
| 197 | + | require.NoError(t, c.Dispatch(nil)) | |||
| 198 | + | require.False(t, fdOpen(fd)) | |||
| 199 | + | } | |||
| 200 | + | ||||
| 201 | + | func TestLockRules(t *testing.T) { | |||
| 202 | + | c := &Conn{q: newQueue(false), surfaces: map[SurfaceID]*Surface{}} | |||
| 203 | + | l := &Lock{c: c, gen: 1} | |||
| 204 | + | c.curLock = l | |||
| 205 | + | require.ErrorContains(t, l.Unlock(), "before the locked event") | |||
| 206 | + | c.q.post(&event{kind: evLocked, id: 2}) // another generation: ignored | |||
| 207 | + | require.NoError(t, c.Dispatch(nil)) | |||
| 208 | + | require.False(t, l.Locked()) | |||
| 209 | + | c.q.post(&event{kind: evLocked, id: 1}) | |||
| 210 | + | require.NoError(t, c.Dispatch(nil)) | |||
| 211 | + | require.True(t, l.Locked()) | |||
| 212 | + | require.ErrorContains(t, l.Close(), "use Unlock") | |||
| 213 | + | c.q.post(&event{kind: evLockFinished, id: 1}) | |||
| 214 | + | require.NoError(t, c.Dispatch(nil)) | |||
| 215 | + | require.False(t, l.Locked()) | |||
| 216 | + | require.ErrorContains(t, l.Unlock(), "before the locked event") | |||
| 217 | + | } | |||
A surface_mock_test.go +520 -0
| @@ -0,0 +1,520 @@ | |||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |
| 2 | + | // github.com/vektra/mockery | |
| 3 | + | // template: testify | |
| 4 | + | ||
| 5 | + | package neferclient | |
| 6 | + | ||
| 7 | + | import ( | |
| 8 | + | mock "github.com/stretchr/testify/mock" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // newMocksurfaceRequests creates a new instance of mocksurfaceRequests. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 12 | + | // The first argument is typically a *testing.T value. | |
| 13 | + | func newMocksurfaceRequests(t interface { | |
| 14 | + | mock.TestingT | |
| 15 | + | Cleanup(func()) | |
| 16 | + | }) *mocksurfaceRequests { | |
| 17 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 18 | + | helper.Helper() | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | mock := &mocksurfaceRequests{} | |
| 22 | + | mock.Mock.Test(t) | |
| 23 | + | ||
| 24 | + | t.Cleanup(func() { | |
| 25 | + | if helper, ok := t.(interface{ Helper() }); ok { | |
| 26 | + | helper.Helper() | |
| 27 | + | } | |
| 28 | + | mock.AssertExpectations(t) | |
| 29 | + | }) | |
| 30 | + | ||
| 31 | + | return mock | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | // mocksurfaceRequests is an autogenerated mock type for the surfaceRequests type | |
| 35 | + | type mocksurfaceRequests struct { | |
| 36 | + | mock.Mock | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | type mocksurfaceRequests_Expecter struct { | |
| 40 | + | mock *mock.Mock | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | func (_m *mocksurfaceRequests) EXPECT() *mocksurfaceRequests_Expecter { | |
| 44 | + | return &mocksurfaceRequests_Expecter{mock: &_m.Mock} | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | // attach provides a mock function for the type mocksurfaceRequests | |
| 48 | + | func (_mock *mocksurfaceRequests) attach(buffer uint64) error { | |
| 49 | + | ret := _mock.Called(buffer) | |
| 50 | + | ||
| 51 | + | if len(ret) == 0 { | |
| 52 | + | panic("no return value specified for attach") | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | var r0 error | |
| 56 | + | if returnFunc, ok := ret.Get(0).(func(uint64) error); ok { | |
| 57 | + | r0 = returnFunc(buffer) | |
| 58 | + | } else { | |
| 59 | + | r0 = ret.Error(0) | |
| 60 | + | } | |
| 61 | + | return r0 | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | // mocksurfaceRequests_attach_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'attach' | |
| 65 | + | type mocksurfaceRequests_attach_Call struct { | |
| 66 | + | *mock.Call | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // attach is a helper method to define mock.On call | |
| 70 | + | // - buffer uint64 | |
| 71 | + | func (_e *mocksurfaceRequests_Expecter) attach(buffer any) *mocksurfaceRequests_attach_Call { | |
| 72 | + | return &mocksurfaceRequests_attach_Call{Call: _e.mock.On("attach", buffer)} | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | func (_c *mocksurfaceRequests_attach_Call) Run(run func(buffer uint64)) *mocksurfaceRequests_attach_Call { | |
| 76 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 77 | + | var arg0 uint64 | |
| 78 | + | if args[0] != nil { | |
| 79 | + | arg0 = args[0].(uint64) | |
| 80 | + | } | |
| 81 | + | run( | |
| 82 | + | arg0, | |
| 83 | + | ) | |
| 84 | + | }) | |
| 85 | + | return _c | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | func (_c *mocksurfaceRequests_attach_Call) Return(err error) *mocksurfaceRequests_attach_Call { | |
| 89 | + | _c.Call.Return(err) | |
| 90 | + | return _c | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | func (_c *mocksurfaceRequests_attach_Call) RunAndReturn(run func(buffer uint64) error) *mocksurfaceRequests_attach_Call { | |
| 94 | + | _c.Call.Return(run) | |
| 95 | + | return _c | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | // commit provides a mock function for the type mocksurfaceRequests | |
| 99 | + | func (_mock *mocksurfaceRequests) commit() error { | |
| 100 | + | ret := _mock.Called() | |
| 101 | + | ||
| 102 | + | if len(ret) == 0 { | |
| 103 | + | panic("no return value specified for commit") | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | var r0 error | |
| 107 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 108 | + | r0 = returnFunc() | |
| 109 | + | } else { | |
| 110 | + | r0 = ret.Error(0) | |
| 111 | + | } | |
| 112 | + | return r0 | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | // mocksurfaceRequests_commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'commit' | |
| 116 | + | type mocksurfaceRequests_commit_Call struct { | |
| 117 | + | *mock.Call | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | // commit is a helper method to define mock.On call | |
| 121 | + | func (_e *mocksurfaceRequests_Expecter) commit() *mocksurfaceRequests_commit_Call { | |
| 122 | + | return &mocksurfaceRequests_commit_Call{Call: _e.mock.On("commit")} | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | func (_c *mocksurfaceRequests_commit_Call) Run(run func()) *mocksurfaceRequests_commit_Call { | |
| 126 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 127 | + | run() | |
| 128 | + | }) | |
| 129 | + | return _c | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | func (_c *mocksurfaceRequests_commit_Call) Return(err error) *mocksurfaceRequests_commit_Call { | |
| 133 | + | _c.Call.Return(err) | |
| 134 | + | return _c | |
| 135 | + | } | |
| 136 | + | ||
| 137 | + | func (_c *mocksurfaceRequests_commit_Call) RunAndReturn(run func() error) *mocksurfaceRequests_commit_Call { | |
| 138 | + | _c.Call.Return(run) | |
| 139 | + | return _c | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | // damageBuffer provides a mock function for the type mocksurfaceRequests | |
| 143 | + | func (_mock *mocksurfaceRequests) damageBuffer(r Rect) error { | |
| 144 | + | ret := _mock.Called(r) | |
| 145 | + | ||
| 146 | + | if len(ret) == 0 { | |
| 147 | + | panic("no return value specified for damageBuffer") | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | var r0 error | |
| 151 | + | if returnFunc, ok := ret.Get(0).(func(Rect) error); ok { | |
| 152 | + | r0 = returnFunc(r) | |
| 153 | + | } else { | |
| 154 | + | r0 = ret.Error(0) | |
| 155 | + | } | |
| 156 | + | return r0 | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | // mocksurfaceRequests_damageBuffer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'damageBuffer' | |
| 160 | + | type mocksurfaceRequests_damageBuffer_Call struct { | |
| 161 | + | *mock.Call | |
| 162 | + | } | |
| 163 | + | ||
| 164 | + | // damageBuffer is a helper method to define mock.On call | |
| 165 | + | // - r Rect | |
| 166 | + | func (_e *mocksurfaceRequests_Expecter) damageBuffer(r any) *mocksurfaceRequests_damageBuffer_Call { | |
| 167 | + | return &mocksurfaceRequests_damageBuffer_Call{Call: _e.mock.On("damageBuffer", r)} | |
| 168 | + | } | |
| 169 | + | ||
| 170 | + | func (_c *mocksurfaceRequests_damageBuffer_Call) Run(run func(r Rect)) *mocksurfaceRequests_damageBuffer_Call { | |
| 171 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 172 | + | var arg0 Rect | |
| 173 | + | if args[0] != nil { | |
| 174 | + | arg0 = args[0].(Rect) | |
| 175 | + | } | |
| 176 | + | run( | |
| 177 | + | arg0, | |
| 178 | + | ) | |
| 179 | + | }) | |
| 180 | + | return _c | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | func (_c *mocksurfaceRequests_damageBuffer_Call) Return(err error) *mocksurfaceRequests_damageBuffer_Call { | |
| 184 | + | _c.Call.Return(err) | |
| 185 | + | return _c | |
| 186 | + | } | |
| 187 | + | ||
| 188 | + | func (_c *mocksurfaceRequests_damageBuffer_Call) RunAndReturn(run func(r Rect) error) *mocksurfaceRequests_damageBuffer_Call { | |
| 189 | + | _c.Call.Return(run) | |
| 190 | + | return _c | |
| 191 | + | } | |
| 192 | + | ||
| 193 | + | // frame provides a mock function for the type mocksurfaceRequests | |
| 194 | + | func (_mock *mocksurfaceRequests) frame() error { | |
| 195 | + | ret := _mock.Called() | |
| 196 | + | ||
| 197 | + | if len(ret) == 0 { | |
| 198 | + | panic("no return value specified for frame") | |
| 199 | + | } | |
| 200 | + | ||
| 201 | + | var r0 error | |
| 202 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |
| 203 | + | r0 = returnFunc() | |
| 204 | + | } else { | |
| 205 | + | r0 = ret.Error(0) | |
| 206 | + | } | |
| 207 | + | return r0 | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | // mocksurfaceRequests_frame_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'frame' | |
| 211 | + | type mocksurfaceRequests_frame_Call struct { | |
| 212 | + | *mock.Call | |
| 213 | + | } | |
| 214 | + | ||
| 215 | + | // frame is a helper method to define mock.On call | |
| 216 | + | func (_e *mocksurfaceRequests_Expecter) frame() *mocksurfaceRequests_frame_Call { | |
| 217 | + | return &mocksurfaceRequests_frame_Call{Call: _e.mock.On("frame")} | |
| 218 | + | } | |
| 219 | + | ||
| 220 | + | func (_c *mocksurfaceRequests_frame_Call) Run(run func()) *mocksurfaceRequests_frame_Call { | |
| 221 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 222 | + | run() | |
| 223 | + | }) | |
| 224 | + | return _c | |
| 225 | + | } | |
| 226 | + | ||
| 227 | + | func (_c *mocksurfaceRequests_frame_Call) Return(err error) *mocksurfaceRequests_frame_Call { | |
| 228 | + | _c.Call.Return(err) | |
| 229 | + | return _c | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | func (_c *mocksurfaceRequests_frame_Call) RunAndReturn(run func() error) *mocksurfaceRequests_frame_Call { | |
| 233 | + | _c.Call.Return(run) | |
| 234 | + | return _c | |
| 235 | + | } | |
| 236 | + | ||
| 237 | + | // setAcquire provides a mock function for the type mocksurfaceRequests | |
| 238 | + | func (_mock *mocksurfaceRequests) setAcquire(timeline uint64, point uint64) error { | |
| 239 | + | ret := _mock.Called(timeline, point) | |
| 240 | + | ||
| 241 | + | if len(ret) == 0 { | |
| 242 | + | panic("no return value specified for setAcquire") | |
| 243 | + | } | |
| 244 | + | ||
| 245 | + | var r0 error | |
| 246 | + | if returnFunc, ok := ret.Get(0).(func(uint64, uint64) error); ok { | |
| 247 | + | r0 = returnFunc(timeline, point) | |
| 248 | + | } else { | |
| 249 | + | r0 = ret.Error(0) | |
| 250 | + | } | |
| 251 | + | return r0 | |
| 252 | + | } | |
| 253 | + | ||
| 254 | + | // mocksurfaceRequests_setAcquire_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setAcquire' | |
| 255 | + | type mocksurfaceRequests_setAcquire_Call struct { | |
| 256 | + | *mock.Call | |
| 257 | + | } | |
| 258 | + | ||
| 259 | + | // setAcquire is a helper method to define mock.On call | |
| 260 | + | // - timeline uint64 | |
| 261 | + | // - point uint64 | |
| 262 | + | func (_e *mocksurfaceRequests_Expecter) setAcquire(timeline any, point any) *mocksurfaceRequests_setAcquire_Call { | |
| 263 | + | return &mocksurfaceRequests_setAcquire_Call{Call: _e.mock.On("setAcquire", timeline, point)} | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | func (_c *mocksurfaceRequests_setAcquire_Call) Run(run func(timeline uint64, point uint64)) *mocksurfaceRequests_setAcquire_Call { | |
| 267 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 268 | + | var arg0 uint64 | |
| 269 | + | if args[0] != nil { | |
| 270 | + | arg0 = args[0].(uint64) | |
| 271 | + | } | |
| 272 | + | var arg1 uint64 | |
| 273 | + | if args[1] != nil { | |
| 274 | + | arg1 = args[1].(uint64) | |
| 275 | + | } | |
| 276 | + | run( | |
| 277 | + | arg0, | |
| 278 | + | arg1, | |
| 279 | + | ) | |
| 280 | + | }) | |
| 281 | + | return _c | |
| 282 | + | } | |
| 283 | + | ||
| 284 | + | func (_c *mocksurfaceRequests_setAcquire_Call) Return(err error) *mocksurfaceRequests_setAcquire_Call { | |
| 285 | + | _c.Call.Return(err) | |
| 286 | + | return _c | |
| 287 | + | } | |
| 288 | + | ||
| 289 | + | func (_c *mocksurfaceRequests_setAcquire_Call) RunAndReturn(run func(timeline uint64, point uint64) error) *mocksurfaceRequests_setAcquire_Call { | |
| 290 | + | _c.Call.Return(run) | |
| 291 | + | return _c | |
| 292 | + | } | |
| 293 | + | ||
| 294 | + | // setBufferScale provides a mock function for the type mocksurfaceRequests | |
| 295 | + | func (_mock *mocksurfaceRequests) setBufferScale(n int32) error { | |
| 296 | + | ret := _mock.Called(n) | |
| 297 | + | ||
| 298 | + | if len(ret) == 0 { | |
| 299 | + | panic("no return value specified for setBufferScale") | |
| 300 | + | } | |
| 301 | + | ||
| 302 | + | var r0 error | |
| 303 | + | if returnFunc, ok := ret.Get(0).(func(int32) error); ok { | |
| 304 | + | r0 = returnFunc(n) | |
| 305 | + | } else { | |
| 306 | + | r0 = ret.Error(0) | |
| 307 | + | } | |
| 308 | + | return r0 | |
| 309 | + | } | |
| 310 | + | ||
| 311 | + | // mocksurfaceRequests_setBufferScale_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setBufferScale' | |
| 312 | + | type mocksurfaceRequests_setBufferScale_Call struct { | |
| 313 | + | *mock.Call | |
| 314 | + | } | |
| 315 | + | ||
| 316 | + | // setBufferScale is a helper method to define mock.On call | |
| 317 | + | // - n int32 | |
| 318 | + | func (_e *mocksurfaceRequests_Expecter) setBufferScale(n any) *mocksurfaceRequests_setBufferScale_Call { | |
| 319 | + | return &mocksurfaceRequests_setBufferScale_Call{Call: _e.mock.On("setBufferScale", n)} | |
| 320 | + | } | |
| 321 | + | ||
| 322 | + | func (_c *mocksurfaceRequests_setBufferScale_Call) Run(run func(n int32)) *mocksurfaceRequests_setBufferScale_Call { | |
| 323 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 324 | + | var arg0 int32 | |
| 325 | + | if args[0] != nil { | |
| 326 | + | arg0 = args[0].(int32) | |
| 327 | + | } | |
| 328 | + | run( | |
| 329 | + | arg0, | |
| 330 | + | ) | |
| 331 | + | }) | |
| 332 | + | return _c | |
| 333 | + | } | |
| 334 | + | ||
| 335 | + | func (_c *mocksurfaceRequests_setBufferScale_Call) Return(err error) *mocksurfaceRequests_setBufferScale_Call { | |
| 336 | + | _c.Call.Return(err) | |
| 337 | + | return _c | |
| 338 | + | } | |
| 339 | + | ||
| 340 | + | func (_c *mocksurfaceRequests_setBufferScale_Call) RunAndReturn(run func(n int32) error) *mocksurfaceRequests_setBufferScale_Call { | |
| 341 | + | _c.Call.Return(run) | |
| 342 | + | return _c | |
| 343 | + | } | |
| 344 | + | ||
| 345 | + | // setDestination provides a mock function for the type mocksurfaceRequests | |
| 346 | + | func (_mock *mocksurfaceRequests) setDestination(w int32, h int32) error { | |
| 347 | + | ret := _mock.Called(w, h) | |
| 348 | + | ||
| 349 | + | if len(ret) == 0 { | |
| 350 | + | panic("no return value specified for setDestination") | |
| 351 | + | } | |
| 352 | + | ||
| 353 | + | var r0 error | |
| 354 | + | if returnFunc, ok := ret.Get(0).(func(int32, int32) error); ok { | |
| 355 | + | r0 = returnFunc(w, h) | |
| 356 | + | } else { | |
| 357 | + | r0 = ret.Error(0) | |
| 358 | + | } | |
| 359 | + | return r0 | |
| 360 | + | } | |
| 361 | + | ||
| 362 | + | // mocksurfaceRequests_setDestination_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setDestination' | |
| 363 | + | type mocksurfaceRequests_setDestination_Call struct { | |
| 364 | + | *mock.Call | |
| 365 | + | } | |
| 366 | + | ||
| 367 | + | // setDestination is a helper method to define mock.On call | |
| 368 | + | // - w int32 | |
| 369 | + | // - h int32 | |
| 370 | + | func (_e *mocksurfaceRequests_Expecter) setDestination(w any, h any) *mocksurfaceRequests_setDestination_Call { | |
| 371 | + | return &mocksurfaceRequests_setDestination_Call{Call: _e.mock.On("setDestination", w, h)} | |
| 372 | + | } | |
| 373 | + | ||
| 374 | + | func (_c *mocksurfaceRequests_setDestination_Call) Run(run func(w int32, h int32)) *mocksurfaceRequests_setDestination_Call { | |
| 375 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 376 | + | var arg0 int32 | |
| 377 | + | if args[0] != nil { | |
| 378 | + | arg0 = args[0].(int32) | |
| 379 | + | } | |
| 380 | + | var arg1 int32 | |
| 381 | + | if args[1] != nil { | |
| 382 | + | arg1 = args[1].(int32) | |
| 383 | + | } | |
| 384 | + | run( | |
| 385 | + | arg0, | |
| 386 | + | arg1, | |
| 387 | + | ) | |
| 388 | + | }) | |
| 389 | + | return _c | |
| 390 | + | } | |
| 391 | + | ||
| 392 | + | func (_c *mocksurfaceRequests_setDestination_Call) Return(err error) *mocksurfaceRequests_setDestination_Call { | |
| 393 | + | _c.Call.Return(err) | |
| 394 | + | return _c | |
| 395 | + | } | |
| 396 | + | ||
| 397 | + | func (_c *mocksurfaceRequests_setDestination_Call) RunAndReturn(run func(w int32, h int32) error) *mocksurfaceRequests_setDestination_Call { | |
| 398 | + | _c.Call.Return(run) | |
| 399 | + | return _c | |
| 400 | + | } | |
| 401 | + | ||
| 402 | + | // setOpaqueRegion provides a mock function for the type mocksurfaceRequests | |
| 403 | + | func (_mock *mocksurfaceRequests) setOpaqueRegion(w int32, h int32, opaque bool) error { | |
| 404 | + | ret := _mock.Called(w, h, opaque) | |
| 405 | + | ||
| 406 | + | if len(ret) == 0 { | |
| 407 | + | panic("no return value specified for setOpaqueRegion") | |
| 408 | + | } | |
| 409 | + | ||
| 410 | + | var r0 error | |
| 411 | + | if returnFunc, ok := ret.Get(0).(func(int32, int32, bool) error); ok { | |
| 412 | + | r0 = returnFunc(w, h, opaque) | |
| 413 | + | } else { | |
| 414 | + | r0 = ret.Error(0) | |
| 415 | + | } | |
| 416 | + | return r0 | |
| 417 | + | } | |
| 418 | + | ||
| 419 | + | // mocksurfaceRequests_setOpaqueRegion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setOpaqueRegion' | |
| 420 | + | type mocksurfaceRequests_setOpaqueRegion_Call struct { | |
| 421 | + | *mock.Call | |
| 422 | + | } | |
| 423 | + | ||
| 424 | + | // setOpaqueRegion is a helper method to define mock.On call | |
| 425 | + | // - w int32 | |
| 426 | + | // - h int32 | |
| 427 | + | // - opaque bool | |
| 428 | + | func (_e *mocksurfaceRequests_Expecter) setOpaqueRegion(w any, h any, opaque any) *mocksurfaceRequests_setOpaqueRegion_Call { | |
| 429 | + | return &mocksurfaceRequests_setOpaqueRegion_Call{Call: _e.mock.On("setOpaqueRegion", w, h, opaque)} | |
| 430 | + | } | |
| 431 | + | ||
| 432 | + | func (_c *mocksurfaceRequests_setOpaqueRegion_Call) Run(run func(w int32, h int32, opaque bool)) *mocksurfaceRequests_setOpaqueRegion_Call { | |
| 433 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 434 | + | var arg0 int32 | |
| 435 | + | if args[0] != nil { | |
| 436 | + | arg0 = args[0].(int32) | |
| 437 | + | } | |
| 438 | + | var arg1 int32 | |
| 439 | + | if args[1] != nil { | |
| 440 | + | arg1 = args[1].(int32) | |
| 441 | + | } | |
| 442 | + | var arg2 bool | |
| 443 | + | if args[2] != nil { | |
| 444 | + | arg2 = args[2].(bool) | |
| 445 | + | } | |
| 446 | + | run( | |
| 447 | + | arg0, | |
| 448 | + | arg1, | |
| 449 | + | arg2, | |
| 450 | + | ) | |
| 451 | + | }) | |
| 452 | + | return _c | |
| 453 | + | } | |
| 454 | + | ||
| 455 | + | func (_c *mocksurfaceRequests_setOpaqueRegion_Call) Return(err error) *mocksurfaceRequests_setOpaqueRegion_Call { | |
| 456 | + | _c.Call.Return(err) | |
| 457 | + | return _c | |
| 458 | + | } | |
| 459 | + | ||
| 460 | + | func (_c *mocksurfaceRequests_setOpaqueRegion_Call) RunAndReturn(run func(w int32, h int32, opaque bool) error) *mocksurfaceRequests_setOpaqueRegion_Call { | |
| 461 | + | _c.Call.Return(run) | |
| 462 | + | return _c | |
| 463 | + | } | |
| 464 | + | ||
| 465 | + | // setRelease provides a mock function for the type mocksurfaceRequests | |
| 466 | + | func (_mock *mocksurfaceRequests) setRelease(timeline uint64, point uint64) error { | |
| 467 | + | ret := _mock.Called(timeline, point) | |
| 468 | + | ||
| 469 | + | if len(ret) == 0 { | |
| 470 | + | panic("no return value specified for setRelease") | |
| 471 | + | } | |
| 472 | + | ||
| 473 | + | var r0 error | |
| 474 | + | if returnFunc, ok := ret.Get(0).(func(uint64, uint64) error); ok { | |
| 475 | + | r0 = returnFunc(timeline, point) | |
| 476 | + | } else { | |
| 477 | + | r0 = ret.Error(0) | |
| 478 | + | } | |
| 479 | + | return r0 | |
| 480 | + | } | |
| 481 | + | ||
| 482 | + | // mocksurfaceRequests_setRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setRelease' | |
| 483 | + | type mocksurfaceRequests_setRelease_Call struct { | |
| 484 | + | *mock.Call | |
| 485 | + | } | |
| 486 | + | ||
| 487 | + | // setRelease is a helper method to define mock.On call | |
| 488 | + | // - timeline uint64 | |
| 489 | + | // - point uint64 | |
| 490 | + | func (_e *mocksurfaceRequests_Expecter) setRelease(timeline any, point any) *mocksurfaceRequests_setRelease_Call { | |
| 491 | + | return &mocksurfaceRequests_setRelease_Call{Call: _e.mock.On("setRelease", timeline, point)} | |
| 492 | + | } | |
| 493 | + | ||
| 494 | + | func (_c *mocksurfaceRequests_setRelease_Call) Run(run func(timeline uint64, point uint64)) *mocksurfaceRequests_setRelease_Call { | |
| 495 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 496 | + | var arg0 uint64 | |
| 497 | + | if args[0] != nil { | |
| 498 | + | arg0 = args[0].(uint64) | |
| 499 | + | } | |
| 500 | + | var arg1 uint64 | |
| 501 | + | if args[1] != nil { | |
| 502 | + | arg1 = args[1].(uint64) | |
| 503 | + | } | |
| 504 | + | run( | |
| 505 | + | arg0, | |
| 506 | + | arg1, | |
| 507 | + | ) | |
| 508 | + | }) | |
| 509 | + | return _c | |
| 510 | + | } | |
| 511 | + | ||
| 512 | + | func (_c *mocksurfaceRequests_setRelease_Call) Return(err error) *mocksurfaceRequests_setRelease_Call { | |
| 513 | + | _c.Call.Return(err) | |
| 514 | + | return _c | |
| 515 | + | } | |
| 516 | + | ||
| 517 | + | func (_c *mocksurfaceRequests_setRelease_Call) RunAndReturn(run func(timeline uint64, point uint64) error) *mocksurfaceRequests_setRelease_Call { | |
| 518 | + | _c.Call.Return(run) | |
| 519 | + | return _c | |
| 520 | + | } | |
| @@ -0,0 +1,520 @@ | |||||
| 1 | + | // Code generated by mockery; DO NOT EDIT. | |||
| 2 | + | // github.com/vektra/mockery | |||
| 3 | + | // template: testify | |||
| 4 | + | ||||
| 5 | + | package neferclient | |||
| 6 | + | ||||
| 7 | + | import ( | |||
| 8 | + | mock "github.com/stretchr/testify/mock" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // newMocksurfaceRequests creates a new instance of mocksurfaceRequests. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 12 | + | // The first argument is typically a *testing.T value. | |||
| 13 | + | func newMocksurfaceRequests(t interface { | |||
| 14 | + | mock.TestingT | |||
| 15 | + | Cleanup(func()) | |||
| 16 | + | }) *mocksurfaceRequests { | |||
| 17 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 18 | + | helper.Helper() | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | mock := &mocksurfaceRequests{} | |||
| 22 | + | mock.Mock.Test(t) | |||
| 23 | + | ||||
| 24 | + | t.Cleanup(func() { | |||
| 25 | + | if helper, ok := t.(interface{ Helper() }); ok { | |||
| 26 | + | helper.Helper() | |||
| 27 | + | } | |||
| 28 | + | mock.AssertExpectations(t) | |||
| 29 | + | }) | |||
| 30 | + | ||||
| 31 | + | return mock | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | // mocksurfaceRequests is an autogenerated mock type for the surfaceRequests type | |||
| 35 | + | type mocksurfaceRequests struct { | |||
| 36 | + | mock.Mock | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | type mocksurfaceRequests_Expecter struct { | |||
| 40 | + | mock *mock.Mock | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | func (_m *mocksurfaceRequests) EXPECT() *mocksurfaceRequests_Expecter { | |||
| 44 | + | return &mocksurfaceRequests_Expecter{mock: &_m.Mock} | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | // attach provides a mock function for the type mocksurfaceRequests | |||
| 48 | + | func (_mock *mocksurfaceRequests) attach(buffer uint64) error { | |||
| 49 | + | ret := _mock.Called(buffer) | |||
| 50 | + | ||||
| 51 | + | if len(ret) == 0 { | |||
| 52 | + | panic("no return value specified for attach") | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | var r0 error | |||
| 56 | + | if returnFunc, ok := ret.Get(0).(func(uint64) error); ok { | |||
| 57 | + | r0 = returnFunc(buffer) | |||
| 58 | + | } else { | |||
| 59 | + | r0 = ret.Error(0) | |||
| 60 | + | } | |||
| 61 | + | return r0 | |||
| 62 | + | } | |||
| 63 | + | ||||
| 64 | + | // mocksurfaceRequests_attach_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'attach' | |||
| 65 | + | type mocksurfaceRequests_attach_Call struct { | |||
| 66 | + | *mock.Call | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // attach is a helper method to define mock.On call | |||
| 70 | + | // - buffer uint64 | |||
| 71 | + | func (_e *mocksurfaceRequests_Expecter) attach(buffer any) *mocksurfaceRequests_attach_Call { | |||
| 72 | + | return &mocksurfaceRequests_attach_Call{Call: _e.mock.On("attach", buffer)} | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | func (_c *mocksurfaceRequests_attach_Call) Run(run func(buffer uint64)) *mocksurfaceRequests_attach_Call { | |||
| 76 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 77 | + | var arg0 uint64 | |||
| 78 | + | if args[0] != nil { | |||
| 79 | + | arg0 = args[0].(uint64) | |||
| 80 | + | } | |||
| 81 | + | run( | |||
| 82 | + | arg0, | |||
| 83 | + | ) | |||
| 84 | + | }) | |||
| 85 | + | return _c | |||
| 86 | + | } | |||
| 87 | + | ||||
| 88 | + | func (_c *mocksurfaceRequests_attach_Call) Return(err error) *mocksurfaceRequests_attach_Call { | |||
| 89 | + | _c.Call.Return(err) | |||
| 90 | + | return _c | |||
| 91 | + | } | |||
| 92 | + | ||||
| 93 | + | func (_c *mocksurfaceRequests_attach_Call) RunAndReturn(run func(buffer uint64) error) *mocksurfaceRequests_attach_Call { | |||
| 94 | + | _c.Call.Return(run) | |||
| 95 | + | return _c | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | // commit provides a mock function for the type mocksurfaceRequests | |||
| 99 | + | func (_mock *mocksurfaceRequests) commit() error { | |||
| 100 | + | ret := _mock.Called() | |||
| 101 | + | ||||
| 102 | + | if len(ret) == 0 { | |||
| 103 | + | panic("no return value specified for commit") | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | var r0 error | |||
| 107 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |||
| 108 | + | r0 = returnFunc() | |||
| 109 | + | } else { | |||
| 110 | + | r0 = ret.Error(0) | |||
| 111 | + | } | |||
| 112 | + | return r0 | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | // mocksurfaceRequests_commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'commit' | |||
| 116 | + | type mocksurfaceRequests_commit_Call struct { | |||
| 117 | + | *mock.Call | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | // commit is a helper method to define mock.On call | |||
| 121 | + | func (_e *mocksurfaceRequests_Expecter) commit() *mocksurfaceRequests_commit_Call { | |||
| 122 | + | return &mocksurfaceRequests_commit_Call{Call: _e.mock.On("commit")} | |||
| 123 | + | } | |||
| 124 | + | ||||
| 125 | + | func (_c *mocksurfaceRequests_commit_Call) Run(run func()) *mocksurfaceRequests_commit_Call { | |||
| 126 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 127 | + | run() | |||
| 128 | + | }) | |||
| 129 | + | return _c | |||
| 130 | + | } | |||
| 131 | + | ||||
| 132 | + | func (_c *mocksurfaceRequests_commit_Call) Return(err error) *mocksurfaceRequests_commit_Call { | |||
| 133 | + | _c.Call.Return(err) | |||
| 134 | + | return _c | |||
| 135 | + | } | |||
| 136 | + | ||||
| 137 | + | func (_c *mocksurfaceRequests_commit_Call) RunAndReturn(run func() error) *mocksurfaceRequests_commit_Call { | |||
| 138 | + | _c.Call.Return(run) | |||
| 139 | + | return _c | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | // damageBuffer provides a mock function for the type mocksurfaceRequests | |||
| 143 | + | func (_mock *mocksurfaceRequests) damageBuffer(r Rect) error { | |||
| 144 | + | ret := _mock.Called(r) | |||
| 145 | + | ||||
| 146 | + | if len(ret) == 0 { | |||
| 147 | + | panic("no return value specified for damageBuffer") | |||
| 148 | + | } | |||
| 149 | + | ||||
| 150 | + | var r0 error | |||
| 151 | + | if returnFunc, ok := ret.Get(0).(func(Rect) error); ok { | |||
| 152 | + | r0 = returnFunc(r) | |||
| 153 | + | } else { | |||
| 154 | + | r0 = ret.Error(0) | |||
| 155 | + | } | |||
| 156 | + | return r0 | |||
| 157 | + | } | |||
| 158 | + | ||||
| 159 | + | // mocksurfaceRequests_damageBuffer_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'damageBuffer' | |||
| 160 | + | type mocksurfaceRequests_damageBuffer_Call struct { | |||
| 161 | + | *mock.Call | |||
| 162 | + | } | |||
| 163 | + | ||||
| 164 | + | // damageBuffer is a helper method to define mock.On call | |||
| 165 | + | // - r Rect | |||
| 166 | + | func (_e *mocksurfaceRequests_Expecter) damageBuffer(r any) *mocksurfaceRequests_damageBuffer_Call { | |||
| 167 | + | return &mocksurfaceRequests_damageBuffer_Call{Call: _e.mock.On("damageBuffer", r)} | |||
| 168 | + | } | |||
| 169 | + | ||||
| 170 | + | func (_c *mocksurfaceRequests_damageBuffer_Call) Run(run func(r Rect)) *mocksurfaceRequests_damageBuffer_Call { | |||
| 171 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 172 | + | var arg0 Rect | |||
| 173 | + | if args[0] != nil { | |||
| 174 | + | arg0 = args[0].(Rect) | |||
| 175 | + | } | |||
| 176 | + | run( | |||
| 177 | + | arg0, | |||
| 178 | + | ) | |||
| 179 | + | }) | |||
| 180 | + | return _c | |||
| 181 | + | } | |||
| 182 | + | ||||
| 183 | + | func (_c *mocksurfaceRequests_damageBuffer_Call) Return(err error) *mocksurfaceRequests_damageBuffer_Call { | |||
| 184 | + | _c.Call.Return(err) | |||
| 185 | + | return _c | |||
| 186 | + | } | |||
| 187 | + | ||||
| 188 | + | func (_c *mocksurfaceRequests_damageBuffer_Call) RunAndReturn(run func(r Rect) error) *mocksurfaceRequests_damageBuffer_Call { | |||
| 189 | + | _c.Call.Return(run) | |||
| 190 | + | return _c | |||
| 191 | + | } | |||
| 192 | + | ||||
| 193 | + | // frame provides a mock function for the type mocksurfaceRequests | |||
| 194 | + | func (_mock *mocksurfaceRequests) frame() error { | |||
| 195 | + | ret := _mock.Called() | |||
| 196 | + | ||||
| 197 | + | if len(ret) == 0 { | |||
| 198 | + | panic("no return value specified for frame") | |||
| 199 | + | } | |||
| 200 | + | ||||
| 201 | + | var r0 error | |||
| 202 | + | if returnFunc, ok := ret.Get(0).(func() error); ok { | |||
| 203 | + | r0 = returnFunc() | |||
| 204 | + | } else { | |||
| 205 | + | r0 = ret.Error(0) | |||
| 206 | + | } | |||
| 207 | + | return r0 | |||
| 208 | + | } | |||
| 209 | + | ||||
| 210 | + | // mocksurfaceRequests_frame_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'frame' | |||
| 211 | + | type mocksurfaceRequests_frame_Call struct { | |||
| 212 | + | *mock.Call | |||
| 213 | + | } | |||
| 214 | + | ||||
| 215 | + | // frame is a helper method to define mock.On call | |||
| 216 | + | func (_e *mocksurfaceRequests_Expecter) frame() *mocksurfaceRequests_frame_Call { | |||
| 217 | + | return &mocksurfaceRequests_frame_Call{Call: _e.mock.On("frame")} | |||
| 218 | + | } | |||
| 219 | + | ||||
| 220 | + | func (_c *mocksurfaceRequests_frame_Call) Run(run func()) *mocksurfaceRequests_frame_Call { | |||
| 221 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 222 | + | run() | |||
| 223 | + | }) | |||
| 224 | + | return _c | |||
| 225 | + | } | |||
| 226 | + | ||||
| 227 | + | func (_c *mocksurfaceRequests_frame_Call) Return(err error) *mocksurfaceRequests_frame_Call { | |||
| 228 | + | _c.Call.Return(err) | |||
| 229 | + | return _c | |||
| 230 | + | } | |||
| 231 | + | ||||
| 232 | + | func (_c *mocksurfaceRequests_frame_Call) RunAndReturn(run func() error) *mocksurfaceRequests_frame_Call { | |||
| 233 | + | _c.Call.Return(run) | |||
| 234 | + | return _c | |||
| 235 | + | } | |||
| 236 | + | ||||
| 237 | + | // setAcquire provides a mock function for the type mocksurfaceRequests | |||
| 238 | + | func (_mock *mocksurfaceRequests) setAcquire(timeline uint64, point uint64) error { | |||
| 239 | + | ret := _mock.Called(timeline, point) | |||
| 240 | + | ||||
| 241 | + | if len(ret) == 0 { | |||
| 242 | + | panic("no return value specified for setAcquire") | |||
| 243 | + | } | |||
| 244 | + | ||||
| 245 | + | var r0 error | |||
| 246 | + | if returnFunc, ok := ret.Get(0).(func(uint64, uint64) error); ok { | |||
| 247 | + | r0 = returnFunc(timeline, point) | |||
| 248 | + | } else { | |||
| 249 | + | r0 = ret.Error(0) | |||
| 250 | + | } | |||
| 251 | + | return r0 | |||
| 252 | + | } | |||
| 253 | + | ||||
| 254 | + | // mocksurfaceRequests_setAcquire_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setAcquire' | |||
| 255 | + | type mocksurfaceRequests_setAcquire_Call struct { | |||
| 256 | + | *mock.Call | |||
| 257 | + | } | |||
| 258 | + | ||||
| 259 | + | // setAcquire is a helper method to define mock.On call | |||
| 260 | + | // - timeline uint64 | |||
| 261 | + | // - point uint64 | |||
| 262 | + | func (_e *mocksurfaceRequests_Expecter) setAcquire(timeline any, point any) *mocksurfaceRequests_setAcquire_Call { | |||
| 263 | + | return &mocksurfaceRequests_setAcquire_Call{Call: _e.mock.On("setAcquire", timeline, point)} | |||
| 264 | + | } | |||
| 265 | + | ||||
| 266 | + | func (_c *mocksurfaceRequests_setAcquire_Call) Run(run func(timeline uint64, point uint64)) *mocksurfaceRequests_setAcquire_Call { | |||
| 267 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 268 | + | var arg0 uint64 | |||
| 269 | + | if args[0] != nil { | |||
| 270 | + | arg0 = args[0].(uint64) | |||
| 271 | + | } | |||
| 272 | + | var arg1 uint64 | |||
| 273 | + | if args[1] != nil { | |||
| 274 | + | arg1 = args[1].(uint64) | |||
| 275 | + | } | |||
| 276 | + | run( | |||
| 277 | + | arg0, | |||
| 278 | + | arg1, | |||
| 279 | + | ) | |||
| 280 | + | }) | |||
| 281 | + | return _c | |||
| 282 | + | } | |||
| 283 | + | ||||
| 284 | + | func (_c *mocksurfaceRequests_setAcquire_Call) Return(err error) *mocksurfaceRequests_setAcquire_Call { | |||
| 285 | + | _c.Call.Return(err) | |||
| 286 | + | return _c | |||
| 287 | + | } | |||
| 288 | + | ||||
| 289 | + | func (_c *mocksurfaceRequests_setAcquire_Call) RunAndReturn(run func(timeline uint64, point uint64) error) *mocksurfaceRequests_setAcquire_Call { | |||
| 290 | + | _c.Call.Return(run) | |||
| 291 | + | return _c | |||
| 292 | + | } | |||
| 293 | + | ||||
| 294 | + | // setBufferScale provides a mock function for the type mocksurfaceRequests | |||
| 295 | + | func (_mock *mocksurfaceRequests) setBufferScale(n int32) error { | |||
| 296 | + | ret := _mock.Called(n) | |||
| 297 | + | ||||
| 298 | + | if len(ret) == 0 { | |||
| 299 | + | panic("no return value specified for setBufferScale") | |||
| 300 | + | } | |||
| 301 | + | ||||
| 302 | + | var r0 error | |||
| 303 | + | if returnFunc, ok := ret.Get(0).(func(int32) error); ok { | |||
| 304 | + | r0 = returnFunc(n) | |||
| 305 | + | } else { | |||
| 306 | + | r0 = ret.Error(0) | |||
| 307 | + | } | |||
| 308 | + | return r0 | |||
| 309 | + | } | |||
| 310 | + | ||||
| 311 | + | // mocksurfaceRequests_setBufferScale_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setBufferScale' | |||
| 312 | + | type mocksurfaceRequests_setBufferScale_Call struct { | |||
| 313 | + | *mock.Call | |||
| 314 | + | } | |||
| 315 | + | ||||
| 316 | + | // setBufferScale is a helper method to define mock.On call | |||
| 317 | + | // - n int32 | |||
| 318 | + | func (_e *mocksurfaceRequests_Expecter) setBufferScale(n any) *mocksurfaceRequests_setBufferScale_Call { | |||
| 319 | + | return &mocksurfaceRequests_setBufferScale_Call{Call: _e.mock.On("setBufferScale", n)} | |||
| 320 | + | } | |||
| 321 | + | ||||
| 322 | + | func (_c *mocksurfaceRequests_setBufferScale_Call) Run(run func(n int32)) *mocksurfaceRequests_setBufferScale_Call { | |||
| 323 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 324 | + | var arg0 int32 | |||
| 325 | + | if args[0] != nil { | |||
| 326 | + | arg0 = args[0].(int32) | |||
| 327 | + | } | |||
| 328 | + | run( | |||
| 329 | + | arg0, | |||
| 330 | + | ) | |||
| 331 | + | }) | |||
| 332 | + | return _c | |||
| 333 | + | } | |||
| 334 | + | ||||
| 335 | + | func (_c *mocksurfaceRequests_setBufferScale_Call) Return(err error) *mocksurfaceRequests_setBufferScale_Call { | |||
| 336 | + | _c.Call.Return(err) | |||
| 337 | + | return _c | |||
| 338 | + | } | |||
| 339 | + | ||||
| 340 | + | func (_c *mocksurfaceRequests_setBufferScale_Call) RunAndReturn(run func(n int32) error) *mocksurfaceRequests_setBufferScale_Call { | |||
| 341 | + | _c.Call.Return(run) | |||
| 342 | + | return _c | |||
| 343 | + | } | |||
| 344 | + | ||||
| 345 | + | // setDestination provides a mock function for the type mocksurfaceRequests | |||
| 346 | + | func (_mock *mocksurfaceRequests) setDestination(w int32, h int32) error { | |||
| 347 | + | ret := _mock.Called(w, h) | |||
| 348 | + | ||||
| 349 | + | if len(ret) == 0 { | |||
| 350 | + | panic("no return value specified for setDestination") | |||
| 351 | + | } | |||
| 352 | + | ||||
| 353 | + | var r0 error | |||
| 354 | + | if returnFunc, ok := ret.Get(0).(func(int32, int32) error); ok { | |||
| 355 | + | r0 = returnFunc(w, h) | |||
| 356 | + | } else { | |||
| 357 | + | r0 = ret.Error(0) | |||
| 358 | + | } | |||
| 359 | + | return r0 | |||
| 360 | + | } | |||
| 361 | + | ||||
| 362 | + | // mocksurfaceRequests_setDestination_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setDestination' | |||
| 363 | + | type mocksurfaceRequests_setDestination_Call struct { | |||
| 364 | + | *mock.Call | |||
| 365 | + | } | |||
| 366 | + | ||||
| 367 | + | // setDestination is a helper method to define mock.On call | |||
| 368 | + | // - w int32 | |||
| 369 | + | // - h int32 | |||
| 370 | + | func (_e *mocksurfaceRequests_Expecter) setDestination(w any, h any) *mocksurfaceRequests_setDestination_Call { | |||
| 371 | + | return &mocksurfaceRequests_setDestination_Call{Call: _e.mock.On("setDestination", w, h)} | |||
| 372 | + | } | |||
| 373 | + | ||||
| 374 | + | func (_c *mocksurfaceRequests_setDestination_Call) Run(run func(w int32, h int32)) *mocksurfaceRequests_setDestination_Call { | |||
| 375 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 376 | + | var arg0 int32 | |||
| 377 | + | if args[0] != nil { | |||
| 378 | + | arg0 = args[0].(int32) | |||
| 379 | + | } | |||
| 380 | + | var arg1 int32 | |||
| 381 | + | if args[1] != nil { | |||
| 382 | + | arg1 = args[1].(int32) | |||
| 383 | + | } | |||
| 384 | + | run( | |||
| 385 | + | arg0, | |||
| 386 | + | arg1, | |||
| 387 | + | ) | |||
| 388 | + | }) | |||
| 389 | + | return _c | |||
| 390 | + | } | |||
| 391 | + | ||||
| 392 | + | func (_c *mocksurfaceRequests_setDestination_Call) Return(err error) *mocksurfaceRequests_setDestination_Call { | |||
| 393 | + | _c.Call.Return(err) | |||
| 394 | + | return _c | |||
| 395 | + | } | |||
| 396 | + | ||||
| 397 | + | func (_c *mocksurfaceRequests_setDestination_Call) RunAndReturn(run func(w int32, h int32) error) *mocksurfaceRequests_setDestination_Call { | |||
| 398 | + | _c.Call.Return(run) | |||
| 399 | + | return _c | |||
| 400 | + | } | |||
| 401 | + | ||||
| 402 | + | // setOpaqueRegion provides a mock function for the type mocksurfaceRequests | |||
| 403 | + | func (_mock *mocksurfaceRequests) setOpaqueRegion(w int32, h int32, opaque bool) error { | |||
| 404 | + | ret := _mock.Called(w, h, opaque) | |||
| 405 | + | ||||
| 406 | + | if len(ret) == 0 { | |||
| 407 | + | panic("no return value specified for setOpaqueRegion") | |||
| 408 | + | } | |||
| 409 | + | ||||
| 410 | + | var r0 error | |||
| 411 | + | if returnFunc, ok := ret.Get(0).(func(int32, int32, bool) error); ok { | |||
| 412 | + | r0 = returnFunc(w, h, opaque) | |||
| 413 | + | } else { | |||
| 414 | + | r0 = ret.Error(0) | |||
| 415 | + | } | |||
| 416 | + | return r0 | |||
| 417 | + | } | |||
| 418 | + | ||||
| 419 | + | // mocksurfaceRequests_setOpaqueRegion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setOpaqueRegion' | |||
| 420 | + | type mocksurfaceRequests_setOpaqueRegion_Call struct { | |||
| 421 | + | *mock.Call | |||
| 422 | + | } | |||
| 423 | + | ||||
| 424 | + | // setOpaqueRegion is a helper method to define mock.On call | |||
| 425 | + | // - w int32 | |||
| 426 | + | // - h int32 | |||
| 427 | + | // - opaque bool | |||
| 428 | + | func (_e *mocksurfaceRequests_Expecter) setOpaqueRegion(w any, h any, opaque any) *mocksurfaceRequests_setOpaqueRegion_Call { | |||
| 429 | + | return &mocksurfaceRequests_setOpaqueRegion_Call{Call: _e.mock.On("setOpaqueRegion", w, h, opaque)} | |||
| 430 | + | } | |||
| 431 | + | ||||
| 432 | + | func (_c *mocksurfaceRequests_setOpaqueRegion_Call) Run(run func(w int32, h int32, opaque bool)) *mocksurfaceRequests_setOpaqueRegion_Call { | |||
| 433 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 434 | + | var arg0 int32 | |||
| 435 | + | if args[0] != nil { | |||
| 436 | + | arg0 = args[0].(int32) | |||
| 437 | + | } | |||
| 438 | + | var arg1 int32 | |||
| 439 | + | if args[1] != nil { | |||
| 440 | + | arg1 = args[1].(int32) | |||
| 441 | + | } | |||
| 442 | + | var arg2 bool | |||
| 443 | + | if args[2] != nil { | |||
| 444 | + | arg2 = args[2].(bool) | |||
| 445 | + | } | |||
| 446 | + | run( | |||
| 447 | + | arg0, | |||
| 448 | + | arg1, | |||
| 449 | + | arg2, | |||
| 450 | + | ) | |||
| 451 | + | }) | |||
| 452 | + | return _c | |||
| 453 | + | } | |||
| 454 | + | ||||
| 455 | + | func (_c *mocksurfaceRequests_setOpaqueRegion_Call) Return(err error) *mocksurfaceRequests_setOpaqueRegion_Call { | |||
| 456 | + | _c.Call.Return(err) | |||
| 457 | + | return _c | |||
| 458 | + | } | |||
| 459 | + | ||||
| 460 | + | func (_c *mocksurfaceRequests_setOpaqueRegion_Call) RunAndReturn(run func(w int32, h int32, opaque bool) error) *mocksurfaceRequests_setOpaqueRegion_Call { | |||
| 461 | + | _c.Call.Return(run) | |||
| 462 | + | return _c | |||
| 463 | + | } | |||
| 464 | + | ||||
| 465 | + | // setRelease provides a mock function for the type mocksurfaceRequests | |||
| 466 | + | func (_mock *mocksurfaceRequests) setRelease(timeline uint64, point uint64) error { | |||
| 467 | + | ret := _mock.Called(timeline, point) | |||
| 468 | + | ||||
| 469 | + | if len(ret) == 0 { | |||
| 470 | + | panic("no return value specified for setRelease") | |||
| 471 | + | } | |||
| 472 | + | ||||
| 473 | + | var r0 error | |||
| 474 | + | if returnFunc, ok := ret.Get(0).(func(uint64, uint64) error); ok { | |||
| 475 | + | r0 = returnFunc(timeline, point) | |||
| 476 | + | } else { | |||
| 477 | + | r0 = ret.Error(0) | |||
| 478 | + | } | |||
| 479 | + | return r0 | |||
| 480 | + | } | |||
| 481 | + | ||||
| 482 | + | // mocksurfaceRequests_setRelease_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'setRelease' | |||
| 483 | + | type mocksurfaceRequests_setRelease_Call struct { | |||
| 484 | + | *mock.Call | |||
| 485 | + | } | |||
| 486 | + | ||||
| 487 | + | // setRelease is a helper method to define mock.On call | |||
| 488 | + | // - timeline uint64 | |||
| 489 | + | // - point uint64 | |||
| 490 | + | func (_e *mocksurfaceRequests_Expecter) setRelease(timeline any, point any) *mocksurfaceRequests_setRelease_Call { | |||
| 491 | + | return &mocksurfaceRequests_setRelease_Call{Call: _e.mock.On("setRelease", timeline, point)} | |||
| 492 | + | } | |||
| 493 | + | ||||
| 494 | + | func (_c *mocksurfaceRequests_setRelease_Call) Run(run func(timeline uint64, point uint64)) *mocksurfaceRequests_setRelease_Call { | |||
| 495 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 496 | + | var arg0 uint64 | |||
| 497 | + | if args[0] != nil { | |||
| 498 | + | arg0 = args[0].(uint64) | |||
| 499 | + | } | |||
| 500 | + | var arg1 uint64 | |||
| 501 | + | if args[1] != nil { | |||
| 502 | + | arg1 = args[1].(uint64) | |||
| 503 | + | } | |||
| 504 | + | run( | |||
| 505 | + | arg0, | |||
| 506 | + | arg1, | |||
| 507 | + | ) | |||
| 508 | + | }) | |||
| 509 | + | return _c | |||
| 510 | + | } | |||
| 511 | + | ||||
| 512 | + | func (_c *mocksurfaceRequests_setRelease_Call) Return(err error) *mocksurfaceRequests_setRelease_Call { | |||
| 513 | + | _c.Call.Return(err) | |||
| 514 | + | return _c | |||
| 515 | + | } | |||
| 516 | + | ||||
| 517 | + | func (_c *mocksurfaceRequests_setRelease_Call) RunAndReturn(run func(timeline uint64, point uint64) error) *mocksurfaceRequests_setRelease_Call { | |||
| 518 | + | _c.Call.Return(run) | |||
| 519 | + | return _c | |||
| 520 | + | } | |||
M wire_test.go +14 -2
| @@ -30,6 +30,7 @@ type wireServer struct { | |||
| 30 | 30 | globals []wireGlobal | |
| 31 | 31 | bound map[uint32]boundObject // global name -> bound object | |
| 32 | 32 | outputs map[uint32]outputSpec | |
| 33 | + | hook func(obj uint32, op uint16, body []byte) // other requests, if set | |
| 33 | 34 | } | |
| 34 | 35 | ||
| 35 | 36 | type wireGlobal struct { | |
| @@ -115,6 +116,7 @@ func isClosed(err error) bool { | |||
| 115 | 116 | ||
| 116 | 117 | func (s *wireServer) serve() { | |
| 117 | 118 | var hdr [8]byte | |
| 119 | + | var buf []byte // reused: the peer must not allocate while draining | |
| 118 | 120 | for { | |
| 119 | 121 | if _, err := io.ReadFull(s.conn, hdr[:]); err != nil { | |
| 120 | 122 | return | |
| @@ -122,7 +124,10 @@ func (s *wireServer) serve() { | |||
| 122 | 124 | obj := binary.LittleEndian.Uint32(hdr[0:4]) | |
| 123 | 125 | w := binary.LittleEndian.Uint32(hdr[4:8]) | |
| 124 | 126 | size, op := int(w>>16), uint16(w) | |
| 125 | - | body := make([]byte, size-8) | |
| 127 | + | if cap(buf) < size-8 { | |
| 128 | + | buf = make([]byte, size-8) | |
| 129 | + | } | |
| 130 | + | body := buf[:size-8] | |
| 126 | 131 | if _, err := io.ReadFull(s.conn, body); err != nil { | |
| 127 | 132 | return | |
| 128 | 133 | } | |
| @@ -165,8 +170,15 @@ func (s *wireServer) handle(obj uint32, op uint16, body []byte) { | |||
| 165 | 170 | if isOutput { | |
| 166 | 171 | s.describe(id, version, spec) | |
| 167 | 172 | } | |
| 168 | - | case op == 0 && body == nil: // wl_output.release (destructor, no args) | |
| 173 | + | case op == 0 && len(body) == 0: // wl_output.release (destructor, no args) | |
| 169 | 174 | s.write(frame(1, 1, appendU32(nil, obj))) | |
| 175 | + | default: | |
| 176 | + | s.mu.Lock() | |
| 177 | + | hook := s.hook | |
| 178 | + | s.mu.Unlock() | |
| 179 | + | if hook != nil { | |
| 180 | + | hook(obj, op, body) | |
| 181 | + | } | |
| 170 | 182 | } | |
| 171 | 183 | } | |
| 172 | 184 | ||
| @@ -30,6 +30,7 @@ type wireServer struct { | |||||
| 30 | globals []wireGlobal | 30 | globals []wireGlobal | ||
| 31 | bound map[uint32]boundObject // global name -> bound object | 31 | bound map[uint32]boundObject // global name -> bound object | ||
| 32 | outputs map[uint32]outputSpec | 32 | outputs map[uint32]outputSpec | ||
| 33 | + | hook func(obj uint32, op uint16, body []byte) // other requests, if set | |||
| 33 | } | 34 | } | ||
| 34 | 35 | ||||
| 35 | type wireGlobal struct { | 36 | type wireGlobal struct { | ||
| @@ -115,6 +116,7 @@ func isClosed(err error) bool { | |||||
| 115 | 116 | ||||
| 116 | func (s *wireServer) serve() { | 117 | func (s *wireServer) serve() { | ||
| 117 | var hdr [8]byte | 118 | var hdr [8]byte | ||
| 119 | + | var buf []byte // reused: the peer must not allocate while draining | |||
| 118 | for { | 120 | for { | ||
| 119 | if _, err := io.ReadFull(s.conn, hdr[:]); err != nil { | 121 | if _, err := io.ReadFull(s.conn, hdr[:]); err != nil { | ||
| 120 | return | 122 | return | ||
| @@ -122,7 +124,10 @@ func (s *wireServer) serve() { | |||||
| 122 | obj := binary.LittleEndian.Uint32(hdr[0:4]) | 124 | obj := binary.LittleEndian.Uint32(hdr[0:4]) | ||
| 123 | w := binary.LittleEndian.Uint32(hdr[4:8]) | 125 | w := binary.LittleEndian.Uint32(hdr[4:8]) | ||
| 124 | size, op := int(w>>16), uint16(w) | 126 | size, op := int(w>>16), uint16(w) | ||
| 125 | - | body := make([]byte, size-8) | 127 | + | if cap(buf) < size-8 { |
| 128 | + | buf = make([]byte, size-8) | |||
| 129 | + | } | |||
| 130 | + | body := buf[:size-8] | |||
| 126 | if _, err := io.ReadFull(s.conn, body); err != nil { | 131 | if _, err := io.ReadFull(s.conn, body); err != nil { | ||
| 127 | return | 132 | return | ||
| 128 | } | 133 | } | ||
| @@ -165,8 +170,15 @@ func (s *wireServer) handle(obj uint32, op uint16, body []byte) { | |||||
| 165 | if isOutput { | 170 | if isOutput { | ||
| 166 | s.describe(id, version, spec) | 171 | s.describe(id, version, spec) | ||
| 167 | } | 172 | } | ||
| 168 | - | case op == 0 && body == nil: // wl_output.release (destructor, no args) | 173 | + | case op == 0 && len(body) == 0: // wl_output.release (destructor, no args) |
| 169 | s.write(frame(1, 1, appendU32(nil, obj))) | 174 | s.write(frame(1, 1, appendU32(nil, obj))) | ||
| 175 | + | default: | |||
| 176 | + | s.mu.Lock() | |||
| 177 | + | hook := s.hook | |||
| 178 | + | s.mu.Unlock() | |||
| 179 | + | if hook != nil { | |||
| 180 | + | hook(obj, op, body) | |||
| 181 | + | } | |||
| 170 | } | 182 | } | ||
| 171 | } | 183 | } | ||
| 172 | 184 | ||||