main 5d6fa26

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.Info().Uint64("surface", h.surf.identity).Uint64("id", uint64(h.surf.windowID())).Uint32("tf", d.TF).Uint32("primaries", d.Primaries).
200		Uint32("max_cll", d.MaxCLL).Uint32("max_fall", d.MaxFALL).Uint32("intent", intent).Msg("surface color description set")
201}
202func (h *colorSurface) UnsetImageDescription(r *cm.WpColorManagementSurfaceV1) {
203	if h.surf == nil || h.surf.destroyed {
204		r.PostError(uint32(cm.WpColorManagementSurfaceV1ErrorInert), "surface destroyed")
205		return
206	}
207	h.surf.next.color = SurfaceColor{}
208	h.surf.server.log.Info().Uint64("surface", h.surf.identity).Uint64("id", uint64(h.surf.windowID())).Msg("surface color description unset")
209}
210
211type colorFeedback struct {
212	s    *Server
213	surf *surface
214	res  *cm.WpColorManagementSurfaceFeedbackV1
215	last *output
216}
217
218func (*colorFeedback) Destroy(*cm.WpColorManagementSurfaceFeedbackV1) {}
219func (h *colorFeedback) preferred(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
220	if h.surf == nil || h.surf.destroyed {
221		r.PostError(uint32(cm.WpColorManagementSurfaceFeedbackV1ErrorInert), "surface destroyed")
222		return
223	}
224	h.s.newColorDescription(r.Client(), r.Version(), id, h.s.outputColor(h.s.outputOfSurface(h.surf)), 0)
225}
226func (h *colorFeedback) GetPreferred(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
227	h.preferred(r, id)
228}
229func (h *colorFeedback) GetPreferredParametric(r *cm.WpColorManagementSurfaceFeedbackV1, id uint32) {
230	h.preferred(r, id)
231}
232func (s *Server) notifyColorFeedback() {
233	for h := range s.colorFeedbacks {
234		if h.surf == nil || h.surf.destroyed {
235			continue
236		}
237		o := s.outputOfSurface(h.surf)
238		if o != h.last {
239			h.last = o
240			h.changed(s.outputColor(o).identity)
241		}
242	}
243}
244func (h *colorFeedback) changed(id uint64) {
245	if h.res.Version() >= 2 {
246		h.res.SendPreferredChanged2(uint32(id>>32), uint32(id))
247	} else {
248		h.res.SendPreferredChanged(uint32(id))
249	}
250}
251func (s *Server) colorOutputChanged(name string) {
252	if s.outputByNameExact(name) == nil {
253		return
254	}
255	if s.colorIdentity == nil {
256		s.colorIdentity = map[string]uint64{}
257	}
258	s.colorIdentity[name] = s.nextColorID()
259	for h := range s.colorOutputs {
260		if h.o != nil && h.o.name() == name && s.outputByNameExact(name) == h.o {
261			h.res.SendImageDescriptionChanged()
262			for _, r := range h.o.resources {
263				if r.Client() == h.res.Client() && r.Version() >= 2 {
264					r.SendDone()
265				}
266			}
267		}
268	}
269	for h := range s.colorFeedbacks {
270		if h.surf != nil && !h.surf.destroyed {
271			if o := s.outputOfSurface(h.surf); o != nil && o.name() == name {
272				h.changed(s.colorIdentity[name])
273			}
274		}
275	}
276}
277func (s *Server) newColorDescription(c server.Client, v int32, id uint32, d colorDescription, failure uint32) {
278	s.createColorDescription(c, v, id, d, failure, true)
279}
280func (s *Server) createColorDescription(c server.Client, v int32, id uint32, d colorDescription, failure uint32, information bool) {
281	h := &colorImage{s: s, d: d, ready: failure == 0, information: information}
282	res, err := cm.NewWpImageDescriptionV1(c, v, id, h)
283	if err != nil {
284		return
285	}
286	if failure != 0 {
287		msg := "output unavailable"
288		if failure == uint32(cm.WpImageDescriptionV1CauseUnsupported) {
289			msg = "unsupported transfer function and primaries combination"
290		}
291		res.SendFailed(failure, msg)
292		return
293	}
294	if d.identity == 0 {
295		d.identity = s.nextColorID()
296		h.d.identity = d.identity
297	}
298	s.colorDescriptions[res.Resource] = d
299	res.OnDestroy = func() { delete(s.colorDescriptions, res.Resource) }
300	if v >= 2 {
301		res.SendReady2(uint32(d.identity>>32), uint32(d.identity))
302	} else {
303		res.SendReady(uint32(d.identity))
304	}
305}
306
307type colorImage struct {
308	s           *Server
309	d           colorDescription
310	ready       bool
311	information bool
312}
313
314func (*colorImage) Destroy(*cm.WpImageDescriptionV1) {}
315func (h *colorImage) GetInformation(r *cm.WpImageDescriptionV1, id uint32) {
316	if !h.ready {
317		r.PostError(uint32(cm.WpImageDescriptionV1ErrorNotReady), "description not ready")
318		return
319	}
320	if !h.information {
321		r.PostError(uint32(cm.WpImageDescriptionV1ErrorNoInformation), "information not allowed")
322		return
323	}
324	info, err := cm.NewWpImageDescriptionInfoV1(r.Client(), r.Version(), id, colorInfo{})
325	if err != nil {
326		return
327	}
328	d := h.d
329	coords := srgbCoords
330	if d.Primaries == bt2020 {
331		coords = bt2020Coords
332	}
333	info.SendPrimaries(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6], coords[7])
334	info.SendPrimariesNamed(d.Primaries)
335	info.SendTfNamed(d.TF)
336	if d.min != 0 || d.max != 0 {
337		info.SendLuminances(d.min, d.max, d.white)
338	}
339	info.SendTargetPrimaries(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5], coords[6], coords[7])
340	info.SendTargetLuminance(d.targetMin, d.targetMax)
341	if d.cllSet {
342		info.SendTargetMaxCll(d.MaxCLL)
343	}
344	if d.fallSet {
345		info.SendTargetMaxFall(d.MaxFALL)
346	}
347	info.SendDone()
348	info.Destroy()
349}
350
351type colorInfo struct{}
352
353func (colorInfo) Destroy(*cm.WpImageDescriptionInfoV1) {}
354
355var srgbCoords = [8]int32{640000, 330000, 300000, 600000, 150000, 60000, 312700, 329000}
356var bt2020Coords = [8]int32{708000, 292000, 170000, 797000, 131000, 46000, 312700, 329000}
357
358type colorParams struct {
359	s              *Server
360	d              colorDescription
361	tfSet, primSet bool
362}
363
364func (h *colorParams) fail(r *cm.WpImageDescriptionCreatorParamsV1, code cm.WpImageDescriptionCreatorParamsV1Error, msg string) {
365	r.PostError(uint32(code), msg)
366}
367func (h *colorParams) Create(r *cm.WpImageDescriptionCreatorParamsV1, id uint32) {
368	if !h.tfSet || !h.primSet {
369		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorIncompleteSet, "transfer and primaries required")
370		return
371	}
372	if h.d.cllSet && h.d.fallSet && h.d.MaxFALL > h.d.MaxCLL {
373		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidLuminance, "MaxFALL exceeds MaxCLL")
374		return
375	}
376	if r.Version() == 1 {
377		for _, x := range []struct {
378			set   bool
379			value uint32
380		}{{h.d.cllSet, h.d.MaxCLL}, {h.d.fallSet, h.d.MaxFALL}} {
381			if x.set && (uint64(x.value)*10000 <= uint64(h.d.targetMin) || x.value > h.d.targetMax) {
382				h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidLuminance, "content luminance outside mastering range")
383				return
384			}
385		}
386	}
387	// Advertised transfer functions and primaries do not imply that every
388	// cross-product can be rendered. Reject unsupported combinations as an
389	// image-description failure, not a protocol error.
390	validSDR := h.d.Primaries == srgb && (h.d.TF == extLinear || h.d.TF == uint32(cm.WpColorManagerV1TransferFunctionSrgb))
391	if !validSDR && !(h.d.Primaries == bt2020 && h.d.TF == pq) {
392		h.s.createColorDescription(r.Client(), r.Version(), id, h.d, uint32(cm.WpImageDescriptionV1CauseUnsupported), false)
393		return
394	}
395	h.s.createColorDescription(r.Client(), r.Version(), id, h.d, 0, false)
396}
397func (h *colorParams) SetTfNamed(r *cm.WpImageDescriptionCreatorParamsV1, tf uint32) {
398	if h.tfSet {
399		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorAlreadySet, "TF already set")
400		return
401	}
402	if tf == gamma22 {
403		h.tfSet = true
404		h.d.TF = tf
405		h.d.Set = true
406		return // Create reports failed(unsupported) for unsupported named TFs.
407	}
408	if tf != pq && tf != extLinear && tf != uint32(cm.WpColorManagerV1TransferFunctionSrgb) {
409		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidTf, "unsupported TF")
410		return
411	}
412	h.tfSet = true
413	h.d.TF = tf
414	h.d.Set = true
415	if tf == pq {
416		h.d.min, h.d.max, h.d.white = 50, 10000, 203
417	} else if tf == extLinear {
418		// Protocol defaults: sRGB primary volume, 80 cd/m² reference white.
419		h.d.min, h.d.max, h.d.white = 2000, 80, 80
420	} else {
421		h.d.min, h.d.max, h.d.white = 100, 100, 100
422	}
423	h.d.targetMin, h.d.targetMax = h.d.min, h.d.max
424}
425func (h *colorParams) SetTfPower(r *cm.WpImageDescriptionCreatorParamsV1, _ uint32) {
426	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "TF power unsupported")
427}
428func (h *colorParams) SetPrimariesNamed(r *cm.WpImageDescriptionCreatorParamsV1, p uint32) {
429	if h.primSet {
430		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorAlreadySet, "primaries already set")
431		return
432	}
433	if p != srgb && p != bt2020 {
434		h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorInvalidPrimariesNamed, "unsupported primaries")
435		return
436	}
437	h.primSet = true
438	h.d.Primaries = p
439}
440func (h *colorParams) SetPrimaries(r *cm.WpImageDescriptionCreatorParamsV1, _, _, _, _, _, _, _, _ int32) {
441	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "custom primaries unsupported")
442}
443func (h *colorParams) SetLuminances(r *cm.WpImageDescriptionCreatorParamsV1, min, max, white uint32) {
444	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "custom luminances unsupported")
445}
446func (h *colorParams) SetMasteringDisplayPrimaries(r *cm.WpImageDescriptionCreatorParamsV1, _, _, _, _, _, _, _, _ int32) {
447	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "mastering primaries unsupported")
448}
449func (h *colorParams) SetMasteringLuminance(r *cm.WpImageDescriptionCreatorParamsV1, _, _ uint32) {
450	h.fail(r, cm.WpImageDescriptionCreatorParamsV1ErrorUnsupportedFeature, "mastering luminance unsupported")
451}
452func (h *colorParams) SetMaxCll(_ *cm.WpImageDescriptionCreatorParamsV1, v uint32) {
453	h.d.MaxCLL = v
454	h.d.cllSet = true
455}
456func (h *colorParams) SetMaxFall(_ *cm.WpImageDescriptionCreatorParamsV1, v uint32) {
457	h.d.MaxFALL = v
458	h.d.fallSet = true
459}

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