feat/data-dnd e05cb15

transform_test.go RawHistoryFolder

1package wayland
2
3import (
4	"testing"
5	"time"
6
7	"github.com/bnema/go-wayland-bindings/server/viewporter"
8	"github.com/bnema/go-wayland-bindings/server/wayland"
9	"github.com/bnema/go-wayland-bindings/server/xdgshell"
10	"github.com/bnema/neferwl/internal/ports"
11	"github.com/bnema/wlturbo"
12	"golang.org/x/sys/unix"
13)
14
15// transformClient maps a toplevel whose 30×20 argb8888 buffer is shown
16// through a viewport, and returns what it needs to commit more state.
17type transformClient struct {
18	c                  *wlturbo.Display
19	comp, surf, vp, bf uint32
20	contents           <-chan ports.SurfaceContent
21}
22
23func newTransformClient(t *testing.T) transformClient {
24	t.Helper()
25	s, events, _, contents, dir := contentServer(t)
26	c := protocolClient(t, s, dir)
27	// set_buffer_transform needs wl_compositor v2.
28	g, _ := c.Registry().FindGlobal("wl_compositor")
29	comp, err := bindWireID(c, g.Name, g.Interface, 6)
30	if err != nil {
31		t.Fatal(err)
32	}
33	wm := bindProtocol(t, c, "xdg_wm_base")
34	shm := bindProtocol(t, c, "wl_shm")
35	vpm := bindProtocol(t, c, "wp_viewporter")
36	registerProtocol(t, c, shm)
37	surf := c.AllocateID()
38	registerProtocol(t, c, surf)
39	requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, surf)
40	vp := c.AllocateID()
41	registerProtocol(t, c, vp)
42	requestProtocol(t, c, vpm, viewporter.WpViewporterRequestGetViewport, vp, surf)
43	xdg := c.AllocateID()
44	serials := make(chan uint32, 4)
45	cp := &configureProxy{serial: serials}
46	cp.SetID(xdg)
47	registerWireProxy(c, cp)
48	requestProtocol(t, c, wm, xdgshell.WmBaseRequestGetXdgSurface, xdg, surf)
49	top := c.AllocateID()
50	registerProtocol(t, c, top)
51	requestProtocol(t, c, xdg, xdgshell.SurfaceRequestGetToplevel, top)
52	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
53	if err := c.Roundtrip(); err != nil {
54		t.Fatal(err)
55	}
56	requestProtocol(t, c, xdg, xdgshell.SurfaceRequestAckConfigure, <-serials)
57	fd, err := unix.MemfdCreate("transform", 0)
58	if err != nil {
59		t.Fatal(err)
60	}
61	t.Cleanup(func() { unix.Close(fd) })
62	size := int32(30 * 20 * 4)
63	if err := unix.Ftruncate(fd, int64(size)); err != nil {
64		t.Fatal(err)
65	}
66	pool, bf := c.AllocateID(), c.AllocateID()
67	registerProtocol(t, c, pool)
68	registerProtocol(t, c, bf)
69	if err := wireRequest(c, shm, uint16(wayland.ShmRequestCreatePool), []int{fd}, pool, size); err != nil {
70		t.Fatal(err)
71	}
72	// argb8888: not opaque by format.
73	requestProtocol(t, c, pool, wayland.ShmPoolRequestCreateBuffer, bf, int32(0), int32(30), int32(20), int32(120), uint32(wayland.ShmFormatArgb8888))
74	requestProtocol(t, c, surf, wayland.SurfaceRequestAttach, bf, int32(0), int32(0))
75	requestProtocol(t, c, surf, wayland.SurfaceRequestCommit)
76	if err := c.Roundtrip(); err != nil {
77		t.Fatal(err)
78	}
79	mapped(t, events, 2*time.Second)
80	tc := transformClient{c: c, comp: comp, surf: surf, vp: vp, bf: bf, contents: contents}
81	if got := tc.next(t); got.Transform != 0 || got.Opaque || got.LogicalW != 30 || got.LogicalH != 20 {
82		t.Fatalf("initial content: %+v", got)
83	}
84	return tc
85}
86
87// next returns the next published content with a buffer.
88func (tc transformClient) next(t *testing.T) ports.SurfaceContent {
89	t.Helper()
90	deadline := time.After(2 * time.Second)
91	for {
92		select {
93		case got := <-tc.contents:
94			if got.SHM != nil {
95				return got
96			}
97		case <-deadline:
98			t.Fatal("no content")
99		}
100	}
101}
102
103func (tc transformClient) commit(t *testing.T) {
104	t.Helper()
105	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestCommit)
106	if err := tc.c.Roundtrip(); err != nil {
107		t.Fatal(err)
108	}
109}
110
111// region creates a wl_region holding rects.
112func (tc transformClient) region(t *testing.T, rects ...ports.Rect) uint32 {
113	t.Helper()
114	id := tc.c.AllocateID()
115	registerProtocol(t, tc.c, id)
116	requestProtocol(t, tc.c, tc.comp, wayland.CompositorRequestCreateRegion, id)
117	for _, r := range rects {
118		requestProtocol(t, tc.c, id, wayland.RegionRequestAdd, int32(r.X), int32(r.Y), int32(r.W), int32(r.H))
119	}
120	return id
121}
122
123// A 90° buffer transform swaps the surface size and maps the viewport
124// source back to buffer pixels; it applies on commit, like the scale.
125func TestBufferTransformContent(t *testing.T) {
126	tc := newTransformClient(t)
127	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestSetBufferTransform, int32(1))
128	tc.commit(t)
129	if got := tc.next(t); got.Transform != 1 || got.Width != 30 || got.Height != 20 || got.LogicalW != 20 || got.LogicalH != 30 {
130		t.Fatalf("rotated content: %+v", got)
131	}
132	// Surface crop x 2..12, y 3..8 of the 20×30 rotated surface. Surface
133	// (x, y) is buffer (y, 20-x): buffer x 3..8, y 8..18.
134	requestProtocol(t, tc.c, tc.vp, viewporter.WpViewportRequestSetSource, int32(2*256), int32(3*256), int32(10*256), int32(5*256))
135	tc.commit(t)
136	if got := tc.next(t); got.LogicalW != 10 || got.LogicalH != 5 || got.Source != [4]float32{3, 8, 5, 10} {
137		t.Fatalf("rotated crop: %+v", got)
138	}
139	// A crop that fits the unrotated buffer but not the rotated surface
140	// (x up to 25 > 20) is out of buffer.
141	requestProtocol(t, tc.c, tc.vp, viewporter.WpViewportRequestSetSource, int32(15*256), int32(0), int32(10*256), int32(5*256))
142	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestCommit)
143	expectProtocolError(t, tc.c, tc.vp, uint32(viewporter.WpViewportErrorOutOfBuffer))
144}
145
146func TestBufferTransformInvalid(t *testing.T) {
147	tc := newTransformClient(t)
148	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestSetBufferTransform, int32(8))
149	expectProtocolError(t, tc.c, tc.surf, uint32(wayland.SurfaceErrorInvalidTransform))
150}
151
152// An opaque region covering the surface marks an alpha buffer opaque; a
153// partial or NULL region does not. The region applies on commit.
154func TestOpaqueRegionContent(t *testing.T) {
155	tc := newTransformClient(t)
156	half := tc.region(t, ports.Rect{W: 15, H: 20})
157	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestSetOpaqueRegion, half)
158	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestDamage, int32(0), int32(0), int32(1), int32(1))
159	tc.commit(t)
160	prev := tc.next(t)
161	if prev.Opaque {
162		t.Fatalf("partial region made the surface opaque: %+v", prev)
163	}
164	if want := (ports.Rect{W: 15, H: 20}); prev.OpaqueRect != want {
165		t.Fatalf("partial region: OpaqueRect %v, want %v", prev.OpaqueRect, want)
166	}
167	// Two halves cover the whole surface, even through a larger rect.
168	// Opacity changes blending everywhere: the commit damages it all.
169	whole := tc.region(t, ports.Rect{W: 15, H: 20}, ports.Rect{X: 15, W: 100, H: 20})
170	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestSetOpaqueRegion, whole)
171	tc.commit(t)
172	got := tc.next(t)
173	if !got.Opaque || got.OpaqueRect != (ports.Rect{}) {
174		t.Fatalf("covering region: %+v", got)
175	}
176	if rects, ok := got.DamageSince(prev.Seq); ok {
177		t.Fatalf("opacity change damaged only %v", rects)
178	}
179	// The surface grows past the region through the viewport: not opaque.
180	requestProtocol(t, tc.c, tc.vp, viewporter.WpViewportRequestSetDestination, int32(200), int32(20))
181	tc.commit(t)
182	if got := tc.next(t); got.Opaque {
183		t.Fatalf("region no longer covers: %+v", got)
184	}
185	requestProtocol(t, tc.c, tc.vp, viewporter.WpViewportRequestSetDestination, int32(-1), int32(-1))
186	tc.commit(t)
187	if got := tc.next(t); !got.Opaque {
188		t.Fatalf("region covers again: %+v", got)
189	}
190	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestSetOpaqueRegion, uint32(0))
191	tc.commit(t)
192	if got := tc.next(t); got.Opaque || got.OpaqueRect != (ports.Rect{}) {
193		t.Fatalf("NULL region: %+v", got)
194	}
195	// A region one pixel short of the surface (what Firefox sets) is not
196	// opaque, but its rect is reported.
197	short := tc.region(t, ports.Rect{W: 29, H: 19})
198	requestProtocol(t, tc.c, tc.surf, wayland.SurfaceRequestSetOpaqueRegion, short)
199	tc.commit(t)
200	if got := tc.next(t); got.Opaque || got.OpaqueRect != (ports.Rect{W: 29, H: 19}) {
201		t.Fatalf("short region: %+v", got)
202	}
203}
204
205// opaqueRect is the largest rect of the region inside the surface, none for
206// an opaque or faded surface.
207func TestOpaqueRect(t *testing.T) {
208	for _, tc := range []struct {
209		name   string
210		region []ports.Rect
211		opaque bool
212		w, h   int
213		fade   float32
214		want   ports.Rect
215	}{
216		{"none", nil, false, 10, 10, 0, ports.Rect{}},
217		{"one pixel short", []ports.Rect{{W: 983, H: 1230}}, false, 984, 1231, 0, ports.Rect{W: 983, H: 1230}},
218		{"clipped to the surface", []ports.Rect{{X: -5, Y: 2, W: 100, H: 100}}, false, 10, 10, 0, ports.Rect{Y: 2, W: 10, H: 8}},
219		{"largest of several", []ports.Rect{{W: 2, H: 2}, {X: 3, Y: 3, W: 5, H: 4}}, false, 10, 10, 0, ports.Rect{X: 3, Y: 3, W: 5, H: 4}},
220		{"outside", []ports.Rect{{X: 10, W: 5, H: 5}}, false, 10, 10, 0, ports.Rect{}},
221		{"wholly opaque", []ports.Rect{{W: 9, H: 9}}, true, 10, 10, 0, ports.Rect{}},
222		{"faded", []ports.Rect{{W: 9, H: 9}}, false, 10, 10, 0.5, ports.Rect{}},
223		{"no size", []ports.Rect{{W: 9, H: 9}}, false, 0, 0, 0, ports.Rect{}},
224	} {
225		t.Run(tc.name, func(t *testing.T) {
226			s := &surface{opaque: tc.region}
227			if got := s.opaqueRect(tc.opaque, tc.w, tc.h, tc.fade); got != tc.want {
228				t.Fatalf("opaqueRect = %v, want %v", got, tc.want)
229			}
230		})
231	}
232}
233
234// Transform and opaque region requested after a queued commit belong to
235// the next commit.
236func TestCapturedTransformAndOpaqueKeepLaterRequests(t *testing.T) {
237	srv := &Server{fifoSurfaces: map[*surface]struct{}{}, constraints: map[*surface]*constraint{}}
238	surf := &surface{server: srv}
239	surf.next.transform = 1
240	surf.next.opaque, surf.next.opaqueSet = []ports.Rect{{W: 4, H: 4}}, true
241	surf.queueUpdate()
242	surf.next.transform = 2
243	surf.next.opaque, surf.next.opaqueSet = nil, true
244	surf.queue[0].applyGraph()
245	if surf.transform != 1 || len(surf.opaque) != 1 {
246		t.Fatalf("captured state: transform %d opaque %v", surf.transform, surf.opaque)
247	}
248	if surf.next.transform != 2 || !surf.next.opaqueSet || surf.next.opaque != nil {
249		t.Fatal("application consumed a later request")
250	}
251	surf.queueUpdate()
252	if u := surf.queue[0]; u.transform != 2 || !u.opaqueSet || u.opaque != nil {
253		t.Fatalf("next commit: transform %d opaque %v/%v", u.transform, u.opaqueSet, u.opaque)
254	}
255	surf.dropQueue()
256}
257
258func TestCovers(t *testing.T) {
259	r := ports.Rect{W: 10, H: 10}
260	for _, tc := range []struct {
261		name  string
262		rects []ports.Rect
263		want  bool
264	}{
265		{"empty", nil, false},
266		{"exact", []ports.Rect{{W: 10, H: 10}}, true},
267		{"larger", []ports.Rect{{X: -5, Y: -5, W: 30, H: 30}}, true},
268		{"two halves", []ports.Rect{{W: 5, H: 10}, {X: 5, W: 5, H: 10}}, true},
269		{"gap", []ports.Rect{{W: 4, H: 10}, {X: 5, W: 5, H: 10}}, false},
270		{"hole", []ports.Rect{{W: 10, H: 4}, {Y: 6, W: 10, H: 4}, {Y: 4, W: 4, H: 2}, {X: 5, Y: 4, W: 5, H: 2}}, false},
271		{"four quarters", []ports.Rect{{W: 5, H: 5}, {X: 5, W: 5, H: 5}, {Y: 5, W: 5, H: 5}, {X: 5, Y: 5, W: 5, H: 5}}, true},
272		{"too many", make([]ports.Rect, maxCoverRects+1), false},
273	} {
274		if got := covers(tc.rects, r); got != tc.want {
275			t.Errorf("%s: %v, want %v", tc.name, got, tc.want)
276		}
277	}
278}

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 23

Tags 10

↑↓ moveenter openesc close