1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8 "github.com/bnema/purego-libwayland/protocol/wayland"
9 "github.com/bnema/purego-libwayland/protocol/xdgshell"
10 "golang.org/x/sys/unix"
11)
12
13// A toplevel with a subsurface (how Firefox draws with the GPU) sends one
14// content: its own buffer, the child at its committed position and the
15// window geometry.
16func TestSubsurfaceContent(t *testing.T) {
17 s, events, contents, dir := dmabufServer(t, ports.DMABufSupport{})
18 c := protocolClient(t, s, dir)
19 comp := bindProtocol(t, c, "wl_compositor")
20 subc := bindProtocol(t, c, "wl_subcompositor")
21 wm := bindProtocol(t, c, "xdg_wm_base")
22 shm := bindProtocol(t, c, "wl_shm")
23 registerProtocol(t, c, shm)
24 fd, err := unix.MemfdCreate("subsurface", 0)
25 if err != nil {
26 t.Fatal(err)
27 }
28 defer unix.Close(fd)
29 if err := unix.Ftruncate(fd, 4*4*4); err != nil {
30 t.Fatal(err)
31 }
32 pool, big, small := c.AllocateID(), c.AllocateID(), c.AllocateID()
33 registerProtocol(t, c, pool)
34 registerProtocol(t, c, big)
35 registerProtocol(t, c, small)
36 if err := c.SendRequestWithFDs(shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(64)); err != nil {
37 t.Fatal(err)
38 }
39 requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, big, int32(0), int32(4), int32(4), int32(16), uint32(0))
40 requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, small, int32(0), int32(2), int32(2), int32(16), uint32(0))
41
42 root, child := c.AllocateID(), c.AllocateID()
43 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, root)
44 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
45 registerProtocol(t, c, root)
46 registerProtocol(t, c, child)
47 sub := c.AllocateID()
48 registerProtocol(t, c, sub)
49 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
50 xdg := c.AllocateID()
51 requestProtocol(t, c, wm, xdgshell.WmBaseRequestGetXdgSurface, xdg, root)
52 serials := make(chan uint32, 4)
53 xp := &configureProxy{serial: serials}
54 xp.SetID(xdg)
55 c.Context().Register(xp)
56 top := c.AllocateID()
57 registerProtocol(t, c, top)
58 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestGetToplevel, top)
59 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
60 if err := c.Roundtrip(); err != nil {
61 t.Fatal(err)
62 }
63 select {
64 case serial := <-serials:
65 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, serial)
66 case <-time.After(2 * time.Second):
67 t.Fatal("configure timeout")
68 }
69 // Child first: nothing to draw until the root maps.
70 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetPosition, int32(1), int32(2))
71 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, small, int32(0), int32(0))
72 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
73 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestSetWindowGeometry, int32(1), int32(1), int32(2), int32(2))
74 requestProtocol(t, c, root, wayland.SurfaceRequestAttach, big, int32(0), int32(0))
75 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
76 if err := c.Roundtrip(); err != nil {
77 t.Fatal(err)
78 }
79 w := mapped(t, events, 2*time.Second)
80 var got ports.SurfaceContent
81 deadline := time.After(2 * time.Second)
82 for got.ID == 0 {
83 select {
84 case got = <-contents:
85 case <-deadline:
86 t.Fatal("no content")
87 }
88 }
89 if got.ID != w.ID || got.Width != 4 || got.Geometry != (ports.Rect{X: 1, Y: 1, W: 2, H: 2}) {
90 t.Fatalf("root %+v", got)
91 }
92 if len(got.Children) != 1 || got.Children[0].X != 1 || got.Children[0].Y != 2 || got.Children[0].Width != 2 || got.Children[0].Below {
93 t.Fatalf("children %+v", got.Children)
94 }
95 // Destroying the subsurface redraws the root without it.
96 requestProtocol(t, c, sub, wayland.SubsurfaceRequestDestroy)
97 if err := c.Roundtrip(); err != nil {
98 t.Fatal(err)
99 }
100 deadline = time.After(2 * time.Second)
101 for {
102 select {
103 case got = <-contents:
104 if len(got.Children) == 0 {
105 return
106 }
107 case <-deadline:
108 t.Fatalf("child kept: %+v", got.Children)
109 }
110 }
111}
112
113// A surface cannot become a subsurface of its own child.
114func TestSubsurfaceLoop(t *testing.T) {
115 s, _, _, dir := dmabufServer(t, ports.DMABufSupport{})
116 c := protocolClient(t, s, dir)
117 comp := bindProtocol(t, c, "wl_compositor")
118 subc := bindProtocol(t, c, "wl_subcompositor")
119 a, b := c.AllocateID(), c.AllocateID()
120 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, a)
121 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, b)
122 s1 := c.AllocateID()
123 registerProtocol(t, c, s1)
124 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s1, b, a)
125 s2 := c.AllocateID()
126 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s2, a, b)
127 expectProtocolError(t, c, subc, uint32(wayland.SubcompositorErrorBadParent))
128}
129
130// A wl_subsurface outlives its parent: the surface keeps its role, so a
131// second get_subsurface is bad_surface.
132func TestSubsurfaceRoleOutlivesParent(t *testing.T) {
133 s, _, _, dir := dmabufServer(t, ports.DMABufSupport{})
134 c := protocolClient(t, s, dir)
135 comp := bindProtocol(t, c, "wl_compositor")
136 subc := bindProtocol(t, c, "wl_subcompositor")
137 a, b := c.AllocateID(), c.AllocateID()
138 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, a)
139 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, b)
140 registerProtocol(t, c, a)
141 s1 := c.AllocateID()
142 registerProtocol(t, c, s1)
143 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s1, b, a)
144 requestProtocol(t, c, a, wayland.SurfaceRequestDestroy)
145 p := c.AllocateID()
146 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, p)
147 s2 := c.AllocateID()
148 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s2, b, p)
149 expectProtocolError(t, c, subc, uint32(wayland.SubcompositorErrorBadSurface))
150}