main f2d166b

fifo.go RawHistoryFolder

1package wayland
2
3import (
4	"sync"
5	"time"
6
7	"github.com/bnema/go-wayland-bindings/server/committiming"
8	"github.com/bnema/go-wayland-bindings/server/contenttype"
9	"github.com/bnema/go-wayland-bindings/server/fifo"
10	"github.com/bnema/go-wayland-bindings/server/wayland"
11	"github.com/bnema/neferwl/internal/ports"
12	"github.com/bnema/purego-libwayland/server"
13	"golang.org/x/sys/unix"
14)
15
16// Presentation constraints on content updates (games, Vulkan FIFO):
17//
18//   - wp_fifo_v1: a commit with wait_barrier waits while the barrier set
19//     by an earlier commit stands; the barrier clears at the output's
20//     next refresh (its flip, or a deadline when it does not flip).
21//   - wp_commit_timing_v1: a commit waits until the refresh that shows it
22//     is not before its timestamp (CLOCK_MONOTONIC).
23//
24// Every commit captures its pending state into an update; the pacer (frames.go)
25// applies queued updates in order when they are ready. Requests arriving after
26// capture stay in live pending state. wp_content_type_v1 is logged only.
27
28func registerPresentationConstraints(d *server.Display, s *Server) error {
29	if err := fifo.NewWpFifoManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
30		_, _ = fifo.NewWpFifoManagerV1(c, int32(v), id, fifoManager{s})
31	}); err != nil {
32		return err
33	}
34	if err := committiming.NewWpCommitTimingManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
35		_, _ = committiming.NewWpCommitTimingManagerV1(c, int32(v), id, timingManager{s})
36	}); err != nil {
37		return err
38	}
39	return contenttype.NewWpContentTypeManagerV1Global(d, 1, func(c server.Client, v, id uint32) {
40		_, _ = contenttype.NewWpContentTypeManagerV1(c, int32(v), id, contentTypeManager{s})
41	})
42}
43
44// fifo_v1
45
46type fifoManager struct{ server *Server }
47
48func (fifoManager) Destroy(*fifo.WpFifoManagerV1) {}
49
50func (m fifoManager) GetFifo(r *fifo.WpFifoManagerV1, id uint32, surf *wayland.Surface) {
51	state := m.server.surfaceOf(surf)
52	if state != nil && state.fifo != nil {
53		r.PostError(uint32(fifo.WpFifoManagerV1ErrorAlreadyExists), "surface already has a fifo")
54		return
55	}
56	h := &fifoHandler{surface: state}
57	res, err := fifo.NewWpFifoV1(r.Client(), r.Version(), id, h)
58	if err != nil || state == nil {
59		return
60	}
61	state.fifo = h
62	res.OnDestroy = func() {
63		if state.fifo == h {
64			state.fifo = nil
65		}
66	}
67}
68
69type fifoHandler struct{ surface *surface }
70
71func (h *fifoHandler) live(r *fifo.WpFifoV1) bool {
72	if h.surface == nil || h.surface.destroyed {
73		r.PostError(uint32(fifo.WpFifoV1ErrorSurfaceDestroyed), "surface destroyed")
74		return false
75	}
76	return true
77}
78func (h *fifoHandler) SetBarrier(r *fifo.WpFifoV1) {
79	if h.live(r) {
80		h.surface.next.barrier = true
81	}
82}
83func (h *fifoHandler) WaitBarrier(r *fifo.WpFifoV1) {
84	if h.live(r) {
85		h.surface.next.wait = true
86	}
87}
88func (*fifoHandler) Destroy(*fifo.WpFifoV1) {}
89
90// commit_timing_v1
91
92type timingManager struct{ server *Server }
93
94func (timingManager) Destroy(*committiming.WpCommitTimingManagerV1) {}
95
96func (m timingManager) GetTimer(r *committiming.WpCommitTimingManagerV1, id uint32, surf *wayland.Surface) {
97	state := m.server.surfaceOf(surf)
98	if state != nil && state.timer != nil {
99		r.PostError(uint32(committiming.WpCommitTimingManagerV1ErrorCommitTimerExists), "surface already has a commit timer")
100		return
101	}
102	h := &timerHandler{surface: state}
103	res, err := committiming.NewWpCommitTimerV1(r.Client(), r.Version(), id, h)
104	if err != nil || state == nil {
105		return
106	}
107	state.timer = h
108	res.OnDestroy = func() {
109		if state.timer == h {
110			state.timer = nil
111		}
112	}
113}
114
115type timerHandler struct{ surface *surface }
116
117func (h *timerHandler) SetTimestamp(r *committiming.WpCommitTimerV1, secHi, secLo, nsec uint32) {
118	switch {
119	case h.surface == nil || h.surface.destroyed:
120		r.PostError(uint32(committiming.WpCommitTimerV1ErrorSurfaceDestroyed), "surface destroyed")
121	case nsec >= 1e9:
122		r.PostError(uint32(committiming.WpCommitTimerV1ErrorInvalidTimestamp), "tv_nsec out of range")
123	case !h.surface.next.at.IsZero():
124		r.PostError(uint32(committiming.WpCommitTimerV1ErrorTimestampExists), "timestamp already set")
125	default:
126		h.surface.next.at = monotonicTime(uint64(secHi)<<32|uint64(secLo), int64(nsec))
127	}
128}
129func (*timerHandler) Destroy(*committiming.WpCommitTimerV1) {}
130
131// maxTimestampAhead bounds how far a commit timestamp can hold a commit;
132// a later one applies early (a deviation from "not before").
133const maxTimestampAhead = time.Second
134
135// monotonicTime converts a CLOCK_MONOTONIC time to wall time, at most
136// maxTimestampAhead from now: a far timestamp must not stall the surface.
137func monotonicTime(sec uint64, nsec int64) time.Time {
138	var now unix.Timespec
139	if unix.ClockGettime(unix.CLOCK_MONOTONIC, &now) != nil {
140		return time.Now()
141	}
142	ahead := time.Duration(nsec - now.Nsec)
143	switch n := uint64(now.Sec); {
144	case sec >= n+2:
145		ahead = maxTimestampAhead
146	case sec+2 <= n:
147		ahead = 0 // in the past: apply now
148	default:
149		ahead += time.Duration(int64(sec)-now.Sec) * time.Second
150	}
151	return time.Now().Add(min(max(ahead, 0), maxTimestampAhead))
152}
153
154// content_type_v1
155
156type contentTypeManager struct{ server *Server }
157
158func (contentTypeManager) Destroy(*contenttype.WpContentTypeManagerV1) {}
159
160func (m contentTypeManager) GetSurfaceContentType(r *contenttype.WpContentTypeManagerV1, id uint32, surf *wayland.Surface) {
161	state := m.server.surfaceOf(surf)
162	if state != nil && state.contentType != nil {
163		r.PostError(uint32(contenttype.WpContentTypeManagerV1ErrorAlreadyConstructed), "surface already has a content type")
164		return
165	}
166	h := &contentTypeHandler{surface: state}
167	res, err := contenttype.NewWpContentTypeV1(r.Client(), r.Version(), id, h)
168	if err != nil || state == nil {
169		return
170	}
171	state.contentType = h
172	res.OnDestroy = func() {
173		// Destroying it sets the type back to none, double-buffered.
174		if state.contentType == h {
175			state.contentType = nil
176			state.next.kind = 0
177		}
178	}
179}
180
181type contentTypeHandler struct{ surface *surface }
182
183func (h *contentTypeHandler) SetContentType(_ *contenttype.WpContentTypeV1, kind uint32) {
184	if h.surface != nil && !h.surface.destroyed && h.surface.contentType == h {
185		h.surface.next.kind = kind
186	}
187}
188func (*contentTypeHandler) Destroy(*contenttype.WpContentTypeV1) {}
189
190func (s *Server) surfaceOf(w *wayland.Surface) *surface {
191	if w == nil {
192		return nil
193	}
194	return s.surfaces[w.Resource]
195}
196
197// Content update queue
198
199// update owns a captured commit until it applies or is discarded.
200type update struct {
201	// pendingCommit is the wl_surface state captured by the commit.
202	pendingCommit
203	vp              *viewport
204	vpW, vpH        int32
205	vpSet           bool
206	vpSrc           [4]server.Fixed
207	vpCrop          bool
208	layout          []childLayout
209	deps            []*update
210	owner           *surface
211	implicit        [4]*syncWait
212	syncStore       commitSync // storage of sync when the commit had explicit-sync points
213	prev            *update
214	synced, bound   bool
215	xdg             *xdgSurface
216	geometry        ports.Rect
217	cons            *constraint
218	region          *ports.Rect
219	layer           *layerSurface
220	layerNext       layerState
221	lockAck         lockConfigure
222	readyGeneration uint64
223	readyResult     bool
224	// refs counts incoming deps and prev links. Retired updates remain readable
225	// until their last incoming link goes away, even after dropQueue.
226	refs    int
227	retired bool
228	failed  bool // acquire observation failed: discard, never publish
229}
230
231// updatePool contains only retired updates with no incoming graph links. A
232// popped update is NOT reusable just because it left its owner's queue: a
233// parent's queued deps can still point to it (including after role teardown).
234// The display owner goroutine changes graph links; sync.Pool only shares dead,
235// zeroed storage between independent displays.
236var updatePool sync.Pool
237
238func (u *update) releaseRef() {
239	u.refs--
240	if u.refs == 0 && u.retired {
241		deps, dmg, bufDmg := u.deps[:0], u.damage[:0], u.bufDamage[:0]
242		*u = update{deps: deps, damage: dmg, bufDamage: bufDmg}
243		updatePool.Put(u)
244	}
245}
246
247// retireUpdate severs outgoing links only after the update is no longer used
248// by applyUpdate/dropQueue. Incoming links keep its identity until consumed.
249func (u *update) retireUpdate() {
250	u.retired = true
251	prev, deps := u.prev, u.deps
252	u.prev, u.deps = nil, deps[:0]
253	if prev != nil {
254		prev.releaseRef()
255	}
256	for i, dep := range deps {
257		deps[i] = nil // the pooled backing array must not retain old dependencies
258		dep.releaseRef()
259	}
260	if u.refs == 0 {
261		*u = update{deps: u.deps, damage: u.damage[:0], bufDamage: u.bufDamage[:0]}
262		updatePool.Put(u)
263	}
264}
265
266type childLayout struct {
267	child *surface
268	x, y  int
269	below bool
270}
271
272func removeLayout(layout []childLayout, child *surface) []childLayout {
273	layout = append([]childLayout(nil), layout...)
274	for i := 0; i < len(layout); i++ {
275		if layout[i].child == child {
276			layout = append(layout[:i:i], layout[i+1:]...)
277			i--
278		}
279	}
280	return layout
281}
282
283func sameLayout(a, b []childLayout) bool {
284	if len(a) != len(b) {
285		return false
286	}
287	for i := range a {
288		if a[i] != b[i] {
289			return false
290		}
291	}
292	return true
293}
294
295// takePending moves the pending state into an update: one-shot state is
296// cleared, sticky state (scale, hints, viewport, geometry, layer) kept.
297func (s *surface) takePending() update {
298	u := update{pendingCommit: s.next}
299	// One-shot requests apply once; sticky ones carry over.
300	s.next = pendingCommit{scale: s.next.scale, transform: s.next.transform, async: s.next.async, color: s.next.color, representation: s.next.representation, kind: s.next.kind, fade: s.next.fade}
301	if v := s.viewport; v != nil {
302		u.vp, u.vpW, u.vpH, u.vpSet, u.vpSrc, u.vpCrop = v, v.pendingW, v.pendingH, v.pendingSet, v.pendingSrc, v.pendingCrop
303	}
304	u.layout = s.sub.pendingLayout
305	if s.xdg != nil {
306		u.xdg, u.geometry = s.xdg, s.xdg.pendingGeometry
307	}
308	if c := s.server.constraints[s]; c != nil && c.pending != nil {
309		u.cons, u.region, c.pending = c, c.pending, nil
310	}
311	if s.layer != nil {
312		u.layer, u.layerNext = s.layer, s.layer.pending
313	}
314	if s.lock != nil {
315		u.lockAck = s.lock.ack
316	}
317	return u
318}
319
320// tooEarly reports whether content applied now would show before at:
321// it reaches the screen one refresh later.
322func (s *surface) tooEarly(at, now time.Time) bool {
323	return !at.IsZero() && at.After(s.server.nextRefresh(s.server.fifoOutput(s), now))
324}
325
326// nextRefresh is when the output's next frame reaches the screen: the
327// vblank after now counted from its last flip (kernel time, refresh
328// period), or one period from now without flips or while VRR is on.
329func (s *Server) nextRefresh(name string, now time.Time) time.Time {
330	p := s.outputPeriod(name)
331	f, ok := s.flips[name]
332	if !ok || f.When <= 0 || f.Refresh <= 0 {
333		return now.Add(p)
334	}
335	mono := monotonic(now)
336	since := mono - f.When
337	if since < 0 || since > time.Second {
338		return now.Add(p)
339	}
340	n := since/f.Refresh + 1
341	return now.Add(f.When + n*f.Refresh - mono)
342}
343
344// queueUpdate queues the pending commit until it can apply.
345func (s *surface) queueUpdate() {
346	u, _ := updatePool.Get().(*update)
347	if u == nil {
348		u = new(update)
349	}
350	deps, dmg, bufDmg := u.deps[:0], u.damage[:0], u.bufDamage[:0]
351	*u = s.takePending()
352	u.deps = deps
353	// The pooled update brings the capacity a retired commit's damage had;
354	// the surface collects the next commit's rects in it. The update keeps
355	// the rects of this commit until it retires.
356	s.next.damage, s.next.bufDamage = dmg, bufDmg
357	if u.sync != nil && u.sync == &s.syncScratch {
358		// The update owns its points from here: the surface's scratch is
359		// free for the next commit while this one waits in the queue.
360		u.syncStore, s.syncScratch = s.syncScratch, commitSync{}
361		u.sync = &u.syncStore
362	}
363	u.owner, u.synced = s, s.effectivelySynced()
364	if s.server.syncWait != nil && u.attached && u.buffer != nil && u.sync == nil {
365		if b, ok := s.server.buffers[u.buffer.Resource].(*dmabufBuffer); ok {
366			err := s.server.syncWait.watchImplicit(b.buf, &u.implicit)
367			if err != nil {
368				// Partial registrations were cancelled; discard this update.
369				u.failed = true
370				s.server.log.Warn().Err(err).Msg("implicit buffer wait")
371			}
372		}
373	}
374	for _, ch := range s.sub.children {
375		for i := len(ch.queue) - 1; i >= 0; i-- {
376			candidate := ch.queue[i]
377			if candidate.synced && !candidate.bound {
378				candidate.bound = true
379				u.deps = append(u.deps, candidate)
380				candidate.refs++
381				break
382			}
383		}
384	}
385	if len(s.queue) > 0 {
386		u.prev = s.queue[len(s.queue)-1]
387		u.prev.refs++
388	}
389	s.queue = append(s.queue, u)
390	s.server.fifoSurfaces[s] = struct{}{}
391}
392
393// applyUpdate applies only the captured commit; later requests remain pending.
394func (s *surface) applyUpdate(u *update) {
395	// A buffer destroyed while queued (a swapchain resize) is skipped: the
396	// surface keeps its content, and is not unmapped behind the client.
397	s.commitSkipped = false
398	s.queuedScale = u.scale
399	s.queuedBuffer = 0
400	if u.buffer != nil {
401		s.queuedBuffer = u.buffer.ID()
402	}
403	if u.buffer != nil && !u.buffer.Resource.Alive() {
404		s.commitSkipped = true
405		// No reader takes this buffer. A release point is safe to signal
406		// only if the acquire has already signalled; dropSync owns that rule.
407		s.dropSync(u.sync)
408		u.sync = nil
409		u.buffer, u.attached = nil, false
410		// Scale, transform and crop describe the same retained buffer.
411		u.scale = s.bufferScale
412		u.transform = s.transform
413		// The viewport change was made for that buffer: keep the crop that
414		// matches the retained content instead of validating a mismatch.
415		if u.vp != nil && (s.lock != nil || u.vp.resource != nil && u.vp.resource.Alive()) {
416			v := &s.committedViewport
417			u.vpW, u.vpH, u.vpSet, u.vpSrc, u.vpCrop = v.destW, v.destH, v.dest, v.src, v.crop
418		}
419	}
420	s.applyCommit(u)
421	s.commitSkipped = false
422}
423
424// dropQueue discards the queued updates of a destroyed surface or role.
425// A live surface still gets its queued frame callbacks.
426func (s *surface) dropQueue() {
427	if s.kind == roleSessionLock {
428		s.dropLockDeps()
429	}
430	s.dropQueuePrefix(len(s.queue))
431}
432
433// dropQueuePrefix retires only the first n updates. Leave the preserved suffix
434// visible to release checks: a buffer shared with it is still client-owned work.
435func (s *surface) dropQueuePrefix(n int) {
436	queue := s.queue[:n]
437	s.queue = s.queue[n:]
438	released := map[*server.Resource]bool{}
439	for _, u := range queue {
440		if s.destroyed {
441			for _, cb := range u.callbacks {
442				cb.Destroy()
443			}
444		} else if len(u.callbacks) > 0 {
445			s.server.queueFrames(s, u.callbacks)
446		}
447		for _, fb := range u.feedback {
448			discard(fb)
449		}
450		s.dropSync(u.sync)
451		for _, w := range u.implicit {
452			s.server.syncWait.cancel(w)
453		}
454		if u.sync != nil {
455			continue // explicit sync: the release point replaced release
456		}
457		if u.buffer != nil && !sameBuffer(u.buffer, s.current) && !released[u.buffer.Resource] && u.buffer.Resource.Alive() && !s.bufferQueuedElsewhere(u.buffer) {
458			released[u.buffer.Resource] = true
459			u.buffer.SendRelease()
460		}
461	}
462	for i, u := range queue {
463		queue[i] = nil // do not retain retired objects in the queue backing array
464		u.retireUpdate()
465	}
466	if len(s.queue) == 0 {
467		s.queue = queue[:0] // preserve steady-state capacity when all were retired
468		delete(s.server.fifoSurfaces, s)
469	}
470}
471
472func (s *surface) bufferQueuedElsewhere(b *wayland.Buffer) bool {
473	for _, other := range s.server.surfaces {
474		if sameBuffer(other.next.buffer, b) {
475			return true
476		}
477		if other != s && sameBuffer(other.current, b) {
478			return true
479		}
480		for _, u := range other.queue {
481			if sameBuffer(u.buffer, b) {
482				return true
483			}
484		}
485	}
486	return false
487}
488
489// setBarrier applies a committed set_barrier.
490func (s *surface) setBarrier(now time.Time) {
491	s.barrier, s.barrierAt = true, now
492	s.server.fifoSurfaces[s] = struct{}{}
493	s.server.wakePacer()
494}
495
496// fifoOutput is the output pacing a surface, "" when none does.
497func (s *Server) fifoOutput(surf *surface) string {
498	name := s.frameOutput(surf)
499	if name != "" && name != suspendedFrameQueue && s.outputByNameExact(name) == nil {
500		return ""
501	}
502	return name
503}
504
505// outputPeriod is one refresh of an output ("": 60 Hz).
506func (s *Server) outputPeriod(name string) time.Duration {
507	if name == suspendedFrameQueue {
508		return suspendedFramePeriod
509	}
510	if o := s.outputByNameExact(name); o != nil && name != "" {
511		return framePeriod(o.place.Info.RefreshMilli)
512	}
513	return defaultFramePeriod
514}
515
516// tickFifo clears the barriers whose latching deadline passed and applies
517// the queued updates now ready. A flip of the surface's output is its
518// deadline; outputs that do not flip (headless, switched away, no output)
519// clear it after one refresh, or 1.5 when they usually flip. It returns
520// the time to the next deadline and whether any surface still waits.
521func (s *Server) tickFifo(now time.Time, flipped map[string]bool) (time.Duration, bool) {
522	for name, f := range flipped {
523		if f {
524			s.lastFlip[name] = now
525		}
526	}
527	for name := range s.lastFlip {
528		if s.outputByNameExact(name) == nil {
529			delete(s.lastFlip, name)
530		}
531	}
532	wait := suspendedFramePeriod
533	s.readinessGeneration++
534	for surf := range s.fifoSurfaces {
535		name := s.fifoOutput(surf)
536		p := s.outputPeriod(name)
537		// The deadline of outputs that usually flip only catches a missed flip.
538		deadline := surf.barrierAt.Add(p)
539		if name != "" && now.Sub(s.lastFlip[name]) < time.Second {
540			deadline = deadline.Add(p / 2)
541		}
542		if surf.barrier && ((flipped[name] && name != "" && s.lastFlip[name].After(surf.barrierAt)) || !now.Before(deadline)) {
543			surf.barrier = false
544		}
545		for len(surf.queue) > 0 && !surf.destroyed {
546			u := surf.queue[0]
547			if u.synced || !u.graphReady(now) {
548				break
549			}
550			s.applyingGraph = true
551			s.graphDrawn = false
552			s.graphFeedback = s.graphFeedback[:0]
553			u.applyGraph()
554			s.applyingGraph = false
555			// Applying a graph changes queues and readiness within this tick.
556			s.readinessGeneration++
557			if s.graphDrawn {
558				surf.redraw()
559			}
560			// Only the last commit per surface can be sampled from this
561			// publication. Earlier callbacks were already queued on apply.
562			for i, fb := range s.graphFeedback {
563				superseded := false
564				for j := i + 1; j < len(s.graphFeedback); j++ {
565					if s.graphFeedback[j].surf == fb.surf && s.graphFeedback[j].fresh {
566						superseded = true
567						break
568					}
569				}
570				if superseded {
571					for _, pending := range fb.pending {
572						discard(pending)
573					}
574					continue
575				}
576				fb.surf.commitFeedback(fb.pending, fb.fresh)
577			}
578			s.graphFeedback = s.graphFeedback[:0]
579		}
580		if surf.barrier {
581			wait = min(wait, deadline.Sub(now))
582		}
583		if len(surf.queue) > 0 {
584			wait = min(wait, p)
585		}
586		if !surf.barrier && len(surf.queue) == 0 {
587			delete(s.fifoSurfaces, surf)
588		}
589	}
590	return max(wait, time.Millisecond), len(s.fifoSurfaces) > 0
591}
592
593func (s *surface) effectivelySynced() bool {
594	for p := s; p != nil; p = p.sub.parent {
595		if p.sub.parent != nil && p.sub.synced {
596			return true
597		}
598	}
599	return false
600}
601
602func (s *surface) flushDesync() {
603	if !s.effectivelySynced() {
604		for _, u := range s.queue {
605			if u.synced && !u.bound {
606				u.synced = false
607			}
608		}
609	}
610	for _, ch := range s.sub.children {
611		ch.flushDesync()
612	}
613	s.server.wakePacer()
614}
615
616func (u *update) graphReady(now time.Time) bool {
617	if u.retired {
618		return true // applied or dropped; a queued parent cannot replay it
619	}
620	s := u.owner
621	gen := s.server.readinessGeneration
622	if gen != 0 && u.readyGeneration == gen {
623		return u.readyResult
624	}
625	if gen != 0 {
626		u.readyGeneration, u.readyResult = gen, false
627	}
628	if u.prev != nil && !u.prev.graphReady(now) {
629		return false
630	}
631	if u.wait && s.barrier || s.tooEarly(u.at, now) {
632		return false
633	}
634	// A dead attachment is skipped by applyUpdate; waiting for its fence
635	// would leave this update and every later one stuck behind it.
636	if u.buffer != nil && !u.buffer.Resource.Alive() {
637		for i, w := range u.implicit {
638			if w != nil {
639				s.server.syncWait.cancel(w)
640				u.implicit[i] = nil
641			}
642		}
643	} else if !u.failed {
644		if !s.syncReady(u.sync) {
645			return false
646		}
647		if u.sync != nil && u.sync.wait != nil && u.sync.wait.failed {
648			u.failed = true
649		}
650		waiting := false
651		for _, w := range u.implicit {
652			if w == nil {
653				continue
654			}
655			if !s.server.syncWait.fired(w) {
656				waiting = true
657			} else if w.failed {
658				u.failed = true
659			}
660		}
661		if waiting && !u.failed {
662			return false
663		}
664	}
665	if u.failed {
666		u.readyResult = true // ready only to retire, never to apply
667		return true
668	}
669	for _, dep := range u.deps {
670		if !dep.owner.destroyed && !dep.graphReady(now) {
671			return false
672		}
673	}
674	u.readyResult = true
675	return true
676}
677
678func (u *update) applyGraph() {
679	if u.retired {
680		return
681	}
682	s := u.owner
683	if !u.failed && u.sync != nil && (u.buffer == nil || u.buffer.Alive()) && !s.prepareSync(u.sync) {
684		u.failed = true
685	}
686	if u.failed {
687		// Earlier commits still apply in order; only this update is discarded.
688		for len(s.queue) > 0 && s.queue[0] != u {
689			s.queue[0].applyGraph()
690		}
691		// Retire captured children too, cancelling their waits without replay.
692		u.dropFailedDeps()
693		for i, queued := range s.queue {
694			if queued == u {
695				s.dropQueuePrefix(i + 1)
696				break
697			}
698		}
699		return
700	}
701	for len(s.queue) > 0 && s.queue[0] != u {
702		s.queue[0].applyGraph()
703	}
704	for _, dep := range u.deps {
705		if !dep.owner.destroyed {
706			dep.applyGraph()
707		}
708	}
709	if len(s.queue) == 0 || s.queue[0] != u {
710		return
711	}
712	s.queue[0] = nil
713	if len(s.queue) == 1 {
714		s.queue = s.queue[:0] // retain capacity for the next commit
715	} else {
716		s.queue = s.queue[1:]
717	}
718	if len(s.queue) > 0 {
719		next := s.queue[0]
720		next.prev = nil
721		u.releaseRef() // next's prev no longer points to u
722	}
723	s.applyUpdate(u)
724	u.retireUpdate()
725}
726
727// dropFailedDeps retires just the child prefixes owned by a rejected commit.
728// Incoming graph refs retain their storage until the failed parent retires.
729func (u *update) dropFailedDeps() {
730	for _, dep := range u.deps {
731		if dep.retired {
732			continue
733		}
734		// Every predecessor in the retired prefix can own a different
735		// dependency graph. Traverse it before severing any incoming refs.
736		for _, prefix := range dep.owner.queue {
737			if !prefix.retired {
738				prefix.dropFailedDeps()
739			}
740			if prefix == dep {
741				break
742			}
743		}
744		for i, queued := range dep.owner.queue {
745			if queued == dep {
746				dep.owner.dropQueuePrefix(i + 1)
747				break
748			}
749		}
750	}
751}
752
753// sameBuffer reports whether two wrappers are the same wl_buffer.
754func sameBuffer(a, b *wayland.Buffer) bool {
755	return a != nil && b != nil && a.Resource == b.Resource
756}
757
758// wakePacer makes the pacer look at its queues. Display goroutine only.
759// While a commit or a tick runs (paceDepth) it only records the request: the
760// outermost caller then ticks inline (paceNow), saving a posted tick.
761func (s *Server) wakePacer() {
762	if s.paceDepth > 0 {
763		s.paceDirty = true
764		return
765	}
766	s.signalPacer()
767}
768
769// signalPacer posts a tick to the display loop; safe from any goroutine.
770// Signals coalesce until the tick runs.
771func (s *Server) signalPacer() {
772	if !s.paceSignal.CompareAndSwap(false, true) {
773		return
774	}
775	s.display.Post(s.paceSignalFn)
776}

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