refactor/kvconf 3462c76

gestures_test.go RawHistoryFolder

1package wayland
2
3import (
4	"slices"
5	"testing"
6
7	"github.com/bnema/go-wayland-bindings/server/pointergestures"
8	"github.com/bnema/go-wayland-bindings/server/wayland"
9	"github.com/bnema/neferwl/internal/ports"
10	"github.com/bnema/wlturbo"
11)
12
13// gestureLog records the events of a zwp_pointer_gesture_*_v1 object as
14// floats: the opcode, then its arguments (fixed values as they are).
15type gestureLog struct {
16	wlturbo.BaseProxy
17	kind   ports.GestureKind
18	events chan []float64
19}
20
21func (p *gestureLog) Dispatch(e *wlturbo.Event) {
22	ev := []float64{float64(e.Opcode)}
23	// Hold has no update: its end is opcode 1.
24	op := e.Opcode
25	if p.kind == ports.GestureHold && op == 1 {
26		op = uint16(pointergestures.ZwpPointerGestureSwipeV1EventEnd)
27	}
28	switch op {
29	case uint16(pointergestures.ZwpPointerGestureSwipeV1EventBegin):
30		// serial, time, surface, fingers
31		for range 4 {
32			ev = append(ev, float64(e.Uint32()))
33		}
34	case uint16(pointergestures.ZwpPointerGestureSwipeV1EventUpdate):
35		// time, then dx and dy (and scale and rotation of a pinch)
36		fixed := 2
37		if p.kind == ports.GesturePinch {
38			fixed = 4
39		}
40		ev = append(ev, float64(e.Uint32()))
41		for range fixed {
42			ev = append(ev, e.Fixed().Float64())
43		}
44	case uint16(pointergestures.ZwpPointerGestureSwipeV1EventEnd):
45		ev = append(ev, float64(e.Uint32()), float64(e.Uint32()), float64(e.Int32()))
46	}
47	p.events <- ev
48}
49
50func gestureObjects(t *testing.T, c *wlturbo.Display, manager uint32) (swipe, pinch, hold chan []float64) {
51	t.Helper()
52	seat := bindProtocol(t, c, "wl_seat")
53	registerProtocol(t, c, seat)
54	pointer := c.AllocateID()
55	registerProtocol(t, c, pointer)
56	requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
57	var logs [3]chan []float64
58	for i, op := range []uint32{pointergestures.ZwpPointerGesturesV1RequestGetSwipeGesture, pointergestures.ZwpPointerGesturesV1RequestGetPinchGesture, pointergestures.ZwpPointerGesturesV1RequestGetHoldGesture} {
59		id := c.AllocateID()
60		p := &gestureLog{kind: ports.GestureKind(i), events: make(chan []float64, 16)}
61		p.SetID(id)
62		registerWireProxy(c, p)
63		requestProtocol(t, c, manager, op, id, pointer)
64		logs[i] = p.events
65	}
66	return logs[0], logs[1], logs[2]
67}
68
69// quiet fails when the log got an event after a roundtrip.
70func quiet(t *testing.T, c *wlturbo.Display, events chan []float64) {
71	t.Helper()
72	if err := c.Roundtrip(); err != nil {
73		t.Fatal(err)
74	}
75	select {
76	case ev := <-events:
77		t.Fatalf("unexpected event %v", ev)
78	default:
79	}
80}
81
82func TestPointerGestures(t *testing.T) {
83	s, events, commands, dir := lifecycleServer(t)
84	c := protocolClient(t, s, dir)
85	manager := bindVersion(t, c, "zwp_pointer_gestures_v1", 3)
86	swipe, pinch, hold := gestureObjects(t, c, manager)
87	w, surf, _ := surfaceMapper(t, c, events)()
88	// Another client with gestures never has the pointer.
89	other := protocolClient(t, s, dir)
90	oSwipe, oPinch, oHold := gestureObjects(t, other, bindVersion(t, other, "zwp_pointer_gestures_v1", 3))
91
92	// No gesture reaches a client before it has the pointer.
93	commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2}
94	commands <- ports.PointerFocus{ID: w.ID, X: 10, Y: 10}
95	quiet(t, c, pinch)
96
97	// A pinch: begin carries the serial, time, surface and fingers; updates
98	// the centre, scale and rotation; the end a later serial.
99	commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2, Time: 1000 * 1e6}
100	begin := next(t, c, pinch)
101	if begin[0] != 0 || begin[2] != 1000 || begin[3] != float64(surf) || begin[4] != 2 {
102		t.Fatalf("pinch begin %v", begin)
103	}
104	commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GesturePinch, DX: 1.5, DY: -2, Scale: 1.25, Rotation: 10, Time: 1008 * 1e6}
105	if ev := next(t, c, pinch); !slices.Equal(ev, []float64{1, 1008, 1.5, -2, 1.25, 10}) {
106		t.Fatalf("pinch update %v", ev)
107	}
108	commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch, Time: 1016 * 1e6}
109	end := next(t, c, pinch)
110	if end[0] != 2 || end[1] <= begin[1] || end[2] != 1016 || end[3] != 0 {
111		t.Fatalf("pinch end %v (begin %v)", end, begin)
112	}
113
114	// A swipe and a hold, the hold ended cancelled.
115	commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GestureSwipe, Fingers: 5, Time: 2000 * 1e6}
116	commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GestureSwipe, DX: 3, DY: 4, Time: 2008 * 1e6}
117	commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GestureSwipe, Time: 2016 * 1e6}
118	if ev := next(t, c, swipe); ev[0] != 0 || ev[4] != 5 {
119		t.Fatalf("swipe begin %v", ev)
120	}
121	if ev := next(t, c, swipe); !slices.Equal(ev, []float64{1, 2008, 3, 4}) {
122		t.Fatalf("swipe update %v", ev)
123	}
124	if ev := next(t, c, swipe); ev[0] != 2 || ev[3] != 0 {
125		t.Fatalf("swipe end %v", ev)
126	}
127	commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GestureHold, Fingers: 3, Time: 3000 * 1e6}
128	commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GestureHold, Cancelled: true, Time: 3008 * 1e6}
129	if ev := next(t, c, hold); ev[0] != 0 || ev[4] != 3 {
130		t.Fatalf("hold begin %v", ev)
131	}
132	if ev := next(t, c, hold); ev[0] != 1 || ev[3] != 1 {
133		t.Fatalf("hold end %v", ev)
134	}
135
136	// A gesture of one kind does not reach the objects of the others, and an
137	// update of a gesture that is not running is dropped.
138	commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GesturePinch, Scale: 2}
139	quiet(t, c, pinch)
140	quiet(t, c, swipe)
141	quiet(t, c, hold)
142
143	// The pointer leaving the window cancels the gesture. Commands run in
144	// order: the next pinch event being this begin proves the dropped
145	// update above sent nothing.
146	commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2, Time: 4000 * 1e6}
147	if ev := next(t, c, pinch); ev[0] != 0 || ev[2] != 4000 {
148		t.Fatalf("pinch begin %v", ev)
149	}
150	commands <- ports.PointerFocus{}
151	if ev := next(t, c, pinch); ev[0] != 2 || ev[3] != 1 {
152		t.Fatalf("pinch not cancelled on leave: %v", ev)
153	}
154	commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch, Time: 4008 * 1e6}
155	quiet(t, c, pinch)
156
157	// Releasing the manager keeps its objects.
158	commands <- ports.PointerFocus{ID: w.ID, X: 10, Y: 10}
159	requestProtocol(t, c, manager, pointergestures.ZwpPointerGesturesV1RequestRelease)
160	commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GestureHold, Fingers: 4, Time: 5000 * 1e6}
161	if ev := next(t, c, hold); ev[0] != 0 || ev[4] != 4 {
162		t.Fatalf("hold begin after release %v", ev)
163	}
164	// The end after the cancel sent nothing: the next pinch event is this
165	// begin.
166	commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2, Time: 6000 * 1e6}
167	if ev := next(t, c, pinch); ev[0] != 0 || ev[2] != 6000 {
168		t.Fatalf("pinch begin after cancelled end %v", ev)
169	}
170
171	// The client without the pointer saw nothing.
172	quiet(t, other, oSwipe)
173	quiet(t, other, oPinch)
174	quiet(t, other, oHold)
175}
176
177// A window with the pointer but no gesture object gets gesture commands
178// without the server failing or the client being disconnected.
179func TestPointerGesturesWithoutObjects(t *testing.T) {
180	s, events, commands, dir := lifecycleServer(t)
181	c := protocolClient(t, s, dir)
182	w, _, _ := surfaceMapper(t, c, events)()
183	commands <- ports.PointerFocus{ID: w.ID, X: 1, Y: 1}
184	commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2}
185	commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GesturePinch, Scale: 2}
186	commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch}
187	if err := c.Roundtrip(); err != nil {
188		t.Fatal(err)
189	}
190}

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 23

Tags 10

↑↓ moveenter openesc close