v0.3.0 cf370a0

subsurface_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close