v0.1.0 d9218d6

forward.go RawHistoryFolder

1package libinput
2
3import (
4	"context"
5	"sync"
6	"time"
7
8	"github.com/bnema/neferwl/internal/ports"
9	"github.com/bnema/zerowrap"
10)
11
12// forwarder decouples reading libinput from core. libinput keeps reading
13// devices at their full rate and never blocks on core; consecutive pointer
14// motions still waiting to be sent collapse into the latest one, while keys
15// and buttons keep their order relative to motion.
16type forwarder struct {
17	mu    sync.Mutex
18	queue []ports.InputEvent
19	wake  chan struct{}
20	stats forwardStats
21	// The queue is unbounded (no event is ever dropped); long is set
22	// while it is past forwardWarn entries, warned once each way.
23	log  zerowrap.Logger
24	long bool
25}
26
27// forwardWarn is the queue length that means core has stalled.
28const forwardWarn = 4096
29
30// forwardStats is reset by take.
31type forwardStats struct {
32	Motions   int           // pointer motions read
33	Coalesced int           // motions merged into a later one
34	Sent      int           // events delivered to core
35	MaxBlock  time.Duration // longest wait for core to accept one event
36}
37
38func newForwarder(log zerowrap.Logger) *forwarder {
39	return &forwarder{wake: make(chan struct{}, 1), log: log}
40}
41
42// push queues ev without blocking.
43func (f *forwarder) push(ev ports.InputEvent) {
44	f.mu.Lock()
45	if m, ok := ev.(ports.PointerMotion); ok {
46		f.stats.Motions++
47		if n := len(f.queue); n > 0 {
48			if prev, tail := f.queue[n-1].(ports.PointerMotion); tail {
49				// Relative deltas add up: games read them, not the position.
50				m.DX += prev.DX
51				m.DY += prev.DY
52				m.UnaccelDX += prev.UnaccelDX
53				m.UnaccelDY += prev.UnaccelDY
54				f.queue[n-1] = m
55				f.stats.Coalesced++
56				f.mu.Unlock()
57				return
58			}
59		}
60	}
61	f.queue = append(f.queue, ev)
62	if len(f.queue) > forwardWarn && !f.long {
63		f.long = true
64		f.log.Warn().Str("component", "input").Int("queued", len(f.queue)).Msg("core is not reading input; events queue up")
65	}
66	f.mu.Unlock()
67	select {
68	case f.wake <- struct{}{}:
69	default:
70	}
71}
72
73func (f *forwarder) pop() (ports.InputEvent, bool) {
74	f.mu.Lock()
75	defer f.mu.Unlock()
76	if len(f.queue) == 0 {
77		return nil, false
78	}
79	ev := f.queue[0]
80	f.queue[0] = nil
81	f.queue = f.queue[1:]
82	if f.long && len(f.queue) < forwardWarn {
83		f.long = false
84		f.log.Warn().Str("component", "input").Int("queued", len(f.queue)).Msg("input queue drained")
85	}
86	return ev, true
87}
88
89// take returns and resets the counters.
90func (f *forwarder) take() forwardStats {
91	f.mu.Lock()
92	defer f.mu.Unlock()
93	s := f.stats
94	f.stats = forwardStats{}
95	return s
96}
97
98// run delivers queued events to input until ctx ends.
99func (f *forwarder) run(ctx context.Context, input chan<- ports.InputEvent) {
100	for {
101		ev, ok := f.pop()
102		if !ok {
103			select {
104			case <-ctx.Done():
105				return
106			case <-f.wake:
107				continue
108			}
109		}
110		start := time.Now()
111		select {
112		case input <- ev:
113		case <-ctx.Done():
114			return
115		}
116		d := time.Since(start)
117		f.mu.Lock()
118		f.stats.Sent++
119		f.stats.MaxBlock = max(f.stats.MaxBlock, d)
120		f.mu.Unlock()
121	}
122}

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 26

Tags 10

↑↓ moveenter openesc close