1package wayland
2
3import (
4 "fmt"
5 "testing"
6 "time"
7
8 "github.com/bnema/go-wayland-bindings/server/fifo"
9 "github.com/bnema/go-wayland-bindings/server/pointerconstraints"
10 "github.com/bnema/go-wayland-bindings/server/wayland"
11 "github.com/bnema/go-wayland-bindings/server/xdgshell"
12 "github.com/bnema/neferwl/internal/ports"
13 "golang.org/x/sys/unix"
14)
15
16func inputChanged(t *testing.T, events <-chan ports.ClientEvent) ports.InputRegionChanged {
17 t.Helper()
18 for {
19 if v, ok := waitEvent(t, events, 2*time.Second).(ports.InputRegionChanged); ok {
20 return v
21 }
22 }
23}
24
25func TestInputRegionCommitCopySubtractAndReset(t *testing.T) {
26 s, events, _, dir := lifecycleServer(t)
27 c := protocolClient(t, s, dir)
28 w, surf, _ := surfaceMapper(t, c, events)()
29 comp := bindProtocol(t, c, "wl_compositor")
30 reg := c.AllocateID()
31 registerProtocol(t, c, reg)
32 requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, reg)
33 requestProtocol(t, c, reg, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1))
34 requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, reg)
35 requestProtocol(t, c, reg, wayland.RegionRequestSubtract, int32(0), int32(0), int32(1), int32(1))
36 requestProtocol(t, c, reg, wayland.RegionRequestDestroy)
37 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
38 if got := inputChanged(t, events); got.ID != w.ID || len(got.Rects) != 1 || got.Rects[0] != (ports.Rect{W: 1, H: 1}) {
39 t.Fatalf("copied region: %+v", got)
40 }
41 empty := c.AllocateID()
42 registerProtocol(t, c, empty)
43 requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, empty)
44 requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, empty)
45 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
46 if got := inputChanged(t, events); got.All || len(got.Rects) != 0 {
47 t.Fatalf("empty region: %+v", got)
48 }
49 requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, uint32(0))
50 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
51 if got := inputChanged(t, events); !got.All {
52 t.Fatalf("reset region: %+v", got)
53 }
54}
55
56func TestRegionSubtractSplits(t *testing.T) {
57 g := ®ion{}
58 g.Add(nil, 0, 0, 10, 10)
59 g.Subtract(nil, 2, 2, 6, 6)
60 if len(g.rects) != 4 || g.boxRect() != (ports.Rect{W: 10, H: 10}) {
61 t.Fatalf("subtract: %+v", g.rects)
62 }
63}
64
65func TestSubsurfaceInputRegionOwnCommitAndOffset(t *testing.T) {
66 s, events, _, dir := lifecycleServer(t)
67 c := protocolClient(t, s, dir)
68 w, root, _ := surfaceMapper(t, c, events)()
69 comp := bindProtocol(t, c, "wl_compositor")
70 subc := bindProtocol(t, c, "wl_subcompositor")
71 child := c.AllocateID()
72 registerProtocol(t, c, child)
73 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
74 sub := c.AllocateID()
75 registerProtocol(t, c, sub)
76 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
77 // Desync so the child's own commits apply without a parent commit.
78 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetDesync)
79 // surfaceMapper owns a one-pixel buffer; construct a second one for the child.
80 shm := bindProtocol(t, c, "wl_shm")
81 registerProtocol(t, c, shm)
82 fd, err := unix.MemfdCreate("input-region-buffer", 0)
83 if err != nil {
84 t.Fatal(err)
85 }
86 defer unix.Close(fd)
87 if err := unix.Ftruncate(fd, 64); err != nil {
88 t.Fatal(err)
89 }
90 pool, buffer := c.AllocateID(), c.AllocateID()
91 registerProtocol(t, c, pool)
92 registerProtocol(t, c, buffer)
93 if err := wireRequest(c, shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(64)); err != nil {
94 t.Fatal(err)
95 }
96 requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, buffer, int32(0), int32(4), int32(4), int32(16), uint32(0))
97 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetPosition, int32(3), int32(4))
98 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
99 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0))
100 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
101 if err := c.Roundtrip(); err != nil {
102 t.Fatal(err)
103 }
104 got := inputChanged(t, events)
105 if len(got.Rects) == 1 {
106 got = inputChanged(t, events)
107 }
108 if got.ID != w.ID || len(got.Rects) != 2 || got.Rects[1] != (ports.Rect{X: 3, Y: 4, W: 4, H: 4}) {
109 t.Fatalf("default child input: %+v", got)
110 }
111 reg := c.AllocateID()
112 registerProtocol(t, c, reg)
113 requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, reg)
114 requestProtocol(t, c, reg, wayland.RegionRequestAdd, int32(1), int32(1), int32(9), int32(9))
115 requestProtocol(t, c, child, wayland.SurfaceRequestSetInputRegion, reg)
116 requestProtocol(t, c, reg, wayland.RegionRequestDestroy)
117 if err := c.Roundtrip(); err != nil {
118 t.Fatal(err)
119 }
120 // No parent commit is needed, but the child's own commit is required.
121 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
122 if err := c.Roundtrip(); err != nil {
123 t.Fatal(err)
124 }
125 got = inputChanged(t, events)
126 if got.ID != w.ID || len(got.Rects) != 2 || got.Rects[1] != (ports.Rect{X: 4, Y: 5, W: 3, H: 3}) {
127 t.Fatalf("committed child input: %+v", got)
128 }
129}
130
131func TestConstraintIntersectsInputRegion(t *testing.T) {
132 s, events, commands, dir := lifecycleServer(t)
133 c := protocolClient(t, s, dir)
134 seat := bindProtocol(t, c, "wl_seat")
135 registerProtocol(t, c, seat)
136 pointer := c.AllocateID()
137 registerProtocol(t, c, pointer)
138 requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
139 w, surf, _ := surfaceMapper(t, c, events)()
140 comp := bindProtocol(t, c, "wl_compositor")
141 region := c.AllocateID()
142 registerProtocol(t, c, region)
143 requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region)
144 requestProtocol(t, c, region, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1))
145 requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region)
146 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
147 if err := c.Roundtrip(); err != nil {
148 t.Fatal(err)
149 }
150 _ = inputChanged(t, events)
151 constraints := bindProtocol(t, c, "zwp_pointer_constraints_v1")
152 confine := c.AllocateID()
153 registerProtocol(t, c, confine)
154 requestProtocol(t, c, constraints, pointerconstraints.ZwpPointerConstraintsV1RequestConfinePointer, confine, surf, pointer, uint32(0), uint32(pointerconstraints.ZwpPointerConstraintsV1LifetimePersistent))
155 commands <- ports.FocusWindow{ID: w.ID}
156 commands <- ports.PointerFocus{ID: w.ID}
157 commands <- ports.PointerMotionTo{ID: w.ID, X: 0, Y: 0}
158 if err := c.Roundtrip(); err != nil {
159 t.Fatal(err)
160 }
161 if got := constrained(t, events); got.Rect != (ports.Rect{W: 1, H: 1}) {
162 t.Fatalf("constraint intersection: %+v", got)
163 }
164}
165
166func TestInputRegionResentAfterRemap(t *testing.T) {
167 s, events, _, dir := lifecycleServer(t)
168 c := protocolClient(t, s, dir)
169 w, surf, xdg := surfaceMapper(t, c, events)()
170 comp := bindProtocol(t, c, "wl_compositor")
171 region := c.AllocateID()
172 registerProtocol(t, c, region)
173 requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region)
174 requestProtocol(t, c, region, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1))
175 requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region)
176 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
177 if err := c.Roundtrip(); err != nil {
178 t.Fatal(err)
179 }
180 if got := inputChanged(t, events); got.ID != w.ID || got.All {
181 t.Fatalf("initial input: %+v", got)
182 }
183 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0))
184 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
185 if err := c.Roundtrip(); err != nil {
186 t.Fatal(err)
187 }
188 unmapped(t, events, w.ID)
189 // Remapping requires a new initial configure and an ack before attaching.
190 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
191 if err := c.Roundtrip(); err != nil {
192 t.Fatal(err)
193 }
194 var serial uint32
195 if !s.display.Do(func() {
196 for _, v := range s.surfaces {
197 if v.xdg != nil && v.xdg.window != nil && v.xdg.window.id == w.ID && len(v.xdg.serials) > 0 {
198 serial = v.xdg.serials[len(v.xdg.serials)-1]
199 }
200 }
201 }) {
202 t.Fatal("server stopped")
203 }
204 if serial == 0 {
205 t.Fatal("no remap configure")
206 }
207 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, serial)
208 buffer := shmBuffer(t, c)
209 requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, buffer, int32(0), int32(0))
210 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
211 if err := c.Roundtrip(); err != nil {
212 t.Fatal(err)
213 }
214 if got := mapped(t, events, 2*time.Second); got.ID != w.ID {
215 t.Fatalf("remapped: %+v", got)
216 }
217 if got := inputChanged(t, events); got.ID != w.ID || got.All || len(got.Rects) != 1 || got.Rects[0] != (ports.Rect{W: 1, H: 1}) {
218 t.Fatalf("remap input: %+v", got)
219 }
220}
221
222func TestRegionOverlappingAddsSubtractUnion(t *testing.T) {
223 g := ®ion{}
224 g.Add(nil, 0, 0, 6, 6)
225 g.Add(nil, 2, 2, 6, 6)
226 g.Subtract(nil, 3, 3, 2, 2)
227 for _, r := range g.rects {
228 if intersectRect(r, ports.Rect{X: 3, Y: 3, W: 2, H: 2}).W != 0 {
229 t.Fatalf("subtracted overlap remains: %+v", g.rects)
230 }
231 }
232 if g.boxRect() != (ports.Rect{W: 8, H: 8}) {
233 t.Fatalf("outer region changed: %+v", g.rects)
234 }
235}
236
237func TestInputRegionDeactivatesConstraint(t *testing.T) {
238 for _, kind := range []string{"confine", "lock"} {
239 for _, persistent := range []bool{false, true} {
240 t.Run(fmt.Sprintf("%s/persistent=%t", kind, persistent), func(t *testing.T) {
241 s, events, commands, dir := lifecycleServer(t)
242 c := protocolClient(t, s, dir)
243 seat := bindProtocol(t, c, "wl_seat")
244 registerProtocol(t, c, seat)
245 pointer := c.AllocateID()
246 registerProtocol(t, c, pointer)
247 requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
248 w, surf, _ := surfaceMapper(t, c, events)()
249 manager := bindProtocol(t, c, "zwp_pointer_constraints_v1")
250 id := c.AllocateID()
251 notices := logProxy(t, c, id)
252 lifetime := uint32(pointerconstraints.ZwpPointerConstraintsV1LifetimeOneshot)
253 if persistent {
254 lifetime = uint32(pointerconstraints.ZwpPointerConstraintsV1LifetimePersistent)
255 }
256 if kind == "lock" {
257 requestProtocol(t, c, manager, pointerconstraints.ZwpPointerConstraintsV1RequestLockPointer, id, surf, pointer, uint32(0), lifetime)
258 } else {
259 requestProtocol(t, c, manager, pointerconstraints.ZwpPointerConstraintsV1RequestConfinePointer, id, surf, pointer, uint32(0), lifetime)
260 }
261 commands <- ports.PointerFocus{ID: w.ID}
262 commands <- ports.FocusWindow{ID: w.ID}
263 if ev := next(t, c, notices); ev[0] != 0 {
264 t.Fatalf("activation: %v", ev)
265 }
266 if got := constrained(t, events); got.ID != w.ID {
267 t.Fatalf("constraint: %+v", got)
268 }
269 comp := bindProtocol(t, c, "wl_compositor")
270 region := c.AllocateID()
271 registerProtocol(t, c, region)
272 requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region)
273 requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region)
274 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
275 if ev := next(t, c, notices); ev[0] != 1 {
276 t.Fatalf("deactivation: %v", ev)
277 }
278 if got := constrained(t, events); got.ID != 0 {
279 t.Fatalf("constraint retained: %+v", got)
280 }
281 if err := c.Roundtrip(); err != nil {
282 t.Fatal(err)
283 }
284 select {
285 case ev := <-notices:
286 t.Fatalf("unexpected reactivation: %v", ev)
287 default:
288 }
289 })
290 }
291 }
292}
293
294func TestFifoQueuedInputRegionsApplyInOrder(t *testing.T) {
295 s, events, _, dir := lifecycleServer(t)
296 c := protocolClient(t, s, dir)
297 w, surf, _ := surfaceMapper(t, c, events)()
298 manager := bindProtocol(t, c, "wp_fifo_manager_v1")
299 f := c.AllocateID()
300 registerProtocol(t, c, f)
301 requestProtocol(t, c, manager, fifo.WpFifoManagerV1RequestGetFifo, f, surf)
302 comp := bindProtocol(t, c, "wl_compositor")
303 region := c.AllocateID()
304 registerProtocol(t, c, region)
305 requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, region)
306 requestProtocol(t, c, region, wayland.RegionRequestAdd, int32(0), int32(0), int32(1), int32(1))
307 // First commit sets a barrier; the next two wait behind it.
308 requestProtocol(t, c, f, fifo.WpFifoV1RequestSetBarrier)
309 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
310 requestProtocol(t, c, f, fifo.WpFifoV1RequestWaitBarrier)
311 requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, region)
312 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
313 requestProtocol(t, c, f, fifo.WpFifoV1RequestWaitBarrier)
314 empty := c.AllocateID()
315 registerProtocol(t, c, empty)
316 requestProtocol(t, c, comp, wayland.CompositorRequestCreateRegion, empty)
317 requestProtocol(t, c, surf, wayland.SurfaceRequestSetInputRegion, empty)
318 requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
319 if err := c.Roundtrip(); err != nil {
320 t.Fatal(err)
321 }
322 first := inputChanged(t, events)
323 second := inputChanged(t, events)
324 if first.ID != w.ID || len(first.Rects) != 1 || first.Rects[0] != (ports.Rect{W: 1, H: 1}) || second.ID != w.ID || second.All || len(second.Rects) != 0 {
325 t.Fatalf("queued input order: first %+v, second %+v", first, second)
326 }
327}