1package wayland
2
3import (
4 "context"
5 "os"
6 "slices"
7
8 "github.com/bnema/go-wayland-bindings/server/drmlease"
9 "github.com/bnema/neferwl/internal/ports"
10 "github.com/bnema/purego-libwayland/server"
11 "golang.org/x/sys/unix"
12)
13
14type leaseDevice struct {
15 server *Server
16 card string
17 global *server.Global
18 fd *os.File // owned by the display after receiving a duplicated DRM fd
19 connectors map[string]ports.LeaseConnector
20 binds map[*drmlease.WpDrmLeaseDeviceV1]*leaseBind
21}
22type leaseBind struct {
23 device *leaseDevice
24 res *drmlease.WpDrmLeaseDeviceV1
25 connectors map[*server.Resource]string
26 known map[*server.Resource]string
27}
28type leaseRequestHandler struct {
29 bind *leaseBind
30 names []string
31 withdrawn bool
32}
33
34// DRM lessee IDs are allocated independently on each card.
35type leaseKey struct {
36 card string
37 id uint32
38}
39
40type leaseObject struct {
41 server *Server
42 res *drmlease.WpDrmLeaseV1
43 card string
44 names []string
45 id uint32
46 finished bool
47}
48type leaseConnectorHandler struct{}
49
50func (leaseConnectorHandler) Destroy(*drmlease.WpDrmLeaseConnectorV1) {}
51
52func (s *Server) forwardLeases(ctx context.Context) {
53 // Messages own their fds: close those still queued when the display stops.
54 defer func() {
55 for {
56 select {
57 case event, ok := <-s.channels.LeaseEvents:
58 if !ok {
59 return
60 }
61 ports.CloseLeaseFiles(event)
62 default:
63 return
64 }
65 }
66 }()
67 for {
68 select {
69 case <-ctx.Done():
70 return
71 case <-s.display.Stopped():
72 return
73 case event, ok := <-s.channels.LeaseEvents:
74 if !ok {
75 return
76 }
77 if !s.display.Do(func() { s.applyLeaseEvent(event) }) {
78 ports.CloseLeaseFiles(event)
79 return
80 }
81 }
82 }
83}
84func (s *Server) applyLeaseEvent(event ports.LeaseMessage) {
85 switch v := event.(type) {
86 case ports.LeaseConnectors:
87 s.updateLeaseConnectors(v)
88 case ports.LeaseReply:
89 obj := s.pendingLeases[v.ID]
90 card := s.pendingLeaseCards[v.ID]
91 delete(s.pendingLeases, v.ID)
92 delete(s.pendingLeaseCards, v.ID)
93 if obj == nil || !obj.res.Alive() || s.security != nil && s.security.Snapshot().Protected {
94 if v.FD != nil {
95 v.FD.Close()
96 }
97 if v.LeaseID != 0 && card != "" {
98 s.sendLeaseRequest(ports.LeaseRevoke{Card: card, LeaseID: v.LeaseID})
99 }
100 if obj != nil {
101 obj.finish()
102 }
103 return
104 }
105 if v.Err != nil || v.FD == nil {
106 if v.FD != nil {
107 v.FD.Close()
108 }
109 obj.finish()
110 return
111 }
112 obj.id = v.LeaseID
113 s.activeLeases[leaseKey{obj.card, v.LeaseID}] = obj
114 obj.res.SendLeaseFd(int(v.FD.Fd()))
115 v.FD.Close()
116 case ports.LeaseFinished:
117 if obj := s.activeLeases[leaseKey{v.Card, v.LeaseID}]; obj != nil {
118 obj.finish()
119 }
120 }
121}
122
123// sendLeaseRequest enqueues without blocking the display. One sender drains
124// this queue in order even when the backend channel is full.
125func (s *Server) sendLeaseRequest(event ports.LeaseMessage) {
126 if s.channels.LeaseRequests == nil {
127 return
128 }
129 s.leaseMu.Lock()
130 s.leaseOut = append(s.leaseOut, event)
131 s.leaseMu.Unlock()
132 select {
133 case s.leaseReady <- struct{}{}:
134 default:
135 }
136}
137func (s *Server) forwardLeaseRequests(ctx context.Context) {
138 for {
139 s.leaseMu.Lock()
140 if len(s.leaseOut) == 0 {
141 s.leaseMu.Unlock()
142 select {
143 case <-s.leaseReady:
144 continue
145 case <-ctx.Done():
146 return
147 case <-s.display.Stopped():
148 return
149 }
150 }
151 event := s.leaseOut[0]
152 s.leaseMu.Unlock()
153 select {
154 case s.channels.LeaseRequests <- event:
155 s.leaseMu.Lock()
156 s.leaseOut[0] = nil
157 s.leaseOut = s.leaseOut[1:]
158 s.leaseMu.Unlock()
159 case <-ctx.Done():
160 return
161 case <-s.display.Stopped():
162 return
163 }
164 }
165}
166func (s *Server) updateLeaseConnectors(msg ports.LeaseConnectors) {
167 // The display owns Device from here on.
168 copyFD := msg.Device
169 d := s.leaseDevices[msg.Card]
170 if d == nil {
171 if copyFD == nil || len(msg.Connectors) == 0 {
172 if copyFD != nil {
173 copyFD.Close()
174 }
175 return
176 }
177 d = &leaseDevice{server: s, card: msg.Card, fd: copyFD, connectors: map[string]ports.LeaseConnector{}, binds: map[*drmlease.WpDrmLeaseDeviceV1]*leaseBind{}}
178 g, err := s.display.AddGlobal(drmlease.WpDrmLeaseDeviceV1Interface, 1, func(c server.Client, v, id uint32) { d.bind(c, v, id) })
179 if err != nil {
180 copyFD.Close()
181 s.log.Warn().Err(err).Str("card", msg.Card).Msg("drm lease global")
182 return
183 }
184 d.global = g
185 s.leaseDevices[msg.Card] = d
186 }
187 if copyFD == nil || len(msg.Connectors) == 0 {
188 if copyFD != nil {
189 copyFD.Close()
190 }
191 d.global.Remove()
192 if d.fd != nil {
193 d.fd.Close()
194 d.fd = nil
195 }
196 // Bound devices stay until the client sends release, as the
197 // protocol asks after global_remove; they offer nothing meanwhile.
198 for _, b := range d.binds {
199 for r := range b.connectors {
200 drmlease.WrapWpDrmLeaseConnectorV1(r).SendWithdrawn()
201 delete(b.connectors, r)
202 }
203 b.res.SendDone()
204 }
205 delete(s.leaseDevices, msg.Card)
206 return
207 }
208 if d.fd != copyFD {
209 if d.fd != nil {
210 d.fd.Close()
211 }
212 d.fd = copyFD
213 }
214 next := map[string]ports.LeaseConnector{}
215 for _, c := range msg.Connectors {
216 next[c.Name] = c
217 }
218 for _, b := range d.binds {
219 changed := false
220 for r, name := range b.connectors {
221 if _, ok := next[name]; !ok && r.Alive() {
222 drmlease.WrapWpDrmLeaseConnectorV1(r).SendWithdrawn()
223 delete(b.connectors, r)
224 changed = true
225 }
226 }
227 for name, c := range next {
228 found := false
229 for _, n := range b.connectors {
230 if n == name {
231 found = true
232 break
233 }
234 }
235 if !found {
236 b.add(c)
237 changed = true
238 }
239 }
240 if changed {
241 b.res.SendDone()
242 }
243 }
244 d.connectors = next
245}
246func (d *leaseDevice) bind(c server.Client, v, id uint32) {
247 b := &leaseBind{device: d, connectors: map[*server.Resource]string{}, known: map[*server.Resource]string{}}
248 r, err := drmlease.NewWpDrmLeaseDeviceV1(c, int32(v), id, b)
249 if err != nil {
250 return
251 }
252 b.res = r
253 d.binds[r] = b
254 r.OnDestroy = func() { delete(d.binds, r) }
255 if d.fd != nil {
256 fd, err := unix.FcntlInt(d.fd.Fd(), unix.F_DUPFD_CLOEXEC, 0)
257 if err == nil {
258 r.SendDrmFd(fd)
259 unix.Close(fd)
260 } else {
261 d.server.log.Warn().Err(err).Str("card", d.card).Msg("lease drm fd")
262 r.SendReleased()
263 r.Destroy()
264 return
265 }
266 }
267 for _, c := range d.connectors {
268 b.add(c)
269 }
270 r.SendDone()
271}
272func (b *leaseBind) add(c ports.LeaseConnector) {
273 r, err := drmlease.NewWpDrmLeaseConnectorV1(b.res.Client(), 1, 0, leaseConnectorHandler{})
274 if err != nil {
275 return
276 }
277 b.connectors[r.Resource] = c.Name
278 b.known[r.Resource] = c.Name
279 r.OnDestroy = func() { delete(b.connectors, r.Resource); delete(b.known, r.Resource) }
280 b.res.SendConnector(r)
281 r.SendName(c.Name)
282 r.SendDescription(c.Description)
283 r.SendConnectorId(c.ConnectorID)
284 r.SendDone()
285}
286func (b *leaseBind) CreateLeaseRequest(_ *drmlease.WpDrmLeaseDeviceV1, id uint32) {
287 _, _ = drmlease.NewWpDrmLeaseRequestV1(b.res.Client(), 1, id, &leaseRequestHandler{bind: b})
288}
289func (b *leaseBind) Release(self *drmlease.WpDrmLeaseDeviceV1) { self.SendReleased(); self.Destroy() }
290func (r *leaseRequestHandler) RequestConnector(self *drmlease.WpDrmLeaseRequestV1, c *drmlease.WpDrmLeaseConnectorV1) {
291 if c == nil {
292 self.PostError(uint32(drmlease.WpDrmLeaseRequestV1ErrorWrongDevice), "connector belongs to another device")
293 return
294 }
295 name, ok := r.bind.known[c.Resource]
296 if !ok {
297 self.PostError(uint32(drmlease.WpDrmLeaseRequestV1ErrorWrongDevice), "connector belongs to another device")
298 return
299 }
300 if _, available := r.bind.connectors[c.Resource]; !available {
301 r.withdrawn = true
302 }
303 if slices.Contains(r.names, name) {
304 self.PostError(uint32(drmlease.WpDrmLeaseRequestV1ErrorDuplicateConnector), "duplicate connector")
305 return
306 }
307 r.names = append(r.names, name)
308}
309func (r *leaseRequestHandler) Submit(self *drmlease.WpDrmLeaseRequestV1, id uint32) {
310 if len(r.names) == 0 {
311 self.PostError(uint32(drmlease.WpDrmLeaseRequestV1ErrorEmptyLease), "empty lease")
312 return
313 }
314 s := r.bind.device.server
315 obj := &leaseObject{server: s, card: r.bind.device.card, names: append([]string(nil), r.names...)}
316 res, err := drmlease.NewWpDrmLeaseV1(self.Client(), 1, id, obj)
317 if err != nil {
318 return
319 }
320 obj.res = res
321 res.OnDestroy = func() { obj.destroy() }
322 if s.security != nil && s.security.Snapshot().Protected {
323 obj.finish()
324 return
325 }
326 for _, name := range r.names {
327 if _, ok := r.bind.device.connectors[name]; !ok || r.withdrawn {
328 obj.finish()
329 return
330 }
331 }
332 s.nextLeaseID++
333 s.pendingLeases[s.nextLeaseID] = obj
334 s.pendingLeaseCards[s.nextLeaseID] = obj.card
335 s.sendLeaseRequest(ports.LeaseRequest{ID: s.nextLeaseID, Card: obj.card, Connectors: obj.names})
336}
337func (o *leaseObject) Destroy(*drmlease.WpDrmLeaseV1) { o.destroy() }
338func (o *leaseObject) destroy() {
339 if o.finished {
340 return
341 }
342 o.finished = true
343 if o.id != 0 {
344 delete(o.server.activeLeases, leaseKey{o.card, o.id})
345 o.server.sendLeaseRequest(ports.LeaseRevoke{Card: o.card, LeaseID: o.id})
346 }
347 for id, p := range o.server.pendingLeases {
348 if p == o {
349 delete(o.server.pendingLeases, id)
350 // Keep pendingLeaseCards as a tombstone for a late successful
351 // reply; applyLeaseEvent revokes it and then removes the tombstone.
352 }
353 }
354}
355func (o *leaseObject) finish() {
356 if o.finished {
357 return
358 }
359 o.finished = true
360 if o.id != 0 {
361 delete(o.server.activeLeases, leaseKey{o.card, o.id})
362 }
363 if o.res.Alive() {
364 o.res.SendFinished()
365 }
366}