1package wayland
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/go-wayland-bindings/server/presentationtime"
8 "github.com/bnema/go-wayland-bindings/server/viewporter"
9 "github.com/bnema/go-wayland-bindings/server/wayland"
10 "github.com/bnema/go-wayland-bindings/server/xdgshell"
11 "github.com/bnema/neferwl/internal/ports"
12 "golang.org/x/sys/unix"
13)
14
15// A parent's dependency is fixed at the commit request, not when its
16// acquire point fires. A later child update cannot slip into that graph.
17func TestSubsurfaceAcquireCapturesChildAtRequest(t *testing.T) {
18 h := newSyncHarness(t)
19 h.releases = map[uint32]*syncReleaseProxy{}
20 c := h.c
21 comp := bindProtocol(t, c, "wl_compositor")
22 subc := bindProtocol(t, c, "wl_subcompositor")
23 child := c.AllocateID()
24 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
25 registerProtocol(t, c, child)
26 sub := c.AllocateID()
27 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, h.surf)
28 registerProtocol(t, c, sub)
29 a := shmBuffer(t, c)
30 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, a, int32(0), int32(0))
31 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
32 root := h.dmabuf()
33 h.commit(root, 1, 2)
34 // This commit came after the parent request, while it was still waiting.
35 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, uint32(0), int32(0), int32(0))
36 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
37 if err := c.Roundtrip(); err != nil {
38 t.Fatal(err)
39 }
40 if got, ok := h.content(40 * time.Millisecond); ok && len(got.Children) != 0 {
41 t.Fatalf("child published before parent's acquire: %+v", got.Children)
42 }
43 h.fire(1)
44 deadline := time.After(2 * time.Second)
45 for {
46 select {
47 case got := <-h.contents:
48 if got.ID != h.win || got.DMABuf == nil {
49 continue
50 }
51 if len(got.Children) != 1 || got.Children[0].Width != 1 {
52 t.Fatalf("parent captured later child update: %+v", got.Children)
53 }
54 return
55 case <-deadline:
56 t.Fatal("parent graph did not publish")
57 }
58 }
59}
60
61// Desync drains cached commits in FIFO order, including an unmap.
62func TestSubsurfaceDesyncQueuedOrderAndUnmap(t *testing.T) {
63 s, events, _, contents, dir := contentServer(t)
64 c := protocolClient(t, s, dir)
65 w, root, _ := surfaceMapper(t, c, events)()
66 drainContents(contents)
67 comp := bindProtocol(t, c, "wl_compositor")
68 subc := bindProtocol(t, c, "wl_subcompositor")
69 child := c.AllocateID()
70 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
71 registerProtocol(t, c, child)
72 sub := c.AllocateID()
73 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
74 registerProtocol(t, c, sub)
75 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
76 if err := c.Roundtrip(); err != nil {
77 t.Fatal(err)
78 }
79 drainContents(contents)
80 for _, b := range []uint32{shmBuffer(t, c), shmBuffer(t, c), 0} {
81 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
82 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
83 }
84 if err := c.Roundtrip(); err != nil {
85 t.Fatal(err)
86 }
87 select {
88 case got := <-contents:
89 t.Fatalf("cached commit published: %+v", got)
90 case <-time.After(40 * time.Millisecond):
91 }
92 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetDesync)
93 if err := c.Roundtrip(); err != nil {
94 t.Fatal(err)
95 }
96 deadline := time.After(2 * time.Second)
97 for {
98 select {
99 case got := <-contents:
100 if got.Seq < 5 {
101 continue // the forwarder can coalesce intermediate publications
102 }
103 if got.ID != w.ID || got.Seq != 5 || len(got.Children) != 0 {
104 t.Fatalf("desync did not apply all three commits: %+v", got)
105 }
106 return
107 case <-deadline:
108 t.Fatal("missing final desync publication")
109 }
110 }
111}
112
113// Pending viewport and scale are captured by the child commit.
114func TestSubsurfaceCachedViewportAndScale(t *testing.T) {
115 s, events, _, contents, dir := contentServer(t)
116 c := protocolClient(t, s, dir)
117 w, root, _ := surfaceMapper(t, c, events)()
118 drainContents(contents)
119 comp := bindVersion(t, c, "wl_compositor", 4)
120 subc := bindProtocol(t, c, "wl_subcompositor")
121 vpm := bindProtocol(t, c, "wp_viewporter")
122 child := c.AllocateID()
123 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
124 registerProtocol(t, c, child)
125 sub := c.AllocateID()
126 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
127 registerProtocol(t, c, sub)
128 vp := c.AllocateID()
129 requestProtocol(t, c, vpm, viewporter.WpViewporterRequestGetViewport, vp, child)
130 registerProtocol(t, c, vp)
131 b := shmBuffer(t, c)
132 requestProtocol(t, c, vp, viewporter.WpViewportRequestSetSource, int32(0), int32(0), int32(128), int32(128))
133 requestProtocol(t, c, vp, viewporter.WpViewportRequestSetDestination, int32(3), int32(4))
134 requestProtocol(t, c, child, wayland.SurfaceRequestSetBufferScale, int32(2))
135 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
136 requestProtocol(t, c, child, wayland.SurfaceRequestDamage, int32(0), int32(0), int32(1), int32(1))
137 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
138 requestProtocol(t, c, vp, viewporter.WpViewportRequestSetDestination, int32(9), int32(9))
139 if err := c.Roundtrip(); err != nil {
140 t.Fatal(err)
141 }
142 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
143 if err := c.Roundtrip(); err != nil {
144 t.Fatal(err)
145 }
146 select {
147 case got := <-contents:
148 if got.ID != w.ID || len(got.Children) != 1 || got.Children[0].LogicalW != 3 || got.Children[0].LogicalH != 4 || got.Children[0].Source != ([4]float32{0, 0, 1, 1}) {
149 t.Fatalf("cached viewport: %+v", got)
150 }
151 case <-time.After(2 * time.Second):
152 t.Fatal("no cached content")
153 }
154}
155
156// Only the final state of a graph is ever sampled by an output. Buffers
157// replaced inside the graph must be released without an output acknowledgement.
158func TestSubsurfaceSupersededBufferReleased(t *testing.T) {
159 s, events, _, contents, dir := contentServer(t)
160 c := protocolClient(t, s, dir)
161 _, root, _ := surfaceMapper(t, c, events)()
162 drainContents(contents)
163 comp := bindProtocol(t, c, "wl_compositor")
164 subc := bindProtocol(t, c, "wl_subcompositor")
165 child := c.AllocateID()
166 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
167 registerProtocol(t, c, child)
168 sub := c.AllocateID()
169 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
170 registerProtocol(t, c, sub)
171 first := shmBuffer(t, c)
172 p := &syncReleaseProxy{released: make(chan struct{}, 4)}
173 p.SetID(first)
174 registerWireProxy(c, p)
175 for _, b := range []uint32{first, shmBuffer(t, c)} {
176 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
177 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
178 }
179 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
180 if err := c.Roundtrip(); err != nil {
181 t.Fatal(err)
182 }
183 select {
184 case <-p.released:
185 case <-time.After(2 * time.Second):
186 t.Fatal("superseded unpublished buffer held for output")
187 }
188}
189
190// A child update captured by a parent stays with that parent after desync.
191func TestSubsurfaceBoundUpdateStaysBound(t *testing.T) {
192 s, events, _, contents, dir := contentServer(t)
193 c := protocolClient(t, s, dir)
194 _, root, _ := surfaceMapper(t, c, events)()
195 drainContents(contents)
196 comp := bindProtocol(t, c, "wl_compositor")
197 subc := bindProtocol(t, c, "wl_subcompositor")
198 child := c.AllocateID()
199 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
200 registerProtocol(t, c, child)
201 sub := c.AllocateID()
202 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
203 registerProtocol(t, c, sub)
204 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
205 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
206 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
207 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetDesync)
208 if err := c.Roundtrip(); err != nil {
209 t.Fatal(err)
210 }
211 select {
212 case got := <-contents:
213 if len(got.Children) != 1 {
214 t.Fatalf("captured update not applied: %+v", got)
215 }
216 case <-time.After(2 * time.Second):
217 t.Fatal("missing parent graph")
218 }
219 select {
220 case got := <-contents:
221 t.Fatalf("bound update replayed: %+v", got)
222 case <-time.After(40 * time.Millisecond):
223 }
224}
225
226// Destroying the parent cannot publish a captured child dependency.
227func TestSubsurfaceParentDestroyDropsCapturedChild(t *testing.T) {
228 s, events, _, contents, dir := contentServer(t)
229 c := protocolClient(t, s, dir)
230 _, root, _ := surfaceMapper(t, c, events)()
231 drainContents(contents)
232 comp := bindProtocol(t, c, "wl_compositor")
233 subc := bindProtocol(t, c, "wl_subcompositor")
234 child := c.AllocateID()
235 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
236 registerProtocol(t, c, child)
237 sub := c.AllocateID()
238 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
239 registerProtocol(t, c, sub)
240 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
241 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
242 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
243 if err := c.Roundtrip(); err != nil {
244 t.Fatal(err)
245 }
246 // That parent commit legitimately publishes the child: consume it
247 // before destroying the parent, so the check below sees only later output.
248 select {
249 case <-contents:
250 case <-time.After(2 * time.Second):
251 t.Fatal("parent commit did not publish")
252 }
253 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
254 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
255 requestProtocol(t, c, root, wayland.SurfaceRequestDestroy)
256 if err := c.Roundtrip(); err != nil {
257 t.Fatal(err)
258 }
259 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetDesync)
260 if err := c.Roundtrip(); err != nil {
261 t.Fatal(err)
262 }
263 select {
264 case got := <-contents:
265 if len(got.Children) != 0 {
266 t.Fatalf("destroyed parent published child: %+v", got)
267 }
268 case <-time.After(40 * time.Millisecond):
269 }
270}
271
272// Destroying a queued child removes it from captured parent layouts and
273// retires its unpublished buffer without publishing its content.
274func TestSubsurfaceDestroyQueuedChild(t *testing.T) {
275 s, events, _, contents, dir := contentServer(t)
276 c := protocolClient(t, s, dir)
277 w, root, _ := surfaceMapper(t, c, events)()
278 drainContents(contents)
279 comp := bindProtocol(t, c, "wl_compositor")
280 subc := bindProtocol(t, c, "wl_subcompositor")
281 child := c.AllocateID()
282 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
283 registerProtocol(t, c, child)
284 sub := c.AllocateID()
285 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
286 registerProtocol(t, c, sub)
287 b := shmBuffer(t, c)
288 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, b, int32(0), int32(0))
289 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
290 requestProtocol(t, c, child, wayland.SurfaceRequestDestroy)
291 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
292 if err := c.Roundtrip(); err != nil {
293 t.Fatal(err)
294 }
295 deadline := time.After(2 * time.Second)
296 for {
297 select {
298 case got := <-contents:
299 if got.ID == w.ID && len(got.Children) != 0 {
300 t.Fatalf("destroyed child published: %+v", got)
301 }
302 if got.ID == w.ID {
303 return
304 }
305 case <-deadline:
306 t.Fatal("no parent update after child destroy")
307 }
308 }
309}
310
311// A queued buffer destroyed before its parent commits is skipped with the
312// crop made for it: the retained buffer keeps its own crop, without error.
313func TestSubsurfaceDestroyedQueuedBufferKeepsCrop(t *testing.T) {
314 s, events, _, contents, dir := contentServer(t)
315 c := protocolClient(t, s, dir)
316 w, root, _ := surfaceMapper(t, c, events)()
317 drainContents(contents)
318 comp := bindProtocol(t, c, "wl_compositor")
319 subc := bindProtocol(t, c, "wl_subcompositor")
320 vpm := bindProtocol(t, c, "wp_viewporter")
321 child := c.AllocateID()
322 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
323 registerProtocol(t, c, child)
324 sub := c.AllocateID()
325 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
326 registerProtocol(t, c, sub)
327 vp := c.AllocateID()
328 requestProtocol(t, c, vpm, viewporter.WpViewporterRequestGetViewport, vp, child)
329 registerProtocol(t, c, vp)
330 small, wide := shmBuffer(t, c), shmBuffer(t, c)
331 requestProtocol(t, c, vp, viewporter.WpViewportRequestSetSource, int32(0), int32(0), int32(256), int32(256))
332 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, small, int32(0), int32(0))
333 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
334 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
335 roundtrip(t, c)
336 drainContents(contents)
337
338 // A 2x1 crop meant for a wider buffer that is destroyed while queued.
339 requestProtocol(t, c, vp, viewporter.WpViewportRequestSetSource, int32(0), int32(0), int32(512), int32(256))
340 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, wide, int32(0), int32(0))
341 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
342 requestProtocol(t, c, wide, wayland.BufferRequestDestroy)
343 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
344 roundtrip(t, c)
345 deadline := time.After(2 * time.Second)
346 for {
347 select {
348 case got := <-contents:
349 if got.ID != w.ID {
350 continue
351 }
352 if len(got.Children) != 1 || got.Children[0].Source != ([4]float32{0, 0, 1, 1}) {
353 t.Fatalf("retained crop: %+v", got)
354 }
355 return
356 case <-deadline:
357 t.Fatal("no parent update")
358 }
359 }
360}
361
362// A synchronized child cannot publish until a parent commits; position and
363// order are part of that same parent update.
364func TestSubsurfaceInitialSyncAndDeferredLayout(t *testing.T) {
365 s, events, _, contents, dir := contentServer(t)
366 c := protocolClient(t, s, dir)
367 rootWindow, root, _ := surfaceMapper(t, c, events)()
368 drainContents(contents)
369 comp := bindProtocol(t, c, "wl_compositor")
370 subc := bindProtocol(t, c, "wl_subcompositor")
371 child := c.AllocateID()
372 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
373 registerProtocol(t, c, child)
374 sub := c.AllocateID()
375 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
376 registerProtocol(t, c, sub)
377 buf := shmBuffer(t, c)
378 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, buf, int32(0), int32(0))
379 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
380 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetPosition, int32(4), int32(5))
381 requestProtocol(t, c, sub, wayland.SubsurfaceRequestPlaceBelow, root)
382 if err := c.Roundtrip(); err != nil {
383 t.Fatal(err)
384 }
385 select {
386 case got := <-contents:
387 t.Fatalf("child/layout published before parent commit: %+v", got)
388 case <-time.After(40 * time.Millisecond):
389 }
390 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
391 if err := c.Roundtrip(); err != nil {
392 t.Fatal(err)
393 }
394 select {
395 case got := <-contents:
396 if got.ID != rootWindow.ID || len(got.Children) != 1 || got.Children[0].X != 4 || got.Children[0].Y != 5 || !got.Children[0].Below {
397 t.Fatalf("parent publication: %+v", got)
398 }
399 case <-time.After(2 * time.Second):
400 t.Fatal("no parent publication")
401 }
402 select {
403 case got := <-contents:
404 t.Fatalf("extra publication: %+v", got)
405 case <-time.After(40 * time.Millisecond):
406 }
407}
408
409func TestSubsurfaceDesyncFlushesOnlyEffectiveTransition(t *testing.T) {
410 s, events, _, contents, dir := contentServer(t)
411 c := protocolClient(t, s, dir)
412 _, root, _ := surfaceMapper(t, c, events)()
413 drainContents(contents)
414 comp := bindProtocol(t, c, "wl_compositor")
415 subc := bindProtocol(t, c, "wl_subcompositor")
416 parent, child := c.AllocateID(), c.AllocateID()
417 for _, id := range []uint32{parent, child} {
418 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, id)
419 registerProtocol(t, c, id)
420 }
421 parentSub, childSub := c.AllocateID(), c.AllocateID()
422 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, parentSub, parent, root)
423 registerProtocol(t, c, parentSub)
424 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, childSub, child, parent)
425 registerProtocol(t, c, childSub)
426 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
427 requestProtocol(t, c, parent, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
428 requestProtocol(t, c, parent, wayland.SurfaceRequestCommit)
429 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
430 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
431 requestProtocol(t, c, childSub, wayland.SubsurfaceRequestSetDesync)
432 if err := c.Roundtrip(); err != nil {
433 t.Fatal(err)
434 }
435 select {
436 case got := <-contents:
437 if len(got.Children) != 0 {
438 t.Fatalf("ineffective desync published: %+v", got.Children)
439 }
440 case <-time.After(40 * time.Millisecond):
441 }
442 requestProtocol(t, c, parentSub, wayland.SubsurfaceRequestSetDesync)
443 if err := c.Roundtrip(); err != nil {
444 t.Fatal(err)
445 }
446 deadline := time.After(2 * time.Second)
447 for {
448 select {
449 case got := <-contents:
450 if len(got.Children) == 2 {
451 return
452 }
453 case <-deadline:
454 t.Fatal("desync did not flush inherited child update")
455 }
456 }
457}
458
459// A toplevel with a subsurface (how Firefox draws with the GPU) sends one
460// content: its own buffer, the child at its committed position and the
461// window geometry.
462func TestSubsurfaceContent(t *testing.T) {
463 s, events, contents, dir := dmabufServer(t, ports.DMABufSupport{})
464 c := protocolClient(t, s, dir)
465 comp := bindProtocol(t, c, "wl_compositor")
466 subc := bindProtocol(t, c, "wl_subcompositor")
467 wm := bindProtocol(t, c, "xdg_wm_base")
468 shm := bindProtocol(t, c, "wl_shm")
469 registerProtocol(t, c, shm)
470 fd, err := unix.MemfdCreate("subsurface", 0)
471 if err != nil {
472 t.Fatal(err)
473 }
474 defer unix.Close(fd)
475 if err := unix.Ftruncate(fd, 4*4*4); err != nil {
476 t.Fatal(err)
477 }
478 pool, big, small := c.AllocateID(), c.AllocateID(), c.AllocateID()
479 registerProtocol(t, c, pool)
480 registerProtocol(t, c, big)
481 registerProtocol(t, c, small)
482 if err := wireRequest(c, shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, int32(64)); err != nil {
483 t.Fatal(err)
484 }
485 requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, big, int32(0), int32(4), int32(4), int32(16), uint32(0))
486 requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, small, int32(0), int32(2), int32(2), int32(16), uint32(0))
487
488 root, child := c.AllocateID(), c.AllocateID()
489 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, root)
490 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
491 registerProtocol(t, c, root)
492 registerProtocol(t, c, child)
493 sub := c.AllocateID()
494 registerProtocol(t, c, sub)
495 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
496 xdg := c.AllocateID()
497 requestProtocol(t, c, wm, xdgshell.WmBaseRequestGetXdgSurface, xdg, root)
498 serials := make(chan uint32, 4)
499 xp := &configureProxy{serial: serials}
500 xp.SetID(xdg)
501 registerWireProxy(c, xp)
502 top := c.AllocateID()
503 registerProtocol(t, c, top)
504 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestGetToplevel, top)
505 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
506 if err := c.Roundtrip(); err != nil {
507 t.Fatal(err)
508 }
509 select {
510 case serial := <-serials:
511 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, serial)
512 case <-time.After(2 * time.Second):
513 t.Fatal("configure timeout")
514 }
515 // Child first: nothing to draw until the root maps.
516 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetPosition, int32(1), int32(2))
517 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, small, int32(0), int32(0))
518 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
519 requestProtocol(t, c, xdg, xdgshell.SurfaceRequestSetWindowGeometry, int32(1), int32(1), int32(2), int32(2))
520 requestProtocol(t, c, root, wayland.SurfaceRequestAttach, big, int32(0), int32(0))
521 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
522 if err := c.Roundtrip(); err != nil {
523 t.Fatal(err)
524 }
525 w := mapped(t, events, 2*time.Second)
526 var got ports.SurfaceContent
527 deadline := time.After(2 * time.Second)
528 for got.ID == 0 {
529 select {
530 case got = <-contents:
531 case <-deadline:
532 t.Fatal("no content")
533 }
534 }
535 if got.ID != w.ID || got.Width != 4 || got.Geometry != (ports.Rect{X: 1, Y: 1, W: 2, H: 2}) {
536 t.Fatalf("root %+v", got)
537 }
538 if len(got.Children) != 1 || got.Children[0].X != 1 || got.Children[0].Y != 2 || got.Children[0].Width != 2 || got.Children[0].Below {
539 t.Fatalf("children %+v", got.Children)
540 }
541 // Destroying the subsurface redraws the root without it.
542 requestProtocol(t, c, sub, wayland.SubsurfaceRequestDestroy)
543 if err := c.Roundtrip(); err != nil {
544 t.Fatal(err)
545 }
546 deadline = time.After(2 * time.Second)
547 for {
548 select {
549 case got = <-contents:
550 if len(got.Children) == 0 {
551 return
552 }
553 case <-deadline:
554 t.Fatalf("child kept: %+v", got.Children)
555 }
556 }
557}
558
559// A surface cannot become a subsurface of its own child.
560func TestSubsurfaceLoop(t *testing.T) {
561 s, _, _, dir := dmabufServer(t, ports.DMABufSupport{})
562 c := protocolClient(t, s, dir)
563 comp := bindProtocol(t, c, "wl_compositor")
564 subc := bindProtocol(t, c, "wl_subcompositor")
565 a, b := c.AllocateID(), c.AllocateID()
566 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, a)
567 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, b)
568 s1 := c.AllocateID()
569 registerProtocol(t, c, s1)
570 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s1, b, a)
571 s2 := c.AllocateID()
572 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s2, a, b)
573 expectProtocolError(t, c, subc, uint32(wayland.SubcompositorErrorBadParent))
574}
575
576// A wl_subsurface outlives its parent: the surface keeps its role, so a
577// second get_subsurface is bad_surface.
578func TestSubsurfaceRoleOutlivesParent(t *testing.T) {
579 s, _, _, dir := dmabufServer(t, ports.DMABufSupport{})
580 c := protocolClient(t, s, dir)
581 comp := bindProtocol(t, c, "wl_compositor")
582 subc := bindProtocol(t, c, "wl_subcompositor")
583 a, b := c.AllocateID(), c.AllocateID()
584 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, a)
585 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, b)
586 registerProtocol(t, c, a)
587 s1 := c.AllocateID()
588 registerProtocol(t, c, s1)
589 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s1, b, a)
590 requestProtocol(t, c, a, wayland.SurfaceRequestDestroy)
591 p := c.AllocateID()
592 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, p)
593 s2 := c.AllocateID()
594 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, s2, b, p)
595 expectProtocolError(t, c, subc, uint32(wayland.SubcompositorErrorBadSurface))
596}
597
598// A parent graph with two child commits only publishes the final child.
599func TestSubsurfaceGraphSupersededFeedback(t *testing.T) {
600 s, events, _, contents, dir := contentServer(t)
601 c := protocolClient(t, s, dir)
602 w, root, _ := surfaceMapper(t, c, events)()
603 s.display.Do(func() { s.windows[w.ID].xdg.window.last.Output = "HEADLESS-1" })
604 drainContents(contents)
605 comp := bindProtocol(t, c, "wl_compositor")
606 subc := bindProtocol(t, c, "wl_subcompositor")
607 pres := bindProtocol(t, c, "wp_presentation")
608 registerProtocol(t, c, pres)
609 child := c.AllocateID()
610 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
611 registerProtocol(t, c, child)
612 sub := c.AllocateID()
613 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
614 registerProtocol(t, c, sub)
615 first := &feedbackEvents{out: make(chan presentedEvent, 2)}
616 id := c.AllocateID()
617 first.SetID(id)
618 registerWireProxy(c, first)
619 requestProtocol(t, c, pres, presentationtime.WpPresentationRequestFeedback, child, id)
620 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
621 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
622 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
623 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
624 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
625 if err := c.Roundtrip(); err != nil {
626 t.Fatal(err)
627 }
628 if err := c.Roundtrip(); err != nil {
629 t.Fatal(err)
630 }
631 select {
632 case ev := <-first.out:
633 if !ev.discarded {
634 t.Fatalf("superseded content presented: %+v", ev)
635 }
636 case <-time.After(2 * time.Second):
637 t.Fatal("superseded feedback not discarded")
638 }
639}
640
641// A subsurface resource remains live after its parent surface is destroyed.
642func TestSubsurfacePositionAfterParentDestroy(t *testing.T) {
643 s, events, _, _, dir := contentServer(t)
644 c := protocolClient(t, s, dir)
645 _, root, _ := surfaceMapper(t, c, events)()
646 comp := bindProtocol(t, c, "wl_compositor")
647 subc := bindProtocol(t, c, "wl_subcompositor")
648 child := c.AllocateID()
649 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
650 registerProtocol(t, c, child)
651 sub := c.AllocateID()
652 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
653 registerProtocol(t, c, sub)
654 requestProtocol(t, c, root, wayland.SurfaceRequestDestroy)
655 roundtrip(t, c)
656 requestProtocol(t, c, sub, wayland.SubsurfaceRequestSetPosition, int32(3), int32(4))
657 roundtrip(t, c)
658}
659
660// A graph without a changed surface must not republish a mapped tree.
661func TestSubsurfaceNoopRootCommitDoesNotPublish(t *testing.T) {
662 s, events, _, contents, dir := contentServer(t)
663 c := protocolClient(t, s, dir)
664 _, root, _ := surfaceMapper(t, c, events)()
665 comp := bindProtocol(t, c, "wl_compositor")
666 subc := bindProtocol(t, c, "wl_subcompositor")
667 child := c.AllocateID()
668 requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, child)
669 registerProtocol(t, c, child)
670 sub := c.AllocateID()
671 requestProtocol(t, c, subc, wayland.SubcompositorRequestGetSubsurface, sub, child, root)
672 registerProtocol(t, c, sub)
673 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
674 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
675 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
676 roundtrip(t, c)
677 drainContents(contents)
678 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
679 roundtrip(t, c)
680 select {
681 case got := <-contents:
682 t.Fatalf("no-op published: %+v", got)
683 case <-time.After(80 * time.Millisecond):
684 }
685 requestProtocol(t, c, child, wayland.SurfaceRequestAttach, shmBuffer(t, c), int32(0), int32(0))
686 requestProtocol(t, c, child, wayland.SurfaceRequestCommit)
687 requestProtocol(t, c, root, wayland.SurfaceRequestCommit)
688 roundtrip(t, c)
689 select {
690 case got := <-contents:
691 if len(got.Children) != 1 {
692 t.Fatalf("child change not published: %+v", got)
693 }
694 case <-time.After(2 * time.Second):
695 t.Fatal("child change did not publish")
696 }
697}