v0.4.0 b6be3a8

colormanagement_test.go RawHistoryFolder

1package wayland
2
3import (
4	"context"
5	"testing"
6
7	cm "github.com/bnema/go-wayland-bindings/server/colormanagement"
8	"github.com/bnema/go-wayland-bindings/server/wayland"
9	"github.com/bnema/neferwl/internal/adapters/logging"
10	"github.com/bnema/neferwl/internal/ports"
11	"github.com/bnema/wlturbo"
12)
13
14type colorEvent struct {
15	wlturbo.BaseProxy
16	events []uint16
17	values [][]uint32
18}
19
20func (p *colorEvent) Dispatch(e *wlturbo.Event) {
21	p.events = append(p.events, e.Opcode)
22	var vals []uint32
23	for i := 0; i < 12; i++ {
24		if len(e.Data())-e.Offset() < 4 {
25			break
26		}
27		vals = append(vals, e.Uint32())
28	}
29	p.values = append(p.values, vals)
30}
31func watchColor(c *wlturbo.Display, id uint32) *colorEvent {
32	p := &colorEvent{}
33	p.SetID(id)
34	registerWireProxy(c, p)
35	return p
36}
37func colorServer(t *testing.T) (*Server, *wlturbo.Display, chan ports.OutputFormats) {
38	t.Helper()
39	dir := t.TempDir()
40	formats := make(chan ports.OutputFormats, 8)
41	s, err := New(Options{RuntimeDir: dir, Outputs: ports.Layout{{Info: ports.OutputInfo{Name: "HEADLESS-1", Width: 8, Height: 8}, Width: 8, Height: 8, Scale: 1}}}, Channels{OutputFormats: formats}, logging.For(context.Background(), "wayland"))
42	if err != nil {
43		t.Fatal(err)
44	}
45	ctx, cancel := context.WithCancel(context.Background())
46	done := make(chan error, 1)
47	go func() { done <- s.Run(ctx) }()
48	t.Cleanup(func() {
49		cancel()
50		if err := <-done; err != nil {
51			t.Error(err)
52		}
53	})
54	return s, protocolClient(t, s, dir), formats
55}
56func TestColorMesaFlow(t *testing.T) {
57	s, c, _ := colorServer(t)
58	manager := bindVersion(t, c, "wp_color_manager_v1", 2)
59	caps := watchColor(c, manager)
60	if err := c.Roundtrip(); err != nil {
61		t.Fatal(err)
62	}
63	var features, tf, prim []uint32
64	for i, op := range caps.events {
65		switch uint32(op) {
66		case cm.WpColorManagerV1EventSupportedFeature:
67			features = append(features, caps.values[i][0])
68		case cm.WpColorManagerV1EventSupportedTfNamed:
69			tf = append(tf, caps.values[i][0])
70		case cm.WpColorManagerV1EventSupportedPrimariesNamed:
71			prim = append(prim, caps.values[i][0])
72		}
73	}
74	if len(features) != 1 || features[0] != uint32(cm.WpColorManagerV1FeatureParametric) || len(tf) != 3 || tf[0] != pq || tf[1] != extLinear || tf[2] != uint32(cm.WpColorManagerV1TransferFunctionSrgb) || len(prim) != 2 || uint32(caps.events[len(caps.events)-1]) != cm.WpColorManagerV1EventDone {
75		t.Fatalf("capabilities %v %v %v %v", features, tf, prim, caps.events)
76	}
77	comp := bindProtocol(t, c, "wl_compositor")
78	wl := c.AllocateID()
79	requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, wl)
80	registerProtocol(t, c, wl)
81	surfaceID := c.AllocateID()
82	requestProtocol(t, c, manager, cm.WpColorManagerV1RequestGetSurface, surfaceID, wl)
83	registerProtocol(t, c, surfaceID)
84	creator := c.AllocateID()
85	requestProtocol(t, c, manager, cm.WpColorManagerV1RequestCreateParametricCreator, creator)
86	registerProtocol(t, c, creator)
87	requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetPrimariesNamed, bt2020)
88	requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetTfNamed, pq)
89	requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetMaxCll, uint32(1000))
90	requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetMaxFall, uint32(400))
91	image := c.AllocateID()
92	requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestCreate, image)
93	ready := watchColor(c, image)
94	if err := c.Roundtrip(); err != nil {
95		t.Fatal(err)
96	}
97	if len(ready.events) != 1 || uint32(ready.events[0]) != cm.WpImageDescriptionV1EventReady2 {
98		t.Fatalf("ready %v", ready.events)
99	}
100	requestProtocol(t, c, surfaceID, cm.WpColorManagementSurfaceV1RequestSetImageDescription, image, uint32(0))
101	if err := c.Roundtrip(); err != nil {
102		t.Fatal(err)
103	}
104	var before SurfaceColor
105	s.display.Do(func() { before = s.surfaceColor(wl) })
106	if before.Set {
107		t.Fatal("color applied before commit")
108	}
109	requestProtocol(t, c, wl, wayland.SurfaceRequestCommit)
110	if err := c.Roundtrip(); err != nil {
111		t.Fatal(err)
112	}
113	s.display.Do(func() { before = s.surfaceColor(wl) })
114	if before != (SurfaceColor{true, pq, bt2020, 1000, 400}) {
115		t.Fatalf("committed color %+v", before)
116	}
117	var routed ports.SurfaceColor
118	s.display.Do(func() {
119		for res, surf := range s.surfaces {
120			if res.ID() == wl {
121				routed = surf.tree(1).Color
122			}
123		}
124	})
125	if routed != (ports.SurfaceColor{TF: ports.ColorTFPQ, Primaries: ports.ColorPrimariesBT2020, MaxCLL: 1000, MaxFALL: 400}) {
126		t.Fatalf("routed color %+v", routed)
127	}
128	requestProtocol(t, c, surfaceID, cm.WpColorManagementSurfaceV1RequestUnsetImageDescription)
129	requestProtocol(t, c, wl, wayland.SurfaceRequestCommit)
130	if err := c.Roundtrip(); err != nil {
131		t.Fatal(err)
132	}
133	s.display.Do(func() { before = s.surfaceColor(wl) })
134	if before.Set {
135		t.Fatal("unset not committed")
136	}
137}
138
139// v3 advertises windows_bt2100; its description is PQ BT.2020, applies to
140// a surface, and allows no get_information.
141func TestColorWindowsBT2100(t *testing.T) {
142	s, c, _ := colorServer(t)
143	manager := bindVersion(t, c, "wp_color_manager_v1", 3)
144	caps := watchColor(c, manager)
145	if err := c.Roundtrip(); err != nil {
146		t.Fatal(err)
147	}
148	var bt2100 bool
149	for i, op := range caps.events {
150		if uint32(op) == cm.WpColorManagerV1EventSupportedFeature && caps.values[i][0] == uint32(cm.WpColorManagerV1FeatureWindowsBt2100) {
151			bt2100 = true
152		}
153	}
154	if !bt2100 {
155		t.Fatalf("windows_bt2100 not advertised: %v", caps.values)
156	}
157	comp := bindProtocol(t, c, "wl_compositor")
158	wl := c.AllocateID()
159	requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, wl)
160	registerProtocol(t, c, wl)
161	surfaceID := c.AllocateID()
162	requestProtocol(t, c, manager, cm.WpColorManagerV1RequestGetSurface, surfaceID, wl)
163	registerProtocol(t, c, surfaceID)
164	image := c.AllocateID()
165	requestProtocol(t, c, manager, cm.WpColorManagerV1RequestCreateWindowsBt2100, image)
166	ready := watchColor(c, image)
167	if err := c.Roundtrip(); err != nil {
168		t.Fatal(err)
169	}
170	if len(ready.events) != 1 || uint32(ready.events[0]) != cm.WpImageDescriptionV1EventReady2 {
171		t.Fatalf("ready %v", ready.events)
172	}
173	requestProtocol(t, c, surfaceID, cm.WpColorManagementSurfaceV1RequestSetImageDescription, image, uint32(cm.WpColorManagerV1RenderIntentPerceptual))
174	requestProtocol(t, c, wl, wayland.SurfaceRequestCommit)
175	if err := c.Roundtrip(); err != nil {
176		t.Fatal(err)
177	}
178	var got SurfaceColor
179	s.display.Do(func() { got = s.surfaceColor(wl) })
180	if got != (SurfaceColor{Set: true, TF: pq, Primaries: bt2020}) {
181		t.Fatalf("committed color %+v", got)
182	}
183	info := c.AllocateID()
184	requestProtocol(t, c, image, cm.WpImageDescriptionV1RequestGetInformation, info)
185	if err := c.Roundtrip(); err == nil {
186		t.Fatal("get_information allowed")
187	}
188}
189
190func (s *Server) surfaceColor(id uint32) SurfaceColor {
191	for r, v := range s.surfaces {
192		if r.ID() == id {
193			return v.ColorDescription()
194		}
195	}
196	return SurfaceColor{}
197}
198func TestColorOutputAndFeedback(t *testing.T) {
199	s, c, _ := colorServer(t)
200	manager := bindVersion(t, c, "wp_color_manager_v1", 2)
201	registerProtocol(t, c, manager)
202	var name uint32
203	for id, g := range c.Registry().GetGlobals() {
204		if g.Interface == "wl_output" {
205			name = id
206		}
207	}
208	out, err := bindWireID(c, name, "wl_output", 4)
209	if err != nil {
210		t.Fatal(err)
211	}
212	registerProtocol(t, c, out)
213	obj := c.AllocateID()
214	requestProtocol(t, c, manager, cm.WpColorManagerV1RequestGetOutput, obj, out)
215	changed := watchColor(c, obj)
216	desc := c.AllocateID()
217	requestProtocol(t, c, obj, cm.WpColorManagementOutputV1RequestGetImageDescription, desc)
218	ready := watchColor(c, desc)
219	if err := c.Roundtrip(); err != nil {
220		t.Fatal(err)
221	}
222	if len(ready.events) != 1 || uint32(ready.events[0]) != cm.WpImageDescriptionV1EventReady2 {
223		t.Fatalf("output ready %v", ready.events)
224	}
225	oldID := uint64(ready.values[0][0])<<32 | uint64(ready.values[0][1])
226	if oldID == 0 {
227		t.Fatal("zero output identity")
228	}
229	info := c.AllocateID()
230	requestProtocol(t, c, desc, cm.WpImageDescriptionV1RequestGetInformation, info)
231	ev := watchColor(c, info)
232	if err := c.Roundtrip(); err != nil {
233		t.Fatal(err)
234	}
235	if len(ev.events) < 5 || ev.values[1][0] != srgb || ev.values[2][0] != uint32(cm.WpColorManagerV1TransferFunctionSrgb) {
236		t.Fatalf("SDR info %v %v", ev.events, ev.values)
237	}
238	comp := bindProtocol(t, c, "wl_compositor")
239	wl := c.AllocateID()
240	requestProtocol(t, c, comp, wayland.CompositorRequestCreateSurface, wl)
241	registerProtocol(t, c, wl)
242	feedback := c.AllocateID()
243	requestProtocol(t, c, manager, cm.WpColorManagerV1RequestGetSurfaceFeedback, feedback, wl)
244	pref := watchColor(c, feedback)
245	if err := c.Roundtrip(); err != nil {
246		t.Fatal(err)
247	}
248	preferredID := c.AllocateID()
249	requestProtocol(t, c, feedback, cm.WpColorManagementSurfaceFeedbackV1RequestGetPreferredParametric, preferredID)
250	preferred := watchColor(c, preferredID)
251	if err := c.Roundtrip(); err != nil {
252		t.Fatal(err)
253	}
254	if len(preferred.events) != 1 || uint32(preferred.events[0]) != cm.WpImageDescriptionV1EventReady2 {
255		t.Fatalf("preferred %v", preferred.events)
256	}
257	s.display.Do(func() {
258		s.setOutputHDR(ports.OutputFormats{Output: "HEADLESS-1", HDR: &ports.OutputHDR{MaxLuminance: 1000, MaxFrameAverage: 400, MinLuminance: .005}})
259	})
260	if err := c.Roundtrip(); err != nil {
261		t.Fatal(err)
262	}
263	// The format report is asynchronous to client requests.
264	if err := c.Roundtrip(); err != nil {
265		t.Fatal(err)
266	}
267	if len(changed.events) == 0 || len(pref.events) == 0 {
268		t.Fatalf("change events output=%v preferred=%v", changed.events, pref.events)
269	}
270	desc2 := c.AllocateID()
271	requestProtocol(t, c, obj, cm.WpColorManagementOutputV1RequestGetImageDescription, desc2)
272	ready2 := watchColor(c, desc2)
273	if err := c.Roundtrip(); err != nil {
274		t.Fatal(err)
275	}
276	newID := uint64(ready2.values[0][0])<<32 | uint64(ready2.values[0][1])
277	if newID == 0 || newID == oldID {
278		t.Fatalf("identities before=%d after=%d", oldID, newID)
279	}
280	if len(pref.values) == 0 || uint64(pref.values[len(pref.values)-1][0])<<32|uint64(pref.values[len(pref.values)-1][1]) != newID {
281		t.Fatalf("preferred identity mismatch: %v, ready=%d", pref.values, newID)
282	}
283	info2 := c.AllocateID()
284	requestProtocol(t, c, desc2, cm.WpImageDescriptionV1RequestGetInformation, info2)
285	hdr := watchColor(c, info2)
286	if err := c.Roundtrip(); err != nil {
287		t.Fatal(err)
288	}
289	if len(hdr.values) < 8 || hdr.values[1][0] != bt2020 || hdr.values[3][0] != 50 || hdr.values[5][0] != 50 || hdr.values[5][1] != 1000 || hdr.values[6][0] != 1000 || hdr.values[7][0] != 400 {
290		t.Fatalf("HDR info %v %v", hdr.events, hdr.values)
291	}
292}
293func TestColorParametricCombinations(t *testing.T) {
294	for _, tc := range []struct {
295		name     string
296		prim, tf uint32
297		ready    bool
298	}{
299		{"sRGB gamma22", srgb, gamma22, false},
300		{"sRGB sRGB", srgb, uint32(cm.WpColorManagerV1TransferFunctionSrgb), true},
301		{"BT2020 PQ", bt2020, pq, true},
302		{"sRGB extended linear", srgb, extLinear, true},
303		{"BT2020 extended linear", bt2020, extLinear, false},
304		{"sRGB PQ", srgb, pq, false},
305		{"BT2020 gamma22", bt2020, gamma22, false},
306	} {
307		t.Run(tc.name, func(t *testing.T) {
308			_, c, _ := colorServer(t)
309			manager := bindVersion(t, c, "wp_color_manager_v1", 2)
310			registerProtocol(t, c, manager)
311			creator := c.AllocateID()
312			requestProtocol(t, c, manager, cm.WpColorManagerV1RequestCreateParametricCreator, creator)
313			registerProtocol(t, c, creator)
314			requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetPrimariesNamed, tc.prim)
315			requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetTfNamed, tc.tf)
316			image := c.AllocateID()
317			requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestCreate, image)
318			ev := watchColor(c, image)
319			if err := c.Roundtrip(); err != nil {
320				t.Fatal(err)
321			}
322			if len(ev.events) != 1 {
323				t.Fatalf("events %v", ev.events)
324			}
325			if tc.ready {
326				if uint32(ev.events[0]) != cm.WpImageDescriptionV1EventReady2 {
327					t.Fatalf("events %v", ev.events)
328				}
329			} else if uint32(ev.events[0]) != cm.WpImageDescriptionV1EventFailed || ev.values[0][0] != uint32(cm.WpImageDescriptionV1CauseUnsupported) {
330				t.Fatalf("failure events %v values %v", ev.events, ev.values)
331			}
332		})
333	}
334}
335
336func TestColorProtocolErrors(t *testing.T) {
337	for _, tc := range []struct {
338		name     string
339		op       uint32
340		args     []any
341		code     uint32
342		complete bool
343	}{
344		{"incomplete", cm.WpImageDescriptionCreatorParamsV1RequestCreate, []any{uint32(90)}, 0, false},
345		{"invalid tf", cm.WpImageDescriptionCreatorParamsV1RequestSetTfNamed, []any{uint32(99)}, 3, false},
346		{"invalid primaries", cm.WpImageDescriptionCreatorParamsV1RequestSetPrimariesNamed, []any{uint32(99)}, 4, false},
347		{"unsupported power", cm.WpImageDescriptionCreatorParamsV1RequestSetTfPower, []any{uint32(22000)}, 2, false},
348		{"unsupported primaries", cm.WpImageDescriptionCreatorParamsV1RequestSetPrimaries, []any{int32(1), int32(1), int32(1), int32(1), int32(1), int32(1), int32(1), int32(1)}, 2, false},
349		{"unsupported luminances", cm.WpImageDescriptionCreatorParamsV1RequestSetLuminances, []any{uint32(1), uint32(100), uint32(100)}, 2, false},
350		{"unsupported mastering primaries", cm.WpImageDescriptionCreatorParamsV1RequestSetMasteringDisplayPrimaries, []any{int32(1), int32(1), int32(1), int32(1), int32(1), int32(1), int32(1), int32(1)}, 2, false},
351		{"unsupported mastering luminance", cm.WpImageDescriptionCreatorParamsV1RequestSetMasteringLuminance, []any{uint32(1), uint32(100)}, 2, false},
352		{"already tf", cm.WpImageDescriptionCreatorParamsV1RequestSetTfNamed, []any{pq}, 1, true},
353		{"already primaries", cm.WpImageDescriptionCreatorParamsV1RequestSetPrimariesNamed, []any{bt2020}, 1, true},
354		{"max fall", cm.WpImageDescriptionCreatorParamsV1RequestCreate, []any{uint32(90)}, 5, true},
355	} {
356		t.Run(tc.name, func(t *testing.T) {
357			_, c, _ := colorServer(t)
358			manager := bindVersion(t, c, "wp_color_manager_v1", 2)
359			registerProtocol(t, c, manager)
360			creator := c.AllocateID()
361			requestProtocol(t, c, manager, cm.WpColorManagerV1RequestCreateParametricCreator, creator)
362			registerProtocol(t, c, creator)
363			if tc.complete {
364				requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetTfNamed, pq)
365				requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetPrimariesNamed, bt2020)
366				if tc.name == "max fall" {
367					requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetMaxCll, uint32(100))
368					requestProtocol(t, c, creator, cm.WpImageDescriptionCreatorParamsV1RequestSetMaxFall, uint32(200))
369				}
370			}
371			args := tc.args
372			if tc.op == cm.WpImageDescriptionCreatorParamsV1RequestCreate {
373				args = []any{c.AllocateID()}
374			}
375			requestProtocol(t, c, creator, tc.op, args...)
376			expectProtocolError(t, c, creator, tc.code)
377		})
378	}
379}

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