1package wayland
2
3import (
4 "image"
5 "math"
6
7 wlr "github.com/bnema/go-wayland-bindings/server/wlroutputmanagement"
8 "github.com/bnema/neferwl/internal/ports"
9 "github.com/bnema/purego-libwayland/server"
10)
11
12type outputConfiguration struct {
13 manager *outputManager
14 res *wlr.ZwlrOutputConfigurationV1
15 heads map[string]*headConfiguration
16 used, cancelled bool
17 serial uint32
18}
19type headConfiguration struct {
20 parent *outputConfiguration
21 res *wlr.ZwlrOutputConfigurationHeadV1
22 change ports.HeadChange
23 unsupported bool
24 modeSet, positionSet, transformSet, scaleSet, syncSet bool
25}
26
27func (c *outputConfiguration) head(r *wlr.ZwlrOutputHeadV1) (string, bool) {
28 if r == nil || !r.Resource.Alive() || r.Client() != c.res.Client() {
29 return "", false
30 }
31 for name, h := range c.manager.heads {
32 if h.res.Resource == r.Resource {
33 return name, true
34 }
35 }
36 return "", false
37}
38func (c *outputConfiguration) add(r *wlr.ZwlrOutputConfigurationV1, head *wlr.ZwlrOutputHeadV1, id uint32, enabled bool) {
39 if c.used {
40 r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorAlreadyUsed), "configuration already used")
41 return
42 }
43 name, ok := c.head(head)
44 if !ok {
45 r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorAlreadyConfiguredHead), "unknown head")
46 return
47 }
48 if _, exists := c.heads[name]; exists {
49 r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorAlreadyConfiguredHead), "head configured twice")
50 return
51 }
52 h := &headConfiguration{parent: c, change: ports.HeadChange{Name: name, Enabled: enabled}}
53 c.heads[name] = h
54 if enabled {
55 res, err := wlr.NewZwlrOutputConfigurationHeadV1(r.Client(), r.Version(), id, h)
56 if err != nil {
57 delete(c.heads, name)
58 return
59 }
60 h.res = res
61 res.OnDestroy = func() { h.res = nil }
62 }
63}
64func (c *outputConfiguration) EnableHead(r *wlr.ZwlrOutputConfigurationV1, id uint32, head *wlr.ZwlrOutputHeadV1) {
65 c.add(r, head, id, true)
66}
67func (c *outputConfiguration) DisableHead(r *wlr.ZwlrOutputConfigurationV1, head *wlr.ZwlrOutputHeadV1) {
68 c.add(r, head, 0, false)
69}
70func (c *outputConfiguration) Destroy(*wlr.ZwlrOutputConfigurationV1) { c.used = true }
71func (c *outputConfiguration) Test(r *wlr.ZwlrOutputConfigurationV1) { c.submit(r, true) }
72func (c *outputConfiguration) Apply(r *wlr.ZwlrOutputConfigurationV1) { c.submit(r, false) }
73func (c *outputConfiguration) submit(r *wlr.ZwlrOutputConfigurationV1, test bool) {
74 if c.used {
75 r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorAlreadyUsed), "configuration already used")
76 return
77 }
78 c.used = true
79 if c.manager.s.managementSerial != c.serial {
80 c.cancelled = true
81 r.SendCancelled()
82 return
83 }
84 if len(c.heads) != len(c.manager.heads) {
85 r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorUnconfiguredHead), "not all heads configured")
86 return
87 }
88 request := ports.OutputApply{ID: c.manager.s.nextOutputApply, Test: test}
89 c.manager.s.nextOutputApply++
90 for _, head := range c.manager.s.outputHeads.Heads {
91 change, ok := c.heads[head.Info.Name]
92 if !ok {
93 r.PostError(uint32(wlr.ZwlrOutputConfigurationV1ErrorUnconfiguredHead), "not all heads configured")
94 return
95 }
96 if change.unsupported {
97 r.SendFailed()
98 return
99 }
100 request.Heads = append(request.Heads, change.change)
101 }
102 if c.manager.s.channels.OutputApply == nil {
103 r.SendFailed()
104 return
105 }
106 c.manager.s.outputReplies[request.ID] = c
107 select {
108 case c.manager.s.channels.OutputApply <- request:
109 default:
110 delete(c.manager.s.outputReplies, request.ID)
111 r.SendFailed()
112 }
113}
114func (h *headConfiguration) once(field *bool, r *wlr.ZwlrOutputConfigurationHeadV1) bool {
115 if *field {
116 r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorAlreadySet), "property already set")
117 return false
118 }
119 *field = true
120 return true
121}
122func (h *headConfiguration) SetMode(r *wlr.ZwlrOutputConfigurationHeadV1, mode *wlr.ZwlrOutputModeV1) {
123 if !h.once(&h.modeSet, r) {
124 return
125 }
126 head := h.parent.manager.heads[h.change.Name]
127 if head == nil || mode == nil || !mode.Resource.Alive() {
128 r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidMode), "invalid mode")
129 return
130 }
131 m, ok := head.modes[mode.Resource]
132 if !ok {
133 r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidMode), "invalid mode")
134 return
135 }
136 h.change.Mode = &m
137}
138func (h *headConfiguration) SetCustomMode(r *wlr.ZwlrOutputConfigurationHeadV1, w, height, refresh int32) {
139 if !h.once(&h.modeSet, r) {
140 return
141 }
142 if w <= 0 || height <= 0 || refresh < 0 {
143 r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidCustomMode), "invalid custom mode")
144 return
145 }
146 h.unsupported = true
147 h.change.Mode = &ports.OutputMode{Width: int(w), Height: int(height), RefreshMilli: int(refresh)}
148}
149func (h *headConfiguration) SetPosition(r *wlr.ZwlrOutputConfigurationHeadV1, x, y int32) {
150 if h.once(&h.positionSet, r) {
151 h.change.Pos = &image.Point{X: int(x), Y: int(y)}
152 }
153}
154func (h *headConfiguration) SetTransform(r *wlr.ZwlrOutputConfigurationHeadV1, v int32) {
155 if !h.once(&h.transformSet, r) {
156 return
157 }
158 if v < 0 || v > 7 {
159 r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidTransform), "invalid transform")
160 return
161 }
162 t := ports.BufferTransform(v)
163 h.change.Transform = &t
164}
165func (h *headConfiguration) SetScale(r *wlr.ZwlrOutputConfigurationHeadV1, v server.Fixed) {
166 if !h.once(&h.scaleSet, r) {
167 return
168 }
169 scale := v.Float()
170 if scale <= 0 || math.IsNaN(scale) {
171 r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidScale), "invalid scale")
172 return
173 }
174 h.change.Scale = scale
175}
176func (h *headConfiguration) SetAdaptiveSync(r *wlr.ZwlrOutputConfigurationHeadV1, v uint32) {
177 if !h.once(&h.syncSet, r) {
178 return
179 }
180 if v > 1 {
181 r.PostError(uint32(wlr.ZwlrOutputConfigurationHeadV1ErrorInvalidAdaptiveSyncState), "invalid adaptive sync")
182 return
183 }
184 h.unsupported = true
185}