colormanagement.go RawHistoryFolder

1package wayland
2
3import (
4	"math"
5
6	cm "github.com/bnema/go-wayland-bindings/server/colormanagement"
7	"github.com/bnema/go-wayland-bindings/server/wayland"
8	"github.com/bnema/purego-libwayland/server"
9)
10
11// SurfaceColor is the committed color encoding of a client surface. Set is false
12// when no explicit description was committed; the default is compositor-defined.
13type SurfaceColor struct {
14	Set                            bool
15	TF, Primaries, MaxCLL, MaxFALL uint32
16}
17
18func (s *surface) ColorDescription() SurfaceColor { return s.color }
19
20type colorDescription struct {
21	SurfaceColor
22	min, max, white, targetMin, targetMax uint32
23	cllSet, fallSet                       bool
24	identity                              uint64
25}
26
27const (
28	srgb      = uint32(cm.WpColorManagerV1PrimariesSrgb)
29	bt2020    = uint32(cm.WpColorManagerV1PrimariesBt2020)
30	gamma22   = uint32(cm.WpColorManagerV1TransferFunctionGamma22)
31	pq        = uint32(cm.WpColorManagerV1TransferFunctionSt2084Pq)
32	extLinear = uint32(cm.WpColorManagerV1TransferFunctionExtLinear)
33)
34
35func (s *Server) nextColorID() uint64 { s.colorID++; return s.colorID }
36func (s *Server) outputColor(o *output) colorDescription {
37	d := colorDescription{SurfaceColor: SurfaceColor{Set: true, TF: uint32(cm.WpColorManagerV1TransferFunctionSrgb), Primaries: srgb}, min: 2000, max: 80, white: 80, targetMin: 2000, targetMax: 80}
38	if o != nil {
39		if h, ok := s.hdrOutputs[o.name()]; ok {
40			d.TF, d.Primaries, d.white = pq, bt2020, 203
41			d.min = uint32(math.Max(0, math.Round(h.MinLuminance*10000)))
42			d.max = d.min/10000 + 10000 // PQ primary volume is fixed by ST 2084.
43			d.targetMin, d.targetMax = d.min, uint32(math.Max(1, math.Round(h.MaxLuminance)))
44			d.MaxCLL = d.targetMax
45			d.MaxFALL = uint32(math.Max(0, math.Round(h.MaxFrameAverage)))
46			d.cllSet, d.fallSet = true, true
47		}
48	}
49	// Identity changes only when the description changes, not on each query.
50	if o != nil {
51		if s.colorIdentity == nil {
52			s.colorIdentity = map[string]uint64{}
53		}
54		d.identity = s.colorIdentity[o.name()]
55		if d.identity == 0 {
56			d.identity = s.nextColorID()
57			s.colorIdentity[o.name()] = d.identity
58		}
59	}
60	return d
61}
62func registerColorManagement(d *server.Display, s *Server) error {
63	return cm.NewWpColorManagerV1Global(d, 3, func(c server.Client, v, id uint32) {
64		r, err := cm.NewWpColorManagerV1(c, int32(v), id, colorManager{s})
65		if err != nil {
66			return
67		}
68		r.SendSupportedIntent(uint32(cm.WpColorManagerV1RenderIntentPerceptual))
69		r.SendSupportedFeature(uint32(cm.WpColorManagerV1FeatureParametric))
70		if v >= 3 {
71			r.SendSupportedFeature(uint32(cm.WpColorManagerV1FeatureWindowsBt2100))
72		}
73		for _, tf := range []uint32{pq, extLinear, uint32(cm.WpColorManagerV1TransferFunctionSrgb)} {
74			r.SendSupportedTfNamed(tf)
75		}
76
77		for _, p := range []uint32{srgb, bt2020} {
78			r.SendSupportedPrimariesNamed(p)
79		}
80		r.SendDone()
81	})
82}
83
84type colorManager struct{ s *Server }
85
86func (colorManager) Destroy(*cm.WpColorManagerV1) {}
87func (m colorManager) GetOutput(r *cm.WpColorManagerV1, id uint32, out *wayland.Output) {
88	h := &colorOutput{s: m.s, o: m.s.outputOf(out)}
89	res, err := cm.NewWpColorManagementOutputV1(r.Client(), r.Version(), id, h)
90	if err != nil {
91		return
92	}
93	h.res = res
94	m.s.colorOutputs[h] = struct{}{}
95	res.OnDestroy = func() { delete(m.s.colorOutputs, h) }
96}
97func (m colorManager) GetSurface(r *cm.WpColorManagerV1, id uint32, wl *wayland.Surface) {
98	var surf *surface
99	if wl != nil {
100		surf = m.s.surfaces[wl.Resource]
101	}
102	if surf != nil && surf.colorControl != nil {
103		r.PostError(uint32(cm.WpColorManagerV1ErrorSurfaceExists), "surface already has color management")
104		return
105	}
106	h := &colorSurface{surf: surf}
107	res, err := cm.NewWpColorManagementSurfaceV1(r.Client(), r.Version(), id, h)
108	if err != nil {
109		return
110	}
111	if surf != nil {
112		surf.colorControl = h
113		res.OnDestroy = func() {
114			if surf.colorControl == h {
115				surf.colorControl = nil
116				surf.next.color = SurfaceColor{}
117			}
118		}
119	}
120}
121func (m colorManager) GetSurfaceFeedback(r *cm.WpColorManagerV1, id uint32, wl *wayland.Surface) {
122	var surf *surface
123	if wl != nil {
124		surf = m.s.surfaces[wl.Resource]
125	}
126	h := &colorFeedback{s: m.s, surf: surf}
127	res, err := cm.NewWpColorManagementSurfaceFeedbackV1(r.Client(), r.Version(), id, h)
128	if err != nil {
129		return
130	}
131	h.res = res
132	m.s.colorFeedbacks[h] = struct{}{}
133	res.OnDestroy = func() { delete(m.s.colorFeedbacks, h) }
134}
135func (colorManager) CreateIccCreator(r *cm.WpColorManagerV1, _ uint32) {
136	r.PostError(uint32(cm.WpColorManagerV1ErrorUnsupportedFeature), "ICC unsupported")
137}
138func (m colorManager) CreateParametricCreator(r *cm.WpColorManagerV1, id uint32) {
139	_, _ = cm.NewWpImageDescriptionCreatorParamsV1(r.Client(), r.Version(), id, &colorParams{s: m.s})
140}
141func (colorManager) CreateWindowsScrgb(r *cm.WpColorManagerV1, _ uint32) {
142	r.PostError(uint32(cm.WpColorManagerV1ErrorUnsupportedFeature), "scRGB unsupported")
143}
144
145// CreateWindowsBt2100 is BT.2020 primaries with the PQ transfer function,
146// as Windows drives HDR screens (DXGI HDR10 swapchains through Wine or
147// DXVK). Its reference white is BT.2408's 203 cd/m²; it has no
148// get_information.
149func (m colorManager) CreateWindowsBt2100(r *cm.WpColorManagerV1, id uint32) {
150	d := colorDescription{SurfaceColor: SurfaceColor{TF: pq, Primaries: bt2020, Set: true}, min: 50, max: 10000, white: 203}
151	d.targetMin, d.targetMax = d.min, d.max
152	m.s.createColorDescription(r.Client(), r.Version(), id, d, 0, false)
153}
154func (colorManager) GetImageDescription(r *cm.WpColorManagerV1, _ uint32, _ *cm.WpImageDescriptionReferenceV1) {
155	r.PostError(uint32(cm.WpColorManagerV1ErrorUnsupportedFeature), "references unsupported")
156}
157
158type colorOutput struct {
159	s   *Server
160	o   *output
161	res *cm.WpColorManagementOutputV1
162}
163
164func (*colorOutput) Destroy(*cm.WpColorManagementOutputV1) {}
165func (h *colorOutput) GetImageDescription(r *cm.WpColorManagementOutputV1, id uint32) {
166	if h.o == nil || h.s.outputByNameExact(h.o.name()) != h.o {
167		h.s.newColorDescription(r.Client(), r.Version(), id, colorDescription{}, uint32(cm.WpImageDescriptionV1CauseNoOutput))
168		return
169	}
170	h.s.newColorDescription(r.Client(), r.Version(), id, h.s.outputColor(h.o), 0)
171}
172
173type colorSurface struct{ surf *surface }
174
175func (h *colorSurface) Destroy(*cm.WpColorManagementSurfaceV1) {
176	if h.surf != nil && !h.surf.destroyed {
177		h.surf.next.color = SurfaceColor{}
178	}
179}
180func (h *colorSurface) SetImageDescription(r *cm.WpColorManagementSurfaceV1, desc *cm.WpImageDescriptionV1, intent uint32) {
181	if h.surf == nil || h.surf.destroyed {
182		r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorInert), "surface destroyed")
183		return
184	}
185	if intent != 0 {
186		r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorRenderIntent), "unsupported intent")
187		return
188	}
189	if desc == nil {
190		r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorImageDescription), "missing description")
191		return
192	}
193	d, ok := h.surf.server.colorDescriptions[desc.Resource]
194	if !ok {
195		r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorImageDescription), "description not ready")
196		return
197	}
198	h.surf.next.color = d.SurfaceColor
199	h.surf.server.log.Debug().Str("component", "wayland").Uint32("tf", d.TF).Uint32("primaries", d.Primaries).Msg("surface color description set")
200}
201func (h *colorSurface) UnsetImageDescription(r *cm.WpColorManagementSurfaceV1) {
202	if h.surf == nil || h.surf.destroyed {
203		r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorInert), "surface destroyed")
204		return
205	}
206	h.surf.next.color = SurfaceColor{}
207	h.surf.server.log.Debug().Str("component", "wayland").Msg("surface color description unset")
208}
209
210type colorFeedback struct {
211	s    *Server
212	surf *surface
213	res  *cm.WpColorManagementSurfaceFeedbackV1
214	last *output
215}
216
217func (*colorFeedback) Destroy(*cm.WpColorManagementSurfaceFeedbackV1) {}
218func (h *colorFeedback) preferred(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
219	if h.surf == nil || h.surf.destroyed {
220		r.PostError(uint32(cm.WpColorManagementSurfaceFeedbackV1ErrorInert), "surface destroyed")
221		return
222	}
223	h.s.newColorDescription(r.Client(), r.Version(), id, h.s.outputColor(h.s.outputOfSurface(h.surf)), 0)
224}
225func (h *colorFeedback) GetPreferred(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
226	h.preferred(r, id)
227}
228func (h *colorFeedback) GetPreferredParametric(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
229	h.preferred(r, id)
230}
231func (s *Server) notifyColorFeedback() {
232	for h := range s.colorFeedbacks {
233		if h.surf == nil || h.surf.destroyed {
234			continue
235		}
236		o := s.outputOfSurface(h.surf)
237		if o != h.last {
238			h.last = o
239			h.changed(s.outputColor(o).identity)
240		}
241	}
242}
243func (h *colorFeedback) changed(id uint64) {
244	if h.res.Version() >= 2 {
245		h.res.SendPreferredChanged2(uint32(id>>32), uint32(id))
246	} else {
247		h.res.SendPreferredChanged(uint32(id))
248	}
249}
250func (s *Server) colorOutputChanged(name string) {
251	if s.outputByNameExact(name) == nil {
252		return
253	}
254	if s.colorIdentity == nil {
255		s.colorIdentity = map[string]uint64{}
256	}
257	s.colorIdentity[name] = s.nextColorID()
258	for h := range s.colorOutputs {
259		if h.o != nil && h.o.name() == name && s.outputByNameExact(name) == h.o {
260			h.res.SendImageDescriptionChanged()
261			for _, r := range h.o.resources {
262				if r.Client() == h.res.Client() && r.Version() >= 2 {
263					r.SendDone()
264				}
265			}
266		}
267	}
268	for h := range s.colorFeedbacks {
269		if h.surf != nil && !h.surf.destroyed {
270			if o := s.outputOfSurface(h.surf); o != nil && o.name() == name {
271				h.changed(s.colorIdentity[name])
272			}
273		}
274	}
275}
276func (s *Server) newColorDescription(c server.Client, v int32, id uint32, d colorDescription, failure uint32) {
277	s.createColorDescription(c, v, id, d, failure, true)
278}
279func (s *Server) createColorDescription(c server.Client, v int32, id uint32, d colorDescription, failure uint32, information bool) {
280	h := &colorImage{s: s, d: d, ready: failure == 0, information: information}
281	res, err := cm.NewWpImageDescriptionV1(c, v, id, h)
282	if err != nil {
283		return
284	}
285	if failure != 0 {
286		msg := "output unavailable"
287		if failure == uint32(cm.WpImageDescriptionV1CauseUnsupported) {
288			msg = "unsupported transfer function and primaries combination"
289		}
290		res.SendFailed(failure, msg)
291		return
292	}
293	if d.identity == 0 {
294		d.identity = s.nextColorID()
295		h.d.identity = d.identity
296	}
297	s.colorDescriptions[res.Resource] = d
298	res.OnDestroy = func() { delete(s.colorDescriptions, res.Resource) }
299	if v >= 2 {
300		res.SendReady2(uint32(d.identity>>32), uint32(d.identity))
301	} else {
302		res.SendReady(uint32(d.identity))
303	}
304}
305
306type colorImage struct {
307	s           *Server
308	d           colorDescription
309	ready       bool
310	information bool
311}
312
313func (*colorImage) Destroy(*cm.WpImageDescriptionV1) {}
314func (h *colorImage) GetInformation(r *cm.WpImageDescriptionV1, id uint32) {
315	if !h.ready {
316		r.PostError(uint32(cm.WpImageDescriptionV1ErrorNotReady), "description not ready")
317		return
318	}
319	if !h.information {
320		r.PostError(uint32(cm.WpImageDescriptionV1ErrorNoInformation), "information not allowed")
321		return
322	}
323	info, err := cm.NewWpImageDescriptionInfoV1(r.Client(), r.Version(), id, colorInfo{})
324	if err != nil {
325		return
326	}
327	d := h.d
328	coords := srgbCoords
329	if d.Primaries == bt2020 {
330		coords = bt2020Coords
331	}
332	info.SendPrimaries(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6], coords[7])
333	info.SendPrimariesNamed(d.Primaries)
334	info.SendTfNamed(d.TF)
335	if d.min != 0 || d.max != 0 {
336		info.SendLuminances(d.min, d.max, d.white)
337	}
338	info.SendTargetPrimaries(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6], coords[7])
339	info.SendTargetLuminance(d.targetMin, d.targetMax)
340	if d.cllSet {
341		info.SendTargetMaxCll(d.MaxCLL)
342	}
343	if d.fallSet {
344		info.SendTargetMaxFall(d.MaxFALL)
345	}
346	info.SendDone()
347	info.Destroy()
348}
349
350type colorInfo struct{}
351
352func (colorInfo) Destroy(*cm.WpImageDescriptionInfoV1) {}
353
354var srgbCoords = [8]int32{640000, 330000, 300000, 600000, 150000, 60000, 312700, 329000}
355var bt2020Coords = [8]int32{708000, 292000, 170000, 797000, 131000, 46000, 312700, 329000}
356
357type colorParams struct {
358	s              *Server
359	d              colorDescription
360	tfSet, primSet bool
361}
362
363func (h *colorParams) fail(r *cm.WpImageDescriptionCreatorParamsV1, code cm.WpImageDescriptionCreatorParamsV1Error, msg string) {
364	r.PostError(uint32(code), msg)
365}
366func (h *colorParams) Create(r *cm.WpImageDescriptionCreatorParamsV1, id uint32) {
367	if !h.tfSet || !h.primSet {
368		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorIncompleteSet, "transfer and primaries required")
369		return
370	}
371	if h.d.cllSet && h.d.fallSet && h.d.MaxFALL > h.d.MaxCLL {
372		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidLuminance, "MaxFALL exceeds MaxCLL")
373		return
374	}
375	if r.Version() == 1 {
376		for _, x := range []struct {
377			set   bool
378			value uint32
379		}{{h.d.cllSet, h.d.MaxCLL}, {h.d.fallSet, h.d.MaxFALL}} {
380			if x.set && (uint64(x.value)*10000 <= uint64(h.d.targetMin) || x.value > h.d.targetMax) {
381				h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidLuminance, "content luminance outside mastering range")
382				return
383			}
384		}
385	}
386	// Advertised transfer functions and primaries do not imply that every
387	// cross-product can be rendered. Reject unsupported combinations as an
388	// image-description failure, not a protocol error.
389	validSDR := h.d.Primaries == srgb && (h.d.TF == extLinear || h.d.TF == uint32(cm.WpColorManagerV1TransferFunctionSrgb))
390	if !validSDR && !(h.d.Primaries == bt2020 && h.d.TF == pq) {
391		h.s.createColorDescription(r.Client(), r.Version(), id, h.d, uint32(cm.WpImageDescriptionV1CauseUnsupported), false)
392		return
393	}
394	h.s.createColorDescription(r.Client(), r.Version(), id, h.d, 0, false)
395}
396func (h *colorParams) SetTfNamed(r *cm.WpImageDescriptionCreatorParamsV1, tf uint32) {
397	if h.tfSet {
398		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorAlreadySet, "TF already set")
399		return
400	}
401	if tf == gamma22 {
402		h.tfSet = true
403		h.d.TF = tf
404		h.d.Set = true
405		return // Create reports failed(unsupported) for unsupported named TFs.
406	}
407	if tf != pq && tf != extLinear && tf != uint32(cm.WpColorManagerV1TransferFunctionSrgb) {
408		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidTf, "unsupported TF")
409		return
410	}
411	h.tfSet = true
412	h.d.TF = tf
413	h.d.Set = true
414	if tf == pq {
415		h.d.min, h.d.max, h.d.white = 50, 10000, 203
416	} else if tf == extLinear {
417		// Protocol defaults: sRGB primary volume, 80 cd/m² reference white.
418		h.d.min, h.d.max, h.d.white = 2000, 80, 80
419	} else {
420		h.d.min, h.d.max, h.d.white = 100, 100, 100
421	}
422	h.d.targetMin, h.d.targetMax = h.d.min, h.d.max
423}
424func (h *colorParams) SetTfPower(r *cm.WpImageDescriptionCreatorParamsV1, _ uint32) {
425	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "TF power unsupported")
426}
427func (h *colorParams) SetPrimariesNamed(r *cm.WpImageDescriptionCreatorParamsV1, p uint32) {
428	if h.primSet {
429		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorAlreadySet, "primaries already set")
430		return
431	}
432	if p != srgb && p != bt2020 {
433		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidPrimariesNamed, "unsupported primaries")
434		return
435	}
436	h.primSet = true
437	h.d.Primaries = p
438}
439func (h *colorParams) SetPrimaries(r *cm.WpImageDescriptionCreatorParamsV1, _, _, _, _, _, _, _, _ int32) {
440	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "custom primaries unsupported")
441}
442func (h *colorParams) SetLuminances(r *cm.WpImageDescriptionCreatorParamsV1, min, max, white uint32) {
443	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "custom luminances unsupported")
444}
445func (h *colorParams) SetMasteringDisplayPrimaries(r *cm.WpImageDescriptionCreatorParamsV1, _, _, _, _, _, _, _, _ int32) {
446	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "mastering primaries unsupported")
447}
448func (h *colorParams) SetMasteringLuminance(r *cm.WpImageDescriptionCreatorParamsV1, _, _ uint32) {
449	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "mastering luminance unsupported")
450}
451func (h *colorParams) SetMaxCll(_ *cm.WpImageDescriptionCreatorParamsV1, v uint32) {
452	h.d.MaxCLL = v
453	h.d.cllSet = true
454}
455func (h *colorParams) SetMaxFall(_ *cm.WpImageDescriptionCreatorParamsV1, v uint32) {
456	h.d.MaxFALL = v
457	h.d.fallSet = true
458}

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