v0.4.0 b6be3a8

drmlease.go RawHistoryFolder

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}

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